@swell/cli 2.5.8 → 2.7.0

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 (55) hide show
  1. package/dist/commands/api/delete.d.ts +1 -0
  2. package/dist/commands/api/delete.js +3 -1
  3. package/dist/commands/api/get.d.ts +1 -0
  4. package/dist/commands/api/get.js +3 -1
  5. package/dist/commands/api/post.d.ts +1 -0
  6. package/dist/commands/api/post.js +3 -1
  7. package/dist/commands/api/put.d.ts +1 -0
  8. package/dist/commands/api/put.js +3 -1
  9. package/dist/commands/app/dev.js +2 -5
  10. package/dist/commands/app/frontend/dev.js +18 -0
  11. package/dist/commands/inspect/content.d.ts +25 -14
  12. package/dist/commands/inspect/content.js +34 -144
  13. package/dist/commands/inspect/functions.d.ts +48 -0
  14. package/dist/commands/inspect/functions.js +83 -0
  15. package/dist/commands/inspect/index.js +8 -7
  16. package/dist/commands/inspect/models.d.ts +17 -2
  17. package/dist/commands/inspect/models.js +114 -47
  18. package/dist/commands/inspect/notifications.d.ts +31 -0
  19. package/dist/commands/inspect/notifications.js +125 -0
  20. package/dist/commands/inspect/settings.d.ts +28 -0
  21. package/dist/commands/inspect/settings.js +82 -0
  22. package/dist/commands/inspect/webhooks.d.ts +34 -0
  23. package/dist/commands/inspect/webhooks.js +61 -0
  24. package/dist/create-app-command.d.ts +7 -0
  25. package/dist/create-app-command.js +66 -3
  26. package/dist/inspect-resource-command.d.ts +91 -0
  27. package/dist/inspect-resource-command.js +232 -0
  28. package/dist/lib/apps/index.d.ts +2 -1
  29. package/dist/lib/apps/index.js +43 -6
  30. package/dist/lib/apps/inspect-scope.d.ts +58 -0
  31. package/dist/lib/apps/inspect-scope.js +60 -0
  32. package/dist/lib/apps/object-id.d.ts +7 -0
  33. package/dist/lib/apps/object-id.js +9 -0
  34. package/dist/lib/apps/paths.js +8 -1
  35. package/dist/lib/apps/resolve.d.ts +16 -0
  36. package/dist/lib/apps/resolve.js +39 -0
  37. package/dist/lib/apps/slug.d.ts +29 -0
  38. package/dist/lib/apps/slug.js +12 -0
  39. package/dist/lib/create/tests/templates/mock-request.js +12 -1
  40. package/dist/lib/create/tests/templates/setup-globals.js +2 -0
  41. package/dist/lib/inspect/content.d.ts +39 -0
  42. package/dist/lib/inspect/content.js +76 -0
  43. package/dist/lib/inspect/notifications.d.ts +115 -0
  44. package/dist/lib/inspect/notifications.js +173 -0
  45. package/dist/lib/inspect/settings.d.ts +61 -0
  46. package/dist/lib/inspect/settings.js +56 -0
  47. package/dist/lib/inspect/table.d.ts +29 -0
  48. package/dist/lib/inspect/table.js +61 -0
  49. package/dist/lib/swell-function-wrapper.d.ts +7 -3
  50. package/dist/lib/swell-function-wrapper.js +53 -15
  51. package/dist/push-app-command.js +3 -2
  52. package/dist/swell-api-command.d.ts +2 -4
  53. package/dist/swell-api-command.js +58 -35
  54. package/oclif.manifest.json +364 -35
  55. package/package.json +1 -1
