@swell/cli 2.9.31 → 2.9.33

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.
Files changed (57) hide show
  1. package/dist/commands/app/action.d.ts +25 -0
  2. package/dist/commands/app/action.js +233 -0
  3. package/dist/commands/app/dev.js +2 -1
  4. package/dist/commands/app/frontend/deploy.d.ts +1 -0
  5. package/dist/commands/app/frontend/deploy.js +4 -0
  6. package/dist/commands/app/frontend/dev.js +4 -2
  7. package/dist/commands/app/push.d.ts +3 -0
  8. package/dist/commands/app/push.js +100 -4
  9. package/dist/commands/app/version.js +3 -0
  10. package/dist/commands/create/function.js +14 -4
  11. package/dist/commands/create/model.js +9 -24
  12. package/dist/commands/create/webhook.js +7 -13
  13. package/dist/commands/inspect/components.d.ts +31 -0
  14. package/dist/commands/inspect/components.js +110 -0
  15. package/dist/commands/inspect/extensions.js +18 -9
  16. package/dist/commands/inspect/frontend.d.ts +37 -0
  17. package/dist/commands/inspect/frontend.js +75 -0
  18. package/dist/commands/inspect/functions.d.ts +11 -1
  19. package/dist/commands/inspect/functions.js +89 -6
  20. package/dist/commands/inspect/index.js +2 -0
  21. package/dist/commands/schema.js +5 -0
  22. package/dist/help/custom-help.js +5 -1
  23. package/dist/inspect-resource-command.d.ts +1 -1
  24. package/dist/inspect-resource-command.js +8 -2
  25. package/dist/lib/api-errors.d.ts +1 -0
  26. package/dist/lib/api-errors.js +28 -0
  27. package/dist/lib/apps/actions.d.ts +45 -0
  28. package/dist/lib/apps/actions.js +114 -0
  29. package/dist/lib/apps/components.d.ts +7 -0
  30. package/dist/lib/apps/components.js +10 -0
  31. package/dist/lib/apps/extension-manifest.d.ts +1 -0
  32. package/dist/lib/apps/extension-manifest.js +43 -0
  33. package/dist/lib/apps/index.d.ts +1 -0
  34. package/dist/lib/apps/index.js +30 -0
  35. package/dist/lib/apps/listing.d.ts +1 -0
  36. package/dist/lib/apps/listing.js +41 -0
  37. package/dist/lib/apps/paths.d.ts +8 -0
  38. package/dist/lib/apps/paths.js +34 -4
  39. package/dist/lib/bundle.js +26 -41
  40. package/dist/lib/create/function.d.ts +1 -1
  41. package/dist/lib/create/function.js +9 -5
  42. package/dist/lib/create/index.js +2 -1
  43. package/dist/lib/create/model.d.ts +7 -3
  44. package/dist/lib/create/model.js +67 -9
  45. package/dist/lib/function-declarations.d.ts +8 -0
  46. package/dist/lib/function-declarations.js +27 -0
  47. package/dist/lib/inspect/extensions.d.ts +4 -3
  48. package/dist/lib/inspect/extensions.js +36 -5
  49. package/dist/lib/swell-function-wrapper.js +13 -5
  50. package/dist/lib/swell-workflow-request.d.ts +34 -0
  51. package/dist/lib/swell-workflow-request.js +58 -0
  52. package/dist/push-app-command.d.ts +2 -1
  53. package/dist/push-app-command.js +43 -22
  54. package/dist/remote-app-command.d.ts +2 -2
  55. package/dist/remote-app-command.js +79 -51
  56. package/oclif.manifest.json +263 -10
  57. package/package.json +2 -1
@@ -1,11 +1,13 @@
1
1
  import { confirm, select } from '@inquirer/prompts';
2
- import { inflect } from 'inflection';
3
2
  import sortBy from 'lodash/sortBy.js';
4
3
  import * as path from 'node:path';
5
4
  import { default as ora } from 'ora';
6
5
  import { AppCommand } from './app-command.js';
6
+ import { errorReasons } from './lib/api-errors.js';
7
7
  import { readRcFile, writeRcFile } from './lib/app-config.js';
