Files
sgauthier 6e64e138e2
All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 35s
planning
2024-10-14 09:15:30 +02:00

333 lines
12 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PreviewState = void 0;
exports.apiRequest = apiRequest;
exports.endpointConstants = exports.apiRoots = void 0;
exports.getDefaultBranchName = getDefaultBranchName;
exports.getPreviewStatus = getPreviewStatus;
exports.isPreviewContext = isPreviewContext;
exports.parseResponse = parseResponse;
exports.readFile = readFile;
exports.readFileMetadata = readFileMetadata;
exports.requestWithBackoff = requestWithBackoff;
exports.throwOnConflictingBranches = throwOnConflictingBranches;
var _asyncLock = require("./asyncLock");
var _unsentRequest = _interopRequireDefault(require("./unsentRequest"));
var _APIError = _interopRequireDefault(require("./APIError"));
const _excluded = ["token", "backend"];
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _extendableBuiltin(cls) {
function ExtendableBuiltin() {
var instance = Reflect.construct(cls, Array.from(arguments));
Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
return instance;
}
ExtendableBuiltin.prototype = Object.create(cls.prototype, {
constructor: {
value: cls,
enumerable: false,
writable: true,
configurable: true
}
});
if (Object.setPrototypeOf) {
Object.setPrototypeOf(ExtendableBuiltin, cls);
} else {
ExtendableBuiltin.__proto__ = cls;
}
return ExtendableBuiltin;
}
class RateLimitError extends _extendableBuiltin(Error) {
constructor(message, resetSeconds) {
super(message);
_defineProperty(this, "resetSeconds", void 0);
if (resetSeconds < 0) {
this.resetSeconds = 1;
} else if (resetSeconds > 60 * 60) {
this.resetSeconds = 60 * 60;
} else {
this.resetSeconds = resetSeconds;
}
}
}
async function parseJsonResponse(response) {
const json = await response.json();
if (!response.ok) {
return Promise.reject(json);
}
return json;
}
function parseResponse(response) {
const contentType = response.headers.get('Content-Type');
if (contentType && contentType.match(/json/)) {
return parseJsonResponse(response);
}
const textPromise = response.text().then(text => {
if (!response.ok) return Promise.reject(text);
return text;
});
return textPromise;
}
async function requestWithBackoff(api, req, attempt = 1) {
if (api.rateLimiter) {
await api.rateLimiter.acquire();
}
try {
const builtRequest = await api.buildRequest(req);
const requestFunction = api.requestFunction || _unsentRequest.default.performRequest;
const response = await requestFunction(builtRequest);
if (response.status === 429) {
// GitLab/Bitbucket too many requests
const text = await response.text().catch(() => 'Too many requests');
throw new Error(text);
} else if (response.status === 403) {
// GitHub too many requests
const json = await response.json().catch(() => ({
message: ''
}));
if (json.message.match('API rate limit exceeded')) {
const now = new Date();
const nextWindowInSeconds = response.headers.has('X-RateLimit-Reset') ? parseInt(response.headers.get('X-RateLimit-Reset')) : now.getTime() / 1000 + 60;
throw new RateLimitError(json.message, nextWindowInSeconds);
}
response.json = () => Promise.resolve(json);
}
return response;
} catch (err) {
if (attempt > 5 || err.message === "Can't refresh access token when using implicit auth") {
throw err;
} else {
if (!api.rateLimiter) {
const timeout = err.resetSeconds || attempt * attempt;
console.log(`Pausing requests for ${timeout} ${attempt === 1 ? 'second' : 'seconds'} due to fetch failures:`, err.message);
api.rateLimiter = (0, _asyncLock.asyncLock)();
api.rateLimiter.acquire();
setTimeout(() => {
var _api$rateLimiter;
(_api$rateLimiter = api.rateLimiter) === null || _api$rateLimiter === void 0 ? void 0 : _api$rateLimiter.release();
api.rateLimiter = undefined;
console.log(`Done pausing requests`);
}, 1000 * timeout);
}
return requestWithBackoff(api, req, attempt + 1);
}
}
}
// Options is an object which contains all the standard network request properties
// for modifying HTTP requests and may contains `params` property
// RequestConfig contains all the standard properties of a Request object and
// several custom properties:
// - "headers" property is an object whose properties and values are string types
// - `token` property to allow passing tokens for users using a private repo.
// - `params` property for customizing response
// - `backend`(compulsory) to specify which backend to be used: Github, Gitlab etc.
const apiRoots = exports.apiRoots = {
github: 'https://api.github.com',
gitlab: 'https://gitlab.com/api/v4',
bitbucket: 'https://api.bitbucket.org/2.0'
};
const endpointConstants = exports.endpointConstants = {
singleRepo: {
bitbucket: '/repositories',
github: '/repos',
gitlab: '/projects'
}
};
const api = {
buildRequest(req) {
return req;
}
};
function constructUrlWithParams(url, params) {
if (params) {
const paramList = [];
for (const key in params) {
paramList.push(`${key}=${encodeURIComponent(params[key])}`);
}
if (paramList.length) {
url += `?${paramList.join('&')}`;
}
}
return url;
}
async function constructRequestHeaders(headerConfig) {
const {
token,
headers
} = headerConfig;
const baseHeaders = _objectSpread({
'Content-Type': 'application/json; charset=utf-8'
}, headers);
if (token) {
baseHeaders['Authorization'] = `Bearer ${token}`;
}
return Promise.resolve(baseHeaders);
}
function handleRequestError(error, responseStatus, backend) {
throw new _APIError.default(error.message, responseStatus, backend);
}
async function apiRequest(path, config, parser = response => parseResponse(response)) {
var _config$apiRoot;
const {
token,
backend
} = config,
props = _objectWithoutProperties(config, _excluded);
const options = _objectSpread({
cache: 'no-cache'
}, props);
const headers = await constructRequestHeaders({
headers: options.headers || {},
token
});
const baseUrl = (_config$apiRoot = config.apiRoot) !== null && _config$apiRoot !== void 0 ? _config$apiRoot : apiRoots[backend];
const url = constructUrlWithParams(`${baseUrl}${path}`, options.params);
let responseStatus = 500;
try {
const req = _unsentRequest.default.fromFetchArguments(url, _objectSpread(_objectSpread({}, options), {}, {
headers
}));
const response = await requestWithBackoff(api, req);
responseStatus = response.status;
const parsedResponse = await parser(response);
return parsedResponse;
} catch (error) {
return handleRequestError(error, responseStatus, backend);
}
}
async function getDefaultBranchName(configs) {
let apiPath;
const {
token,
backend,
repo,
apiRoot
} = configs;
switch (backend) {
case 'gitlab':
{
apiPath = `/projects/${encodeURIComponent(repo)}`;
break;
}
case 'bitbucket':
{
apiPath = `/repositories/${repo}`;
break;
}
default:
{
apiPath = `/repos/${repo}`;
}
}
const repoInfo = await apiRequest(apiPath, {
token,
backend,
apiRoot
});
let defaultBranchName;
if (backend === 'bitbucket') {
const {
mainbranch: {
name
}
} = repoInfo;
defaultBranchName = name;
} else {
const {
default_branch
} = repoInfo;
defaultBranchName = default_branch;
}
return defaultBranchName;
}
async function readFile(id, fetchContent, localForage, isText) {
const key = id ? isText ? `gh.${id}` : `gh.${id}.blob` : null;
const cached = key ? await localForage.getItem(key) : null;
if (cached) {
return cached;
}
const content = await fetchContent();
if (key) {
await localForage.setItem(key, content);
}
return content;
}
function getFileMetadataKey(id) {
return `gh.${id}.meta`;
}
async function readFileMetadata(id, fetchMetadata, localForage) {
const key = id ? getFileMetadataKey(id) : null;
const cached = key && (await localForage.getItem(key));
if (cached) {
return cached;
}
const metadata = await fetchMetadata();
if (key) {
await localForage.setItem(key, metadata);
}
return metadata;
}
/**
* Keywords for inferring a status that will provide a deploy preview URL.
*/
const PREVIEW_CONTEXT_KEYWORDS = ['deploy'];
/**
* Check a given status context string to determine if it provides a link to a
* deploy preview. Checks for an exact match against `previewContext` if given,
* otherwise checks for inclusion of a value from `PREVIEW_CONTEXT_KEYWORDS`.
*/
function isPreviewContext(context, previewContext) {
if (previewContext) {
return context === previewContext;
}
return PREVIEW_CONTEXT_KEYWORDS.some(keyword => context.includes(keyword));
}
let PreviewState = exports.PreviewState = /*#__PURE__*/function (PreviewState) {
PreviewState["Other"] = "other";
PreviewState["Success"] = "success";
return PreviewState;
}({});
/**
* Retrieve a deploy preview URL from an array of statuses. By default, a
* matching status is inferred via `isPreviewContext`.
*/
function getPreviewStatus(statuses, previewContext) {
return statuses.find(({
context
}) => {
return isPreviewContext(context, previewContext);
});
}
function getConflictingBranches(branchName) {
// for cms/posts/post-1, conflicting branches are cms/posts, cms
const parts = branchName.split('/');
parts.pop();
const conflictingBranches = parts.reduce((acc, _, index) => {
acc = [...acc, parts.slice(0, index + 1).join('/')];
return acc;
}, []);
return conflictingBranches;
}
async function throwOnConflictingBranches(branchName, getBranch, apiName) {
const possibleConflictingBranches = getConflictingBranches(branchName);
const conflictingBranches = await Promise.all(possibleConflictingBranches.map(b => getBranch(b).then(b => b.name).catch(() => '')));
const conflictingBranch = conflictingBranches.filter(Boolean)[0];
if (conflictingBranch) {
throw new _APIError.default(`Failed creating branch '${branchName}' since there is already a branch named '${conflictingBranch}'. Please delete the '${conflictingBranch}' branch and try again`, 500, apiName);
}
}