@swell/cli 2.2.1 → 2.3.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.
- package/dist/app-command.js +1 -1
- package/dist/commands/api/delete.js +4 -1
- package/dist/commands/api/get.js +1 -0
- package/dist/commands/api/index.js +4 -1
- package/dist/commands/api/post.js +1 -0
- package/dist/commands/api/put.js +1 -0
- package/dist/commands/app/frontend/dev.js +1 -1
- package/dist/commands/app/init.d.ts +8 -7
- package/dist/commands/app/init.js +36 -18
- package/dist/commands/app/pull.js +2 -2
- package/dist/commands/app/push.js +2 -2
- package/dist/commands/app/version.d.ts +1 -0
- package/dist/commands/app/version.js +15 -5
- package/dist/commands/create/app.d.ts +8 -4
- package/dist/commands/create/app.js +68 -51
- package/dist/commands/create/content.d.ts +5 -6
- package/dist/commands/create/content.js +21 -37
- package/dist/commands/create/function.d.ts +6 -7
- package/dist/commands/create/function.js +94 -31
- package/dist/commands/create/index.js +8 -0
- package/dist/commands/create/model.d.ts +5 -6
- package/dist/commands/create/model.js +21 -33
- package/dist/commands/create/notification.d.ts +9 -9
- package/dist/commands/create/notification.js +117 -95
- package/dist/commands/create/setting.d.ts +21 -0
- package/dist/commands/create/setting.js +120 -0
- package/dist/commands/create/tests.d.ts +4 -5
- package/dist/commands/create/tests.js +8 -11
- package/dist/commands/create/webhook.d.ts +22 -0
- package/dist/commands/create/webhook.js +176 -0
- package/dist/commands/schema.d.ts +1 -0
- package/dist/commands/schema.js +51 -5
- package/dist/commands/theme/init.d.ts +8 -4
- package/dist/commands/theme/init.js +21 -8
- package/dist/create-app-command.d.ts +1 -0
- package/dist/create-app-command.js +15 -10
- package/dist/create-config-command.js +2 -2
- package/dist/help/custom-help.d.ts +89 -0
- package/dist/help/custom-help.js +337 -0
- package/dist/help/types.d.ts +75 -0
- package/dist/help/types.js +1 -0
- package/dist/lib/apps/app-config.js +2 -2
- package/dist/lib/apps/index.d.ts +2 -1
- package/dist/lib/apps/index.js +21 -5
- package/dist/lib/apps/paths.js +7 -6
- package/dist/lib/create/notification.d.ts +1 -0
- package/dist/lib/create/schemas.d.ts +1 -0
- package/dist/lib/create/schemas.js +1 -0
- package/dist/lib/create/setting.d.ts +15 -0
- package/dist/lib/create/setting.js +27 -0
- package/dist/lib/create/tests/templates/env-dts.js +1 -0
- package/dist/lib/create/tests/templates/swell-client.js +2 -0
- package/dist/lib/create/tests/templates/tsconfig.js +2 -0
- package/dist/lib/create/tests/templates/vitest-config.js +1 -0
- package/dist/lib/create/webhook.d.ts +32 -0
- package/dist/lib/create/webhook.js +52 -0
- package/dist/lib/proxy.js +62 -5
- package/dist/swell-api-command.d.ts +14 -0
- package/dist/swell-api-command.js +113 -7
- package/oclif.manifest.json +609 -259
- package/package.json +3 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CreateConfigCommand } from '../../create-config-command.js';
|
|
2
|
+
import { HelpMeta } from '../../help/types.js';
|
|
3
|
+
import { ConfigType } from '../../lib/apps/index.js';
|
|
4
|
+
export default class CreateSetting extends CreateConfigCommand {
|
|
5
|
+
static args: {
|
|
6
|
+
name: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static description: string;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static helpMeta: HelpMeta;
|
|
11
|
+
static flags: {
|
|
12
|
+
label: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
13
|
+
description: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
14
|
+
fields: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
15
|
+
overwrite: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
16
|
+
yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
17
|
+
};
|
|
18
|
+
static summary: string;
|
|
19
|
+
createType: ConfigType;
|
|
20
|
+
run(): Promise<void>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { input } from '@inquirer/prompts';
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
|
+
import { CreateConfigCommand } from '../../create-config-command.js';
|
|
4
|
+
import { ConfigType } from '../../lib/apps/index.js';
|
|
5
|
+
import { toFileName } from '../../lib/create/index.js';
|
|
6
|
+
import { SCHEMAS } from '../../lib/create/schemas.js';
|
|
7
|
+
import { parseFields, toSettingLabel, } from '../../lib/create/setting.js';
|
|
8
|
+
export default class CreateSetting extends CreateConfigCommand {
|
|
9
|
+
static args = {
|
|
10
|
+
name: Args.string({
|
|
11
|
+
default: '',
|
|
12
|
+
description: 'Setting name (e.g., api-config)',
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
15
|
+
static description = 'Create a setting configuration in the settings folder.';
|
|
16
|
+
static examples = [
|
|
17
|
+
'$ swell create setting',
|
|
18
|
+
'$ swell create setting api-config -y',
|
|
19
|
+
'$ swell create setting api-config -f api_key:short_text,enabled:boolean -l "API Config" -y',
|
|
20
|
+
];
|
|
21
|
+
static helpMeta = {
|
|
22
|
+
usageDirect: '<name> [...] -y',
|
|
23
|
+
};
|
|
24
|
+
static flags = {
|
|
25
|
+
label: Flags.string({
|
|
26
|
+
char: 'l',
|
|
27
|
+
default: '',
|
|
28
|
+
description: 'Display label (default: titleized name)',
|
|
29
|
+
}),
|
|
30
|
+
description: Flags.string({
|
|
31
|
+
char: 'd',
|
|
32
|
+
default: '',
|
|
33
|
+
description: 'Description',
|
|
34
|
+
}),
|
|
35
|
+
fields: Flags.string({
|
|
36
|
+
char: 'f',
|
|
37
|
+
default: '',
|
|
38
|
+
description: 'Fields as id:type pairs (e.g., api_key:short_text,enabled:boolean)',
|
|
39
|
+
}),
|
|
40
|
+
overwrite: Flags.boolean({
|
|
41
|
+
default: false,
|
|
42
|
+
description: 'Overwrite existing file',
|
|
43
|
+
}),
|
|
44
|
+
yes: Flags.boolean({
|
|
45
|
+
char: 'y',
|
|
46
|
+
description: 'Skip prompts, require all arguments',
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
static summary = 'Create a setting configuration in the settings folder.';
|
|
50
|
+
createType = ConfigType.SETTING;
|
|
51
|
+
async run() {
|
|
52
|
+
const { args, flags } = await this.parse(CreateSetting);
|
|
53
|
+
const confirmYes = Boolean(flags.yes);
|
|
54
|
+
// NON-INTERACTIVE PATH
|
|
55
|
+
if (confirmYes) {
|
|
56
|
+
const argName = args.name;
|
|
57
|
+
if (!argName) {
|
|
58
|
+
this.error('Missing required argument for non-interactive mode: NAME\n\nExample: swell create setting api-config -y', { exit: 1 });
|
|
59
|
+
}
|
|
60
|
+
const name = toFileName(argName);
|
|
61
|
+
const label = flags.label || toSettingLabel(name);
|
|
62
|
+
const description = flags.description || '';
|
|
63
|
+
const fieldsRaw = (flags.fields || '');
|
|
64
|
+
const fieldPairs = fieldsRaw
|
|
65
|
+
? fieldsRaw
|
|
66
|
+
.split(',')
|
|
67
|
+
.map((v) => v.trim())
|
|
68
|
+
.filter(Boolean)
|
|
69
|
+
: [];
|
|
70
|
+
const fileName = toFileName(name);
|
|
71
|
+
const fileBody = {
|
|
72
|
+
$schema: SCHEMAS.SETTING,
|
|
73
|
+
description,
|
|
74
|
+
fields: parseFields(fieldPairs),
|
|
75
|
+
label,
|
|
76
|
+
};
|
|
77
|
+
await this.createFile({ fileBody, fileName }, flags.overwrite,
|
|
78
|
+
/* shouldConfirm */ false);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// INTERACTIVE PATH
|
|
82
|
+
let name = args.name;
|
|
83
|
+
if (!name) {
|
|
84
|
+
name = await input({
|
|
85
|
+
default: 'my-setting',
|
|
86
|
+
message: 'Setting name',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
name = toFileName(name);
|
|
90
|
+
let { label, description } = flags;
|
|
91
|
+
const { fields: fieldsFlag, overwrite } = flags;
|
|
92
|
+
if (!label) {
|
|
93
|
+
label = await input({
|
|
94
|
+
default: toSettingLabel(name),
|
|
95
|
+
message: 'Setting label',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (!description) {
|
|
99
|
+
description = await input({
|
|
100
|
+
default: '',
|
|
101
|
+
message: 'Describe what this setting is for',
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const fieldsRaw = (fieldsFlag || '');
|
|
105
|
+
const fieldPairs = fieldsRaw
|
|
106
|
+
? fieldsRaw
|
|
107
|
+
.split(',')
|
|
108
|
+
.map((v) => v.trim())
|
|
109
|
+
.filter(Boolean)
|
|
110
|
+
: [];
|
|
111
|
+
const fileName = toFileName(name);
|
|
112
|
+
const fileBody = {
|
|
113
|
+
$schema: SCHEMAS.SETTING,
|
|
114
|
+
description,
|
|
115
|
+
fields: parseFields(fieldPairs),
|
|
116
|
+
label,
|
|
117
|
+
};
|
|
118
|
+
await this.createFile({ fileBody, fileName }, overwrite);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { AppCommand } from '../../app-command.js';
|
|
2
|
+
import { HelpMeta } from '../../help/types.js';
|
|
2
3
|
export default class CreateTests extends AppCommand {
|
|
3
4
|
static description: string;
|
|
4
|
-
static
|
|
5
|
-
static
|
|
6
|
-
command: string;
|
|
7
|
-
description: string;
|
|
8
|
-
}[];
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static helpMeta: HelpMeta;
|
|
9
7
|
static flags: {
|
|
10
8
|
overwrite: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
9
|
yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
12
10
|
};
|
|
11
|
+
static summary: string;
|
|
13
12
|
run(): Promise<void>;
|
|
14
13
|
private logScaffoldResult;
|
|
15
14
|
}
|
|
@@ -4,28 +4,25 @@ import { toAppId } from '../../lib/create/index.js';
|
|
|
4
4
|
import { createTestsScaffold, } from '../../lib/create/tests.js';
|
|
5
5
|
export default class CreateTests extends AppCommand {
|
|
6
6
|
static description = 'Initialize a vitest + Cloudflare Workers test setup that reuses swell-cli authentication.';
|
|
7
|
-
static summary = 'Create test scaffolding for your Swell app.';
|
|
8
7
|
static examples = [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
description: 'Create a basic test setup using TypeScript.',
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
command: 'swell create tests --overwrite',
|
|
15
|
-
description: 'Regenerate test files, overwriting existing ones.',
|
|
16
|
-
},
|
|
8
|
+
'$ swell create tests',
|
|
9
|
+
'$ swell create tests --overwrite',
|
|
17
10
|
];
|
|
11
|
+
static helpMeta = {
|
|
12
|
+
usageDirect: '[-y]',
|
|
13
|
+
};
|
|
18
14
|
static flags = {
|
|
19
15
|
overwrite: Flags.boolean({
|
|
20
16
|
default: false,
|
|
21
|
-
description: '
|
|
17
|
+
description: 'Overwrite existing test files',
|
|
22
18
|
}),
|
|
23
19
|
yes: Flags.boolean({
|
|
24
20
|
char: 'y',
|
|
25
21
|
default: false,
|
|
26
|
-
description: '
|
|
22
|
+
description: 'Skip prompts, require all arguments',
|
|
27
23
|
}),
|
|
28
24
|
};
|
|
25
|
+
static summary = 'Create tests scaffolding for your Swell app.';
|
|
29
26
|
async run() {
|
|
30
27
|
const { flags } = await this.parse(CreateTests);
|
|
31
28
|
const appIdFromConfig = (this.swellConfig.get('id') ||
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CreateConfigCommand } from '../../create-config-command.js';
|
|
2
|
+
import { HelpMeta } from '../../help/types.js';
|
|
3
|
+
import { ConfigType } from '../../lib/apps/index.js';
|
|
4
|
+
export default class CreateWebhook extends CreateConfigCommand {
|
|
5
|
+
static args: {
|
|
6
|
+
name: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static description: string;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static helpMeta: HelpMeta;
|
|
11
|
+
static flags: {
|
|
12
|
+
url: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
13
|
+
events: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
14
|
+
description: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
15
|
+
enabled: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
16
|
+
overwrite: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
17
|
+
yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
18
|
+
};
|
|
19
|
+
static summary: string;
|
|
20
|
+
createType: ConfigType;
|
|
21
|
+
run(): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { confirm, input } from '@inquirer/prompts';
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
|
+
import { CreateConfigCommand } from '../../create-config-command.js';
|
|
4
|
+
import { ConfigType } from '../../lib/apps/index.js';
|
|
5
|
+
import { toFileName } from '../../lib/create/index.js';
|
|
6
|
+
import { SCHEMAS } from '../../lib/create/schemas.js';
|
|
7
|
+
import { parseEvents, validateEventFormat, validateUrl, } from '../../lib/create/webhook.js';
|
|
8
|
+
export default class CreateWebhook extends CreateConfigCommand {
|
|
9
|
+
static args = {
|
|
10
|
+
name: Args.string({
|
|
11
|
+
default: '',
|
|
12
|
+
description: 'Webhook name (e.g., order-sync)',
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
15
|
+
static description = 'Create a webhook configuration in the webhooks folder.';
|
|
16
|
+
static examples = [
|
|
17
|
+
'$ swell create webhook',
|
|
18
|
+
'$ swell create webhook order-sync -u https://example.com/hook -e order.created -y',
|
|
19
|
+
'$ swell create webhook payment-handler -u https://api.example.com/payments -e payment.succeeded,payment.failed --enabled -y',
|
|
20
|
+
];
|
|
21
|
+
static helpMeta = {
|
|
22
|
+
usageDirect: '<name> -u <url> -e <events> [...] -y',
|
|
23
|
+
};
|
|
24
|
+
static flags = {
|
|
25
|
+
url: Flags.string({
|
|
26
|
+
char: 'u',
|
|
27
|
+
default: '',
|
|
28
|
+
description: 'Endpoint URL (required with -y)',
|
|
29
|
+
}),
|
|
30
|
+
events: Flags.string({
|
|
31
|
+
char: 'e',
|
|
32
|
+
default: '',
|
|
33
|
+
description: 'Events to listen for (e.g., order.created,payment.succeeded)',
|
|
34
|
+
}),
|
|
35
|
+
description: Flags.string({
|
|
36
|
+
char: 'd',
|
|
37
|
+
default: '',
|
|
38
|
+
description: 'Description',
|
|
39
|
+
}),
|
|
40
|
+
enabled: Flags.boolean({
|
|
41
|
+
default: false,
|
|
42
|
+
description: 'Enable webhook (default: false)',
|
|
43
|
+
}),
|
|
44
|
+
overwrite: Flags.boolean({
|
|
45
|
+
default: false,
|
|
46
|
+
description: 'Overwrite existing file',
|
|
47
|
+
}),
|
|
48
|
+
yes: Flags.boolean({
|
|
49
|
+
char: 'y',
|
|
50
|
+
description: 'Skip prompts, require all arguments',
|
|
51
|
+
}),
|
|
52
|
+
};
|
|
53
|
+
static summary = 'Create a webhook configuration in the webhooks folder.';
|
|
54
|
+
createType = ConfigType.WEBHOOK;
|
|
55
|
+
async run() {
|
|
56
|
+
const { args, flags } = await this.parse(CreateWebhook);
|
|
57
|
+
const confirmYes = Boolean(flags.yes);
|
|
58
|
+
// NON-INTERACTIVE PATH
|
|
59
|
+
if (confirmYes) {
|
|
60
|
+
const argName = args.name;
|
|
61
|
+
if (!argName) {
|
|
62
|
+
this.error('Missing required argument for non-interactive mode: NAME\n\nExample: swell create webhook payment-handler -u https://example.com/hook -e order.created -y', { exit: 1 });
|
|
63
|
+
}
|
|
64
|
+
if (!flags.url) {
|
|
65
|
+
this.error('Missing required flag for non-interactive mode: --url\n\nExample: swell create webhook payment-handler -u https://example.com/hook -e order.created -y', { exit: 1 });
|
|
66
|
+
}
|
|
67
|
+
if (!flags.events) {
|
|
68
|
+
this.error('Missing required flag for non-interactive mode: --events\n\nExample: swell create webhook payment-handler -u https://example.com/hook -e order.created -y', { exit: 1 });
|
|
69
|
+
}
|
|
70
|
+
// Validate URL format
|
|
71
|
+
validateUrl(flags.url, this.error.bind(this));
|
|
72
|
+
// Parse and validate events
|
|
73
|
+
const events = parseEvents(flags.events);
|
|
74
|
+
if (events.length === 0) {
|
|
75
|
+
this.error('At least one event is required.\n\nExample: swell create webhook payment-handler -u https://example.com/hook -e order.created -y', { exit: 1 });
|
|
76
|
+
}
|
|
77
|
+
validateEventFormat(events, this.error.bind(this));
|
|
78
|
+
const name = toFileName(argName);
|
|
79
|
+
const fileName = toFileName(name);
|
|
80
|
+
const fileBody = {
|
|
81
|
+
$schema: SCHEMAS.WEBHOOK,
|
|
82
|
+
description: flags.description || '',
|
|
83
|
+
enabled: flags.enabled,
|
|
84
|
+
events,
|
|
85
|
+
url: flags.url,
|
|
86
|
+
};
|
|
87
|
+
await this.createFile({ fileBody, fileName }, flags.overwrite,
|
|
88
|
+
/* shouldConfirm */ false);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// INTERACTIVE PATH
|
|
92
|
+
let name = args.name;
|
|
93
|
+
if (!name) {
|
|
94
|
+
name = await input({
|
|
95
|
+
default: 'my-webhook',
|
|
96
|
+
message: 'Webhook name',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
name = toFileName(name);
|
|
100
|
+
// URL prompt (required)
|
|
101
|
+
let { url } = flags;
|
|
102
|
+
if (url) {
|
|
103
|
+
validateUrl(url, this.error.bind(this));
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
url = await input({
|
|
107
|
+
message: 'Webhook endpoint URL',
|
|
108
|
+
validate(value) {
|
|
109
|
+
if (!value) {
|
|
110
|
+
return 'URL is required';
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const parsed = new URL(value);
|
|
114
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
115
|
+
return 'URL must use HTTP or HTTPS protocol';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return 'Invalid URL format';
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// Events prompt (required)
|
|
126
|
+
let events;
|
|
127
|
+
if (flags.events) {
|
|
128
|
+
events = parseEvents(flags.events);
|
|
129
|
+
validateEventFormat(events, this.error.bind(this));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const eventsInput = await input({
|
|
133
|
+
message: 'Events to subscribe (e.g., order.created,payment.succeeded)',
|
|
134
|
+
validate(value) {
|
|
135
|
+
if (!value) {
|
|
136
|
+
return 'At least one event is required';
|
|
137
|
+
}
|
|
138
|
+
const parsed = parseEvents(value);
|
|
139
|
+
for (const event of parsed) {
|
|
140
|
+
const parts = event.split('.');
|
|
141
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
142
|
+
return `Invalid event format: ${event}. Use model.action pattern.`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
events = parseEvents(eventsInput);
|
|
149
|
+
}
|
|
150
|
+
// Description prompt (optional)
|
|
151
|
+
let { description, enabled } = flags;
|
|
152
|
+
const { overwrite } = flags;
|
|
153
|
+
if (!description) {
|
|
154
|
+
description = await input({
|
|
155
|
+
default: '',
|
|
156
|
+
message: 'Describe what this webhook does',
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// Enabled prompt (optional)
|
|
160
|
+
if (!enabled) {
|
|
161
|
+
enabled = await confirm({
|
|
162
|
+
default: false,
|
|
163
|
+
message: 'Enable webhook now?',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const fileName = toFileName(name);
|
|
167
|
+
const fileBody = {
|
|
168
|
+
$schema: SCHEMAS.WEBHOOK,
|
|
169
|
+
description,
|
|
170
|
+
enabled,
|
|
171
|
+
events,
|
|
172
|
+
url,
|
|
173
|
+
};
|
|
174
|
+
await this.createFile({ fileBody, fileName }, overwrite);
|
|
175
|
+
}
|
|
176
|
+
}
|
package/dist/commands/schema.js
CHANGED
|
@@ -5,6 +5,7 @@ import ajvErrors from 'ajv-errors';
|
|
|
5
5
|
import addFormats from 'ajv-formats';
|
|
6
6
|
import * as fs from 'node:fs';
|
|
7
7
|
import parseJson from 'parse-json';
|
|
8
|
+
import { bundleFunction } from '../lib/bundle.js';
|
|
8
9
|
import { SCHEMAS_BASE_URL } from '../lib/constants.js';
|
|
9
10
|
import { SwellCommand } from '../swell-command.js';
|
|
10
11
|
const SCHEMA_DEFINITIONS = Object.freeze({
|
|
@@ -23,9 +24,13 @@ const SCHEMA_DEFINITIONS = Object.freeze({
|
|
|
23
24
|
webhook: {
|
|
24
25
|
description: 'Webhook definitions (/webhooks/*.json)',
|
|
25
26
|
},
|
|
27
|
+
function: {
|
|
28
|
+
description: 'Function definitions (/functions/*.ts or *.js)',
|
|
29
|
+
hasJsonSchema: false,
|
|
30
|
+
},
|
|
26
31
|
});
|
|
27
32
|
export default class Schema extends SwellCommand {
|
|
28
|
-
static summary = 'View or validate Swell config
|
|
33
|
+
static summary = 'View or validate Swell config schemas.';
|
|
29
34
|
static examples = [
|
|
30
35
|
{
|
|
31
36
|
description: 'List available schema types',
|
|
@@ -44,22 +49,26 @@ export default class Schema extends SwellCommand {
|
|
|
44
49
|
command: '<%= config.bin %> <%= command.id %> content --format=dts',
|
|
45
50
|
},
|
|
46
51
|
{
|
|
47
|
-
description: 'Validate a file',
|
|
52
|
+
description: 'Validate a JSON config file',
|
|
48
53
|
command: '<%= config.bin %> <%= command.id %> content myfile.json',
|
|
49
54
|
},
|
|
50
55
|
{
|
|
51
56
|
description: 'Validate from stdin',
|
|
52
57
|
command: 'cat myfile.json | <%= config.bin %> <%= command.id %> content -',
|
|
53
58
|
},
|
|
59
|
+
{
|
|
60
|
+
description: 'Validate a function file',
|
|
61
|
+
command: '<%= config.bin %> <%= command.id %> function myfunction.ts',
|
|
62
|
+
},
|
|
54
63
|
];
|
|
55
64
|
static args = {
|
|
56
65
|
type: Args.string({
|
|
57
66
|
required: false,
|
|
58
|
-
description: 'Schema type (e.g. model, content, setting, notification, webhook)',
|
|
67
|
+
description: 'Schema type (e.g. model, content, setting, notification, webhook, function)',
|
|
59
68
|
}),
|
|
60
69
|
file: Args.string({
|
|
61
70
|
required: false,
|
|
62
|
-
description: 'Path to
|
|
71
|
+
description: 'Path to file to validate (or use "-" for stdin)',
|
|
63
72
|
}),
|
|
64
73
|
};
|
|
65
74
|
static flags = {
|
|
@@ -90,7 +99,9 @@ export default class Schema extends SwellCommand {
|
|
|
90
99
|
if (format) {
|
|
91
100
|
throw new Error('--format cannot be used with file validation');
|
|
92
101
|
}
|
|
93
|
-
await
|
|
102
|
+
await (type === 'function'
|
|
103
|
+
? this.validateFunction(file)
|
|
104
|
+
: this.validate(type, file));
|
|
94
105
|
return;
|
|
95
106
|
}
|
|
96
107
|
// Show schema info or output schema
|
|
@@ -119,6 +130,13 @@ export default class Schema extends SwellCommand {
|
|
|
119
130
|
this.log(`Run "swell schema --help" for more information.`);
|
|
120
131
|
}
|
|
121
132
|
async outputSchema(type, format) {
|
|
133
|
+
const definition = SCHEMA_DEFINITIONS[type];
|
|
134
|
+
// Guard against json-schema formats for types without JSON Schema
|
|
135
|
+
if ('hasJsonSchema' in definition &&
|
|
136
|
+
definition.hasJsonSchema === false &&
|
|
137
|
+
(format === 'json-schema' || format === 'json-schema-bundle')) {
|
|
138
|
+
throw new Error(`'${type}' does not have a JSON Schema. Use --format=dts for TypeScript declarations.`);
|
|
139
|
+
}
|
|
122
140
|
let output;
|
|
123
141
|
switch (format) {
|
|
124
142
|
case 'json-schema': {
|
|
@@ -202,6 +220,34 @@ export default class Schema extends SwellCommand {
|
|
|
202
220
|
}
|
|
203
221
|
this.log('Valid model definition');
|
|
204
222
|
}
|
|
223
|
+
async validateFunction(file) {
|
|
224
|
+
if (!fs.existsSync(file)) {
|
|
225
|
+
throw new Error(`File not found: ${file}`);
|
|
226
|
+
}
|
|
227
|
+
const filePath = fs.realpathSync(file);
|
|
228
|
+
// Attempt to bundle the function (validates syntax, imports, etc.)
|
|
229
|
+
let bundleResult;
|
|
230
|
+
try {
|
|
231
|
+
bundleResult = await bundleFunction(filePath);
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
throw new Error(`Function compilation failed:\n• ${error.message}`);
|
|
235
|
+
}
|
|
236
|
+
const { config } = bundleResult;
|
|
237
|
+
// Validate config exists
|
|
238
|
+
if (!config) {
|
|
239
|
+
throw new Error('Invalid function:\n• Function must export a `config` object');
|
|
240
|
+
}
|
|
241
|
+
// Validate trigger exclusivity
|
|
242
|
+
const triggers = ['route', 'model', 'cron'].filter((t) => config[t]);
|
|
243
|
+
if (triggers.length === 0) {
|
|
244
|
+
throw new Error('Invalid function config:\n• Config must specify one of: route, model, cron');
|
|
245
|
+
}
|
|
246
|
+
if (triggers.length > 1) {
|
|
247
|
+
throw new Error(`Invalid function config:\n• Multiple triggers specified: ${triggers.join(', ')}. Use exactly one.`);
|
|
248
|
+
}
|
|
249
|
+
this.log('Valid function definition');
|
|
250
|
+
}
|
|
205
251
|
getFileInput(file) {
|
|
206
252
|
if (!fs.existsSync(file)) {
|
|
207
253
|
throw new Error(`File not found: ${file}`);
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { HelpMeta } from '../../help/types.js';
|
|
1
2
|
import InitApp from '../app/init.js';
|
|
2
3
|
export default class InitTheme extends InitApp {
|
|
4
|
+
protected commandExample: string;
|
|
5
|
+
static args: {
|
|
6
|
+
id: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
3
8
|
static description: string;
|
|
4
|
-
static examples:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}[];
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static helpMeta: HelpMeta;
|
|
11
|
+
static flags: any;
|
|
8
12
|
appType: string;
|
|
9
13
|
}
|
|
@@ -1,15 +1,28 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
1
2
|
import InitApp from '../app/init.js';
|
|
2
3
|
export default class InitTheme extends InitApp {
|
|
4
|
+
// Override command example for error messages
|
|
5
|
+
commandExample = 'swell theme init';
|
|
6
|
+
static args = {
|
|
7
|
+
id: Args.string({
|
|
8
|
+
default: '',
|
|
9
|
+
description: 'Theme identifier (defaults to current directory name)',
|
|
10
|
+
}),
|
|
11
|
+
};
|
|
3
12
|
static description = 'Initialize theme swell.json in the current directory.';
|
|
4
13
|
static examples = [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
command: 'swell theme init -y',
|
|
11
|
-
description: 'Initialize theme and accept all default values.',
|
|
12
|
-
},
|
|
14
|
+
'$ swell theme init',
|
|
15
|
+
'$ swell theme init my-theme',
|
|
16
|
+
'$ swell theme init -y',
|
|
17
|
+
'$ swell theme init my-theme -y',
|
|
18
|
+
'$ swell theme init my-theme --storefront-app proxima -y',
|
|
13
19
|
];
|
|
20
|
+
static helpMeta = {
|
|
21
|
+
usageDirect: '[id] --storefront-app <app> [...] -y',
|
|
22
|
+
};
|
|
23
|
+
// Inherit all flags from InitApp (which inherits from CreateApp)
|
|
24
|
+
static flags = {
|
|
25
|
+
...InitApp.flags,
|
|
26
|
+
};
|
|
14
27
|
appType = 'theme';
|
|
15
28
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SwellCommand } from './swell-command.js';
|
|
2
2
|
export declare abstract class CreateAppCommand extends SwellCommand {
|
|
3
|
+
protected commandExample: string;
|
|
3
4
|
static baseFlags: {
|
|
4
5
|
frontend: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
5
6
|
'storefront-app': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
@@ -13,24 +13,26 @@ import style from './lib/style.js';
|
|
|
13
13
|
import { SwellCommand } from './swell-command.js';
|
|
14
14
|
const execAsync = promisify(exec);
|
|
15
15
|
export class CreateAppCommand extends SwellCommand {
|
|
16
|
+
// Command name used in error message examples; override in subclasses
|
|
17
|
+
commandExample = 'swell create app';
|
|
16
18
|
static baseFlags = {
|
|
17
19
|
frontend: Flags.string({
|
|
18
|
-
description:
|
|
20
|
+
description: 'Framework: astro | angular | hono | nuxt | nextjs | none',
|
|
19
21
|
options: getFrontendProjectSlugs(true, false),
|
|
20
22
|
}),
|
|
21
23
|
'storefront-app': Flags.string({
|
|
22
|
-
description:
|
|
24
|
+
description: 'Target storefront app ID',
|
|
23
25
|
}),
|
|
24
26
|
'integration-type': Flags.string({
|
|
25
|
-
description:
|
|
27
|
+
description: 'Integration: generic | payment | shipping | tax',
|
|
26
28
|
options: ['generic', 'payment', 'shipping', 'tax'],
|
|
27
29
|
}),
|
|
28
30
|
'integration-id': Flags.string({
|
|
29
|
-
description:
|
|
31
|
+
description: 'Service ID (e.g., card, fedex)',
|
|
30
32
|
}),
|
|
31
33
|
yes: Flags.boolean({
|
|
32
34
|
char: 'y',
|
|
33
|
-
description:
|
|
35
|
+
description: 'Skip prompts, require all arguments',
|
|
34
36
|
}),
|
|
35
37
|
};
|
|
36
38
|
devApi;
|
|
@@ -354,12 +356,12 @@ export class CreateAppCommand extends SwellCommand {
|
|
|
354
356
|
getProjectType(frameworkType) {
|
|
355
357
|
const projectType = FrontendProjectTypes.find((type) => type.slug === frameworkType);
|
|
356
358
|
if (!projectType) {
|
|
357
|
-
this.error(`Could not find project type: ${frameworkType}\n\nValid values: ${getFrontendProjectValidValues(true, false)}\n\nExample:
|
|
359
|
+
this.error(`Could not find project type: ${frameworkType}\n\nValid values: ${getFrontendProjectValidValues(true, false)}\n\nExample: ${this.commandExample} reviews --type admin --frontend astro -y`, {
|
|
358
360
|
exit: 1,
|
|
359
361
|
});
|
|
360
362
|
}
|
|
361
363
|
if (!projectType.installCommand) {
|
|
362
|
-
this.error(`Project type ${projectType.name} cannot be installed (legacy type)\n\nValid values: ${getFrontendProjectValidValues(true, false)}\n\nExample:
|
|
364
|
+
this.error(`Project type ${projectType.name} cannot be installed (legacy type)\n\nValid values: ${getFrontendProjectValidValues(true, false)}\n\nExample: ${this.commandExample} reviews --type admin --frontend astro -y`, {
|
|
363
365
|
exit: 1,
|
|
364
366
|
});
|
|
365
367
|
}
|
|
@@ -378,9 +380,12 @@ export class CreateAppCommand extends SwellCommand {
|
|
|
378
380
|
workspaces: ['frontend'],
|
|
379
381
|
devDependencies: {
|
|
380
382
|
'@swell/app-types': '^1.0.5',
|
|
383
|
+
typescript: '^5.9.3',
|
|
381
384
|
},
|
|
382
385
|
name,
|
|
383
|
-
scripts: {
|
|
386
|
+
scripts: {
|
|
387
|
+
typecheck: '([ -z "$(find functions -name \'*.ts\' 2>/dev/null | head -1)" ] || tsc --noEmit) && ([ ! -f test/tsconfig.json ] || tsc --noEmit -p test) && ([ ! -f frontend/tsconfig.json ] || tsc --noEmit -p frontend)',
|
|
388
|
+
},
|
|
384
389
|
version: config.get('version'),
|
|
385
390
|
};
|
|
386
391
|
const tsConfig = {
|
|
@@ -388,10 +393,10 @@ export class CreateAppCommand extends SwellCommand {
|
|
|
388
393
|
lib: ['esnext', 'webworker'],
|
|
389
394
|
module: 'esnext',
|
|
390
395
|
target: 'esnext',
|
|
396
|
+
moduleResolution: 'bundler',
|
|
391
397
|
types: ['@swell/app-types'],
|
|
392
398
|
},
|
|
393
|
-
exclude: ['node_modules', 'frontend'],
|
|
394
|
-
include: ['**/*.ts'],
|
|
399
|
+
exclude: ['node_modules', 'frontend', 'test', 'vitest.config.ts'],
|
|
395
400
|
};
|
|
396
401
|
await writeJsonFile(path.join(configPath, 'package.json'), packageJson);
|
|
397
402
|
await writeJsonFile(path.join(configPath, 'tsconfig.json'), tsConfig);
|