8
- import { AppConfig, ConfigType, FunctionProcessingError, IgnoringFileError, SwellJsonFields, allBaseFilesInDir, allConfigFilesPaths, appConfigFromFile, batchAppFilesByType, deleteFile, writeFile, writeJsonFile, PUSH_CONCURRENCY, } from './lib/apps/index.js';
8
+ import { isUnloadableComponentFile } from './lib/apps/components.js';
9
+ import { AppConfig, ConfigType, FunctionProcessingError, IgnoringFileError, SwellJsonFields, allBaseFilesInDir, allConfigFilesPaths, appConfigFromFile, batchAppFilesByType, deleteFile, kebabCaseFilePath, writeFile, writeJsonFile, PUSH_CONCURRENCY, } from './lib/apps/index.js';
10
+ import { isCredentialFile } from './lib/apps/paths.js';
9
11
  import { default as localConfig } from './lib/config.js';
10
12
  import { getAppFrontendHost, getLocalFrontendHost, getLoginHost, getStorefrontFrontendHost, } from './lib/constants.js';
11
13
  import { toAppId } from './lib/create/index.js';
@@ -176,7 +178,7 @@ export class RemoteAppCommand extends AppCommand {
176
178
  adminPath: `/apps/${this.app.id}/configs/${configId}/data`,
177
179
  }, { query: { ...(themeId ? { theme_id: themeId } : undefined) } });
178
180
  }
179
- async getCreateUpdateApp(updateApp, force = false) {
181
+ async getCreateUpdateApp(updateApp) {
180
182
  const spinner = ora();
181
183
  let sourceApp;
182
184
  try {
@@ -205,9 +207,6 @@ export class RemoteAppCommand extends AppCommand {
205
207
  ? { private_id: this.swellConfig.store.id }
206
208
  : undefined),
207
209
  ...(sourceApp?.id ? { source_id: sourceApp.id } : undefined),
208
- // Overwrite name/description/logo_icon on the server
209
- // instead of only filling them in when empty
210
- ...(force ? { $force_meta: true } : undefined),
211
210
  };
212
211
  const appLabel = this.app.type === 'theme' ? 'theme' : 'app';
213
212
  let app = updateApp || {};
@@ -405,38 +404,37 @@ export class RemoteAppCommand extends AppCommand {
405
404
  });
406
405
  return versionsResponse.results || [];
407
406
  }