@@ -9,6 +9,7 @@ export default class ApiDelete extends SwellApiCommand {
9
9
  static flags: {
10
10
  live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
11
  api: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
12
+ header: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
12
13
  };
13
14
  static examples: string[];
14
15
  get method(): HttpMethod;
@@ -1,6 +1,6 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import { HttpMethod } from '../../lib/api.js';
3
- import { SwellApiCommand } from '../../swell-api-command.js';
3
+ import { SwellApiCommand, headerFlag } from '../../swell-api-command.js';
4
4
  export default class ApiDelete extends SwellApiCommand {
5
5
  static summary = 'Send a DELETE request to the Swell API.';
6
6
  static description = `Remove existing resources from the Swell API.
@@ -21,10 +21,12 @@ export default class ApiDelete extends SwellApiCommand {
21
21
  options: ['backend', 'frontend'],
22
22
  default: 'backend',
23
23
  }),
24
+ header: headerFlag,
24
25
  };
25
26
  static examples = [
26
27
  'swell api delete /products/abc123',
27
28
  'swell api delete /functions/my-app/clear-cache',
29
+ `swell api delete /functions/my-app/clear-cache -H 'Authorization: Bearer xxx'`,
28
30
  'swell api delete /products/abc123 --api frontend',
29
31
  ];
30
32
  get method() {
@@ -9,6 +9,7 @@ export default class ApiGet extends SwellApiCommand {
9
9
  static flags: {
10
10
  live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
11
  api: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
12
+ header: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
12
13
  };
13
14
  static examples: string[];
14
15
  get method(): HttpMethod;
@@ -1,6 +1,6 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import { HttpMethod } from '../../lib/api.js';
3
- import { SwellApiCommand } from '../../swell-api-command.js';
3
+ import { SwellApiCommand, headerFlag } from '../../swell-api-command.js';
4
4
  export default class ApiGet extends SwellApiCommand {
5
5
  static summary = 'Send a GET request to the Swell API.';
6
6
  static description = `Retrieve data from the Swell API.
@@ -22,12 +22,14 @@ export default class ApiGet extends SwellApiCommand {
22
22
  options: ['backend', 'frontend'],
23
23
  default: 'backend',
24
24
  }),
25
+ header: headerFlag,
25
26
  };
26
27
  static examples = [
27
28
  'swell api get /products',
28
29
  'swell api get "/products?limit=10&category=shoes"',
29
30
  'swell api get /products/abc123',
30
31
  'swell api get /functions/my-app/get-inventory',
32
+ `swell api get /functions/my-app/get-inventory -H 'Authorization: Bearer xxx'`,
31
33
  'swell api get /products --api frontend',
32
34
  ];
33
35
  get method() {
@@ -10,6 +10,7 @@ export default class ApiPost extends SwellApiCommand {
10
10
  body: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
11
11
  live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
12
12
  api: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
13
+ header: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
13
14
  };
14
15
  static examples: string[];
15
16
  get method(): HttpMethod;
@@ -1,6 +1,6 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import { HttpMethod } from '../../lib/api.js';
3
- import { SwellApiCommand } from '../../swell-api-command.js';
3
+ import { SwellApiCommand, headerFlag } from '../../swell-api-command.js';
4
4
  export default class ApiPost extends SwellApiCommand {
5
5
  static summary = 'Send a POST request to the Swell API.';
6
6
  static description = `Create new resources in the Swell API.
@@ -25,11 +25,13 @@ export default class ApiPost extends SwellApiCommand {
25
25
  options: ['backend', 'frontend'],
26
26
  default: 'backend',
27
27
  }),
28
+ header: headerFlag,
28
29
  };
29
30
  static examples = [
30
31
  'swell api post /products --body \'{"name": "Product", "price": 100}\'',
31
32
  'swell api post /products --body ./product.json',
32
33
  'swell api post /functions/my-app/create-order --body \'{"items":["sku-1"]}\'',
34
+ `swell api post /functions/my-app/webhook --body @./payload.json -H 'Stripe-Signature: t=123,v1=abc'`,
33
35
  `swell api post /products --body '{"name": "Product"}' --api frontend`,
34
36
  ];
35
37
  get method() {
@@ -10,6 +10,7 @@ export default class ApiPut extends SwellApiCommand {
10
10
  body: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
11
11
  live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
12
12
  api: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
13
+ header: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
13
14
  };
14
15
  static examples: string[];
15
16
  get method(): HttpMethod;
@@ -1,6 +1,6 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import { HttpMethod } from '../../lib/api.js';
3
- import { SwellApiCommand } from '../../swell-api-command.js';
3
+ import { SwellApiCommand, headerFlag } from '../../swell-api-command.js';
4
4
  export default class ApiPut extends SwellApiCommand {
5
5
  static summary = 'Send a PUT request to the Swell API.';
6
6
  static description = `Update existing resources in the Swell API.
@@ -25,11 +25,13 @@ export default class ApiPut extends SwellApiCommand {
25
25
  options: ['backend', 'frontend'],
26
26
  default: 'backend',
27
27
  }),
28
+ header: headerFlag,
28
29
  };
