@swell/cli 2.0.8 → 2.0.10

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.
@@ -3,6 +3,7 @@ export default class AppThemeDev extends PushAppCommand {
3
3
  static examples: string[];
4
4
  static flags: {
5
5
  bundle: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
6
+ force: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
6
7
  local: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
7
8
  'no-push': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
8
9
  'storefront-id': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
@@ -21,6 +21,10 @@ export default class AppThemeDev extends PushAppCommand {
21
21
  bundle: Flags.boolean({
22
22
  description: 'bundle assets',
23
23
  }),
24
+ force: Flags.boolean({
25
+ default: false,
26
+ description: 'force push all configurations',
27
+ }),
24
28
  local: Flags.boolean({
25
29
  description: 'connect to a running local storefront app dev server',
26
30
  }),
@@ -45,13 +49,18 @@ export default class AppThemeDev extends PushAppCommand {
45
49
  }
46
50
  async run() {
47
51
  const { flags } = await this.parse(AppThemeDev);
48
- const { local } = flags;
52
+ const { force, local } = flags;
49
53
  const noPush = flags['no-push'];
50
54
  if (!(await this.ensureAppExists(undefined, false))) {
51
55
  return;
52
56
  }
57
+ if (local) {
58
+ const currentStore = localConfig.getDefaultStore();
59
+ const sessionId = localConfig.getSessionId(currentStore);
60
+ this.storefrontLocalUrl = this.localFrontendUrl(currentStore, sessionId);
61
+ }
53
62
  if (!noPush) {
54
- await this.pushAppConfigs();
63
+ await this.pushAppConfigs(force);
55
64
  }
56
65
  await this.getStorefrontToPush(flags);
57
66
  this.logStorefrontConnected();
@@ -136,7 +145,7 @@ export default class AppThemeDev extends PushAppCommand {
136
145
  const freePort = (await getPort({ port: portNumbers(3000, 3100) })) || FALLBACK_PORT;
137
146
  spinner.start(`Starting theme dev server on port ${freePort}...`);
138
147
  const storefrontUrl = local
139
- ? this.localFrontendUrl(currentStore, sessionId)
148
+ ? this.storefrontLocalUrl
140
149
  : this.storefrontFrontendUrl(currentStore, this.storefront, undefined, true);
141
150
  const bs = BrowserSync.create();
142
151
  bs.init({
@@ -80,7 +80,7 @@ class AppConfig {
80
80
  this.appPath = path.join(appPath, filePath);
81
81
  }
82
82
  if (filePathExists(this.appPath)) {
83
- this.fileHash = hashFile(this.appPath);
83
+ this.fileHash = hashFile(this.appPath, undefined, filePath);
84
84
  this.hash = this.hash || this.fileHash;
85
85
  this.fileData = fs.readFileSync(this.appPath);
86
86
  this.fileContent = this.fileData.toString('utf8');
@@ -131,18 +131,24 @@ declare function filePathExists(filePath: string): boolean;
131
131
  declare function writeFile(file: string, data: string): Promise<void>;
132
132
  declare function deleteFile(file: string): Promise<void>;
133
133
  declare function writeJsonFile(file: string, data?: any): Promise<void>;
134
- declare function hashFile(filePath: string, fileContents?: Buffer): string;
135
- declare function hashString(contents: string): string;
134
+ declare function hashFile(filePath: string, fileContents?: Buffer, relativePath?: string): string;
135
+ declare function hashString(...contents: (Buffer | string)[]): string;
136
136
  declare function appAssetImage(appPath: string, fileName: string): string | undefined;
137
137
  declare function appConfigFromFile(filePath: string, configType: ConfigType, appPath: string): AppConfig;
138
138
  declare function findAppConfig(app: App, filePath: string, configType: ConfigType): AppConfig | undefined;
139
+ interface BatchAppFiles {
140
+ configs: AppConfig[];
141
+ type: ConfigType;
142
+ label: string;
143
+ concurrency: number;
144
+ }
139
145
  /**
140
146
  * Get files to push in batches prioritized by dependencies
141
147
  *
142
- * @param configs - List of app configs
143
- * @returns {Array} Batches of files to push
148
+ * @param {AppConfig[]} configs - List of app configs
149
+ * @returns {BatchAppFiles[]} Batches of files to push
144
150
  */
145
- declare function batchAppFilesByType(configs: AppConfig[]): Array<any>;
151
+ declare function batchAppFilesByType(configs: AppConfig[]): BatchAppFiles[];
146
152
  export { PUSH_CONCURRENCY, App, AppVersion, ConfigInputFields, ConfigPaths, ConfigPathsType, ConfigType, FrontendProjectType, FrontendProjectTypes, StorefrontConfigPaths, StorefrontConfigTypes, SwellJsonFields, ThemeConfigPaths, ThemeConfigTypes, appAssetImage, appConfigFromFile, batchAppFilesByType, deleteFile, filePathExists, findAppConfig, getAppSlugId, getConfigTypeFromPath, getConfigTypeKeyFromValue, getFrontendProjectType, hashFile, hashString, writeFile, writeJsonFile, };
147
153
  export { AppConfig, FunctionProcessingError, IgnoringFileError, } from './app-config.js';
148
154
  export { allBaseFilesInDir, allConfigDirsPaths, allConfigFilesInDir, allConfigFilesPaths, allConfigFilesPathsByType, getAllConfigPaths, globAllFilesByPath, isPathDirectory, } from './paths.js';
@@ -1,4 +1,4 @@
1
- import { hash as blake3hash } from 'blake3-wasm';
1
+ import { createHash as createBlake3Hash } from 'blake3-wasm';
2
2
  import { pluralize, titleize } from 'inflection';
3
3
  import * as fs from 'node:fs';
4
4
  import * as path from 'node:path';
@@ -184,14 +184,18 @@ async function writeJsonFile(file, data = {}) {
184
184
  return writeFile(file, `${JSON.stringify(data, null, 2)}\n`);
185
185
  }
186
186
  // Hash method adapted from wrangler-sdk
187
- function hashFile(filePath, fileContents) {
187
+ function hashFile(filePath, fileContents, relativePath) {
188
188
  const contents = fileContents ?? fs.readFileSync(filePath);
189
- const base64Contents = contents.toString('base64');
190
- const extension = path.extname(filePath).slice(1);
191
- return hashString(base64Contents + extension);
189
+ // Include relative file path because we typically cache by hash including file metadata
190
+ // This allows de-duplication of cache entries for the same file contents
191
+ return hashString(contents, relativePath || '');
192
192
  }
193
- function hashString(contents) {
194
- return blake3hash(contents).toString('hex').slice(0, 32);
193
+ function hashString(...contents) {
194
+ const hash = createBlake3Hash();
195
+ for (const content of contents) {
196
+ hash.update(content);
197
+ }
198
+ return hash.digest('hex', { length: 16 });
195
199
  }
196
200
  function appAssetImage(appPath, fileName) {
197
201
  const assetsDirPath = path.join(appPath, ConfigPaths['ASSET']);
@@ -227,28 +231,26 @@ function findAppConfig(app, filePath, configType) {
227
231
  /**
228
232
  * Get files to push in batches prioritized by dependencies
229
233
  *
230
- * @param configs - List of app configs
231
- * @returns {Array} Batches of files to push
234
+ * @param {AppConfig[]} configs - List of app configs
235
+ * @returns {BatchAppFiles[]} Batches of files to push
232
236
  */
233
237
  function batchAppFilesByType(configs) {
234
238
  const modelsWithExtends = configs.filter((config) => config.type === 'model' && config.values.extends);
235
239
  const modelsWithoutExtends = configs.filter((config) => config.type === 'model' && !config.values.extends);
236
240
  const contentTypesWithCollection = configs.filter((config) => config.type === 'content' && config.values.collection);
237
241
  const contentTypesWithoutCollection = configs.filter((config) => config.type === 'content' && !config.values.collection);
238
- const prioritized = [
242
+ const prioritized = new Set([
239
243
  ...modelsWithExtends,
240
244
  ...modelsWithoutExtends,
241
245
  ...contentTypesWithCollection,
242
246
  ...contentTypesWithoutCollection,
243
- ];
247
+ ]);
244
248
  const sorted = [
245
- ...prioritized,
246
- ...configs.filter((config) => !prioritized.includes(config)),
249
+ ...prioritized.values(),
250
+ ...configs.filter((config) => !prioritized.has(config)),
247
251
  ];
248
252
  const batches = [];
249
- // eslint-disable-next-line guard-for-in
250
- for (const type in ConfigTypeBatchOrder) {
251
- const configType = ConfigTypeBatchOrder[type];
253
+ for (const configType of ConfigTypeBatchOrder) {
252
254
  const configsByType = sorted.filter((config) => config.type === configType);
253
255
  const label = configType === ConfigType.FILE ? 'App files' : titleize(configType);
254
256
  const concurrency = configType === ConfigType.NOTIFICATION || configType === ConfigType.ASSET
@@ -375,7 +375,7 @@ export class PushAppCommand extends RemoteAppCommand {
375
375
  const packageHash = localConfigs.find((config) => config.filePath === 'package.json')?.hash;
376
376
  frontendHashes += `|${packageHash}`;
377
377
  }
378
- return frontendHashes?.length > 0 ? hashString(frontendHashes) : undefined;
378
+ return frontendHashes.length > 0 ? hashString(frontendHashes) : undefined;
379
379
  }
380
380
  getFrontendProjectType(required = true) {
381
381
  this.frontendPath = path.join(this.appPath, 'frontend');
@@ -11,6 +11,7 @@ export declare abstract class RemoteAppCommand extends AppCommand {
11
11
  appCreated: boolean;
12
12
  appType: string;
13
13
  storefront?: any;
14
+ storefrontLocalUrl?: string;
14
15
  themeSyncApp?: boolean;
15
16
  appFrontendUrl(storeId: string, installedAppId: string): string;
16
17
  cleanRemoteConfigs(remoteConfigs: AppConfig[], deleteDeprecated?: boolean): Promise<AppConfig[]>;
@@ -26,6 +26,8 @@ export class RemoteAppCommand extends AppCommand {
26
26
  appType = 'any';
27
27
  // API instance of the target storefront, if applicable
28
28
  storefront = null;
29
+ // Indicates we are syncing with a local app frontend via proxy
30
+ storefrontLocalUrl = '';
29
31
  // Indicates app configs are being synced along with theme configs
30
32
  themeSyncApp = false;
31
33
  appFrontendUrl(storeId, installedAppId) {
@@ -334,6 +336,9 @@ export class RemoteAppCommand extends AppCommand {
334
336
  ...configJson,
335
337
  ...(themeId ? { theme_id: themeId } : undefined),
336
338
  ...(this.themeSyncApp ? { $sync_app: true } : undefined),
339
+ ...(this.storefrontLocalUrl
340
+ ? { $storefront_local_url: this.storefrontLocalUrl }
341
+ : undefined),
337
342
  },
338
343
  });
339
344
  }
@@ -352,7 +357,7 @@ export class RemoteAppCommand extends AppCommand {
352
357
  try {
353
358
  const localConfig = localBatchConfigs.find((config) => config.filePath === batchConfig.filePath);
354
359
  if (localConfig) {
355
- const hash = hashFile(batchConfig.appPath);
360
+ const hash = hashFile(batchConfig.appPath, undefined, batchConfig.filePath);
356
361
  if (hash === batchConfig.hash) {
357
362
  return false;
358
363
  }
@@ -374,9 +379,8 @@ export class RemoteAppCommand extends AppCommand {
374
379
  }
375
380
  if (pullFiles.length > 0) {
376
381
  while (pullFiles.length > 0) {
377
- await Promise.all(pullFiles
378
- .splice(0, PUSH_CONCURRENCY)
379
- .map(async (config) => {
382
+ // eslint-disable-next-line no-await-in-loop
383
+ await Promise.all(pullFiles.splice(0, PUSH_CONCURRENCY).map(async (config) => {
380
384
  await this.pullRemoteFile(config);
381
385
  pulled.push(config.id);
382
386
  }));
@@ -388,6 +392,7 @@ export class RemoteAppCommand extends AppCommand {
388
392
  const removeList = filesToRemove
389
393
  .map((config) => config.filePath)
390
394
  .join('\n');
395
+ // eslint-disable-next-line no-await-in-loop
391
396
  const confirmed = await confirm({
392
397
  default: false,
393
398
  message: `The following files are not in the remote app:\n\n${removeList}\n\nDo you want to delete these files locally?`,
@@ -397,6 +402,7 @@ export class RemoteAppCommand extends AppCommand {
397
402
  }
398
403
  }
399
404
  while (filesToRemove.length > 0) {
405
+ // eslint-disable-next-line no-await-in-loop
400
406
  await Promise.all(filesToRemove.splice(0, PUSH_CONCURRENCY).map(async (config) => {
401
407
  await this.removeLocalFile(config);
402
408
  removed.push(config.id);
@@ -474,10 +480,9 @@ export class RemoteAppCommand extends AppCommand {
474
480
  this.log(`${style.appConfigName(`${batch.label} ${updatedLog}`)}`);
475
481
  }
476
482
  if (pushFiles.length > 0) {
477
- while (pushFiles.length) {
478
- await Promise.all(pushFiles
479
- .splice(0, batch.concurrency)
480
- .map(async (config) => {
483
+ while (pushFiles.length > 0) {
484
+ // eslint-disable-next-line no-await-in-loop
485
+ await Promise.all(pushFiles.splice(0, batch.concurrency).map(async (config) => {
481
486
  const pushedConfig = await this.pushRemoteFile(config);
482
487
  if (pushedConfig) {
483
488
  pushed.push(pushedConfig.id);
@@ -490,7 +495,8 @@ export class RemoteAppCommand extends AppCommand {
490
495
  }
491
496
  // Remove any deleted files
492
497
  if (filesToRemove.length > 0) {
493
- while (filesToRemove.length) {
498
+ while (filesToRemove.length > 0) {
499
+ // eslint-disable-next-line no-await-in-loop
494
500
  await Promise.all(filesToRemove.splice(0, batch.concurrency).map(async (config) => {
495
501
  await this.removeRemoteFile(config);
496
502
  removed.push(config.id);
@@ -1473,6 +1473,12 @@
1473
1473
  "allowNo": false,
1474
1474
  "type": "boolean"
1475
1475
  },
1476
+ "force": {
1477
+ "description": "force push all configurations",
1478
+ "name": "force",
1479
+ "allowNo": false,
1480
+ "type": "boolean"
1481
+ },
1476
1482
  "local": {
1477
1483
  "description": "connect to a running local storefront app dev server",
1478
1484
  "name": "local",
@@ -1835,5 +1841,5 @@
1835
1841
  ]
1836
1842
  }
1837
1843
  },
1838
- "version": "2.0.8"
1844
+ "version": "2.0.10"
1839
1845
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swell/cli",
3
- "version": "2.0.8",
3
+ "version": "2.0.10",
4
4
  "type": "module",
5
5
  "description": "Swell's command line interface/utility",
6
6
  "keywords": [