@swell/cli
Version:
Swell's command line interface/utility
169 lines (168 loc) • 5.74 kB
JavaScript
import fetch from 'node-fetch';
import { stringify } from 'qs';
import config from './config.js';
import { API_BASE_URL, getAdminApiBaseUrl } from './constants.js';
const defaultHeaders = (opts) => ({
'Content-Type': 'application/json',
...opts,
});
const authenticatedHeaders = (storeId, opts = {}) => {
let { secretKey, sessionId, ...moreOpts } = opts;
sessionId ??= config.getSessionId(storeId);
if (secretKey) {
return defaultHeaders({
Authorization: `Basic ${Buffer.from(`${storeId}:${secretKey}`).toString('base64')}`,
...moreOpts,
});
}
if (sessionId) {
return defaultHeaders({
'X-Session': sessionId,
...moreOpts,
});
}
return defaultHeaders(moreOpts);
};
export default class Api {
envId;
secretKey;
storeId;
constructor(storeId, envId, secretKey) {
this.storeId = storeId;
this.envId = envId;
this.secretKey = secretKey;
}
async api(pathOpts, options) {
const storeId = this.storeId || config.getDefaultStore();
let path;
const fetchOptions = {
...options,
headers: {
'User-Agent': 'swell-cli/2.0',
...options?.headers,
...(this.envId ? { 'Swell-Env': this.envId } : undefined),
...authenticatedHeaders(storeId, {
secretKey: this.secretKey,
sessionId: options.sessionId,
}),
},
};
path = this.isAdmin()
? `${getAdminApiBaseUrl(storeId)}/${this.truncatePath(pathOpts.adminPath)}`
: `${API_BASE_URL}/${this.truncatePath(pathOpts.backendPath)}`;
if (options.query) {
path += `?${stringify(options.query)}`;
}
path = path
.replace('${STORE_ID}', storeId) // replace store id in template string
.replaceAll(/([^:]\/)\/+/g, '$1'); // remove duplicate slashes
if (!path)
throw new Error('Operation not supported.');
const res = await fetch(path, fetchOptions);
const resData = await res.text();
if (!res.ok) {
// Try to parse error message
let errorJson;
try {
errorJson = JSON.parse(resData);
}
catch {
// ignore
}
const error = new Error(errorJson?.error?.message || errorJson?.error || resData);
error.status = res.status;
throw error;
}
try {
return JSON.parse(resData);
}
catch (error) {
// respData could be "{}" (length: >=2) but it shouldn't be "{", " " (whitespace - length: 1) or
// "" (empty response - length: 0) while logging the error.
if (resData.length >= 2)
console.log(error);
return null;
}
}
async delete(pathOpts, options = {}) {
// ensure post body is stringified
if (options.body)
options.body = JSON.stringify(options.body);
const response = await this.api(pathOpts, {
method: 'DELETE',
...options,
});
return this.waitForAsyncResponse(response, pathOpts, options);
}
async get(pathOpts, options = {}) {
return this.api(pathOpts, {
method: 'GET',
...options,
});
}
isAdmin() {
return !this.secretKey;
}
async post(pathOpts, options = {}) {
// ensure post body is stringified
if (options.body)
options.body = JSON.stringify(options.body);
const response = await this.api(pathOpts, {
method: 'POST',
...options,
});
return this.waitForAsyncResponse(response, pathOpts, options);
}
async put(pathOpts, options = {}) {
// ensure post body is stringified
if (options.body)
options.body = JSON.stringify(options.body);
const response = await this.api(pathOpts, {
method: 'PUT',
...options,
});
return this.waitForAsyncResponse(response, pathOpts, options);
}
async setStoreEnv(storeId, envId) {
if (envId) {
const hasTestEnv = await this.isTestEnvEnabled();
if (!hasTestEnv) {
throw new Error('Test environment is not enabled for this store.');
}
}
this.storeId = storeId;
this.envId = envId;
}
async isTestEnvEnabled() {
const clientRecord = await this.get({
adminPath: `/client`,
});
return clientRecord.test_enabled !== false;
}
truncatePath(path) {
return path?.startsWith('/') ? path.slice(1) : path;
}
async waitForAsyncResponse(response, pathOpts, options = {}) {
const { onAsyncGetPath, spinner } = options;
if (!response?.async_progressing || !onAsyncGetPath) {
return response;
}
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
const asyncResponse = await this.api(onAsyncGetPath(response), {
method: 'GET',
onAsyncGetPath,
});
if (asyncResponse?.async_error) {
throw new Error(asyncResponse.async_error);
}
if (!asyncResponse?.async_progressing && spinner) {
spinner.suffixText = '';
}
else if (asyncResponse?.async_progress > 0 && spinner) {
spinner.suffixText = `${asyncResponse.async_progress}%`;
}
return this.waitForAsyncResponse(asyncResponse, pathOpts, options);
}
}