@swell/cli 2.2.1 → 2.3.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 (59) hide show
  1. package/dist/app-command.js +1 -1
  2. package/dist/commands/api/delete.js +4 -1
  3. package/dist/commands/api/get.js +1 -0
  4. package/dist/commands/api/index.js +4 -1
  5. package/dist/commands/api/post.js +1 -0
  6. package/dist/commands/api/put.js +1 -0
  7. package/dist/commands/app/init.d.ts +8 -7
  8. package/dist/commands/app/init.js +36 -18
  9. package/dist/commands/app/pull.js +2 -2
  10. package/dist/commands/app/push.js +2 -2
  11. package/dist/commands/app/version.d.ts +1 -0
  12. package/dist/commands/app/version.js +14 -4
  13. package/dist/commands/create/app.d.ts +8 -4
  14. package/dist/commands/create/app.js +68 -51
  15. package/dist/commands/create/content.d.ts +5 -6
  16. package/dist/commands/create/content.js +21 -37
  17. package/dist/commands/create/function.d.ts +6 -7
  18. package/dist/commands/create/function.js +94 -31
  19. package/dist/commands/create/index.js +8 -0
  20. package/dist/commands/create/model.d.ts +5 -6
  21. package/dist/commands/create/model.js +21 -33
  22. package/dist/commands/create/notification.d.ts +9 -9
  23. package/dist/commands/create/notification.js +117 -95
  24. package/dist/commands/create/setting.d.ts +21 -0
  25. package/dist/commands/create/setting.js +120 -0
  26. package/dist/commands/create/tests.d.ts +4 -5
  27. package/dist/commands/create/tests.js +8 -11
  28. package/dist/commands/create/webhook.d.ts +22 -0
  29. package/dist/commands/create/webhook.js +176 -0
  30. package/dist/commands/schema.d.ts +1 -0
  31. package/dist/commands/schema.js +51 -5
  32. package/dist/commands/theme/init.d.ts +8 -4
  33. package/dist/commands/theme/init.js +21 -8
  34. package/dist/create-app-command.d.ts +1 -0
  35. package/dist/create-app-command.js +15 -10
  36. package/dist/create-config-command.js +2 -2
  37. package/dist/help/custom-help.d.ts +89 -0
  38. package/dist/help/custom-help.js +337 -0
  39. package/dist/help/types.d.ts +75 -0
  40. package/dist/help/types.js +1 -0
  41. package/dist/lib/apps/app-config.js +2 -2
  42. package/dist/lib/apps/index.d.ts +2 -1
  43. package/dist/lib/apps/index.js +21 -5
  44. package/dist/lib/apps/paths.js +7 -6
  45. package/dist/lib/create/notification.d.ts +1 -0
  46. package/dist/lib/create/schemas.d.ts +1 -0
  47. package/dist/lib/create/schemas.js +1 -0
  48. package/dist/lib/create/setting.d.ts +15 -0
  49. package/dist/lib/create/setting.js +27 -0
  50. package/dist/lib/create/tests/templates/env-dts.js +1 -0
  51. package/dist/lib/create/tests/templates/swell-client.js +2 -0
  52. package/dist/lib/create/tests/templates/tsconfig.js +2 -0
  53. package/dist/lib/create/tests/templates/vitest-config.js +1 -0
  54. package/dist/lib/create/webhook.d.ts +32 -0
  55. package/dist/lib/create/webhook.js +52 -0
  56. package/dist/swell-api-command.d.ts +14 -0
  57. package/dist/swell-api-command.js +113 -7
  58. package/oclif.manifest.json +609 -259
  59. package/package.json +2 -1
@@ -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 summary: string;
5
- static examples: {
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
- command: 'swell create tests',
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: 'overwrite existing test files if they already exist',
17
+ description: 'Overwrite existing test files',
22
18
  }),
23
19
  yes: Flags.boolean({
24
20
  char: 'y',
25
21
  default: false,
26
- description: 'accept defaults, skip prompts (no prompts currently, included for consistency)',
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
+ }
@@ -19,6 +19,7 @@ export default class Schema extends SwellCommand {
19
19
  private showSchemaInfo;
20
20
  private outputSchema;
21
21
  private validate;
22
+ private validateFunction;
22
23
  private getFileInput;
23
24
  private readFromStdin;
24
25
  private getSchemaUrl;
@@ -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 JSON Schemas.';
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 JSON file to validate (or use "-" for stdin)',
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 this.validate(type, file);
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
- command: string;
6
- description: string;
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
- command: 'swell theme init',
7
- description: 'Initialize theme following command prompts.',
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: `create a starter framework for a hosted frontend (default in -y: none)`,
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: `id of an installed storefront app to create a theme for, if applicable`,
24
+ description: 'Target storefront app ID',
23
25
  }),
24
26
  'integration-type': Flags.string({
25
- description: `integration type for integration app, if applicable`,
27
+ description: 'Integration: generic | payment | shipping | tax',
26
28
  options: ['generic', 'payment', 'shipping', 'tax'],
27
29
  }),
28
30
  'integration-id': Flags.string({
29
- description: `unique identifier for the integration service (required for payment/shipping/tax; use 'card' for credit cards)`,
31
+ description: 'Service ID (e.g., card, fedex)',
30
32
  }),
31
33
  yes: Flags.boolean({
32
34
  char: 'y',
33
- description: `accept all default values; non-interactive, never prompts; errors if required values are missing`,
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: swell create app reviews --type admin --frontend astro -y`, {
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: swell create app reviews --type admin --frontend astro -y`, {
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);
@@ -1,7 +1,7 @@
1
1
  import { confirm } from '@inquirer/prompts';
2
2
  import * as path from 'node:path';
3
3
  import { AppCommand } from './app-command.js';
4
- import { ConfigPaths, filePathExists, writeFile, writeJsonFile, } from './lib/apps/index.js';
4
+ import { AllConfigPaths, filePathExists, writeFile, writeJsonFile, } from './lib/apps/index.js';
5
5
  /**
6
6
  * A base class for Swell CLI Create commands for file and input handling.
7
7
  *
@@ -14,7 +14,7 @@ export class CreateConfigCommand extends AppCommand {
14
14
  createType = '';
15
15
  async createFile({ extension = 'json', fileBody, fileName }, overwrite, shouldConfirm = true) {
16
16
  const isJson = extension === 'json';
17
- const filePath = path.join(this.appPath, ConfigPaths[this.createType.toUpperCase()], `${fileName}.${extension}`);
17
+ const filePath = path.join(this.appPath, AllConfigPaths[this.createType.toUpperCase()], `${fileName}.${extension}`);
18
18
  if (shouldConfirm) {
19
19
  this.log(`\nCreating app ${this.createType} in ${filePath}`);
20
20
  this.log(`\n${isJson ? JSON.stringify(fileBody, null, 4) : fileBody}\n`);