@swell/cli 2.6.0 → 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 (42) 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/functions.d.ts +48 -0
  5. package/dist/commands/inspect/functions.js +83 -0
  6. package/dist/commands/inspect/index.js +8 -7
  7. package/dist/commands/inspect/models.d.ts +17 -2
  8. package/dist/commands/inspect/models.js +114 -47
  9. package/dist/commands/inspect/notifications.d.ts +31 -0
  10. package/dist/commands/inspect/notifications.js +125 -0
  11. package/dist/commands/inspect/settings.d.ts +28 -0
  12. package/dist/commands/inspect/settings.js +82 -0
  13. package/dist/commands/inspect/webhooks.d.ts +34 -0
  14. package/dist/commands/inspect/webhooks.js +61 -0
  15. package/dist/create-app-command.d.ts +7 -0
  16. package/dist/create-app-command.js +66 -3
  17. package/dist/inspect-resource-command.d.ts +91 -0
  18. package/dist/inspect-resource-command.js +232 -0
  19. package/dist/lib/apps/index.d.ts +2 -1
  20. package/dist/lib/apps/index.js +43 -6
  21. package/dist/lib/apps/inspect-scope.d.ts +58 -0
  22. package/dist/lib/apps/inspect-scope.js +60 -0
  23. package/dist/lib/apps/object-id.d.ts +7 -0
  24. package/dist/lib/apps/object-id.js +9 -0
  25. package/dist/lib/apps/paths.js +8 -1
  26. package/dist/lib/apps/resolve.d.ts +16 -0
  27. package/dist/lib/apps/resolve.js +39 -0
  28. package/dist/lib/apps/slug.d.ts +29 -0
  29. package/dist/lib/apps/slug.js +12 -0
  30. package/dist/lib/inspect/content.d.ts +39 -0
  31. package/dist/lib/inspect/content.js +76 -0
  32. package/dist/lib/inspect/notifications.d.ts +115 -0
  33. package/dist/lib/inspect/notifications.js +173 -0
  34. package/dist/lib/inspect/settings.d.ts +61 -0
  35. package/dist/lib/inspect/settings.js +56 -0
  36. package/dist/lib/inspect/table.d.ts +29 -0
  37. package/dist/lib/inspect/table.js +61 -0
  38. package/dist/push-app-command.js +3 -2
  39. package/dist/swell-api-command.d.ts +0 -4
  40. package/dist/swell-api-command.js +2 -18
  41. package/oclif.manifest.json +328 -35
  42. package/package.json +1 -1
@@ -1,15 +1,16 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import { FetchError } from 'node-fetch';
3
3
  import { getCurrentAppSlugId } from '../../lib/apps/index.js';
4
+ import { resolveInspectScope, } from '../../lib/apps/inspect-scope.js';
5
+ import { resolveAppId } from '../../lib/apps/resolve.js';
6
+ import { slugFromApp } from '../../lib/apps/slug.js';
4
7
  import { default as localConfig } from '../../lib/config.js';
5
8
  import { SwellCommand } from '../../swell-command.js';