407
+ // A refused request fails with every reason the API gives, so that they
408
+ // reach stderr together with the failure
408
409
  async handleRequestErrors(request, spinnerError) {
409
- let showedErrors = false;
410
+ let response;
410
411
  try {
411
- const response = await request();
412
- if (response?.status !== 200 && response?.errors) {
413
- this.showErrors(response.errors, spinnerError);
414
- showedErrors = true;
415
- this.error('Something went wrong.');
416
- }
417
- return response;
412
+ response = await request();
418
413
  }
419
414
  catch (error) {
420
415
  let jsonError;
421
416
  try {
422
417
  jsonError = JSON.parse(error.message);
423
418
  }
424
- catch (error) {
419
+ catch (parseError) {
425
420
  // Skip JSON parse error
426
- if (!(error instanceof SyntaxError)) {
427
- throw error;
421
+ if (!(parseError instanceof SyntaxError)) {
422
+ throw parseError;
428
423
  }
429
424
  }
430
- if (!showedErrors) {
431
- if (jsonError && jsonError.error) {
432
- this.showErrors(jsonError, spinnerError);
433
- }
434
- else if (typeof error.message === 'string') {
435
- this.showErrors(error, spinnerError);
436
- }
425
+ spinnerError?.();
426
+ const reasons = jsonError?.error ? errorReasons(jsonError) : [];
427
+ if (reasons.length > 0) {
428
+ this.error(reasons.join('\n'));
437
429
  }
438
430
  throw error;
439
431
  }
432
+ if (response?.status !== 200 && response?.errors) {
433
+ spinnerError?.();
434
+ const reasons = errorReasons(response.errors);
435
+ this.error(reasons.length > 0 ? reasons.join('\n') : 'Something went wrong.');
436
+ }
437
+ return response;
440
438
  }
441
439
  async init() {
442
440
  await super.init();
@@ -489,7 +487,11 @@ export class RemoteAppCommand extends AppCommand {
489
487
  const removed = [];
490
488
  const [localConfigs, remoteConfigs] = await Promise.all([
491
489
  this.getLocalAppConfigs(),
492
- this.pullAppJsonAndRemoteConfigs().then((remoteConfigs) => this.cleanRemoteConfigs(remoteConfigs, false)),
490
+ this.pullAppJsonAndRemoteConfigs()
491
+ .then((remoteConfigs) => this.cleanRemoteConfigs(remoteConfigs, false))
492
+ // An older push could send .env and .dev.vars files, which must not
493
+ // replace the local ones
494
+ .then((remoteConfigs) => remoteConfigs.filter((config) => !isCredentialFile(config.filePath))),
493
495
  ]);
494
496
  const batches = batchAppFilesByType(remoteConfigs);
495
497
  for (const batch of batches) {
@@ -632,7 +634,7 @@ export class RemoteAppCommand extends AppCommand {
632
634
  if (pushFiles.length > 0) {
633
635
  while (pushFiles.length > 0) {
634
636
  // eslint-disable-next-line no-await-in-loop
635
- await Promise.all(pushFiles.splice(0, batch.concurrency).map(async (config) => {
637
+ await this.runTogether(pushFiles.splice(0, batch.concurrency).map(async (config) => {
636
638
  const pushedConfig = await this.pushRemoteFile(config);
637
639
  if (pushedConfig) {
638
640
  pushed.push(pushedConfig.id);
@@ -647,7 +649,7 @@ export class RemoteAppCommand extends AppCommand {
647
649
  if (filesToRemove.length > 0) {
648
650
  while (filesToRemove.length > 0) {
649
651
  // eslint-disable-next-line no-await-in-loop
650
- await Promise.all(filesToRemove.splice(0, batch.concurrency).map(async (config) => {
652
+ await this.runTogether(filesToRemove.splice(0, batch.concurrency).map(async (config) => {
651
653
  await this.removeRemoteFile(config);
652
654
  removed.push(config.id);
653
655
  }));
@@ -662,15 +664,43 @@ export class RemoteAppCommand extends AppCommand {
662
664
  }
663
665
  async pushRemoteFile(config, log = true) {
664
666
  const spinner = ora();
667
+ // A file the app already has keeps deploying under its name
668
+ const unloadable = isUnloadableComponentFile(config.filePath);
669
+ const pushedBefore = this.app?.configs?.some((remote) => remote.filePath === config.filePath);
670
+ if (unloadable && !pushedBefore) {
671
+ log &&
672
+ spinner.fail(`${config.filePath} would never load: name a component with letters, digits and dashes only`);
673
+ this.failedFiles.push(config.filePath);
674
+ return;
675
+ }
665
676
  log && spinner.start(`Pushing ${config.filePath}`);
677
+ // A refused push has failed the spinner, and its reasons end the command
678
+ let isRefused = false;
666
679
  try {
667
680
  const postData = await config.postData();
668
681
  if (!postData) {
669
682
  spinner.stop();
670
683
  return;
671
684
  }
672
- const result = await this.handleRequestErrors(async () => this.postConfigData(postData), () => log && spinner.fail(`Error pushing ${config.filePath}`));
685
+ const result = await this.handleRequestErrors(async () => this.postConfigData(postData), () => {
686
+ isRefused = true;
687
+ log && spinner.fail(`Error pushing ${config.filePath}`);
688
+ });
673
689
  log && spinner.succeed(`${this.timestampStyled()} ${config.filePath}`);
690
+ // A config named in its values deploys under that name instead of its
691
+ // file's, and a model as its collection, whatever name it gives
692
+ const deployName = config.type === ConfigType.MODEL
693
+ ? postData.values?.collection
694
+ : postData.name;
695
+ const kebabPath = deployName === config.name
696
+ ? kebabCaseFilePath(config.filePath)
697
+ : undefined;
698
+ if (log && kebabPath) {
699
+ ora().warn(`${config.filePath} deploys as '${config.name}': use kebab-case: ${kebabPath}`);
700
+ }
701
+ if (log && unloadable) {
702
+ ora().warn(`${config.filePath} never loads: name a component with letters, digits and dashes only`);
703
+ }
674
704
  return result;
675
705
  }
676
706
  catch (error) {
@@ -686,10 +716,31 @@ export class RemoteAppCommand extends AppCommand {
686
716
  this.failedFiles.push(config.filePath);
687
717
  return;
688
718
  }
719
+ if (isRefused) {
720
+ // Each reason names its file, as a batch fails with all of them
721
+ this.error(error.message
722
+ .split('\n')
723
+ .map((reason) => `${config.filePath}: ${reason}`)
724
+ .join('\n'));
725
+ }
689
726
  log && spinner.fail(error.message);
690
727
  throw error;
691
728
  }
692
729
  }
730
+ // Runs pushes or removals together and, when some fail, fails once with
731
+ // the reasons of each of them, not only the first
732
+ async runTogether(tasks) {
733
+ const results = await Promise.allSettled(tasks);
734
+ const failures = results.flatMap((result) => result.status === 'rejected' ? [result.reason] : []);
735
+ if (failures.length === 1) {
736
+ throw failures[0];
737
+ }
738
+ if (failures.length > 1) {
739
+ this.error(failures
740
+ .map((failure) => failure?.message ?? String(failure))
741
+ .join('\n'));
742
+ }
743
+ }
693
744
  async removeLocalFile(config, log = true) {
694
745
  if (!config.fileData) {
695
746
  return;
@@ -709,29 +760,6 @@ export class RemoteAppCommand extends AppCommand {
709
760
  spinner.warn(`${this.timestampStyled()} ${style.strike(config.filePath)}`);
710
761
  return result;
711
762
  }
712
- showErrors(errors, spinnerError) {
713
- const actualErrors = Object.keys(errors).filter((err) => Object.prototype.hasOwnProperty.call(errors, err));
714
- if (spinnerError) {
715
- spinnerError();
716
- }
717
- else {
718
- this.logError(inflect('Error', actualErrors.length));
719
- }
720
- for (const err of actualErrors) {
721
- if (!errors[err]?.message)
722
- continue;
723
- // we need to display `id` instead of `private_id`
724
- // developers don't have any knowledge of private ids
725
- const displayErr = err === 'private_id' ? 'id' : err;
726
- this.logError([
727
- ' -',
728
- `${displayErr}:`.toLowerCase().replace(/^error:/, ''),
729
- errors[err].message,
730
- ]
731
- .filter(Boolean)
732
- .join(' '));
733
- }
734
- }
735
763
  storefrontFrontendUrl(storeId, storefront, branchId, preview = true) {
736
764
  const storefrontSlug = storefront && preview
737
765
  ? `${storeId}--${storefront.id.toLowerCase()}--preview`
@@ -721,6 +721,126 @@
721
721
  "_uninstall.js"
722
722
  ]
723
723
  },
724
+ "app:action": {
725
+ "aliases": [],
726
+ "args": {
727
+ "id": {
728
+ "description": "id of the action, as declared in content or settings",
729
+ "name": "id",
730
+ "required": true
731
+ }
732
+ },
733
+ "description": "Runs a function action of the app as the logged-in dashboard user, the way a click in the dashboard does. The action is looked up in the app's content and settings as pushed.\n\nA record action or action field needs --record, and a bulk action --ids or --all. Pass the values of the action's modal with --values. When the same id is declared in more than one place, pick one with --collection, --view, --settings or --source.",
734
+ "examples": [
735
+ "swell app action sync_now",
736
+ "swell app action ship --record 64e8dfb633c44a0039477251",
737
+ "swell app action ship --record 64e8dfb633c44a0039477251 --values '{\"carrier\":\"ups\"}'",
738
+ "swell app action ship-selected --ids 64e8dfb633c44a0039477251,64e8dfb633c44a0039477252",
739
+ "swell app action ship --collection shipments --view edit --record 64e8dfb633c44a0039477251",
740
+ "swell app action export --source bulk --all"
741
+ ],
742
+ "flags": {
743
+ "app-path": {
744
+ "description": "Path to your app directory",
745
+ "name": "app-path",
746
+ "hasDynamicHelp": false,
747
+ "multiple": false,
748
+ "type": "option"
749
+ },
750
+ "all": {
751
+ "description": "run a bulk action on all records its view lists with no filters set, as selecting all in the dashboard does",
752
+ "exclusive": [
753
+ "ids"
754
+ ],
755
+ "name": "all",
756
+ "allowNo": false,
757
+ "type": "boolean"
758
+ },
759
+ "collection": {
760
+ "description": "collection of the content that declares the action, or the model's name for a collection of the app",
761
+ "name": "collection",
762
+ "hasDynamicHelp": false,
763
+ "multiple": false,
764
+ "type": "option"
765
+ },
766
+ "ids": {
767
+ "description": "comma-separated ids of the records a bulk action runs on",
768
+ "exclusive": [
769
+ "all"
770
+ ],
771
+ "name": "ids",
772
+ "hasDynamicHelp": false,
773
+ "multiple": false,
774
+ "type": "option"
775
+ },
776
+ "live": {
777
+ "description": "run in the live environment instead of test",
778
+ "name": "live",
779
+ "allowNo": false,
780
+ "type": "boolean"
781
+ },
782
+ "record": {
783
+ "description": "id of the record a record action or action field runs on",
784
+ "name": "record",
785
+ "hasDynamicHelp": false,
786
+ "multiple": false,
787
+ "type": "option"
788
+ },
789
+ "settings": {
790
+ "description": "name of the settings that declare the action",
791
+ "name": "settings",
792
+ "hasDynamicHelp": false,
793
+ "multiple": false,
794
+ "type": "option"
795
+ },
796
+ "source": {
797
+ "description": "where the action is declared: list or record (a view's actions), bulk (a view's bulk actions), field (an action field) or settings (a settings action)",
798
+ "name": "source",
799
+ "hasDynamicHelp": false,
800
+ "multiple": false,
801
+ "options": [
802
+ "list",
803
+ "record",
804
+ "bulk",
805
+ "field",
806
+ "settings"
807
+ ],
808
+ "type": "option"
809
+ },
810
+ "values": {
811
+ "description": "values of the action's modal fields, as JSON",
812
+ "name": "values",
813
+ "hasDynamicHelp": false,
814
+ "multiple": false,
815
+ "type": "option"
816
+ },
817
+ "view": {
818
+ "description": "id of the content view that declares the action",
819
+ "name": "view",
820
+ "hasDynamicHelp": false,
821
+ "multiple": false,
822
+ "type": "option"
823
+ }
824
+ },
825
+ "hasDynamicHelp": false,
826
+ "hiddenAliases": [],
827
+ "id": "app:action",
828
+ "pluginAlias": "@swell/cli",
829
+ "pluginName": "@swell/cli",
830
+ "pluginType": "core",
831
+ "strict": true,
832
+ "summary": "Run an app action from the command line.",
833
+ "enableJsonFlag": false,
834
+ "requiresSwellConfig": true,
835
+ "delayOrientation": false,
836
+ "isESM": true,
837
+ "relativePath": [
838
+ "dist",
839
+ "commands",
840
+ "app",
841
+ "action.js"
842
+ ]
843
+ },
724
844
  "app:delete": {
725
845
  "aliases": [],
726
846
  "args": {},
@@ -1237,7 +1357,7 @@
1237
1357
  "name": "file"
1238
1358
  }
1239
1359
  },
1240
- "description": "Push all app files, a specific file, or a specific configuration\ntype to an app in your store's test environment.\n\nIf the app does not exist, it will be created and its global ID saved to a .swellrc file.\n\n- If no file is specified, all configuration files will be pushed to the store.\n This includes the app icon (assets/icon.png), screenshots (assets/screenshots/),\n README.md, and swell.json.\n- If a file is specified, only that file will be pushed to the store.\n- If a directory is specified, only files in that directory will be pushed.\n\nPushing anything under frontend/ also builds and deploys the app frontend, so\nthe deployed frontend matches what was pushed. Use --no-deploy to skip it.\n\nApp file directories:\nassets/\ncontent/\nfrontend/\ncomponents/\nfunctions/\nmodels/\nnotifications/\nsettings/\ntheme/\nwebhooks/",
1360
+ "description": "Push all app files, a specific file, or a specific configuration\ntype to an app in your store's test environment.\n\nIf the app does not exist, it will be created and its global ID saved to a .swellrc file.\n\n- If no file is specified, all configuration files will be pushed to the store.\n This includes the app icon (assets/icon.png), screenshots (assets/screenshots/),\n DESCRIPTION.md, and swell.json.\n- If a file is specified, only that file will be pushed to the store.\n- If a directory is specified, only files in that directory will be pushed.\n\nPushing anything under frontend/ also builds and deploys the app frontend, so\nthe deployed frontend matches what was pushed. Use --no-deploy to skip it.\n\nApp file directories:\nassets/\ncontent/\nfrontend/\ncomponents/\nfunctions/\nmodels/\nnotifications/\nsettings/\ntheme/\nwebhooks/",
1241
1361
  "examples": [
1242
1362
  "swell app push",
1243
1363
  "swell app push content",
@@ -1968,12 +2088,13 @@
1968
2088
  },
1969
2089
  "trigger": {
1970
2090
  "default": "",
1971
- "description": "cron | model | route | workflow",
2091
+ "description": "cron | model | route | action | workflow",
1972
2092
  "name": "trigger",
1973
2093
  "options": [
1974
2094
  "cron",
1975
2095
  "model",
1976
2096
  "route",
2097
+ "action",
1977
2098
  "workflow"
1978
2099
  ]
1979
2100
  }
@@ -1983,6 +2104,7 @@
1983
2104
  "$ swell create function order-handler.ts model -e order.created,order.updated -y",
1984
2105
  "$ swell create function cleanup.ts cron -s \"0 0 * * *\" -y",
1985
2106
  "$ swell create function api-endpoint.ts route -r public -y",
2107
+ "$ swell create function mark-shipped.ts action -y",
1986
2108
  "$ swell create function run-import.ts --type workflow -y",
1987
2109
  "$ swell create function run-import.ts workflow -y"
1988
2110
  ],
@@ -2086,6 +2208,11 @@
2086
2208
  "flag": "-r, --route=<option>",
2087
2209
  "description": "Access: public (default) | private"
2088
2210
  },
2211
+ {
2212
+ "name": "action",
2213
+ "flag": "",
2214
+ "description": "Runs from a dashboard action"
2215
+ },
2089
2216
  {
2090
2217
  "name": "workflow",
2091
2218
  "flag": "--type=workflow",
@@ -2144,7 +2271,8 @@
2144
2271
  "examples": [
2145
2272
  "$ swell create model",
2146
2273
  "$ swell create model products -y",
2147
- "$ swell create model visitors -f title:string,photo:string -l \"Visitors\" -y"
2274
+ "$ swell create model visitors -f title:string,photo:file -l \"Visitors\" -y",
2275
+ "$ swell create model reviews -f owner:link:accounts,tags:array:string -y"
2148
2276
  ],
2149
2277
  "flags": {
2150
2278
  "app-path": {
@@ -2174,7 +2302,7 @@
2174
2302
  },
2175
2303
  "fields": {
2176
2304
  "char": "f",
2177
- "description": "Fields as id:type pairs (e.g., title:string,price:currency)",
2305
+ "description": "Fields as id:type pairs, id:link:<model> or id:array:<type> (e.g., title:string,owner:link:accounts)",
2178
2306
  "name": "fields",
2179
2307
  "default": "",
2180
2308
  "hasDynamicHelp": false,
@@ -2475,7 +2603,7 @@
2475
2603
  "examples": [
2476
2604
  "$ swell create webhook",
2477
2605
  "$ swell create webhook order-sync -u https://example.com/hook -e order.created -y",
2478
- "$ swell create webhook payment-handler -u https://api.example.com/payments -e payment.succeeded,payment.failed --enabled -y"
2606
+ "$ swell create webhook payment-handler -u https://api.example.com/payments -e payment.succeeded,payment.failed --no-enabled -y"
2479
2607
  ],
2480
2608
  "flags": {
2481
2609
  "app-path": {
@@ -2513,9 +2641,9 @@
2513
2641
  "type": "option"
2514
2642
  },
2515
2643
  "enabled": {
2516
- "description": "Enable webhook (default: false)",
2644
+ "description": "Enable webhook (default: true)",
2517
2645
  "name": "enabled",
2518
- "allowNo": false,
2646
+ "allowNo": true,
2519
2647
  "type": "boolean"
2520
2648
  },
2521
2649
  "overwrite": {
@@ -2551,6 +2679,68 @@
2551
2679
  "webhook.js"
2552
2680
  ]
2553
2681
  },
2682
+ "inspect:components": {
2683
+ "aliases": [],
2684
+ "args": {
2685
+ "identifier": {
2686
+ "description": "Resource identifier — see \"Identifier forms\" below.",
2687
+ "name": "identifier",
2688
+ "required": false
2689
+ }
2690
+ },
2691
+ "description": "Lists the components of installed apps, grouped by app. Pass --app=<slug> or --app=. (current swell.json) to scope.\n\nList output: paste-back key + the targets the component renders in and the extension it belongs to.\n\nPass an identifier to view a single component config as JSON.\n\nIdentifier forms:\n app.<app>.<name> — full paste-back key (list column 1)\n <name> — requires --app= scope\n <24-char id> — any scope\n",
2692
+ "examples": [
2693
+ "swell inspect components",
2694
+ "swell inspect components --app=my-app",
2695
+ "swell inspect components app.my-app.card",
2696
+ "swell inspect components card --app=my-app",
2697
+ "swell inspect components --live"
2698
+ ],
2699
+ "flags": {
2700
+ "app": {
2701
+ "description": "Filter by app slug, or \".\" for current swell.json.",
2702
+ "name": "app",
2703
+ "hasDynamicHelp": false,
2704
+ "multiple": false,
2705
+ "type": "option"
2706
+ },
2707
+ "live": {
2708
+ "description": "Use live environment (default: test).",
2709
+ "name": "live",
2710
+ "allowNo": false,
2711
+ "type": "boolean"
2712
+ },
2713
+ "json": {
2714
+ "description": "Emit pure JSON without the \"Next steps\" footer. Detail mode only.",
2715
+ "name": "json",
2716
+ "allowNo": false,
2717
+ "type": "boolean"
2718
+ },
2719
+ "yes": {
2720
+ "char": "y",
2721
+ "description": "No-op; accepted for agent compatibility.",
2722
+ "hidden": true,
2723
+ "name": "yes",
2724
+ "allowNo": false,
2725
+ "type": "boolean"
2726
+ }
2727
+ },
2728
+ "hasDynamicHelp": false,
2729
+ "hiddenAliases": [],
2730
+ "id": "inspect:components",
2731
+ "pluginAlias": "@swell/cli",
2732
+ "pluginName": "@swell/cli",
2733
+ "pluginType": "core",
2734
+ "strict": true,
2735
+ "summary": "Components with their targets.",
2736
+ "isESM": true,
2737
+ "relativePath": [
2738
+ "dist",
2739
+ "commands",
2740
+ "inspect",
2741
+ "components.js"
2742
+ ]
2743
+ },
2554
2744
  "inspect:content": {
2555
2745
  "aliases": [],
2556
2746
  "args": {
@@ -2678,6 +2868,67 @@
2678
2868
  "extensions.js"
2679
2869
  ]
2680
2870
  },
2871
+ "inspect:frontend": {
2872
+ "aliases": [],
2873
+ "args": {
2874
+ "identifier": {
2875
+ "description": "Resource identifier — see \"Identifier forms\" below.",
2876
+ "name": "identifier",
2877
+ "required": false
2878
+ }
2879
+ },
2880
+ "description": "Lists the frontends of installed apps. Pass --app=<slug> or --app=. (current swell.json) to scope.\n\nList output: paste-back key + hosting (managed or self-hosted), framework, and the address the store serves the frontend at.\n\nPass an identifier to view an app's frontend as JSON: the app's frontend selection (package or self-hosted URL) with its installation and address.\n\nIdentifier forms:\n app.<app> — full paste-back key (list column 1)\n <app> — the app's slug\n <24-char id> — the app's id\n",
2881
+ "examples": [
2882
+ "swell inspect frontend",
2883
+ "swell inspect frontend --app=.",
2884
+ "swell inspect frontend app.my-app",
2885
+ "swell inspect frontend --live"
2886
+ ],
2887
+ "flags": {
2888
+ "app": {
2889
+ "description": "Filter by app slug, or \".\" for current swell.json.",
2890
+ "name": "app",
2891
+ "hasDynamicHelp": false,
2892
+ "multiple": false,
2893
+ "type": "option"
2894
+ },
2895
+ "live": {
2896
+ "description": "Use live environment (default: test).",
2897
+ "name": "live",
2898
+ "allowNo": false,
2899
+ "type": "boolean"
2900
+ },
2901
+ "json": {
2902
+ "description": "Emit pure JSON without the \"Next steps\" footer. Detail mode only.",
2903
+ "name": "json",
2904
+ "allowNo": false,
2905
+ "type": "boolean"
2906
+ },
2907
+ "yes": {
2908
+ "char": "y",
2909
+ "description": "No-op; accepted for agent compatibility.",
2910
+ "hidden": true,
2911
+ "name": "yes",
2912
+ "allowNo": false,
2913
+ "type": "boolean"
2914
+ }
2915
+ },
2916
+ "hasDynamicHelp": false,
2917
+ "hiddenAliases": [],
2918
+ "id": "inspect:frontend",
2919
+ "pluginAlias": "@swell/cli",
2920
+ "pluginName": "@swell/cli",
2921
+ "pluginType": "core",
2922
+ "strict": true,
2923
+ "summary": "App frontends with hosting and address.",
2924
+ "isESM": true,
2925
+ "relativePath": [
2926
+ "dist",
2927
+ "commands",
2928
+ "inspect",
2929
+ "frontend.js"
2930
+ ]
2931
+ },
2681
2932
  "inspect:functions": {
2682
2933
  "aliases": [],
2683
2934
  "args": {
@@ -2687,7 +2938,7 @@
2687
2938
  "required": false
2688
2939
  }
2689
2940
  },
2690
- "description": "Lists functions across all apps, grouped by app. Pass --app=<slug> or --app=. (current swell.json) to scope.\n\nList output: paste-back key + status (disabled, trigger, next cron run, last failure).\n\nPass an identifier to view a single record as JSON.\n\nIdentifier forms:\n app.<app>.<name> — full paste-back key (list column 1)\n <name> — requires --app= scope\n <24-char id> — any scope\n",
2941
+ "description": "Lists functions across all apps, grouped by app. Pass --app=<slug> or --app=. (current swell.json) to scope.\n\nList output: paste-back key + status (disabled or auto-disabled, failing since and failure count, the date it gets disabled, trigger, next cron run) and whether its app is inactive, which stops all of its functions. The detail view also shows the last failed delivery.\n\nPass an identifier to view a single record as JSON.\n\nIdentifier forms:\n app.<app>.<name> — full paste-back key (list column 1)\n <name> — requires --app= scope\n <24-char id> — any scope\n",
2691
2942
  "examples": [
2692
2943
  "swell inspect functions",
2693
2944
  "swell inspect functions --app=my-app",
@@ -2751,7 +3002,9 @@
2751
3002
  "swell inspect functions app.my-app.payment-sync",
2752
3003
  "swell inspect workflow-runs --workflow app.my-app.run-import --status active",
2753
3004
  "swell inspect notifications com.orders.receipt.v2",
2754
- "swell inspect settings app.my-app"
3005
+ "swell inspect settings app.my-app",
3006
+ "swell inspect components --app=.",
3007
+ "swell inspect frontend app.my-app"
2755
3008
  ],
2756
3009
  "flags": {},
2757
3010
  "hasDynamicHelp": false,
@@ -3738,5 +3991,5 @@
3738
3991
  ]
3739
3992
  }
3740
3993
  },
3741
- "version": "2.9.31"
3994
+ "version": "2.9.33"
3742
3995
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swell/cli",
3
- "version": "2.9.31",
3
+ "version": "2.9.33",
4
4
  "type": "module",
5
5
  "description": "Swell's command line interface/utility",
6
6
  "keywords": [
@@ -30,6 +30,7 @@
30
30
  "@npmcli/git": "5.0.3",
31
31
  "@oclif/core": "3.7.1",
32
32
  "@oclif/plugin-help": "6.0.4",
33
+ "@swell/app-types": "1.3.0",
33
34
  "ajv": "8.20.0",
34
35
  "ajv-errors": "3.0.0",
35
36
  "ajv-formats": "3.0.1",