@swell/cli 2.0.1-alpha.17 → 2.0.1-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-command.d.ts +1 -1
- package/dist/app-command.js +3 -3
- package/dist/commands/app/dev.d.ts +3 -5
- package/dist/commands/app/dev.js +3 -6
- package/dist/commands/app/frontend/deploy.d.ts +1 -3
- package/dist/commands/app/frontend/deploy.js +2 -5
- package/dist/commands/app/frontend/dev.d.ts +4 -6
- package/dist/commands/app/frontend/dev.js +12 -15
- package/dist/commands/app/info.js +1 -3
- package/dist/commands/app/init.js +2 -2
- package/dist/commands/app/install.js +0 -1
- package/dist/commands/app/pull.d.ts +3 -3
- package/dist/commands/app/pull.js +16 -16
- package/dist/commands/app/push.d.ts +4 -4
- package/dist/commands/app/push.js +28 -31
- package/dist/commands/app/release.d.ts +3 -3
- package/dist/commands/app/release.js +43 -45
- package/dist/commands/app/version.d.ts +4 -4
- package/dist/commands/app/version.js +28 -28
- package/dist/commands/create/app.d.ts +15 -15
- package/dist/commands/create/app.js +66 -66
- package/dist/commands/create/frontend.js +4 -7
- package/dist/commands/env.d.ts +12 -0
- package/dist/commands/env.js +31 -0
- package/dist/commands/login.js +1 -3
- package/dist/commands/logs.js +0 -2
- package/dist/commands/theme/dev.d.ts +6 -8
- package/dist/commands/theme/dev.js +30 -33
- package/dist/commands/theme/init.d.ts +1 -1
- package/dist/commands/theme/init.js +1 -1
- package/dist/commands/theme/pull.d.ts +7 -7
- package/dist/commands/theme/pull.js +21 -21
- package/dist/commands/theme/push.d.ts +7 -7
- package/dist/commands/theme/push.js +24 -27
- package/dist/create-app-command.d.ts +7 -7
- package/dist/create-app-command.js +146 -147
- package/dist/create-config-command.js +0 -2
- package/dist/env/local.d.ts +8 -0
- package/dist/env/local.js +7 -0
- package/dist/env/production.d.ts +8 -0
- package/dist/env/production.js +7 -0
- package/dist/env/review.d.ts +8 -0
- package/dist/env/review.js +7 -0
- package/dist/env/staging.d.ts +8 -0
- package/dist/env/staging.js +7 -0
- package/dist/lib/api.d.ts +3 -3
- package/dist/lib/api.js +35 -35
- package/dist/lib/app-config.d.ts +2 -2
- package/dist/lib/app-config.js +12 -11
- package/dist/lib/apps/app-config.d.ts +19 -19
- package/dist/lib/apps/app-config.js +59 -65
- package/dist/lib/apps/index.d.ts +56 -56
- package/dist/lib/apps/index.js +40 -42
- package/dist/lib/apps/paths.d.ts +1 -1
- package/dist/lib/apps/paths.js +4 -4
- package/dist/lib/config.d.ts +4 -1
- package/dist/lib/config.js +35 -1
- package/dist/lib/constants.js +7 -8
- package/dist/lib/create/index.js +2 -2
- package/dist/lib/stores.d.ts +4 -2
- package/dist/lib/stores.js +4 -2
- package/dist/push-app-command.d.ts +34 -34
- package/dist/push-app-command.js +391 -402
- package/dist/remote-app-command.d.ts +24 -24
- package/dist/remote-app-command.js +245 -259
- package/oclif.manifest.json +152 -119
- package/package.json +2 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { confirm } from '@inquirer/prompts';
|
|
1
2
|
import { inflect } from 'inflection';
|
|
3
|
+
import sortBy from 'lodash/sortBy.js';
|
|
2
4
|
import * as path from 'node:path';
|
|
3
5
|
import { default as ora } from 'ora';
|
|
4
|
-
import sortBy from 'lodash/sortBy.js';
|
|
5
|
-
import { confirm } from '@inquirer/prompts';
|
|
6
6
|
import { AppCommand } from './app-command.js';
|
|
7
7
|
import { readRcFile, writeRcFile } from './lib/app-config.js';
|
|
8
|
-
import { AppConfig,
|
|
8
|
+
import { AppConfig, FunctionProcessingError, IgnoringFileError, SwellJsonFields, allBaseFilesInDir, allConfigFilesPaths, appConfigFromFile, batchAppFilesByType, deleteFile, hashFile, writeFile, writeJsonFile, } from './lib/apps/index.js';
|
|
9
9
|
import { default as localConfig } from './lib/config.js';
|
|
10
|
-
import {
|
|
10
|
+
import { getAppFrontendHost, getLocalFrontendHost, getLoginHost, getStorefrontFrontendHost, } from './lib/constants.js';
|
|
11
11
|
import { toAppId } from './lib/create/index.js';
|
|
12
12
|
import style from './lib/style.js';
|
|
13
13
|
const PUSH_CONCURRENCY = 3;
|
|
@@ -17,37 +17,51 @@ const PUSH_CONCURRENCY = 3;
|
|
|
17
17
|
*/
|
|
18
18
|
export class RemoteAppCommand extends AppCommand {
|
|
19
19
|
// allows child commands to set orientation
|
|
20
|
+
static delayOrientation = false;
|
|
20
21
|
// most of the times this will be the target environment
|
|
21
22
|
static orientation;
|
|
22
|
-
static delayOrientation = false;
|
|
23
|
-
appType = 'any';
|
|
24
23
|
// API instance of the current app definition
|
|
25
24
|
app = {};
|
|
26
25
|
// Indicates the app was created during the command execution
|
|
27
26
|
appCreated = false;
|
|
27
|
+
appType = 'any';
|
|
28
28
|
// API instance of the target storefront, if applicable
|
|
29
29
|
storefront = null;
|
|
30
30
|
// Indicates app configs are being synced along with theme configs
|
|
31
31
|
themeSyncApp = false;
|
|
32
|
+
appFrontendUrl(storeId, installedAppId) {
|
|
33
|
+
return getAppFrontendHost(storeId, installedAppId);
|
|
34
|
+
}
|
|
35
|
+
async cleanRemoteConfigs(remoteConfigs, deleteDeprecated = true) {
|
|
36
|
+
// Delete remote configs that do not have file_path etc
|
|
37
|
+
const deprecatedConfigs = remoteConfigs.filter((config) => this.isConfigDeprecated(config));
|
|
38
|
+
if (deleteDeprecated && deprecatedConfigs.length > 0) {
|
|
39
|
+
const spinner = ora();
|
|
40
|
+
spinner.start('Cleaning up deprecated configurations, please wait...');
|
|
41
|
+
await Promise.all(deprecatedConfigs.map((config) => this.removeRemoteFile(config, false)));
|
|
42
|
+
spinner.stop();
|
|
43
|
+
}
|
|
44
|
+
const validConfigs = remoteConfigs.filter((config) => !this.isConfigDeprecated(config));
|
|
45
|
+
return validConfigs;
|
|
46
|
+
}
|
|
32
47
|
dashboardUrl(flags = {}, storeId) {
|
|
33
48
|
const { env } = flags;
|
|
34
49
|
const envFlag = env || this.ctor.orientation?.env;
|
|
35
50
|
const outputEnv = envFlag === 'live' ? '' : envFlag ? 'test' : '';
|
|
36
51
|
return `${getLoginHost(storeId)}/admin/${outputEnv ? `${outputEnv}/` : ''}apps/${this.app.id}`;
|
|
37
52
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
?
|
|
46
|
-
:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return getLocalFrontendHost(storeId, sessionId);
|
|
53
|
+
async deleteConfig(configId, filePath, appConfigId) {
|
|
54
|
+
const themeId = this.app.type === 'theme' && this.storefront?.theme_id;
|
|
55
|
+
return this.api.delete({
|
|
56
|
+
adminPath: `/apps/${this.app.id}/configs/${appConfigId || configId}`,
|
|
57
|
+
}, {
|
|
58
|
+
query: {
|
|
59
|
+
...(filePath ? { file_path: filePath } : undefined),
|
|
60
|
+
...(themeId ? { theme_id: themeId } : undefined),
|
|
61
|
+
...(appConfigId ? { app_config_id: appConfigId } : undefined),
|
|
62
|
+
...(this.themeSyncApp ? { $sync_app: true } : undefined),
|
|
63
|
+
},
|
|
64
|
+
});
|
|
51
65
|
}
|
|
52
66
|
async getApp(id = '') {
|
|
53
67
|
const app = await this.api.get({ adminPath: `/apps/${id}` });
|
|
@@ -78,8 +92,8 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
78
92
|
else if (!themeConfig && appConfig) {
|
|
79
93
|
app.configs.push(AppConfig.create({
|
|
80
94
|
...appConfig,
|
|
81
|
-
appPath: this.appPath,
|
|
82
95
|
appConfigId: appConfig.id,
|
|
96
|
+
appPath: this.appPath,
|
|
83
97
|
id: undefined,
|
|
84
98
|
}));
|
|
85
99
|
}
|
|
@@ -146,6 +160,12 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
146
160
|
}
|
|
147
161
|
return app;
|
|
148
162
|
}
|
|
163
|
+
async getConfigData(configId) {
|
|
164
|
+
const themeId = this.app.type === 'theme' && this.storefront?.theme_id;
|
|
165
|
+
return this.api.get({
|
|
166
|
+
adminPath: `/apps/${this.app.id}/configs/${configId}/data`,
|
|
167
|
+
}, { query: { ...(themeId ? { theme_id: themeId } : undefined) } });
|
|
168
|
+
}
|
|
149
169
|
async getCreateUpdateApp(updateApp) {
|
|
150
170
|
const spinner = ora();
|
|
151
171
|
let sourceApp;
|
|
@@ -160,7 +180,6 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
160
180
|
if (!this.swellConfig.store.id) {
|
|
161
181
|
throw new Error('App swell.json is missing or `id` is not defined.');
|
|
162
182
|
}
|
|
163
|
-
/* eslint-disable camelcase */
|
|
164
183
|
const body = {
|
|
165
184
|
...this.swellConfig.store,
|
|
166
185
|
// we store locally the private id as the id, but we need to switch it
|
|
@@ -172,7 +191,6 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
172
191
|
...(sourceApp?.id ? { source_id: sourceApp.id } : undefined),
|
|
173
192
|
};
|
|
174
193
|
const appLabel = this.app.type === 'theme' ? 'theme' : 'app';
|
|
175
|
-
/* eslint-enable camelcase */
|
|
176
194
|
let app = updateApp || {};
|
|
177
195
|
if (!app.id) {
|
|
178
196
|
// May already have a dev instance
|
|
@@ -220,45 +238,34 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
220
238
|
// check if app is already installed on store
|
|
221
239
|
return this.api.get({ adminPath: `/client/apps/${this.app.id}` }, {});
|
|
222
240
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async updateInstalledApp(version, spinner) {
|
|
235
|
-
await this.api.put({ adminPath: `/client/apps/${this.app.id}` }, {
|
|
236
|
-
body: { version },
|
|
237
|
-
spinner,
|
|
238
|
-
onAsyncGetPath: (response) => ({
|
|
239
|
-
adminPath: `/client/app-async-status/${response.id}`,
|
|
240
|
-
}),
|
|
241
|
-
});
|
|
241
|
+
getLocalAppConfigs() {
|
|
242
|
+
const localConfigs = [];
|
|
243
|
+
for (const { configFile, configType } of allConfigFilesPaths(this.appPath)) {
|
|
244
|
+
const config = appConfigFromFile(configFile, configType, this.appPath);
|
|
245
|
+
localConfigs.push(config);
|
|
246
|
+
}
|
|
247
|
+
for (const filePath of allBaseFilesInDir(this.appPath)) {
|
|
248
|
+
const config = appConfigFromFile(filePath, 'file', this.appPath);
|
|
249
|
+
localConfigs.push(config);
|
|
250
|
+
}
|
|
251
|
+
return localConfigs;
|
|
242
252
|
}
|
|
243
|
-
async
|
|
253
|
+
async getVersion(version) {
|
|
244
254
|
const versionsResponse = await this.api.get({ adminPath: `/apps/${this.app.id}/versions` }, {
|
|
245
255
|
query: {
|
|
256
|
+
limit: 1,
|
|
246
257
|
...(version ? { version } : undefined),
|
|
247
258
|
},
|
|
248
259
|
});
|
|
249
|
-
return versionsResponse.results
|
|
260
|
+
return versionsResponse.results?.[0];
|
|
250
261
|
}
|
|
251
|
-
async
|
|
262
|
+
async getVersions(version) {
|
|
252
263
|
const versionsResponse = await this.api.get({ adminPath: `/apps/${this.app.id}/versions` }, {
|
|
253
264
|
query: {
|
|
254
|
-
limit: 1,
|
|
255
265
|
...(version ? { version } : undefined),
|
|
256
266
|
},
|
|
257
267
|
});
|
|
258
|
-
return versionsResponse.results
|
|
259
|
-
}
|
|
260
|
-
async updateVersion(version, body, spinner) {
|
|
261
|
-
await this.handleRequestErrors(async () => this.api.put({ adminPath: `/apps/${this.app.id}/versions` }, { body: { version, ...body } }), () => spinner?.fail('Error updating version.'));
|
|
268
|
+
return versionsResponse.results || [];
|
|
262
269
|
}
|
|
263
270
|
async handleRequestErrors(request, spinnerError) {
|
|
264
271
|
let showedErrors = false;
|
|
@@ -299,6 +306,21 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
299
306
|
await this.logOrientation();
|
|
300
307
|
}
|
|
301
308
|
}
|
|
309
|
+
async installApp(version, spinner) {
|
|
310
|
+
return this.api.post({ adminPath: `/client/apps` }, {
|
|
311
|
+
body: { app_id: this.app.id, version },
|
|
312
|
+
onAsyncGetPath: (response) => ({
|
|
313
|
+
adminPath: `/client/app-async-status/${response.id}`,
|
|
314
|
+
}),
|
|
315
|
+
spinner,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
isConfigDeprecated(config) {
|
|
319
|
+
return !config.file_path;
|
|
320
|
+
}
|
|
321
|
+
localFrontendUrl(storeId, sessionId) {
|
|
322
|
+
return getLocalFrontendHost(storeId, sessionId);
|
|
323
|
+
}
|
|
302
324
|
async logOrientation() {
|
|
303
325
|
const klass = this.ctor;
|
|
304
326
|
const { flags } = await this.parse(klass);
|
|
@@ -316,191 +338,6 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
316
338
|
},
|
|
317
339
|
});
|
|
318
340
|
}
|
|
319
|
-
async getConfigData(configId) {
|
|
320
|
-
const themeId = this.app.type === 'theme' && this.storefront?.theme_id;
|
|
321
|
-
return await this.api.get({
|
|
322
|
-
adminPath: `/apps/${this.app.id}/configs/${configId}/data`,
|
|
323
|
-
}, { query: { ...(themeId ? { theme_id: themeId } : undefined) } });
|
|
324
|
-
}
|
|
325
|
-
async deleteConfig(configId, filePath, appConfigId) {
|
|
326
|
-
const themeId = this.app.type === 'theme' && this.storefront?.theme_id;
|
|
327
|
-
return this.api.delete({
|
|
328
|
-
adminPath: `/apps/${this.app.id}/configs/${appConfigId || configId}`,
|
|
329
|
-
}, {
|
|
330
|
-
query: {
|
|
331
|
-
...(filePath ? { file_path: filePath } : undefined),
|
|
332
|
-
...(themeId ? { theme_id: themeId } : undefined),
|
|
333
|
-
...(appConfigId ? { app_config_id: appConfigId } : undefined),
|
|
334
|
-
...(this.themeSyncApp ? { $sync_app: true } : undefined),
|
|
335
|
-
},
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
async pushRemoteFile(config, log = true) {
|
|
339
|
-
const spinner = ora();
|
|
340
|
-
log && spinner.start(`Pushing ${config.filePath}`);
|
|
341
|
-
try {
|
|
342
|
-
const postData = await config.postData();
|
|
343
|
-
if (!postData) {
|
|
344
|
-
spinner.stop();
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
const result = await this.handleRequestErrors(async () => this.postConfigData(postData), () => log && spinner.fail(`Error pushing ${config.filePath}`));
|
|
348
|
-
log && spinner.succeed(`${this.timestampStyled()} ${config.filePath}`);
|
|
349
|
-
return result;
|
|
350
|
-
}
|
|
351
|
-
catch (error) {
|
|
352
|
-
if (error instanceof IgnoringFileError) {
|
|
353
|
-
log &&
|
|
354
|
-
spinner.fail(`Ignoring file: ${error.message} in ${config.filePath}`);
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
if (error instanceof FunctionProcessingError) {
|
|
358
|
-
log &&
|
|
359
|
-
spinner.fail(`${error.message} ${error.originalError?.message?.toLowerCase()}`);
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
log && spinner.fail(error.message);
|
|
363
|
-
throw error;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
async pullRemoteFile(config, log = true) {
|
|
367
|
-
const spinner = ora();
|
|
368
|
-
log && spinner.start(`Pulling ${config.filePath}`);
|
|
369
|
-
try {
|
|
370
|
-
const result = await this.handleRequestErrors(async () => this.getConfigData(config.id), () => log && spinner.fail(`Error pulling ${config.filePath}`));
|
|
371
|
-
await writeFile(config.appPath, result);
|
|
372
|
-
log && spinner.succeed(`${this.timestampStyled()} ${config.filePath}`);
|
|
373
|
-
return result;
|
|
374
|
-
}
|
|
375
|
-
catch (error) {
|
|
376
|
-
log && spinner.fail(error.message);
|
|
377
|
-
throw error;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
async removeRemoteFile(config, log = true) {
|
|
381
|
-
const spinner = ora();
|
|
382
|
-
log && spinner.start(`Removing ${config.filePath}`);
|
|
383
|
-
const result = await this.handleRequestErrors(async () => this.deleteConfig(config.id, config.filePath, config.appConfigId), () => log && spinner.fail(`Error removing ${config.filePath}`));
|
|
384
|
-
log &&
|
|
385
|
-
spinner.warn(`${this.timestampStyled()} ${style.strike(config.filePath)}`);
|
|
386
|
-
return result;
|
|
387
|
-
}
|
|
388
|
-
async removeLocalFile(config, log = true) {
|
|
389
|
-
if (!config.fileData) {
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
const spinner = ora();
|
|
393
|
-
log && spinner.start(`Removing ${config.filePath}`);
|
|
394
|
-
const result = await this.handleRequestErrors(async () => deleteFile(config.appPath), () => log && spinner.fail(`Error removing ${config.filePath}`));
|
|
395
|
-
log &&
|
|
396
|
-
spinner.warn(`${this.timestampStyled()} ${style.strike(config.filePath)}`);
|
|
397
|
-
return result;
|
|
398
|
-
}
|
|
399
|
-
showErrors(errors, spinnerError) {
|
|
400
|
-
const actualErrors = Object.keys(errors).filter((err) => Object.prototype.hasOwnProperty.call(errors, err));
|
|
401
|
-
if (spinnerError) {
|
|
402
|
-
spinnerError();
|
|
403
|
-
}
|
|
404
|
-
else {
|
|
405
|
-
this.logError(inflect('Error', actualErrors.length));
|
|
406
|
-
}
|
|
407
|
-
for (const err of actualErrors) {
|
|
408
|
-
if (!errors[err]?.message)
|
|
409
|
-
continue;
|
|
410
|
-
// we need to display `id` instead of `private_id`
|
|
411
|
-
// developers don't have any knowledge of private ids
|
|
412
|
-
const displayErr = err === 'private_id' ? 'id' : err;
|
|
413
|
-
this.logError([
|
|
414
|
-
' -',
|
|
415
|
-
`${displayErr}:`.toLowerCase().replace(/^error:/, ''),
|
|
416
|
-
errors[err].message,
|
|
417
|
-
]
|
|
418
|
-
.filter(Boolean)
|
|
419
|
-
.join(' '));
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
swellRcPath() {
|
|
423
|
-
return path.join(this.appPath, '.swellrc');
|
|
424
|
-
}
|
|
425
|
-
getLocalAppConfigs() {
|
|
426
|
-
const localConfigs = [];
|
|
427
|
-
for (const { configFile, configType } of allConfigFilesPaths(this.appPath)) {
|
|
428
|
-
const config = appConfigFromFile(configFile, configType, this.appPath);
|
|
429
|
-
localConfigs.push(config);
|
|
430
|
-
}
|
|
431
|
-
for (const filePath of allBaseFilesInDir(this.appPath)) {
|
|
432
|
-
const config = appConfigFromFile(filePath, 'file', this.appPath);
|
|
433
|
-
localConfigs.push(config);
|
|
434
|
-
}
|
|
435
|
-
return localConfigs;
|
|
436
|
-
}
|
|
437
|
-
async pushAppConfigs(force) {
|
|
438
|
-
const pushed = [];
|
|
439
|
-
const removed = [];
|
|
440
|
-
const localConfigs = this.getLocalAppConfigs();
|
|
441
|
-
const remoteConfigs = await this.cleanRemoteConfigs(this.app?.configs || []);
|
|
442
|
-
// Do not allow more than 10K files
|
|
443
|
-
if (localConfigs.length > 10000) {
|
|
444
|
-
this.error(`Apps only support up to 10,000 files. Found ${localConfigs.length} files.`);
|
|
445
|
-
}
|
|
446
|
-
const batches = batchAppFilesByType(localConfigs);
|
|
447
|
-
for (const batch of batches) {
|
|
448
|
-
const remoteBatchConfigs = remoteConfigs.filter((config) => config.type === batch.type);
|
|
449
|
-
const pushFiles = batch.configs.filter((batchConfig) => {
|
|
450
|
-
const remoteConfig = remoteBatchConfigs.find((config) => {
|
|
451
|
-
return batchConfig.filePath === config.filePath;
|
|
452
|
-
});
|
|
453
|
-
return Boolean(force || !remoteConfig || remoteConfig.hash !== batchConfig.hash);
|
|
454
|
-
});
|
|
455
|
-
const filesToRemove = remoteBatchConfigs.filter((config) => {
|
|
456
|
-
return !batch.configs.find((batchConfig) => {
|
|
457
|
-
return batchConfig.filePath === config.filePath;
|
|
458
|
-
});
|
|
459
|
-
});
|
|
460
|
-
// Skip if there are no config changes
|
|
461
|
-
if (!pushFiles.length && !filesToRemove.length) {
|
|
462
|
-
continue;
|
|
463
|
-
}
|
|
464
|
-
const updatedLog = pushFiles.length > 0
|
|
465
|
-
? remoteBatchConfigs.length > 0
|
|
466
|
-
? `(${pushFiles.length}/${batch.configs.length})`
|
|
467
|
-
: `(${pushFiles.length})`
|
|
468
|
-
: '';
|
|
469
|
-
if (pushFiles.length > 0 || filesToRemove.length > 0) {
|
|
470
|
-
this.log(`${style.appConfigName(`${batch.label} ${updatedLog}`)}`);
|
|
471
|
-
}
|
|
472
|
-
if (pushFiles.length > 0) {
|
|
473
|
-
const pushConcurrency = batch.type === 'ASSET' ? 1 : PUSH_CONCURRENCY;
|
|
474
|
-
while (pushFiles.length) {
|
|
475
|
-
await Promise.all(pushFiles
|
|
476
|
-
.splice(0, pushConcurrency)
|
|
477
|
-
.map(async (config) => {
|
|
478
|
-
const pushedConfig = await this.pushRemoteFile(config);
|
|
479
|
-
if (pushedConfig) {
|
|
480
|
-
pushed.push(pushedConfig.id);
|
|
481
|
-
if (config.filePath === 'swell.json') {
|
|
482
|
-
this.app = await this.getApp(this.app.id);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}));
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
// Remove any deleted files
|
|
489
|
-
if (filesToRemove.length > 0) {
|
|
490
|
-
while (filesToRemove.length) {
|
|
491
|
-
await Promise.all(filesToRemove.splice(0, PUSH_CONCURRENCY).map(async (config) => {
|
|
492
|
-
await this.removeRemoteFile(config);
|
|
493
|
-
removed.push(config.id);
|
|
494
|
-
}));
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
this.log();
|
|
498
|
-
}
|
|
499
|
-
if (pushed.length === 0 && removed.length === 0) {
|
|
500
|
-
this.log(`${style.success('✔')} All files up to date.\n`);
|
|
501
|
-
}
|
|
502
|
-
return { pushed, removed };
|
|
503
|
-
}
|
|
504
341
|
async pullAppConfigs(force, confirmRemove = true) {
|
|
505
342
|
const pulled = [];
|
|
506
343
|
const removed = [];
|
|
@@ -525,13 +362,9 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
525
362
|
catch { }
|
|
526
363
|
return true;
|
|
527
364
|
});
|
|
528
|
-
const filesToRemove = localBatchConfigs.filter((config) =>
|
|
529
|
-
return !batch.configs.find((batchConfig) => {
|
|
530
|
-
return batchConfig.filePath === config.filePath;
|
|
531
|
-
});
|
|
532
|
-
});
|
|
365
|
+
const filesToRemove = localBatchConfigs.filter((config) => !batch.configs.some((batchConfig) => batchConfig.filePath === config.filePath));
|
|
533
366
|
// Skip if there are no config changes
|
|
534
|
-
if (
|
|
367
|
+
if (pullFiles.length === 0 && filesToRemove.length === 0) {
|
|
535
368
|
continue;
|
|
536
369
|
}
|
|
537
370
|
const updatedLog = pullFiles.length > 0
|
|
@@ -541,7 +374,8 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
541
374
|
this.log(`${style.appConfigName(`${batch.label} ${updatedLog}`)}`);
|
|
542
375
|
}
|
|
543
376
|
if (pullFiles.length > 0) {
|
|
544
|
-
while (pullFiles.length) {
|
|
377
|
+
while (pullFiles.length > 0) {
|
|
378
|
+
// eslint-disable-next-line no-await-in-loop
|
|
545
379
|
await Promise.all(pullFiles
|
|
546
380
|
.splice(0, PUSH_CONCURRENCY)
|
|
547
381
|
.map(async (config) => {
|
|
@@ -556,15 +390,17 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
556
390
|
const removeList = filesToRemove
|
|
557
391
|
.map((config) => config.filePath)
|
|
558
392
|
.join('\n');
|
|
393
|
+
// eslint-disable-next-line no-await-in-loop
|
|
559
394
|
const confirmed = await confirm({
|
|
560
|
-
message: `The following files are not in the remote app:\n\n${removeList}\n\nDo you want to delete these files locally?`,
|
|
561
395
|
default: false,
|
|
396
|
+
message: `The following files are not in the remote app:\n\n${removeList}\n\nDo you want to delete these files locally?`,
|
|
562
397
|
});
|
|
563
398
|
if (!confirmed) {
|
|
564
399
|
filesToRemove.splice(0);
|
|
565
400
|
}
|
|
566
401
|
}
|
|
567
|
-
while (filesToRemove.length) {
|
|
402
|
+
while (filesToRemove.length > 0) {
|
|
403
|
+
// eslint-disable-next-line no-await-in-loop
|
|
568
404
|
await Promise.all(filesToRemove.splice(0, PUSH_CONCURRENCY).map(async (config) => {
|
|
569
405
|
await this.removeLocalFile(config);
|
|
570
406
|
removed.push(config.id);
|
|
@@ -587,7 +423,7 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
587
423
|
}
|
|
588
424
|
else {
|
|
589
425
|
const swellConfigJson = {};
|
|
590
|
-
for (const key
|
|
426
|
+
for (const key of Object.keys(SwellJsonFields)) {
|
|
591
427
|
const configKey = SwellJsonFields[key];
|
|
592
428
|
swellConfigJson[configKey] = this.app[configKey];
|
|
593
429
|
}
|
|
@@ -598,40 +434,190 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
598
434
|
swellConfig.id = swellJson?.id;
|
|
599
435
|
return [swellConfig, ...allOtherConfigs];
|
|
600
436
|
}
|
|
601
|
-
|
|
602
|
-
|
|
437
|
+
async pullRemoteFile(config, log = true) {
|
|
438
|
+
const spinner = ora();
|
|
439
|
+
log && spinner.start(`Pulling ${config.filePath}`);
|
|
440
|
+
try {
|
|
441
|
+
const result = await this.handleRequestErrors(async () => this.getConfigData(config.id), () => log && spinner.fail(`Error pulling ${config.filePath}`));
|
|
442
|
+
await writeFile(config.appPath, result);
|
|
443
|
+
log && spinner.succeed(`${this.timestampStyled()} ${config.filePath}`);
|
|
444
|
+
return result;
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
log && spinner.fail(error.message);
|
|
448
|
+
throw error;
|
|
449
|
+
}
|
|
603
450
|
}
|
|
604
|
-
async
|
|
605
|
-
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
451
|
+
async pushAppConfigs(force) {
|
|
452
|
+
const pushed = [];
|
|
453
|
+
const removed = [];
|
|
454
|
+
const localConfigs = this.getLocalAppConfigs();
|
|
455
|
+
const remoteConfigs = await this.cleanRemoteConfigs(this.app?.configs || []);
|
|
456
|
+
// Do not allow more than 10K files
|
|
457
|
+
if (localConfigs.length > 10000) {
|
|
458
|
+
this.error(`Apps only support up to 10,000 files. Found ${localConfigs.length} files.`);
|
|
459
|
+
}
|
|
460
|
+
const batches = batchAppFilesByType(localConfigs);
|
|
461
|
+
for (const batch of batches) {
|
|
462
|
+
const remoteBatchConfigs = remoteConfigs.filter((config) => config.type === batch.type);
|
|
463
|
+
const pushFiles = batch.configs.filter((batchConfig) => {
|
|
464
|
+
const remoteConfig = remoteBatchConfigs.find((config) => batchConfig.filePath === config.filePath);
|
|
465
|
+
return Boolean(force || !remoteConfig || remoteConfig.hash !== batchConfig.hash);
|
|
466
|
+
});
|
|
467
|
+
const filesToRemove = remoteBatchConfigs.filter((config) => !batch.configs.some((batchConfig) => batchConfig.filePath === config.filePath));
|
|
468
|
+
// Skip if there are no config changes
|
|
469
|
+
if (pushFiles.length === 0 && filesToRemove.length === 0) {
|
|
470
|
+
continue;
|
|
612
471
|
}
|
|
613
|
-
|
|
472
|
+
const updatedLog = pushFiles.length > 0
|
|
473
|
+
? remoteBatchConfigs.length > 0
|
|
474
|
+
? `(${pushFiles.length}/${batch.configs.length})`
|
|
475
|
+
: `(${pushFiles.length})`
|
|
476
|
+
: '';
|
|
477
|
+
if (pushFiles.length > 0 || filesToRemove.length > 0) {
|
|
478
|
+
this.log(`${style.appConfigName(`${batch.label} ${updatedLog}`)}`);
|
|
479
|
+
}
|
|
480
|
+
if (pushFiles.length > 0) {
|
|
481
|
+
const pushConcurrency = batch.type === 'ASSET' ? 1 : PUSH_CONCURRENCY;
|
|
482
|
+
while (pushFiles.length > 0) {
|
|
483
|
+
// eslint-disable-next-line no-await-in-loop
|
|
484
|
+
await Promise.all(pushFiles
|
|
485
|
+
.splice(0, pushConcurrency)
|
|
486
|
+
.map(async (config) => {
|
|
487
|
+
const pushedConfig = await this.pushRemoteFile(config);
|
|
488
|
+
if (pushedConfig) {
|
|
489
|
+
pushed.push(pushedConfig.id);
|
|
490
|
+
if (config.filePath === 'swell.json') {
|
|
491
|
+
this.app = await this.getApp(this.app.id);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}));
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// Remove any deleted files
|
|
498
|
+
if (filesToRemove.length > 0) {
|
|
499
|
+
while (filesToRemove.length > 0) {
|
|
500
|
+
// eslint-disable-next-line no-await-in-loop
|
|
501
|
+
await Promise.all(filesToRemove.splice(0, PUSH_CONCURRENCY).map(async (config) => {
|
|
502
|
+
await this.removeRemoteFile(config);
|
|
503
|
+
removed.push(config.id);
|
|
504
|
+
}));
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
this.log();
|
|
614
508
|
}
|
|
615
|
-
|
|
616
|
-
|
|
509
|
+
if (pushed.length === 0 && removed.length === 0) {
|
|
510
|
+
this.log(`${style.success('✔')} All files up to date.\n`);
|
|
511
|
+
}
|
|
512
|
+
return { pushed, removed };
|
|
513
|
+
}
|
|
514
|
+
async pushRemoteFile(config, log = true) {
|
|
515
|
+
const spinner = ora();
|
|
516
|
+
log && spinner.start(`Pushing ${config.filePath}`);
|
|
517
|
+
try {
|
|
518
|
+
const postData = await config.postData();
|
|
519
|
+
if (!postData) {
|
|
520
|
+
spinner.stop();
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const result = await this.handleRequestErrors(async () => this.postConfigData(postData), () => log && spinner.fail(`Error pushing ${config.filePath}`));
|
|
524
|
+
log && spinner.succeed(`${this.timestampStyled()} ${config.filePath}`);
|
|
525
|
+
return result;
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
if (error instanceof IgnoringFileError) {
|
|
529
|
+
log &&
|
|
530
|
+
spinner.fail(`Ignoring file: ${error.message} in ${config.filePath}`);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
if (error instanceof FunctionProcessingError) {
|
|
534
|
+
log &&
|
|
535
|
+
spinner.fail(`${error.message} ${error.originalError?.message?.toLowerCase()}`);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
log && spinner.fail(error.message);
|
|
539
|
+
throw error;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
async removeLocalFile(config, log = true) {
|
|
543
|
+
if (!config.fileData) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const spinner = ora();
|
|
547
|
+
log && spinner.start(`Removing ${config.filePath}`);
|
|
548
|
+
const result = await this.handleRequestErrors(async () => deleteFile(config.appPath), () => log && spinner.fail(`Error removing ${config.filePath}`));
|
|
549
|
+
log &&
|
|
550
|
+
spinner.warn(`${this.timestampStyled()} ${style.strike(config.filePath)}`);
|
|
551
|
+
return result;
|
|
552
|
+
}
|
|
553
|
+
async removeRemoteFile(config, log = true) {
|
|
554
|
+
const spinner = ora();
|
|
555
|
+
log && spinner.start(`Removing ${config.filePath}`);
|
|
556
|
+
const result = await this.handleRequestErrors(async () => this.deleteConfig(config.id, config.filePath, config.appConfigId), () => log && spinner.fail(`Error removing ${config.filePath}`));
|
|
557
|
+
log &&
|
|
558
|
+
spinner.warn(`${this.timestampStyled()} ${style.strike(config.filePath)}`);
|
|
559
|
+
return result;
|
|
560
|
+
}
|
|
561
|
+
showErrors(errors, spinnerError) {
|
|
562
|
+
const actualErrors = Object.keys(errors).filter((err) => Object.prototype.hasOwnProperty.call(errors, err));
|
|
563
|
+
if (spinnerError) {
|
|
564
|
+
spinnerError();
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
this.logError(inflect('Error', actualErrors.length));
|
|
568
|
+
}
|
|
569
|
+
for (const err of actualErrors) {
|
|
570
|
+
if (!errors[err]?.message)
|
|
571
|
+
continue;
|
|
572
|
+
// we need to display `id` instead of `private_id`
|
|
573
|
+
// developers don't have any knowledge of private ids
|
|
574
|
+
const displayErr = err === 'private_id' ? 'id' : err;
|
|
575
|
+
this.logError([
|
|
576
|
+
' -',
|
|
577
|
+
`${displayErr}:`.toLowerCase().replace(/^error:/, ''),
|
|
578
|
+
errors[err].message,
|
|
579
|
+
]
|
|
580
|
+
.filter(Boolean)
|
|
581
|
+
.join(' '));
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
storefrontFrontendUrl(storeId, storefront, branchId, preview = true) {
|
|
585
|
+
const storefrontSlug = storefront && preview
|
|
586
|
+
? `${storeId}--${storefront.id.toLowerCase()}`
|
|
587
|
+
: storeId;
|
|
588
|
+
return getStorefrontFrontendHost(storefrontSlug, branchId);
|
|
589
|
+
}
|
|
590
|
+
swellRcPath() {
|
|
591
|
+
return path.join(this.appPath, '.swellrc');
|
|
617
592
|
}
|
|
618
593
|
timestampStyled() {
|
|
619
594
|
const date = new Date();
|
|
620
595
|
return style.dim(`[${date.toLocaleTimeString()}]`);
|
|
621
596
|
}
|
|
597
|
+
async updateInstalledApp(version, spinner) {
|
|
598
|
+
await this.api.put({ adminPath: `/client/apps/${this.app.id}` }, {
|
|
599
|
+
body: { version },
|
|
600
|
+
onAsyncGetPath: (response) => ({
|
|
601
|
+
adminPath: `/client/app-async-status/${response.id}`,
|
|
602
|
+
}),
|
|
603
|
+
spinner,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
async updateVersion(version, body, spinner) {
|
|
607
|
+
await this.handleRequestErrors(async () => this.api.put({ adminPath: `/apps/${this.app.id}/versions` }, { body: { version, ...body } }), () => spinner?.fail('Error updating version.'));
|
|
608
|
+
}
|
|
622
609
|
/**
|
|
623
610
|
* Reads the .swellrc file and sets up the remote app configuration.
|
|
624
611
|
*
|
|
625
612
|
* @returns App
|
|
626
613
|
*/
|
|
627
614
|
initApp() {
|
|
628
|
-
|
|
615
|
+
const app = this.swellConfig.store || {};
|
|
629
616
|
// id refers to remote app id, so it's undefined initially
|
|
630
617
|
app.id = undefined;
|
|
631
618
|
// swell.json id refers to private id
|
|
632
619
|
if (this.swellConfig.get('id')) {
|
|
633
620
|
// use the same formula as the backend api
|
|
634
|
-
// eslint-disable-next-line camelcase
|
|
635
621
|
app.private_id = toAppId(this.swellConfig.get('id'));
|
|
636
622
|
}
|
|
637
623
|
// Default name to private id
|