@swell/cli 2.2.0 → 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 (79) 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/dev.js +5 -3
  8. package/dist/commands/app/init.d.ts +8 -7
  9. package/dist/commands/app/init.js +36 -18
  10. package/dist/commands/app/pull.js +2 -2
  11. package/dist/commands/app/push.js +2 -2
  12. package/dist/commands/app/version.d.ts +1 -0
  13. package/dist/commands/app/version.js +14 -4
  14. package/dist/commands/create/app.d.ts +8 -4
  15. package/dist/commands/create/app.js +68 -51
  16. package/dist/commands/create/content.d.ts +5 -6
  17. package/dist/commands/create/content.js +22 -38
  18. package/dist/commands/create/function.d.ts +6 -7
  19. package/dist/commands/create/function.js +94 -31
  20. package/dist/commands/create/index.js +13 -1
  21. package/dist/commands/create/model.d.ts +5 -6
  22. package/dist/commands/create/model.js +21 -33
  23. package/dist/commands/create/notification.d.ts +9 -9
  24. package/dist/commands/create/notification.js +117 -95
  25. package/dist/commands/create/setting.d.ts +21 -0
  26. package/dist/commands/create/setting.js +120 -0
  27. package/dist/commands/create/tests.d.ts +14 -0
  28. package/dist/commands/create/tests.js +76 -0
  29. package/dist/commands/create/webhook.d.ts +22 -0
  30. package/dist/commands/create/webhook.js +176 -0
  31. package/dist/commands/inspect/content.js +1 -1
  32. package/dist/commands/schema.d.ts +1 -0
  33. package/dist/commands/schema.js +51 -5
  34. package/dist/commands/theme/init.d.ts +8 -4
  35. package/dist/commands/theme/init.js +21 -8
  36. package/dist/create-app-command.d.ts +1 -0
  37. package/dist/create-app-command.js +15 -10
  38. package/dist/create-config-command.js +2 -2
  39. package/dist/help/custom-help.d.ts +89 -0
  40. package/dist/help/custom-help.js +337 -0
  41. package/dist/help/types.d.ts +75 -0
  42. package/dist/help/types.js +1 -0
  43. package/dist/lib/apps/app-config.js +2 -2
  44. package/dist/lib/apps/index.d.ts +2 -1
  45. package/dist/lib/apps/index.js +21 -4
  46. package/dist/lib/apps/paths.js +7 -6
  47. package/dist/lib/create/notification.d.ts +1 -0
  48. package/dist/lib/create/schemas.d.ts +1 -0
  49. package/dist/lib/create/schemas.js +1 -0
  50. package/dist/lib/create/setting.d.ts +15 -0
  51. package/dist/lib/create/setting.js +27 -0
  52. package/dist/lib/create/tests/templates/env-dts.d.ts +1 -0
  53. package/dist/lib/create/tests/templates/env-dts.js +15 -0
  54. package/dist/lib/create/tests/templates/index.d.ts +8 -0
  55. package/dist/lib/create/tests/templates/index.js +8 -0
  56. package/dist/lib/create/tests/templates/integration-test.d.ts +1 -0
  57. package/dist/lib/create/tests/templates/integration-test.js +19 -0
  58. package/dist/lib/create/tests/templates/mock-request.d.ts +2 -0
  59. package/dist/lib/create/tests/templates/mock-request.js +112 -0
  60. package/dist/lib/create/tests/templates/setup-globals.d.ts +1 -0
  61. package/dist/lib/create/tests/templates/setup-globals.js +23 -0
  62. package/dist/lib/create/tests/templates/swell-client.d.ts +1 -0
  63. package/dist/lib/create/tests/templates/swell-client.js +128 -0
  64. package/dist/lib/create/tests/templates/tsconfig.d.ts +1 -0
  65. package/dist/lib/create/tests/templates/tsconfig.js +17 -0
  66. package/dist/lib/create/tests/templates/unit-test.d.ts +1 -0
  67. package/dist/lib/create/tests/templates/unit-test.js +42 -0
  68. package/dist/lib/create/tests/templates/vitest-config.d.ts +2 -0
  69. package/dist/lib/create/tests/templates/vitest-config.js +122 -0
  70. package/dist/lib/create/tests/types.d.ts +21 -0
  71. package/dist/lib/create/tests/types.js +1 -0
  72. package/dist/lib/create/tests.d.ts +4 -0
  73. package/dist/lib/create/tests.js +113 -0
  74. package/dist/lib/create/webhook.d.ts +32 -0
  75. package/dist/lib/create/webhook.js +52 -0
  76. package/dist/swell-api-command.d.ts +14 -0
  77. package/dist/swell-api-command.js +113 -7
  78. package/oclif.manifest.json +2879 -0
  79. package/package.json +2 -1