6
9
  export default class Models extends SwellCommand {
7
- static summary = 'Inspect models for collections deployed in your store.';
8
- static description = `
9
- View models for collections available in your Swell store.
10
+ static summary = 'Collection models and fields (hierarchical).';
11
+ static description = `Lists all collections grouped by type. Pass --app=<slug> or --app=. (current swell.json) to filter to one app's models.
10
12
 
11
- Without a collection path, this command lists all available collections grouped by type.
12
- With a collection path, it retrieves the model for that specific collection in JSON format.
13
+ Pass a collection path to view a single model as JSON. Paths are fully qualified, so --app is ignored in detail mode.
13
14
  `;
14
15
  static args = {
15
16
  'collection-path': Args.string({
@@ -18,41 +19,58 @@ With a collection path, it retrieves the model for that specific collection in J
18
19
  }),
19
20
  };
20
21
  static flags = {
22
+ app: Flags.string({
23
+ description: 'Filter by app slug, or "." for current swell.json.',
24
+ }),
21
25
  live: Flags.boolean({
22
- description: 'Use the live environment instead of test (default: test).',
26
+ description: 'Use live environment (default: test).',
27
+ default: false,
28
+ }),
29
+ json: Flags.boolean({
30
+ description: 'Emit pure JSON without the "Next steps" footer. Detail mode only.',
23
31
  default: false,
24
32
  }),
25
33
  yes: Flags.boolean({
26
34
  char: 'y',
27
- description: 'Accept all prompts automatically (for agent compatibility; no functional effect).',
35
+ description: 'No-op; accepted for agent compatibility.',
28
36
  default: false,
37
+ hidden: true,
29
38
  }),
30
39
  };
31
40
  static examples = [
32
41
  'swell inspect models',
42
+ 'swell inspect models --app=my-app',
43
+ 'swell inspect models --live',
33
44
  'swell inspect models /products',
34
45
  'swell inspect models /content/blogs',
35
46
  'swell inspect models /apps/myapp/orders',
36
- 'swell inspect models /orders --live',
47
+ 'swell inspect models /products --json',
37
48
  ];
38
49
  async run() {
39
50
  const { args, flags } = await this.parse(Models);
40
51
  const { 'collection-path': path } = args;
41
- const { live } = flags;
52
+ const { app, json, live } = flags;
53
+ if (json && !path) {
54
+ this.error('--json is only valid in detail mode. Pass a collection path to inspect a single model, or drop --json for the list view.', { exit: 1 });
55
+ }
42
56
  if (!live) {
43
57
  await this.api.setEnv('test');
44
58
  }
45
- // Detail mode: show schema for a specific model
59
+ // Detail mode: show schema for a specific model. Paths are fully
60
+ // qualified, so --app is ignored here and we skip scope resolution.
46
61
  if (path) {
47
62
  if (!path.startsWith('/')) {
48
63
  throw new Error(`Collection path must start with '/'. Did you mean '/${path}'?`);
49
64
  }
50
- await this.showModelDetail(path);
65
+ await this.showModelDetail(path, json);
51
66
  return;
52
67
  }
68
+ const scope = await resolveInspectScope({
69
+ resolveAppId: (slug) => resolveAppId(this.api, slug),
70
+ readCurrentAppSlug: () => getCurrentAppSlugId(),
71
+ }, { app });
53
72
  const store = localConfig.getDefaultStore();
54
- // List mode: show all models
55
- await this.listModels(store, live);
73
+ await this.listModels(store, live, scope);
56
74
  }
57
75
  async catch(error) {
58
76
  if (error instanceof FetchError) {
@@ -61,25 +79,32 @@ With a collection path, it retrieves the model for that specific collection in J
61
79
  }
62
80
  return this.error(error.message, { exit: 1 });
63
81
  }
64
- async listModels(store, live) {
82
+ async listModels(store, live, scope) {
65
83
  const { results: apps } = await this.api.get({
66
84
  adminPath: '/apps',
67
85
  });
68
- const appsById = Object.fromEntries(apps.map((app) => [app.id, app]));
69
- const { results: models } = await this.api.getAll({
70
- adminPath: '/data/:models',
71
- }, {
72
- query: {
73
- deprecated: { $ne: true },
74
- development: { $ne: true },
75
- abstract: { $ne: true },
76
- reserved: { $ne: true },
86
+ const allAppsById = Object.fromEntries(apps.map((app) => [app.id, app]));
87
+ const appsById = scope.appId
88
+ ? allAppsById[scope.appId]
89
+ ? { [scope.appId]: allAppsById[scope.appId] }
90
+ : {}
91
+ : allAppsById;
92
+ const baseModelsQuery = {
93
+ deprecated: { $ne: true },
94
+ development: { $ne: true },
95
+ abstract: { $ne: true },
96
+ reserved: { $ne: true },
97
+ };
98
+ const modelsQuery = scope.appId
99
+ ? { ...baseModelsQuery, app_id: scope.appId }
100
+ : {
101
+ ...baseModelsQuery,
77
102
  $or: [
78
103
  { app_id: { $exists: false } },
79
- { app_id: { $in: Object.keys(appsById) } },
104
+ { app_id: { $in: Object.keys(allAppsById) } },
80
105
  ],
81
- },
82
- });
106
+ };
107
+ const { results: models } = await this.api.getAll({ adminPath: '/data/:models' }, { query: modelsQuery });
83
108
  const standardModels = [];
84
109
  const modelsByAppId = {};
85
110
  for (const model of models) {
@@ -91,31 +116,27 @@ With a collection path, it retrieves the model for that specific collection in J
91
116
  standardModels.push(model);
92
117
  }
93
118
  }
94
- this.log(`Collections available in '${store}' ${live ? '[live]' : '[test]'}:`);
95
- this.log();
96
- this.showStandardModels(standardModels);
119
+ this.log(`Collections in '${store}' ${live ? '[live]' : '[test]'}`);
120
+ if (!scope.appId) {
121
+ this.log();
122
+ this.showStandardModels(standardModels);
123
+ }
97
124
  await this.showAppModels(appsById, modelsByAppId);
98
- // Show next step hint
99
125
  this.log();
100
- this.log('View collection model');
101
- this.log(' swell inspect models <collection-path>');
126
+ this.log('Run "swell inspect models <path>" to view a collection model.');
102
127
  this.log();
103
- this.log('Examples:');
104
- this.log(' swell inspect models /products');
105
- this.log(' swell inspect models /content/blogs');
106
- this.log(' swell inspect models /apps/myapp/orders');
107
128
  }
108
129
  showStandardModels(models) {
109
- this.showModels(models, 'Standard');
130
+ this.showModels(models, '── standard');
110
131
  }
111
132
  async showAppModels(appsById, modelsByAppId) {
112
133
  const currentAppSlugId = await getCurrentAppSlugId();
113
134
  for (const [appId, app] of Object.entries(appsById)) {
114
135
  const models = modelsByAppId[appId];
115
136
  if (models) {
116
- const appSlugId = this.getAppSlugId(app);
137
+ const appSlugId = slugFromApp(app);
117
138
  const pathPrefix = `/apps/${appSlugId}`;
118
- const label = `${app.name} App ${currentAppSlugId === appSlugId ? '(current app)' : ''}`;
139
+ const label = `── ${appSlugId}${currentAppSlugId === appSlugId ? ' (current app)' : ''}`;
119
140
  this.log();
120
141
  this.showModels(models, label, { pathPrefix });
121
142
  }
@@ -123,7 +144,6 @@ With a collection path, it retrieves the model for that specific collection in J
123
144
  }
124
145
  showModels(models, label, options) {
125
146
  this.log(label);
126
- this.log();
127
147
  const sortedModels = [...models].sort((a, b) => {
128
148
  const pathA = this.getModelPath(a, options?.pathPrefix);
129
149
  const pathB = this.getModelPath(b, options?.pathPrefix);
@@ -153,10 +173,7 @@ With a collection path, it retrieves the model for that specific collection in J
153
173
  }
154
174
  return `/${model.name}`;
155
175
  }
156
- getAppSlugId(app) {
157
- return app.public_id || app.private_id.replace(/^_/, '');
158
- }
159
- async showModelDetail(path) {
176
+ async showModelDetail(path, json) {
160
177
  const [modelPath, subModelKey, namespace] = await this.resolveModelPath(path);
161
178
  let model = null;
162
179
  if (namespace) {
@@ -181,14 +198,15 @@ With a collection path, it retrieves the model for that specific collection in J
181
198
  this.throwModelNotFound(path);
182
199
  }
183
200
  if (!subModelKey) {
184
- this.showModel(model);
201
+ this.emitModel(model, json);
185
202
  return;
186
203
  }
187
204
  const subModel = model.fields[subModelKey];
188
205
  if (!subModel) {
189
206
  this.throwModelNotFound(path);
190
207
  }
191
- this.showModel(subModel);
208
+ // Sub-model fields have no runtime address of their own; emit JSON only.
209
+ this.log(JSON.stringify(subModel, null, 2));
192
210
  }
193
211
  async resolveModelPath(path) {
194
212
  if (path.startsWith('/apps/')) {
@@ -217,7 +235,56 @@ With a collection path, it retrieves the model for that specific collection in J
217
235
  throwModelNotFound(path) {
218
236
  throw new Error(`No model found for collection '${path}'.\nList available collections: swell inspect models`);
219
237
  }
220
- showModel(model) {
238
+ emitModel(model, json) {
221
239
  this.log(JSON.stringify(model, null, 2));
240
+ if (json) {
241
+ return;
242
+ }
243
+ const lines = this.modelHints(model);
244
+ if (lines.length === 0) {
245
+ return;
246
+ }
247
+ this.log();
248
+ this.log('Next steps:');
249
+ for (const line of lines) {
250
+ this.log(` ${line}`);
251
+ }
252
+ this.log();
253
+ }
254
+ /**
255
+ * Detail-mode hints for a model record. Mirrors `InspectResourceCommand.hints`
256
+ * but lives here because `models` doesn't extend that base.
257
+ *
258
+ * Two hints: the records collection at the model's runtime address, and the
259
+ * mutation event stream filtered by canonical model string. Both interpolate
260
+ * from the record's own fields — `app_id`, `namespace`, `name` — so we stay
261
+ * consistent with column 1 if the platform renames anything.
262
+ *
263
+ * Canonical model string: `accounts` (system), `content/blogs` (namespaced
264
+ * system), `apps/<hex>/<col>` (app). The records path mirrors that with a
265
+ * leading slash.
266
+ */
267
+ modelHints(model) {
268
+ if (!model.name) {
269
+ return [];
270
+ }
271
+ let apiPath;
272
+ let canonical;
273
+ if (model.app_id) {
274
+ apiPath = `/apps/${model.app_id}/${model.name}`;
275
+ canonical = `apps/${model.app_id}/${model.name}`;
276
+ }
277
+ else if (model.namespace) {
278
+ apiPath = `/${model.namespace}/${model.name}`;
279
+ canonical = `${model.namespace}/${model.name}`;
280
+ }
281
+ else {
282
+ apiPath = `/${model.name}`;
283
+ canonical = model.name;
284
+ }
285
+ return [
286
+ `swell api get '${apiPath}?limit=10'`,
287
+ `swell api get '/events?where[model]=${canonical}&limit=10'`,
288
+ ];
222
289
  }
223
290
  }
@@ -0,0 +1,31 @@
1
+ import { InspectResourceCommand, InspectResourceCommandParsed } from '../../inspect-resource-command.js';
2
+ import { InspectScope } from '../../lib/apps/inspect-scope.js';
3
+ import { NotificationRecord } from '../../lib/inspect/notifications.js';
4
+ import { GroupInfo } from '../../lib/inspect/table.js';
5
+ export default class Notifications extends InspectResourceCommand {
6
+ static summary: string;
7
+ static description: string;
8
+ static args: {
9
+ identifier: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
10
+ };
11
+ static flags: {
12
+ app: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
13
+ live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
14
+ json: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
15
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
16
+ };
17
+ static examples: string[];
18
+ protected resourceLabel: string;
19
+ protected resourceLabelSingular: string;
20
+ protected adminPath: string;
21
+ protected commandName: string;
22
+ run(): Promise<void>;
23
+ protected keyFor(record: NotificationRecord, appSlugById: Record<string, string>): string;
24
+ protected groupForRecord(record: NotificationRecord, appSlugById: Record<string, string>): GroupInfo;
25
+ protected metaFor(r: NotificationRecord): string | undefined;
26
+ protected hints(record: NotificationRecord): string[];
27
+ protected showDetail(identifier: string, scope: InspectScope, flags: InspectResourceCommandParsed['flags']): Promise<void>;
28
+ private resolveSlugOrHex;
29
+ private lookupTriplet;
30
+ private lookupAndDisambiguate;
31
+ }
@@ -0,0 +1,125 @@
1
+ import { InspectResourceCommand, inspectResourceBaseArgs, inspectResourceBaseFlags, } from '../../inspect-resource-command.js';
2
+ import { isObjectId } from '../../lib/apps/object-id.js';
3
+ import { buildAppSlugMap, resolveAppId } from '../../lib/apps/resolve.js';
4
+ import { buildNotificationTemplate, expandModelCandidates, formatDisambiguationError, formatNotificationKey, groupNotificationRecord, parseNotificationKey, } from '../../lib/inspect/notifications.js';
5
+ export default class Notifications extends InspectResourceCommand {
6
+ static summary = 'Notifications across apps and platform.';
7
+ static description = `Lists notifications across all apps plus platform 'com.*' system records, grouped by owner. Pass --app=<slug> or --app=. (current swell.json) to scope.
8
+
9
+ App keys include the model (app.<slug>.<model>.<name>) since identity is (api, model, name, app_id?) — two notifications in one app can share a name on different models. Own-app prefix apps/<own>/... is stripped in display.
10
+
11
+ Pass an identifier to view a single record as JSON.
12
+
13
+ Identifier forms:
14
+ com.<model>.<name> — platform record
15
+ app.<app>.<model>.<name> — app record (full key)
16
+ app.<app>.<name> — legacy; ambiguous → errors with candidates
17
+ <name> — requires --app= scope
18
+ <24-char id> — any scope
19
+ `;
20
+ static args = { ...inspectResourceBaseArgs };
21
+ static flags = { ...inspectResourceBaseFlags };
22
+ static examples = [
23
+ 'swell inspect notifications',
24
+ 'swell inspect notifications --app=my-app',
25
+ 'swell inspect notifications com.orders.receipt.v2',
26
+ 'swell inspect notifications app.notify_me.subscriptions.back-in-stock',
27
+ 'swell inspect notifications back-in-stock --app=notify_me',
28
+ 'swell inspect notifications --live',
29
+ ];
30
+ resourceLabel = 'Notifications';
31
+ resourceLabelSingular = 'notification';
32
+ adminPath = '/data/:notifications';
33
+ commandName = 'notifications';
34
+ async run() {
35
+ const { args, flags } = await this.parse(Notifications);
36
+ await this.runInspect({ args, flags });
37
+ }
38
+ keyFor(record, appSlugById) {
39
+ return formatNotificationKey(record, appSlugById);
40
+ }
41
+ groupForRecord(record, appSlugById) {
42
+ return groupNotificationRecord(record, appSlugById);
43
+ }
44
+ metaFor(r) {
45
+ return r.enabled === false ? 'disabled' : undefined;
46
+ }
47
+ hints(record) {
48
+ const template = buildNotificationTemplate(record);
49
+ if (!template) {
50
+ return [];
51
+ }
52
+ return [
53
+ `swell api get '/notifications?where[template]=${template}&limit=10'`,
54
+ ];
55
+ }
56
+ async showDetail(identifier, scope, flags) {
57
+ const parsed = parseNotificationKey(identifier);
58
+ let record = null;
59
+ switch (parsed.kind) {
60
+ case 'hex':
61
+ case 'system_id': {
62
+ record = await this.api.get({
63
+ adminPath: `${this.adminPath}/${parsed.id}`,
64
+ });
65
+ break;
66
+ }
67
+ case 'app_full': {
68
+ const appId = await this.resolveSlugOrHex(parsed.slug);
69
+ const candidates = await expandModelCandidates(parsed.model, appId, (s) => resolveAppId(this.api, s));
70
+ record = await this.lookupTriplet(appId, candidates, parsed.name);
71
+ break;
72
+ }
73
+ case 'app_short': {
74
+ const appId = await this.resolveSlugOrHex(parsed.slug);
75
+ record = await this.lookupAndDisambiguate(appId, parsed.name, identifier);
76
+ break;
77
+ }
78
+ case 'bare': {
79
+ if (!scope.appId) {
80
+ this.error(`Bare ${this.resourceLabelSingular} name '${identifier}' requires --app=<slug> or --app=. to scope. ` +
81
+ `Alternatively, pass a full key (app.<app>.<model>.<name> or com.<model>.<name>).`, { exit: 1 });
82
+ }
83
+ record = await this.lookupAndDisambiguate(scope.appId, parsed.name, identifier);
84
+ break;
85
+ }
86
+ case 'invalid': {
87
+ this.error(`Invalid ${this.resourceLabelSingular} identifier '${identifier}'. ` +
88
+ `Expected bare name, app.<app>.<model>.<name>, com.<model>.<name>, or 24-char id.`, { exit: 1 });
89
+ }
90
+ }
91
+ if (!record) {
92
+ this.throwNotFound(identifier);
93
+ }
94
+ this.emitDetail(record, flags);
95
+ }
96
+ async resolveSlugOrHex(slugOrHex) {
97
+ return isObjectId(slugOrHex)
98
+ ? slugOrHex
99
+ : resolveAppId(this.api, slugOrHex);
100
+ }
101
+ async lookupTriplet(appId, modelCandidates, name) {
102
+ const response = await this.api.get({ adminPath: this.adminPath }, {
103
+ query: {
104
+ app_id: appId,
105
+ model: { $in: modelCandidates },
106
+ name,
107
+ limit: 1,
108
+ },
109
+ });
110
+ return response?.results?.[0] ?? null;
111
+ }
112
+ async lookupAndDisambiguate(appId, name, identifier) {
113
+ // Fetch one past the display cap so the formatter can distinguish
114
+ // "exactly 10 candidates" from "10+ candidates and we're truncating".
115
+ const response = await this.api.get({ adminPath: this.adminPath }, { query: { app_id: appId, name, limit: 11 } });
116
+ const results = response?.results ?? [];
117
+ if (results.length <= 1) {
118
+ return results[0] ?? null;
119
+ }
120
+ const appSlugById = await buildAppSlugMap(this.api);
121
+ this.error(formatDisambiguationError(results, appSlugById, identifier), {
122
+ exit: 1,
123
+ });
124
+ }
125
+ }
@@ -0,0 +1,28 @@
1
+ import { InspectResourceCommand, InspectResourceCommandParsed } from '../../inspect-resource-command.js';
2
+ import { InspectScope } from '../../lib/apps/inspect-scope.js';
3
+ import { SettingRecord } from '../../lib/inspect/settings.js';
4
+ import { GroupInfo } from '../../lib/inspect/table.js';
5
+ export default class Settings extends InspectResourceCommand {
6
+ static summary: string;
7
+ static description: string;
8
+ static args: {
9
+ identifier: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
10
+ };
11
+ static flags: {
12
+ app: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
13
+ live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
14
+ json: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
15
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
16
+ };
17
+ static examples: string[];
18
+ protected resourceLabel: string;
19
+ protected resourceLabelSingular: string;
20
+ protected adminPath: string;
21
+ protected commandName: string;
22
+ run(): Promise<void>;
23
+ protected keyFor(record: SettingRecord, appSlugById: Record<string, string>): string;
24
+ protected groupForRecord(record: SettingRecord, appSlugById: Record<string, string>): GroupInfo;
25
+ protected filterListResults<T>(results: T[]): T[];
26
+ protected hints(record: SettingRecord): string[];
27
+ protected showDetail(identifier: string, _scope: InspectScope, flags: InspectResourceCommandParsed['flags']): Promise<void>;
28
+ }
@@ -0,0 +1,82 @@
1
+ import { InspectResourceCommand, inspectResourceBaseArgs, inspectResourceBaseFlags, } from '../../inspect-resource-command.js';
2
+ import { isObjectId } from '../../lib/apps/object-id.js';
3
+ import { resolveAppId } from '../../lib/apps/resolve.js';
4
+ import { formatSettingsKey, groupSettingsRecord, isDeprecatedRecord, parseSettingsKey, } from '../../lib/inspect/settings.js';
5
+ export default class Settings extends InspectResourceCommand {
6
+ static summary = 'App and platform settings records.';
7
+ static description = `Lists app settings records plus platform 'com.*' system records, grouped by owner. Pass --app=<slug> or --app=. (current swell.json) to scope.
8
+
9
+ Each app collapses to one settings record at push time, so the list shows one row per app.
10
+
11
+ Pass an identifier to view a single record as JSON.
12
+
13
+ Identifier forms:
14
+ <name> — system record (e.g. taxes, payments)
15
+ app.<slug> — app record
16
+ com.<name> — full system id
17
+ <24-char id> — direct fetch
18
+
19
+ Deprecated records (general, admin) are filtered from the list but remain reachable by name.
20
+ `;
21
+ static args = { ...inspectResourceBaseArgs };
22
+ static flags = { ...inspectResourceBaseFlags };
23
+ static examples = [
24
+ 'swell inspect settings',
25
+ 'swell inspect settings --app=my-app',
26
+ 'swell inspect settings taxes',
27
+ 'swell inspect settings app.notify_me',
28
+ 'swell inspect settings com.payments',
29
+ 'swell inspect settings --live',
30
+ 'swell inspect settings taxes --json',
31
+ ];
32
+ resourceLabel = 'Settings';
33
+ resourceLabelSingular = 'settings record';
34
+ adminPath = '/data/:settings';
35
+ commandName = 'settings';
36
+ async run() {
37
+ const { args, flags } = await this.parse(Settings);
38
+ await this.runInspect({ args, flags });
39
+ }
40
+ keyFor(record, appSlugById) {
41
+ return formatSettingsKey(record, appSlugById);
42
+ }
43
+ groupForRecord(record, appSlugById) {
44
+ return groupSettingsRecord(record, appSlugById);
45
+ }
46
+ filterListResults(results) {
47
+ return results.filter((r) => !isDeprecatedRecord(r));
48
+ }
49
+ hints(record) {
50
+ if (!record.name) {
51
+ return [];
52
+ }
53
+ return [`swell api get '/settings/${record.name}'`];
54
+ }
55
+ async showDetail(identifier, _scope, flags) {
56
+ const parsed = parseSettingsKey(identifier);
57
+ let segment;
58
+ switch (parsed.kind) {
59
+ case 'app': {
60
+ segment = isObjectId(parsed.ref)
61
+ ? parsed.ref
62
+ : await resolveAppId(this.api, parsed.ref);
63
+ break;
64
+ }
65
+ case 'path': {
66
+ segment = parsed.segment;
67
+ break;
68
+ }
69
+ case 'invalid': {
70
+ this.error(`Invalid ${this.resourceLabelSingular} identifier '${identifier}'. ` +
71
+ `Expected bare name (e.g. taxes), app.<slug>, full id (e.g. com.taxes), or 24-char id.`, { exit: 1 });
72
+ }
73
+ }
74
+ const record = await this.api.get({
75
+ adminPath: `${this.adminPath}/${segment}`,
76
+ });
77
+ if (!record) {
78
+ this.throwNotFound(identifier);
79
+ }
80
+ this.emitDetail(record, flags);
81
+ }
82
+ }
@@ -0,0 +1,34 @@
1
+ import { InspectResourceCommand } from '../../inspect-resource-command.js';
2
+ interface WebhookRecord {
3
+ id?: string;
4
+ alias?: string | null;
5
+ enabled?: boolean;
6
+ auto_disabled?: boolean;
7
+ events?: string[];
8
+ attempts_failed?: number;
9
+ date_final_attempt?: string;
10
+ app_id?: string;
11
+ }
12
+ export default class Webhooks extends InspectResourceCommand {
13
+ static summary: string;
14
+ static description: string;
15
+ static args: {
16
+ identifier: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
17
+ };
18
+ static flags: {
19
+ app: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
20
+ live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
21
+ json: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
22
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
23
+ };
24
+ static examples: string[];
25
+ protected resourceLabel: string;
26
+ protected resourceLabelSingular: string;
27
+ protected adminPath: string;
28
+ protected commandName: string;
29
+ protected nameField: string;
30
+ run(): Promise<void>;
31
+ protected metaFor(r: WebhookRecord): string | undefined;
32
+ protected hints(record: WebhookRecord): string[];
33
+ }
34
+ export {};
@@ -0,0 +1,61 @@
1
+ import { InspectResourceCommand, inspectResourceBaseArgs, inspectResourceBaseFlags, } from '../../inspect-resource-command.js';
2
+ export default class Webhooks extends InspectResourceCommand {
3
+ static summary = 'Webhooks with delivery state.';
4
+ static description = `Lists webhooks across all apps, grouped by app. Pass --app=<slug> or --app=. (current swell.json) to scope.
5
+
6
+ List output: paste-back key + status (disabled, events, failures since date).
7
+
8
+ Pass an identifier to view a single record as JSON.
9
+
10
+ Identifier forms:
11
+ app.<app>.<alias> — full paste-back key (list column 1)
12
+ <alias> — requires --app= scope
13
+ <24-char id> — any scope
14
+ `;
15
+ static args = { ...inspectResourceBaseArgs };
16
+ static flags = { ...inspectResourceBaseFlags };
17
+ static examples = [
18
+ 'swell inspect webhooks',
19
+ 'swell inspect webhooks --app=my-app',
20
+ 'swell inspect webhooks app.my-app.order-sync',
21
+ 'swell inspect webhooks order-sync --app=my-app',
22
+ 'swell inspect webhooks --live',
23
+ ];
24
+ resourceLabel = 'Webhooks';
25
+ resourceLabelSingular = 'webhook';
26
+ adminPath = '/data/:webhooks';
27
+ commandName = 'webhooks';
28
+ nameField = 'alias';
29
+ async run() {
30
+ const { args, flags } = await this.parse(Webhooks);
31
+ await this.runInspect({ args, flags });
32
+ }
33
+ metaFor(r) {
34
+ const parts = [];
35
+ if (r.auto_disabled) {
36
+ parts.push('auto-disabled');
37
+ }
38
+ else if (!r.enabled) {
39
+ parts.push('disabled');
40
+ }
41
+ if (r.events?.length) {
42
+ parts.push(r.events.join(','));
43
+ }
44
+ const failed = r.attempts_failed ?? 0;
45
+ if (failed > 0) {
46
+ const suffix = r.date_final_attempt
47
+ ? ` since ${r.date_final_attempt}`
48
+ : '';
49
+ parts.push(`${failed} fails${suffix}`);
50
+ }
51
+ return parts.length > 0 ? parts.join(' · ') : undefined;
52
+ }
53
+ hints(record) {
54
+ if (!record.id) {
55
+ return [];
56
+ }
57
+ return [
58
+ `swell api get '/events:webhooks?where[webhook_id]=${record.id}&limit=10'`,
59
+ ];
60
+ }
61
+ }
@@ -14,6 +14,11 @@ export declare abstract class CreateAppCommand extends SwellCommand {
14
14
  addAllowedHostsToAngular(configPath: string): Promise<void>;
15
15
  addAllowedHostsToAstro(configPath: string): Promise<void>;
16
16
  addAllowedHostsToNuxt(configPath: string): Promise<void>;
17
+ /**
18
+ * For frameworks whose dev server is Vite directly. Astro/Nuxt nest the
19
+ * vite block inside their own defineConfig — see addViteAllowedHosts.
20
+ */
21
+ addAllowedHostsToVite(configPath: string): Promise<void>;
17
22
  /**
18
23
  * Add vite allowedHosts configuration to a framework config file.
19
24
  * Supports config files that use a defineX({}) pattern (Astro, Nuxt, etc.)
@@ -29,8 +34,10 @@ export declare abstract class CreateAppCommand extends SwellCommand {
29
34
  getInstalledStorefrontApps(): Promise<any[] | boolean>;
30
35
  getProjectType(frameworkType: string): {
31
36
  installCommand: string;
37
+ appTypes?: string[] | undefined;
32
38
  buildCommand?: string | undefined;
33
39
  devCommand: string;
40
+ displayOrder?: number | undefined;
34
41
  mainPackage: string;
35
42
  name: string;
36
43
  slug: string;