@swell/cli 2.6.0 → 2.7.1

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 (46) hide show
  1. package/dist/commands/app/frontend/dev.js +18 -0
  2. package/dist/commands/inspect/content.d.ts +25 -14
  3. package/dist/commands/inspect/content.js +34 -144
  4. package/dist/commands/inspect/extensions.d.ts +49 -0
  5. package/dist/commands/inspect/extensions.js +424 -0
  6. package/dist/commands/inspect/functions.d.ts +48 -0
  7. package/dist/commands/inspect/functions.js +83 -0
  8. package/dist/commands/inspect/index.js +8 -7
  9. package/dist/commands/inspect/models.d.ts +17 -2
  10. package/dist/commands/inspect/models.js +114 -47
  11. package/dist/commands/inspect/notifications.d.ts +31 -0
  12. package/dist/commands/inspect/notifications.js +125 -0
  13. package/dist/commands/inspect/settings.d.ts +28 -0
  14. package/dist/commands/inspect/settings.js +82 -0
  15. package/dist/commands/inspect/webhooks.d.ts +34 -0
  16. package/dist/commands/inspect/webhooks.js +61 -0
  17. package/dist/create-app-command.d.ts +7 -0
  18. package/dist/create-app-command.js +80 -9
  19. package/dist/inspect-resource-command.d.ts +91 -0
  20. package/dist/inspect-resource-command.js +232 -0
  21. package/dist/lib/apps/index.d.ts +2 -1
  22. package/dist/lib/apps/index.js +43 -6
  23. package/dist/lib/apps/inspect-scope.d.ts +58 -0
  24. package/dist/lib/apps/inspect-scope.js +60 -0
  25. package/dist/lib/apps/object-id.d.ts +7 -0
  26. package/dist/lib/apps/object-id.js +9 -0
  27. package/dist/lib/apps/paths.js +8 -1
  28. package/dist/lib/apps/resolve.d.ts +16 -0
  29. package/dist/lib/apps/resolve.js +39 -0
  30. package/dist/lib/apps/slug.d.ts +29 -0
  31. package/dist/lib/apps/slug.js +12 -0
  32. package/dist/lib/inspect/content.d.ts +39 -0
  33. package/dist/lib/inspect/content.js +76 -0
  34. package/dist/lib/inspect/extensions.d.ts +267 -0
  35. package/dist/lib/inspect/extensions.js +690 -0
  36. package/dist/lib/inspect/notifications.d.ts +115 -0
  37. package/dist/lib/inspect/notifications.js +173 -0
  38. package/dist/lib/inspect/settings.d.ts +61 -0
  39. package/dist/lib/inspect/settings.js +56 -0
  40. package/dist/lib/inspect/table.d.ts +29 -0
  41. package/dist/lib/inspect/table.js +61 -0
  42. package/dist/push-app-command.js +3 -2
  43. package/dist/swell-api-command.d.ts +0 -4
  44. package/dist/swell-api-command.js +2 -18
  45. package/oclif.manifest.json +392 -35
  46. package/package.json +1 -1
@@ -182,6 +182,24 @@ export default class AppFrontendDev extends PushAppCommand {
182
182
  }
183
183
  break;
184
184
  }
