@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.
- package/dist/commands/app/frontend/dev.js +18 -0
- package/dist/commands/inspect/content.d.ts +25 -14
- package/dist/commands/inspect/content.js +34 -144
- package/dist/commands/inspect/functions.d.ts +48 -0
- package/dist/commands/inspect/functions.js +83 -0
- package/dist/commands/inspect/index.js +8 -7
- package/dist/commands/inspect/models.d.ts +17 -2
- package/dist/commands/inspect/models.js +114 -47
- package/dist/commands/inspect/notifications.d.ts +31 -0
- package/dist/commands/inspect/notifications.js +125 -0
- package/dist/commands/inspect/settings.d.ts +28 -0
- package/dist/commands/inspect/settings.js +82 -0
- package/dist/commands/inspect/webhooks.d.ts +34 -0
- package/dist/commands/inspect/webhooks.js +61 -0
- package/dist/create-app-command.d.ts +7 -0
- package/dist/create-app-command.js +66 -3
- package/dist/inspect-resource-command.d.ts +91 -0
- package/dist/inspect-resource-command.js +232 -0
- package/dist/lib/apps/index.d.ts +2 -1
- package/dist/lib/apps/index.js +43 -6
- package/dist/lib/apps/inspect-scope.d.ts +58 -0
- package/dist/lib/apps/inspect-scope.js +60 -0
- package/dist/lib/apps/object-id.d.ts +7 -0
- package/dist/lib/apps/object-id.js +9 -0
- package/dist/lib/apps/paths.js +8 -1
- package/dist/lib/apps/resolve.d.ts +16 -0
- package/dist/lib/apps/resolve.js +39 -0
- package/dist/lib/apps/slug.d.ts +29 -0
- package/dist/lib/apps/slug.js +12 -0
- package/dist/lib/inspect/content.d.ts +39 -0
- package/dist/lib/inspect/content.js +76 -0
- package/dist/lib/inspect/notifications.d.ts +115 -0
- package/dist/lib/inspect/notifications.js +173 -0
- package/dist/lib/inspect/settings.d.ts +61 -0
- package/dist/lib/inspect/settings.js +56 -0
- package/dist/lib/inspect/table.d.ts +29 -0
- package/dist/lib/inspect/table.js +61 -0
- package/dist/push-app-command.js +3 -2
- package/dist/swell-api-command.d.ts +0 -4
- package/dist/swell-api-command.js +2 -18
- package/oclif.manifest.json +328 -35
- 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 = '
|
|
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
|
-
|
|
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
|
|
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: '
|
|
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 /
|
|
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
|
-
|
|
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
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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(
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
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('
|
|
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, '
|
|
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 =
|
|
137
|
+
const appSlugId = slugFromApp(app);
|
|
117
138
|
const pathPrefix = `/apps/${appSlugId}`;
|
|
118
|
-
const label =
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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;
|