@vtex/api
Version:
VTEX I/O API client
510 lines (509 loc) • 22.8 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Apps = void 0;
const archiver_1 = __importDefault(require("archiver"));
const tar_fs_1 = require("tar-fs");
const zlib_1 = require("zlib");
const HttpClient_1 = require("../../HttpClient");
const utils_1 = require("../../utils");
const appsStaleIfError_1 = require("../../utils/appsStaleIfError");
const InfraClient_1 = require("./InfraClient");
const createRoutes = ({ account, workspace }) => {
const routes = {
Acknowledge: (app, service) => `${routes.App(app)}/acknowledge/${service}`,
App: (app) => `${routes.Apps()}/${app}`,
AppBundle: (locator, path) => `${routes.AppOrRegistry(locator)}/bundle/${path}`,
AppOrRegistry: ({ name, version, build }) => build
? `${routes.Apps()}/${name}@${version}+${build}`
: `${routes.Registry()}/${name}/${version}`,
Apps: () => `${routes.Workspace}/apps`,
Dependencies: () => `${routes.Workspace}/dependencies`,
File: (locator, path) => `${routes.Files(locator)}/${path}`,
FileFromApps: (app, path) => `${routes.Workspace}/apps/${app}/files/${path}`,
Files: (locator) => `${routes.AppOrRegistry(locator)}/files`,
Link: (app) => `${routes.Workspace}/v2/links/${app}`,
Links: () => `${routes.Workspace}/links`,
Master: `/${account}/master`,
Meta: () => `${routes.Workspace}/v2/apps`,
Registry: () => `${routes.Master}/registry`,
ResolveDependencies: () => `${routes.Workspace}/dependencies/_resolve`,
ResolveDependenciesWithManifest: () => `${routes.Workspace}/v2/apps/_resolve`,
Settings: (app) => `${routes.App(app)}/settings`,
Unlink: (app) => `${routes.Links()}/${app}`,
Workspace: `/${account}/${workspace}`,
};
return routes;
};
const getVendorAndName = ({ id }) => (0, utils_1.removeVersionFromAppId)(id);
const notFound = (e) => {
if (e.response && e.response.status === 404) {
return {};
}
throw e;
};
const zipObj = (keys, values) => {
let idx = 0;
const len = Math.min(keys.length, values.length);
const out = {};
while (idx < len) {
out[keys[idx]] = values[idx];
idx += 1;
}
return out;
};
// 🚨🚨🚨
// In order to make changes in here, please also change the colossus App so we
// share the same cache key and do NOT call Apps unecessarily
const workspaceFields = [
'_activationDate',
'_buildFeatures',
'_isRoot',
'_resolvedDependencies',
'credentialType',
'link',
'name',
'registry',
'settingsSchema',
'vendor',
'version',
].join(',');
class Apps extends InfraClient_1.InfraClient {
get routes() {
return this._routes;
}
constructor(context, options) {
super('apps@0.x', context, options, true);
this.installApp = (descriptor, tracingConfig) => {
if (descriptor.startsWith('infra:service-')) {
return this.installRuntime(descriptor, tracingConfig);
}
const metric = 'apps-install';
return this.http.post(this.routes.Apps(), { id: descriptor }, {
metric,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.uninstallApp = (app, tracingConfig) => {
const metric = 'apps-uninstall';
return this.http.delete(this.routes.App(app), {
metric,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.acknowledgeApp = (app, service, tracingConfig) => {
const metric = 'apps-ack';
return this.http.put(this.routes.Acknowledge(app, service), null, {
metric,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.link = async (app, files, { zlib } = {}, tracingConfig) => {
if (!(files[0] && files[0].path)) {
throw new Error('Argument files must be an array of {path, content}, where content can be a String, a Buffer or a ReadableStream.');
}
const emptyChanges = files.filter(file => !file.content);
if (emptyChanges.length > 0) {
throw new Error(`Missing content for paths: ${emptyChanges.map(e => e.path).join('; ')}`);
}
const indexOfManifest = files.findIndex(({ path }) => path === 'manifest.json');
if (indexOfManifest === -1) {
throw new Error('No manifest.json file found in files.');
}
const zip = (0, archiver_1.default)('zip', { zlib });
// Throw stream errors so they reject the promise chain.
zip.on('error', (e) => {
throw e;
});
const metric = 'apps-link';
const request = this.http.put(this.routes.Link(app), zip, {
headers: { 'Content-Type': 'application/zip' },
metric,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
files.forEach(({ content, path }) => zip.append(content, { name: path }));
const finalize = zip.finalize();
try {
const [response] = await Promise.all([request, finalize]);
response.bundleSize = zip.pointer();
return response;
}
catch (e) {
e.bundleSize = zip.pointer();
throw e;
}
};
this.patch = async (app, changes, { zlib } = {}, tracingConfig) => {
if (!(changes[0] && changes[0].path)) {
throw new Error('Argument changes must be an array of {path, content}, where content can be a String, a Buffer or a ReadableStream.');
}
const files = changes.filter(change => !!change.content);
const deletedFiles = changes
.filter(change => !change.content)
.map(change => change.path)
.join(':');
const zip = (0, archiver_1.default)('zip', { zlib });
// Throw stream errors so they reject the promise chain.
zip.on('error', (e) => {
throw e;
});
const metric = 'apps-patch';
const request = this.http.patch(this.routes.Link(app), zip, {
headers: { 'Content-Type': 'application/zip' },
metric,
params: { deletedFiles },
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
files.forEach(({ content, path }) => zip.append(content, { name: path }));
const finalize = zip.finalize();
const [response] = await Promise.all([request, finalize]);
return response;
};
this.unlink = (app, tracingConfig) => {
return this.http.delete(this.routes.Unlink(app), {
tracing: {
requestSpanNameSuffix: 'apps-unlink',
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.unlinkAll = (tracingConfig) => {
return this.http.delete(this.routes.Links(), {
tracing: {
requestSpanNameSuffix: 'apps-unlink-all',
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.saveAppSettings = (app, settings, tracingConfig) => {
const headers = { 'Content-Type': 'application/json' };
const metric = 'apps-save';
return this.http.put(this.routes.Settings(app), settings, {
headers,
metric,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.listApps = ({ oldVersion, since, service } = {}, tracingConfig) => {
const params = {
oldVersion,
service,
since,
};
const metric = 'apps-list';
const inflightKey = HttpClient_1.inflightUrlWithQuery;
return this.http.get(this.routes.Apps(), {
inflightKey,
metric,
params,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.listAppFiles = (app, { prefix, nextMarker } = {}, tracingConfig) => {
const locator = (0, utils_1.parseAppId)(app);
const linked = !!locator.build;
const params = {
marker: nextMarker,
prefix,
};
const metric = linked ? 'apps-list-files' : 'registry-list-files';
const inflightKey = HttpClient_1.inflightUrlWithQuery;
return this.http.get(this.routes.Files(locator), {
inflightKey,
metric,
params,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.listLinks = (tracingConfig) => {
const inflightKey = HttpClient_1.inflightURL;
const metric = 'apps-list-links';
return this.http.get(this.routes.Links(), {
inflightKey,
metric,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.getAppFile = (app, path, staleIfError, tracingConfig) => {
const { logger } = this.context;
const locator = (0, utils_1.parseAppId)(app);
const linked = !!locator.build;
const inflightKey = HttpClient_1.inflightURL;
if (staleIfError && this.memoryCache) {
(0, appsStaleIfError_1.saveVersion)(app, this.memoryCache);
}
const metric = linked ? 'apps-get-file' : 'registry-get-file';
try {
return this.http.getBuffer(this.routes.File(locator, path), {
cacheable: linked ? HttpClient_1.CacheType.Memory : HttpClient_1.CacheType.Disk,
inflightKey,
metric,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
}
catch (error) {
logger.error({ error, message: 'getAppFile failed', app, path });
if (staleIfError && this.memoryCache) {
return (0, appsStaleIfError_1.getFallbackFile)(app, path, this.memoryCache, this);
}
throw error;
}
};
this.getAppJSON = (app, path, nullIfNotFound, tracingConfig) => {
const locator = (0, utils_1.parseAppId)(app);
const linked = !!locator.build;
const inflightKey = HttpClient_1.inflightURL;
const metric = linked ? 'apps-get-json' : 'registry-get-json';
return this.http.get(this.routes.File(locator, path), {
cacheable: linked ? HttpClient_1.CacheType.Memory : HttpClient_1.CacheType.Any,
inflightKey,
metric,
nullIfNotFound,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.getFileFromApps = (app, path, nullIfNotFound, tracingConfig) => {
const inflightKey = HttpClient_1.inflightURL;
const metric = 'get-file-from-apps';
return this.http.get(this.routes.FileFromApps(app, path), {
cacheable: HttpClient_1.CacheType.Memory,
inflightKey,
metric,
nullIfNotFound,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.getAppFileStream = (app, path, tracingConfig) => {
const locator = (0, utils_1.parseAppId)(app);
const metric = locator.build ? 'apps-get-file-s' : 'registry-get-file-s';
return this.http.getStream(this.routes.File(locator, path), {
metric,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.getApp = (app, tracingConfig) => {
const metric = 'apps-get-app';
const inflightKey = HttpClient_1.inflightURL;
return this.http.get(this.routes.App(app), {
inflightKey,
metric,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.getAppSettings = (app, tracingConfig) => {
const inflightKey = HttpClient_1.inflightURL;
const metric = 'apps-get-settings';
return this.http.get(this.routes.Settings(app), {
inflightKey,
metric,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.getAllAppsSettings = (listAppsOptions = {}, tracingConfig) => {
return this.listApps(listAppsOptions, tracingConfig).then(({ data: installedApps }) => {
const names = installedApps.map(getVendorAndName);
const settingsPromises = names.map(vendorAndName => this.getAppSettings(vendorAndName, tracingConfig).catch(notFound));
return Promise.all(settingsPromises).then((settings) => {
return zipObj(names, settings);
});
});
};
this.getAppBundle = (app, bundlePath, generatePackageJson, tracingConfig) => {
const locator = (0, utils_1.parseAppId)(app);
const params = generatePackageJson && { _packageJSONEngine: 'npm', _packageJSONFilter: 'vtex.render-builder@x' };
const metric = locator.build ? 'apps-get-bundle' : 'registry-get-bundle';
return this.http.getStream(this.routes.AppBundle(locator, bundlePath), {
headers: {
Accept: 'application/x-gzip',
'Accept-Encoding': 'gzip',
},
metric,
params,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.unpackAppBundle = (app, bundlePath, unpackPath, generatePackageJson, tracingConfig) => {
return this.getAppBundle(app, bundlePath, generatePackageJson, tracingConfig)
.then(stream => stream
.pipe((0, zlib_1.createGunzip)())
.pipe((0, tar_fs_1.extract)(unpackPath)));
};
this.getAppsMetaInfos = async (filter, staleWhileRevalidate = true, tracingConfig) => {
var _a;
const { account, production, recorder, workspace, logger } = this.context;
const metric = 'get-apps-meta';
const inflightKey = HttpClient_1.inflightURL;
const key = (0, appsStaleIfError_1.getMetaInfoKey)(account, workspace);
const cachedResponse = production && staleWhileRevalidate
? await ((_a = this.diskCache) === null || _a === void 0 ? void 0 : _a.get(key))
: undefined;
if (cachedResponse && recorder) {
recorder.record(cachedResponse.headers);
}
const metaInfoPromise = this.http
.getRaw(this.routes.Meta(), {
ignoreRecorder: Boolean(cachedResponse),
inflightKey,
metric,
params: { fields: workspaceFields },
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
})
.then(response => {
const { data, headers: responseHeaders } = response;
if (this.diskCache && production) {
this.diskCache.set(key, {
appsMetaInfo: data.apps || [],
headers: responseHeaders,
});
}
return response;
});
let appsMetaInfo;
if (cachedResponse) {
appsMetaInfo = cachedResponse.appsMetaInfo;
metaInfoPromise.catch(error => {
logger.warn({ message: 'Unable to update stale cache', error });
});
}
else {
appsMetaInfo = await metaInfoPromise.then(response => response.data.apps);
}
if (filter) {
return appsMetaInfo.filter(appMeta => { var _a; return (_a = appMeta === null || appMeta === void 0 ? void 0 : appMeta._resolvedDependencies) === null || _a === void 0 ? void 0 : _a.filter; });
}
return appsMetaInfo;
};
this.getDependencies = (filter = '', tracingConfig) => {
const params = { filter };
const metric = 'apps-get-deps';
const inflightKey = HttpClient_1.inflightUrlWithQuery;
return this.http.get(this.routes.Dependencies(), {
inflightKey,
metric,
params,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.updateDependencies = (tracingConfig) => {
const metric = 'apps-update-deps';
return this.http.put(this.routes.Dependencies(), null, {
metric,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.updateDependency = (name, version, registry, tracingConfig) => {
const metric = 'apps-update-dep';
return this.http.patch(this.routes.Apps(), [{ name, version, registry }], {
metric,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.resolveDependencies = (apps, registries, filter = '', tracingConfig) => {
const params = { apps, registries, filter };
const metric = 'apps-resolve-deps';
const inflightKey = HttpClient_1.inflightUrlWithQuery;
return this.http.get(this.routes.ResolveDependencies(), {
inflightKey,
metric,
params,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.resolveDependenciesWithManifest = (manifest, filter = '', tracingConfig) => {
const params = { filter };
const metric = 'apps-resolve-deps-m';
return this.http.post(this.routes.ResolveDependenciesWithManifest(), manifest, {
metric,
params,
tracing: {
requestSpanNameSuffix: metric,
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.installRuntime = (descriptor, tracingConfig) => {
const { account, workspace } = this.context;
const [name, version] = descriptor.split('@');
return this.http.patch(`http://apps.aws-us-east-1.vtex.io/${account}/${workspace}/apps`, [
{
name,
version,
},
], {
tracing: {
requestSpanNameSuffix: 'apps-install-runtime',
...tracingConfig === null || tracingConfig === void 0 ? void 0 : tracingConfig.tracing,
},
});
};
this.diskCache = options && options.diskCache;
this.memoryCache = options && options.memoryCache;
this._routes = createRoutes(context);
}
}
exports.Apps = Apps;