29
30
  static examples = [
30
31
  'swell api put /products/{id} --body \'{"id": "abc123", "name": "Updated"}\'',
31
32
  'swell api put /products/abc123 --body ./product.json',
32
33
  'swell api put /functions/my-app/update-stock --body \'{"sku":"abc","qty":10}\'',
34
+ `swell api put /functions/my-app/update-stock --body '{"sku":"abc"}' -H 'X-Idempotency-Key: 42'`,
33
35
  `swell api put /products/abc123 --body '{"name": "Updated"}' --api frontend`,
34
36
  ];
35
37
  get method() {
@@ -257,11 +257,8 @@ ENVIRONMENT = "development"
257
257
  .map((m) => String(m).toUpperCase())
258
258
  .join(', '))}`);
259
259
  }
260
- if (config.route.headers) {
261
- const headers = Object.entries(config.route.headers).map(([key, value]) => `${key}: ${value}`);
262
- this.log(` Headers:`);
263
- for (const header of headers)
264
- this.log(` ${style.dim(header)}`);
260
+ if (config.route.headers?.length) {
261
+ this.log(` Headers: ${style.dim(config.route.headers.join(', '))}`);
265
262
  }
266
263
  if (config.route.cache?.timeout) {
267
264
  this.log(` Cache timeout: ${style.dim(config.route.cache.timeout)}`);
@@ -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,48 @@
1
+ import { InspectResourceCommand } from '../../inspect-resource-command.js';
2
+ interface FunctionRecord {
3
+ id?: string;
4
+ name?: string;
5
+ enabled?: boolean;
6
+ cron?: {
7
+ schedule?: string;
8
+ };
9
+ route?: {
10
+ headers?: string[];
11
+ methods?: string[];
12
+ public?: boolean;
13
+ };
14
+ model?: {
15
+ events?: string[];
16
+ };
17
+ date_cron_scheduled?: string;
18
+ date_final_attempt?: string;
19
+ app_id?: string;
20
+ }
21
+ export default class Functions extends InspectResourceCommand {
22
+ static summary: string;
23
+ static description: string;
24
+ static args: {
25
+ identifier: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
26
+ };
27
+ static flags: {
28
+ app: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
29
+ live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
30
+ json: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
31
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
32
+ };
33
+ static examples: string[];
34
+ protected resourceLabel: string;
35
+ protected resourceLabelSingular: string;
36
+ protected adminPath: string;
37
+ protected commandName: string;
38
+ run(): Promise<void>;
39
+ protected metaFor(r: FunctionRecord): string | undefined;
40
+ /**
41
+ * Model-triggered functions emit `/events:webhooks` rows on each invocation.
42
+ * Route and cron functions do not — pointing the agent at an empty query
43
+ * would teach them their function isn't running. The logs hint always
44
+ * applies as long as we have a function name.
45
+ */
46
+ protected hints(record: FunctionRecord): string[];
47
+ }
48
+ export {};
@@ -0,0 +1,83 @@
1
+ import { InspectResourceCommand, inspectResourceBaseArgs, inspectResourceBaseFlags, } from '../../inspect-resource-command.js';
2
+ function deriveTrigger(r) {
3
+ if (r.cron?.schedule) {
4
+ return `cron (${r.cron.schedule})`;
5
+ }
6
+ if (r.model?.events?.length) {
7
+ const n = r.model.events.length;
8
+ return `model (${n} event${n === 1 ? '' : 's'})`;
9
+ }
10
+ if (r.route) {
11
+ const { methods } = r.route;
12
+ if (!methods?.length)
13
+ return 'route';
14
+ return methods.length <= 2
15
+ ? `route (${methods.join(',')})`
16
+ : `route (${methods.length} methods)`;
17
+ }
18
+ return undefined;
19
+ }
20
+ export default class Functions extends InspectResourceCommand {
21
+ static summary = 'Functions with scheduling and trigger state.';
22
+ static description = `Lists functions across all apps, grouped by app. Pass --app=<slug> or --app=. (current swell.json) to scope.
23
+
24
+ List output: paste-back key + status (disabled, trigger, next cron run, last failure).
25
+
26
+ Pass an identifier to view a single record as JSON.
27
+
28
+ Identifier forms:
29
+ app.<app>.<name> — full paste-back key (list column 1)
30
+ <name> — requires --app= scope
31
+ <24-char id> — any scope
32
+ `;
33
+ static args = { ...inspectResourceBaseArgs };
34
+ static flags = { ...inspectResourceBaseFlags };
35
+ static examples = [
36
+ 'swell inspect functions',
37
+ 'swell inspect functions --app=my-app',
38
+ 'swell inspect functions app.my-app.payment-sync',
39
+ 'swell inspect functions payment-sync --app=my-app',
40
+ 'swell inspect functions --live',
41
+ ];
42
+ resourceLabel = 'Functions';
43
+ resourceLabelSingular = 'function';
44
+ adminPath = '/data/:functions';
45
+ commandName = 'functions';
46
+ async run() {
47
+ const { args, flags } = await this.parse(Functions);
48
+ await this.runInspect({ args, flags });
49
+ }
50
+ metaFor(r) {
51
+ const parts = [];
52
+ if (!r.enabled) {
53
+ parts.push('disabled');
54
+ }
55
+ const trigger = deriveTrigger(r);
56
+ if (trigger) {
57
+ parts.push(trigger);
58
+ }
59
+ if (r.date_cron_scheduled) {
60
+ parts.push(`next ${r.date_cron_scheduled}`);
61
+ }
62
+ if (r.date_final_attempt) {
63
+ parts.push(`last fail ${r.date_final_attempt}`);
64
+ }
65
+ return parts.length > 0 ? parts.join(' · ') : undefined;
66
+ }
67
+ /**
68
+ * Model-triggered functions emit `/events:webhooks` rows on each invocation.
69
+ * Route and cron functions do not — pointing the agent at an empty query
70
+ * would teach them their function isn't running. The logs hint always
71
+ * applies as long as we have a function name.
72
+ */
73
+ hints(record) {
74
+ const lines = [];
75
+ if (record.model?.events?.length && record.id) {
76
+ lines.push(`swell api get '/events:webhooks?where[function_id]=${record.id}&limit=10'`);
77
+ }
78
+ if (record.name) {
79
+ lines.push(`swell logs --type function -s '${record.name}'`);
80
+ }
81
+ return lines;
82
+ }
83
+ }
@@ -1,16 +1,17 @@
1
1
  import { Command } from '@oclif/core';
2
2
  export default class Inspect extends Command {
3
3
  static summary = 'Inspect deployed artifacts in your store.';
4
- static description = `View configuration and data for deployed resources.
5
-
6
- Inspect models, content views, and other deployed artifacts in your Swell store.`;
4
+ static description = `Subcommands list globally and group by app. Pass --app=<slug> or --app=. (current swell.json) to scope. Pass an identifier to print the full record as JSON, followed by a "Next steps" footer of related commands. Use --json to omit the footer for piping.
5
+ `;
7
6
  static examples = [
8
- 'swell inspect models',
9
7
  'swell inspect models /products',
10
- 'swell inspect models /apps/myapp/orders',
8
+ 'swell inspect content app.honest_reviews.reviews',
9
+ 'swell inspect webhooks --app=my-app',
10
+ 'swell inspect functions app.my-app.payment-sync',
11
+ 'swell inspect notifications com.orders.receipt.v2',
12
+ 'swell inspect settings app.my-app',
11
13
  ];
12
14
  async run() {
13
- this.log('Use "swell inspect models" to inspect deployed artifacts.');
14
- this.log('Run "swell inspect --help" for more information.');
15
+ this.log('Run "swell inspect --help" to see available subcommands.');
15
16
  }
16
17
  }
@@ -6,7 +6,9 @@ export default class Models extends SwellCommand {
6
6
  'collection-path': import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
7
7
  };
8
8
  static flags: {
9
+ app: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
9
10
  live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
+ json: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
10
12
  yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
13
  };
12
14
  static examples: string[];
@@ -17,10 +19,23 @@ export default class Models extends SwellCommand {
17
19
  private showAppModels;
18
20
  private showModels;
19
21
  private getModelPath;
20
- private getAppSlugId;
21
22
  private showModelDetail;
22
23
  private resolveModelPath;
23
24
  private resolveAppModelPath;
24
25
  private throwModelNotFound;
25
- private showModel;
26
+ private emitModel;
27
+ /**
28
+ * Detail-mode hints for a model record. Mirrors `InspectResourceCommand.hints`
29
+ * but lives here because `models` doesn't extend that base.
30
+ *
31
+ * Two hints: the records collection at the model's runtime address, and the
32
+ * mutation event stream filtered by canonical model string. Both interpolate
33
+ * from the record's own fields — `app_id`, `namespace`, `name` — so we stay
34
+ * consistent with column 1 if the platform renames anything.
35
+ *
36
+ * Canonical model string: `accounts` (system), `content/blogs` (namespaced
37
+ * system), `apps/<hex>/<col>` (app). The records path mirrors that with a
38
+ * leading slash.
39
+ */
40
+ private modelHints;
26
41
  }