@@ -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`);
@@ -0,0 +1,89 @@
1
+ import { Command, Help } from '@oclif/core';
2
+ /**
3
+ * Custom Help class for Swell CLI
4
+ *
5
+ * Renders help in a cleaner format with:
6
+ * - Two-line USAGE with "# interactive" comment
7
+ * - Grouped flags by category (type options vs general)
8
+ * - Custom sections for conditional dependencies (TYPE OPTIONS, APP TYPES, etc.)
9
+ *
10
+ * Commands opt-in to custom formatting by defining a static `helpMeta` property.
11
+ * Commands without helpMeta use standard oclif formatting.
12
+ */
13
+ export default class CustomHelp extends Help {
14
+ /**
15
+ * Override showCommandHelp to prevent duplicate summary for commands with helpMeta.
16
+ * The default implementation logs summary separately, but our formatCommand includes it.
17
+ * @param command - The command to display help for
18
+ * @returns Promise that resolves when help is displayed
19
+ */
20
+ showCommandHelp(command: Command.Loadable): Promise<void>;
21
+ /**
22
+ * Override formatCommand to provide custom help output for commands with helpMeta.
23
+ * Falls back to default formatting for commands without helpMeta.
24
+ * @param command - The command to format help for
25
+ * @returns Formatted help text as a string
26
+ */
27
+ protected formatCommand(command: Command.Loadable): string;
28
+ /**
29
+ * Get helpMeta from a command, handling the Command.Loadable type.
30
+ * The helpMeta is stored on the command class itself, accessible via the cached command.
31
+ * @param command - The command to extract helpMeta from
32
+ * @returns The helpMeta object if present on the command, otherwise undefined
33
+ */
34
+ private getHelpMeta;
35
+ /**
36
+ * Format USAGE section with two-line pattern:
37
+ * - Line 1: Interactive mode (no args) with "# interactive" comment
38
+ * - Line 2: Direct/non-interactive mode with args
39
+ * @param command - The command to format usage for
40
+ * @param helpMeta - The help metadata for the command
41
+ * @returns Formatted USAGE section as an array of strings
42
+ */
43
+ private formatUsageSection;
44
+ /**
45
+ * Auto-generate direct usage line from command definition.
46
+ * @param command - The command to generate direct usage for
47
+ * @returns The generated direct usage line as a string
48
+ */
49
+ private generateDirectUsage;
50
+ /**
51
+ * Format ARGUMENTS section with compact display.
52
+ * @param command - The command to format arguments for
53
+ * @returns Formatted ARGUMENTS section as an array of strings
54
+ */
55
+ private formatArgumentsSection;
56
+ /**
57
+ * Format TYPE section (APP TYPES, etc.) with requires/optional indicators.
58
+ * @param section - The type section configuration with types and their requirements
59
+ * @returns Formatted TYPE section as an array of strings
60
+ */
61
+ private formatTypeSection;
62
+ /**
63
+ * Format VARIANT section (TRIGGER OPTIONS, etc.) - compact inline format.
64
+ * @param section - The variant section configuration with variants and their descriptions
65
+ * @returns Formatted VARIANT section as an array of strings
66
+ */
67
+ private formatVariantSection;
68
+ /**
69
+ * Format FLAGS section with filtering and sorting.
70
+ * @param command - The command to format flags for
71
+ * @param title - The section title to display
72
+ * @param filter - Function to filter flags by name
73
+ * @returns Formatted FLAGS section as an array of strings
74
+ */
75
+ private formatFlagsSection;
76
+ /**
77
+ * Format DESCRIPTION section - shows description if it differs from summary.
78
+ * Only shown when there's meaningful additional content.
79
+ * @param command - The command to format description for
80
+ * @returns Formatted DESCRIPTION section as an array of strings
81
+ */
82
+ private formatDescriptionSection;
83
+ /**
84
+ * Format EXAMPLES section - clean, label-free examples.
85
+ * @param command - The command to format examples for
86
+ * @returns Formatted EXAMPLES section as an array of strings
87
+ */
88
+ private formatExamplesSection;
89
+ }
@@ -0,0 +1,337 @@
1
+ import { Help } from '@oclif/core';
2
+ /**
3
+ * Custom Help class for Swell CLI
4
+ *
5
+ * Renders help in a cleaner format with:
6
+ * - Two-line USAGE with "# interactive" comment
7
+ * - Grouped flags by category (type options vs general)
8
+ * - Custom sections for conditional dependencies (TYPE OPTIONS, APP TYPES, etc.)
9
+ *
10
+ * Commands opt-in to custom formatting by defining a static `helpMeta` property.
11
+ * Commands without helpMeta use standard oclif formatting.
12
+ */
13
+ export default class CustomHelp extends Help {
14
+ /**
15
+ * Override showCommandHelp to prevent duplicate summary for commands with helpMeta.
16
+ * The default implementation logs summary separately, but our formatCommand includes it.
17
+ * @param command - The command to display help for
18
+ * @returns Promise that resolves when help is displayed
19
+ */
20
+ async showCommandHelp(command) {
21
+ const helpMeta = this.getHelpMeta(command);
22
+ if (helpMeta) {
23
+ // For commands with helpMeta, just log formatCommand output
24
+ // (which includes the description/summary already)
25
+ this.log(this.formatCommand(command));
26
+ }
27
+ else {
28
+ // For commands without helpMeta, use default behavior
29
+ await super.showCommandHelp(command);
30
+ }
31
+ }
32
+ /**
33
+ * Override formatCommand to provide custom help output for commands with helpMeta.
34
+ * Falls back to default formatting for commands without helpMeta.
35
+ * @param command - The command to format help for
36
+ * @returns Formatted help text as a string
37
+ */
38
+ formatCommand(command) {
39
+ const helpMeta = this.getHelpMeta(command);
40
+ // No helpMeta - use default oclif formatting
41
+ if (!helpMeta) {
42
+ return super.formatCommand(command);
43
+ }
44
+ // Custom formatting for commands with helpMeta
45
+ const lines = [];
46
+ // Description (single line) - use summary if available, else first line of description
47
+ const desc = command.summary || command.description?.split('\n')[0];
48
+ if (desc) {
49
+ lines.push(desc, '');
50
+ }
51
+ // USAGE section
52
+ lines.push(...this.formatUsageSection(command, helpMeta));
53
+ // ARGUMENTS section
54
+ const argsSection = this.formatArgumentsSection(command);
55
+ if (argsSection.length > 0) {
56
+ lines.push('', ...argsSection);
57
+ }
58
+ // TYPE SECTION (e.g., APP TYPES) - for commands with type-based conditional deps
59
+ if (helpMeta.typeSection) {
60
+ lines.push('', ...this.formatTypeSection(helpMeta.typeSection));
61
+ }
62
+ // VARIANT SECTION (e.g., TRIGGER OPTIONS) - for simpler variant-based deps
63
+ if (helpMeta.variantSection) {
64
+ lines.push('', ...this.formatVariantSection(helpMeta.variantSection));
65
+ }
66
+ // TYPE-SPECIFIC FLAGS (when using typeSection)
67
+ if (helpMeta.typeFlags && helpMeta.typeFlags.length > 0) {
68
+ const typeSpecificFlags = this.formatFlagsSection(command, 'TYPE OPTIONS', (name) => helpMeta.typeFlags.includes(name));
69
+ if (typeSpecificFlags.length > 0) {
70
+ lines.push('', ...typeSpecificFlags);
71
+ }
72
+ }
73
+ // GENERAL FLAGS section
74
+ const excludeFlags = new Set([
75
+ ...(helpMeta.variantFlags || []),
76
+ ...(helpMeta.typeFlags || []),
77
+ ]);
78
+ const flagsSection = this.formatFlagsSection(command, 'FLAGS', (name) => !excludeFlags.has(name));
79
+ if (flagsSection.length > 0) {
80
+ lines.push('', ...flagsSection);
81
+ }
82
+ // DESCRIPTION section (if description differs from summary)
83
+ const descSection = this.formatDescriptionSection(command);
84
+ if (descSection.length > 0) {
85
+ lines.push('', ...descSection);
86
+ }
87
+ // EXAMPLES section
88
+ const examplesSection = this.formatExamplesSection(command);
89
+ if (examplesSection.length > 0) {
90
+ lines.push('', ...examplesSection);
91
+ }
92
+ return lines.join('\n');
93
+ }
94
+ /**
95
+ * Get helpMeta from a command, handling the Command.Loadable type.
96
+ * The helpMeta is stored on the command class itself, accessible via the cached command.
97
+ * @param command - The command to extract helpMeta from
98
+ * @returns The helpMeta object if present on the command, otherwise undefined
99
+ */
100
+ getHelpMeta(command) {
101
+ // Command.Loadable stores all static properties on the object itself
102
+ // We need to cast to access the helpMeta property
103
+ return command.helpMeta;
104
+ }
105
+ /**
106
+ * Format USAGE section with two-line pattern:
107
+ * - Line 1: Interactive mode (no args) with "# interactive" comment
108
+ * - Line 2: Direct/non-interactive mode with args
109
+ * @param command - The command to format usage for
110
+ * @param helpMeta - The help metadata for the command
111
+ * @returns Formatted USAGE section as an array of strings
112
+ */
113
+ formatUsageSection(command, helpMeta) {
114
+ const lines = ['USAGE'];
115
+ // Convert colon separator to space (e.g., "create:app" -> "create app")
116
+ const cmdId = command.id.replaceAll(':', ' ');
117
+ // First line: interactive (no args)
118
+ const interactiveLine = ` $ swell ${cmdId}`;
119
+ const padding = Math.max(50 - interactiveLine.length, 2);
120
+ lines.push(`${interactiveLine}${' '.repeat(padding)}# interactive`);
121
+ // Second line: direct mode with args
122
+ if (helpMeta.usageDirect) {
123
+ lines.push(` $ swell ${cmdId} ${helpMeta.usageDirect}`);
124
+ }
125
+ else {
126
+ // Auto-generate from args and flags
127
+ const directLine = this.generateDirectUsage(command);
128
+ if (directLine !== interactiveLine) {
129
+ lines.push(directLine);
130
+ }
131
+ }
132
+ return lines;
133
+ }
134
+ /**
135
+ * Auto-generate direct usage line from command definition.
136
+ * @param command - The command to generate direct usage for
137
+ * @returns The generated direct usage line as a string
138
+ */
139
+ generateDirectUsage(command) {
140
+ const cmdId = command.id.replaceAll(':', ' ');
141
+ const parts = [` $ swell ${cmdId}`];
142
+ // Add args
143
+ if (command.args) {
144
+ const argEntries = Object.entries(command.args);
145
+ for (const [name] of argEntries) {
146
+ parts.push(`<${name.toLowerCase()}>`);
147
+ }
148
+ }
149
+ // Check if there are any optional flags
150
+ const flags = command.flags || {};
151
+ const hasOptionalFlags = Object.keys(flags).some((f) => f !== 'yes' && f !== 'help');
152
+ if (hasOptionalFlags) {
153
+ parts.push('[...]');
154
+ }
155
+ // Add -y if the command has it
156
+ if (flags.yes) {
157
+ parts.push('-y');
158
+ }
159
+ return parts.join(' ');
160
+ }
161
+ /**
162
+ * Format ARGUMENTS section with compact display.
163
+ * @param command - The command to format arguments for
164
+ * @returns Formatted ARGUMENTS section as an array of strings
165
+ */
166
+ formatArgumentsSection(command) {
167
+ if (!command.args || Object.keys(command.args).length === 0) {
168
+ return [];
169
+ }
170
+ const lines = ['ARGUMENTS'];
171
+ const argEntries = Object.entries(command.args);
172
+ // Calculate padding for alignment
173
+ const maxNameLength = Math.max(...argEntries.map(([name]) => name.length));
174
+ const padding = Math.max(maxNameLength + 4, 8);
175
+ for (const [name, arg] of argEntries) {
176
+ const argName = name.toUpperCase();
177
+ const desc = arg.description || '';
178
+ lines.push(` ${argName.padEnd(padding)}${desc}`);
179
+ }
180
+ return lines;
181
+ }
182
+ /**
183
+ * Format TYPE section (APP TYPES, etc.) with requires/optional indicators.
184
+ * @param section - The type section configuration with types and their requirements
185
+ * @returns Formatted TYPE section as an array of strings
186
+ */
187
+ formatTypeSection(section) {
188
+ if (!section)
189
+ return [];
190
+ const lines = [section.title];
191
+ for (let i = 0; i < section.types.length; i++) {
192
+ const type = section.types[i];
193
+ lines.push(` ${type.name.padEnd(12)} ${type.description}`);
194
+ if (type.requires) {
195
+ for (const req of type.requires) {
196
+ lines.push(`${''.padEnd(15)}Requires: ${req}`);
197
+ }
198
+ }
199
+ if (type.optional) {
200
+ for (const opt of type.optional) {
201
+ lines.push(`${''.padEnd(15)}Optional: ${opt}`);
202
+ }
203
+ }
204
+ // Add blank line between types for readability (except last)
205
+ if (i < section.types.length - 1) {
206
+ lines.push('');
207
+ }
208
+ }
209
+ return lines;
210
+ }
211
+ /**
212
+ * Format VARIANT section (TRIGGER OPTIONS, etc.) - compact inline format.
213
+ * @param section - The variant section configuration with variants and their descriptions
214
+ * @returns Formatted VARIANT section as an array of strings
215
+ */
216
+ formatVariantSection(section) {
217
+ if (!section)
218
+ return [];
219
+ const lines = [section.title];
220
+ for (const variant of section.variants) {
221
+ // Format: " model -e, --events=<value> Description"
222
+ const flagPart = variant.flag.padEnd(28);
223
+ lines.push(` ${variant.name.padEnd(8)} ${flagPart} ${variant.description}`);
224
+ }
225
+ return lines;
226
+ }
227
+ /**
228
+ * Format FLAGS section with filtering and sorting.
229
+ * @param command - The command to format flags for
230
+ * @param title - The section title to display
231
+ * @param filter - Function to filter flags by name
232
+ * @returns Formatted FLAGS section as an array of strings
233
+ */
234
+ formatFlagsSection(command, title, filter) {
235
+ if (!command.flags) {
236
+ return [];
237
+ }
238
+ const flagEntries = Object.entries(command.flags).filter(([name, flag]) => filter(name) && name !== 'help' && !flag.hidden);
239
+ if (flagEntries.length === 0) {
240
+ return [];
241
+ }
242
+ const lines = [title];
243
+ // Sort flags: short flags first, then long-only flags, -y last
244
+ flagEntries.sort(([aName, aFlag], [bName, bFlag]) => {
245
+ // -y always last
246
+ if (aName === 'yes')
247
+ return 1;
248
+ if (bName === 'yes')
249
+ return -1;
250
+ // Flags with short char before flags without
251
+ const aHasChar = Boolean(aFlag.char);
252
+ const bHasChar = Boolean(bFlag.char);
253
+ if (aHasChar && !bHasChar)
254
+ return -1;
255
+ if (!aHasChar && bHasChar)
256
+ return 1;
257
+ // Alphabetical
258
+ return aName.localeCompare(bName);
259
+ });
260
+ for (const [name, flag] of flagEntries) {
261
+ const shortChar = flag.char ? `-${flag.char}, ` : ' ';
262
+ const longFlag = `--${name}`;
263
+ const isBoolean = flag.type === 'boolean';
264
+ const hasOptions = 'options' in flag && flag.options;
265
+ const valueStr = isBoolean ? '' : hasOptions ? '=<option>' : '=<value>';
266
+ const flagStr = `${shortChar}${longFlag}${valueStr}`;
267
+ // Get description, clean up redundant "(required in -y)" text
268
+ let desc = flag.summary || flag.description || '';
269
+ desc = desc
270
+ .replaceAll(/\s*\(required (?:with|in) -y\)/gi, '')
271
+ .replaceAll(/\s*\(required for .+ with -y\)/gi, '')
272
+ .replaceAll(/\s*\(default in -y: .+\)/gi, '')
273
+ .split('\n')[0]
274
+ .trim();
275
+ lines.push(` ${flagStr.padEnd(30)} ${desc}`);
276
+ }
277
+ return lines;
278
+ }
279
+ /**
280
+ * Format DESCRIPTION section - shows description if it differs from summary.
281
+ * Only shown when there's meaningful additional content.
282
+ * @param command - The command to format description for
283
+ * @returns Formatted DESCRIPTION section as an array of strings
284
+ */
285
+ formatDescriptionSection(command) {
286
+ // Get description (may be multi-line)
287
+ const { description } = command;
288
+ if (!description) {
289
+ return [];
290
+ }
291
+ // Skip if description equals summary (no additional info)
292
+ const summary = command.summary || description.split('\n')[0];
293
+ if (description === summary) {
294
+ return [];
295
+ }
296
+ const lines = ['DESCRIPTION'];
297
+ // Format each line of description with proper indentation
298
+ const descLines = description.split('\n');
299
+ for (const line of descLines) {
300
+ lines.push(` ${line}`);
301
+ }
302
+ return lines;
303
+ }
304
+ /**
305
+ * Format EXAMPLES section - clean, label-free examples.
306
+ * @param command - The command to format examples for
307
+ * @returns Formatted EXAMPLES section as an array of strings
308
+ */
309
+ formatExamplesSection(command) {
310
+ if (!command.examples || command.examples.length === 0) {
311
+ return [];
312
+ }
313
+ const lines = ['EXAMPLES'];
314
+ for (const example of command.examples) {
315
+ if (typeof example === 'string') {
316
+ // String example - check if it's a command
317
+ if (example.trim().startsWith('$')) {
318
+ lines.push(` ${example.trim()}`);
319
+ }
320
+ else if (example.includes('<%= command.id %>')) {
321
+ // oclif template syntax
322
+ const cmd = example.replace('<%= command.id %>', command.id);
323
+ lines.push(` $ swell ${cmd.trim()}`);
324
+ }
325
+ // Skip description-only lines
326
+ }
327
+ else if (typeof example === 'object' && example.command) {
328
+ // Object example with command property
329
+ const cmd = example.command.startsWith('swell ')
330
+ ? example.command
331
+ : `swell ${example.command}`;
332
+ lines.push(` $ ${cmd}`);
333
+ }
334
+ }
335
+ return lines;
336
+ }
337
+ }
@@ -0,0 +1,75 @@
1
+ import { Command } from '@oclif/core';
2
+ /**
3
+ * Represents a type variant with conditional flag requirements.
4
+ * Used for complex commands like `create app` where different types
5
+ * have different required/optional flags.
6
+ */
7
+ export interface TypeVariant {
8
+ /** Type name (e.g., 'admin', 'theme', 'integration') */
9
+ name: string;
10
+ /** Short description of what this type does */
11
+ description: string;
12
+ /** Flags required when using this type */
13
+ requires?: string[];
14
+ /** Flags optionally available for this type */
15
+ optional?: string[];
16
+ }
17
+ /**
18
+ * Represents a simpler variant option where one argument maps to one flag.
19
+ * Used for commands like `create function` where trigger type determines the flag.
20
+ */
21
+ export interface VariantOption {
22
+ /** Variant name (e.g., 'model', 'cron', 'route') */
23
+ name: string;
24
+ /** Flag signature (e.g., '-e, --events=<value>') */
25
+ flag: string;
26
+ /** Description of what this flag does */
27
+ description: string;
28
+ }
29
+ /**
30
+ * Metadata for custom help formatting.
31
+ * Add this as a static property on commands that need enhanced help output.
32
+ */
33
+ export interface HelpMeta {
34
+ /**
35
+ * Custom usage line for direct/non-interactive mode.
36
+ * Example: '<id> -t <type> [...] -y'
37
+ */
38
+ usageDirect?: string;
39
+ /**
40
+ * Type section for complex conditional dependencies.
41
+ * Renders as a detailed breakdown showing each type with its requirements.
42
+ */
43
+ typeSection?: {
44
+ /** Section title (e.g., 'APP TYPES') */
45
+ title: string;
46
+ /** List of type variants */
47
+ types: TypeVariant[];
48
+ };
49
+ /**
50
+ * Flags to group into a separate "TYPE OPTIONS" section.
51
+ * These are excluded from the general FLAGS section.
52
+ */
53
+ typeFlags?: string[];
54
+ /**
55
+ * Variant section for simpler conditional dependencies.
56
+ * Renders as a compact table: variant -> flag -> description
57
+ */
58
+ variantSection?: {
59
+ /** Section title (e.g., 'TRIGGER OPTIONS') */
60
+ title: string;
61
+ /** List of variant options */
62
+ variants: VariantOption[];
63
+ };
64
+ /**
65
+ * Flags to exclude from the general FLAGS section when using variantSection.
66
+ */
67
+ variantFlags?: string[];
68
+ }
69
+ /**
70
+ * Command class with optional helpMeta static property.
71
+ * Uses Command.Cached since that's what Help receives from Command.Loadable.
72
+ */
73
+ export type CommandClassWithMeta = Command.Cached & {
74
+ helpMeta?: HelpMeta;
75
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -4,7 +4,7 @@ import { detectFilenameMime } from 'mime-detect';
4
4
  import * as fs from 'node:fs';
5
5
  import * as path from 'node:path';
6
6
  import { bundleFunction } from '../bundle.js';
7
- import { ConfigPaths, ConfigType, filePathExists, hashFile, } from './index.js';
7
+ import { AllConfigPaths, ConfigType, filePathExists, hashFile, } from './index.js';
8
8
  export class IgnoringFileError extends Error {
9
9
  constructor(message) {
10
10
  super(message);
@@ -68,7 +68,7 @@ export class AppConfig {
68
68
  }
69
69
  else {
70
70
  // Note this shouldn't be necessary unless the config was created before file_path was added
71
- const typePath = ConfigPaths[String(type).toUpperCase()];
71
+ const typePath = AllConfigPaths[String(type).toUpperCase()];
72
72
  filePath = `${typePath}/${name}`;
73
73
  if (!typePath) {
74
74
  throw new Error(`Missing path for app config type: ${type}`);
@@ -106,7 +106,8 @@ export declare const ThemeConfigTypes: ConfigTypeKey[];
106
106
  export type ConfigPathsType = {
107
107
  [key in ConfigTypeKey]: string;
108
108
  };
109
- export declare const ConfigPaths: ConfigPathsType;
109
+ export declare const AllConfigPaths: ConfigPathsType;
110
+ export declare const StandardConfigPaths: ConfigPathsType;
110
111
  export declare const StorefrontConfigPaths: ConfigPathsType;
111
112
  export declare const ThemeConfigPaths: ConfigPathsType;
112
113
  export declare enum ConfigInputFields {