185
+ case 'react':
186
+ case 'react-storefront': {
187
+ // Vite emits the Local URL as its ready signal — no separate
188
+ // detector needed.
189
+ if (/➜\s+local:\s+http:\/\//i.test(string) ||
190
+ /➜\s+network:/i.test(string)) {
191
+ if (!serverReadyDetected) {
192
+ serverReadyDetected = true;
193
+ this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
194
+ setTimeout(() => this.logLocalPreviewUrl(), 100);
195
+ if (!isAppDev) {
196
+ this.watchForChanges();
197
+ }
198
+ }
199
+ return false;
200
+ }
201
+ break;
202
+ }
185
203
  case 'nuxt': {
186
204
  // 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel)
187
205
  if (/➜ local:\s+http:\/\//i.test(string) ||
@@ -1,25 +1,36 @@
1
- import { SwellCommand } from '../../swell-command.js';
2
- export default class Content extends SwellCommand {
1
+ import { InspectResourceCommand, InspectResourceCommandParsed } from '../../inspect-resource-command.js';
2
+ import { InspectScope } from '../../lib/apps/inspect-scope.js';
3
+ import { GroupInfo } from '../../lib/inspect/table.js';
4
+ interface ContentView {
5
+ id: string;
6
+ name: string;
7
+ app_id?: string;
8
+ collection?: string;
9
+ fields?: any[];
10
+ source_id?: string;
11
+ source_type?: string;
12
+ views?: any[];
13
+ }
14
+ export default class Content extends InspectResourceCommand {
3
15
  static summary: string;
4
16
  static description: string;
5
17
  static args: {
6
- 'content-id': import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
18
+ identifier: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
7
19
  };
8
20
  static flags: {
21
+ app: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
9
22
  live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
23
+ json: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
10
24
  yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
25
  };
12
26
  static examples: string[];
27
+ protected resourceLabel: string;
28
+ protected resourceLabelSingular: string;
29
+ protected adminPath: string;
30
+ protected commandName: string;
13
31
  run(): Promise<void>;
14
- protected catch(error: Error): Promise<any>;
15
- private listContent;
16
- private showContentViews;
17
- private showContentView;
18
- private resolveContentId;
19
- private showContentDetail;
20
- private reverseResolveContentId;
21
- private getAppBySlug;
22
- private getAppSlugId;
23
- private throwContentNotFound;
24
- private showContent;
32
+ protected keyFor(record: ContentView, appSlugById: Record<string, string>): string;
33
+ protected groupForRecord(record: ContentView, appSlugById: Record<string, string>): GroupInfo;
34
+ protected showDetail(identifier: string, scope: InspectScope, flags: InspectResourceCommandParsed['flags']): Promise<void>;
25
35
  }
36
+ export {};
@@ -1,159 +1,49 @@
1
- import { Args, Flags } from '@oclif/core';
2
- import { FetchError } from 'node-fetch';
3
- import { default as localConfig } from '../../lib/config.js';
4
- import { SwellCommand } from '../../swell-command.js';
5
- export default class Content extends SwellCommand {
6
- static summary = 'Inspect content view extensions deployed in your store.';
7
- static description = `
8
- View content view extensions available in your Swell store.
1
+ import { InspectResourceCommand, inspectResourceBaseArgs, inspectResourceBaseFlags, } from '../../inspect-resource-command.js';
2
+ import { resolveAppId } from '../../lib/apps/resolve.js';
3
+ import { groupContentRecord, resolveContentKey, reverseResolveContentId, } from '../../lib/inspect/content.js';
4
+ export default class Content extends InspectResourceCommand {
5
+ static summary = 'App and platform content views.';
6
+ static description = `Lists content view extensions across all apps plus platform 'custom' sources, grouped by owner. Pass --app=<slug> or --app=. (current swell.json) to scope.
9
7
 
10
- Without a content ID, this command lists all available content views.
11
- With a content ID, it retrieves the configuration for that specific content view in JSON format.
8
+ Pass an identifier to view a single record as JSON.
9
+
10
+ Identifier forms:
11
+ app.<app>.<name> — app-owned view (list column 1)
12
+ custom.<source>.<name> — platform custom view (e.g. custom.admin.products)
13
+ <name> — requires --app= scope
12
14
  `;
13
- static args = {
14
- 'content-id': Args.string({
15
- description: 'Optional content view ID. Example: custom.admin.products, app.myapp.products',
16
- required: false,
17
- }),
18
- };
19
- static flags = {
20
- live: Flags.boolean({
21
- description: 'Use the live environment instead of test (default: test).',
22
- default: false,
23
- }),
24
- yes: Flags.boolean({
25
- char: 'y',
26
- description: 'Accept all prompts automatically (for agent compatibility; no functional effect).',
27
- default: false,
28
- }),
29
- };
15
+ static args = { ...inspectResourceBaseArgs };
16
+ static flags = { ...inspectResourceBaseFlags };
30
17
  static examples = [
31
18
  'swell inspect content',
32
- 'swell inspect content custom.admin.products',
19
+ 'swell inspect content --app=my-app',
33
20
  'swell inspect content app.honest_reviews.reviews',
34
- 'swell inspect content app.myapp.products --live',
21
+ 'swell inspect content custom.admin.products',
22
+ 'swell inspect content reviews --app=honest_reviews',
23
+ 'swell inspect content --live',
35
24
  ];
25
+ resourceLabel = 'Content views';
26
+ resourceLabelSingular = 'content view';
27
+ adminPath = '/data/:content';
28
+ commandName = 'content';
36
29
  async run() {
37
30
  const { args, flags } = await this.parse(Content);
38
- const { 'content-id': contentId } = args;
39
- const { live } = flags;
40
- if (!live) {
41
- await this.api.setEnv('test');
42
- }
43
- // Detail mode: show configuration for a specific content view
44
- if (contentId) {
45
- await this.showContentDetail(contentId);
46
- return;
47
- }
48
- const store = localConfig.getDefaultStore();
49
- // List mode: show all content views
50
- await this.listContent(store, live);
51
- }
52
- async catch(error) {
53
- if (error instanceof FetchError) {
54
- const message = `Could not connect to Swell API. Please try again later: ${error.message}`;
55
- return this.error(message, { exit: 2, code: error.code });
56
- }
57
- return this.error(error.message, { exit: 1 });
58
- }
59
- async listContent(store, live) {
60
- const { results: apps } = await this.api.get({
61
- adminPath: '/apps',
62
- });
63
- const appsById = Object.fromEntries(apps.map((app) => [app.id, app]));
64
- const { results: contentViews } = await this.api.getAll({
65
- adminPath: '/data/:content',
66
- });
67
- // Sort by resolved ID in reverse order (custom.* first)
68
- const sortedContentViews = [...contentViews].sort((a, b) => {
69
- const resolvedA = this.resolveContentId(a, appsById);
70
- const resolvedB = this.resolveContentId(b, appsById);
71
- return resolvedB.localeCompare(resolvedA);
72
- });
73
- this.log(`Content views extensions available in '${store}' ${live ? '[live]' : '[test]'}:`);
74
- this.log();
75
- this.showContentViews(sortedContentViews, appsById);
76
- // Show next step hints
77
- this.log();
78
- this.log('Examples:');
79
- this.log(' swell inspect content custom.admin.products');
80
- this.log(' swell inspect content app.honest_reviews.accounts');
81
- }
82
- showContentViews(contentViews, appsById) {
83
- const maxNameWidth = Math.max(...contentViews.map((cv) => cv.name.length), 15);
84
- for (const cv of contentViews) {
85
- this.showContentView(cv, maxNameWidth, appsById);
86
- }
31
+ await this.runInspect({ args, flags });
87
32
  }
88
- showContentView(contentView, nameWidth, appsById) {
89
- const resolvedId = this.resolveContentId(contentView, appsById);
90
- const paddedName = contentView.name.padEnd(nameWidth + 2);
91
- this.log(`${paddedName}${resolvedId}`);
33
+ keyFor(record, appSlugById) {
34
+ return resolveContentKey(record.id, appSlugById);
92
35
  }
93
- resolveContentId(contentView, appsById) {
94
- const { id, name } = contentView;
95
- // Parse ID: app.{appId}.{name} or custom.{source}.{name}
96
- const match = id.match(/^(app|custom)\.([^.]+)\.(.+)$/);
97
- if (!match)
98
- return id; // fallback to original
99
- const [, type, identifier] = match;
100
- if (type === 'custom') {
101
- return id; // custom IDs are already resolved
102
- }
103
- // type === 'app': resolve app ID to slug
104
- const appId = identifier;
105
- const app = appsById[appId];
106
- if (!app) {
107
- return id; // fallback if app not found
108
- }
109
- const appSlug = this.getAppSlugId(app);
110
- return `app.${appSlug}.${name}`;
36
+ groupForRecord(record, appSlugById) {
37
+ return groupContentRecord(record, appSlugById);
111
38
  }
112
- async showContentDetail(resolvedId) {
113
- // Reverse resolve to get unresolved ID for API
114
- const unresolvedId = await this.reverseResolveContentId(resolvedId);
115
- // Fetch content view
116
- const content = await this.api.get({
117
- adminPath: `/data/:content/${unresolvedId}`,
39
+ async showDetail(identifier, scope, flags) {
40
+ const unresolved = await reverseResolveContentId(identifier, scope, (slug) => resolveAppId(this.api, slug));
41
+ const record = await this.api.get({
42
+ adminPath: `${this.adminPath}/${unresolved}`,
118
43
  });
119
- if (!content) {
120
- this.throwContentNotFound(resolvedId);
121
- }
122
- this.showContent(content);
123
- }
124
- async reverseResolveContentId(resolvedId) {
125
- // Parse ID: app.{slug}.{name} or custom.{source}.{name}
126
- const match = resolvedId.match(/^(app|custom)\.([^.]+)\.(.+)$/);
127
- if (!match) {
128
- throw new Error(`Invalid content ID format: ${resolvedId}`);
129
- }
130
- const [, type, identifier, name] = match;
131
- if (type === 'custom') {
132
- return resolvedId; // custom IDs don't need reverse resolution
44
+ if (!record) {
45
+ this.throwNotFound(identifier);
133
46
  }
134
- // Check if identifier is already an app ID (ObjectID format)
135
- if (/^[\da-f]{24}$/.test(identifier)) {
136
- return resolvedId; // already unresolved format
137
- }
138
- // type === 'app': resolve slug to app ID
139
- const appSlug = identifier;
140
- const app = await this.getAppBySlug(appSlug);
141
- return `app.${app.id}.${name}`;
142
- }
143
- async getAppBySlug(slug) {
144
- const app = await this.api.get({ adminPath: `/apps/${slug}` });
145
- if (!app) {
146
- throw new Error(`App '${slug}' not found.\nList available content: swell inspect content`);
147
- }
148
- return app;
149
- }
150
- getAppSlugId(app) {
151
- return app.public_id || app.private_id.replace(/^_/, '');
152
- }
153
- throwContentNotFound(id) {
154
- throw new Error(`No content view found for '${id}'.\nList available content: swell inspect content`);
155
- }
156
- showContent(content) {
157
- this.log(JSON.stringify(content, null, 2));
47
+ this.emitDetail(record, flags);
158
48
  }
159
49
  }
@@ -0,0 +1,49 @@
1
+ import { SwellCommand } from '../../swell-command.js';
2
+ export default class Extensions extends SwellCommand {
3
+ static summary: string;
4
+ static description: string;
5
+ static args: {
6
+ identifier: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ app: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
10
+ live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
+ json: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
12
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
13
+ };
14
+ static examples: string[];
15
+ run(): Promise<void>;
16
+ protected catch(error: Error): Promise<any>;
17
+ /**
18
+ * List mode is bounded to two parallel round-trip phases regardless of app
19
+ * count: phase 1 fans `/client/apps`, the three settings singletons, and a
20
+ * single `/data/:functions` filter; phase 2 fans `/apps/<id>/configs?type=component`
21
+ * only for apps with a non-empty `extensions[]`.
22
+ *
23
+ * `include` / `aggregate` are NOT used here — the CLI's `node-fetch`
24
+ * transport cannot transmit GET-with-body, and URL-form transmission of
25
+ * those payloads is silently dropped server-side. Verified empirically
26
+ * against the live admin API. Don't reach for those primitives.
27
+ */
28
+ private showList;
29
+ /**
30
+ * Detail mode is bounded to one round-trip's worth of latency: the install
31
+ * record (or `/apps/<id>` fallback), the relevant settings singleton(s),
32
+ * `/data/:functions?where[app_id]=<id>&where[extension]=<extId>`, and
33
+ * `/apps/<id>/configs?type=component` — all in parallel.
34
+ */
35
+ private showDetail;
36
+ /**
37
+ * `extensions` emits a synthesized envelope rather than a raw API record —
38
+ * other inspect subcommands print one upstream record, but here the JSON
39
+ * nests `manifest`, `native_bindings[].record`, `bound.functions[]`, and
40
+ * `bound.components[]` under shared status fields.
41
+ *
42
+ * The `Next steps:` footer mixes runnable shell commands and merchant-UI
43
+ * lines (prefixed `(merchant)`). The structured `action` and `action_owner`
44
+ * fields above give an agent a parseable signal independent of the footer.
45
+ */
46
+ private emitDetail;
47
+ private fetchSafe;
48
+ private printPreamble;
49
+ }