@swell/cli 2.1.0 → 2.2.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 (87) hide show
  1. package/dist/commands/api/delete.d.ts +15 -0
  2. package/dist/commands/api/delete.js +26 -0
  3. package/dist/commands/api/get.d.ts +15 -0
  4. package/dist/commands/api/get.js +31 -0
  5. package/dist/commands/api/index.d.ts +7 -0
  6. package/dist/commands/api/index.js +16 -0
  7. package/dist/commands/api/post.d.ts +16 -0
  8. package/dist/commands/api/post.js +33 -0
  9. package/dist/commands/api/put.d.ts +16 -0
  10. package/dist/commands/api/put.js +33 -0
  11. package/dist/commands/app/dev.d.ts +9 -9
  12. package/dist/commands/app/dev.js +158 -136
  13. package/dist/commands/app/frontend/dev.d.ts +1 -1
  14. package/dist/commands/app/frontend/dev.js +84 -78
  15. package/dist/commands/app/install.js +3 -3
  16. package/dist/commands/app/pull.d.ts +1 -1
  17. package/dist/commands/app/pull.js +6 -6
  18. package/dist/commands/app/push.d.ts +3 -3
  19. package/dist/commands/app/push.js +8 -6
  20. package/dist/commands/app/release.d.ts +1 -1
  21. package/dist/commands/app/release.js +1 -1
  22. package/dist/commands/app/version.d.ts +1 -1
  23. package/dist/commands/app/version.js +5 -3
  24. package/dist/commands/create/app.d.ts +10 -2
  25. package/dist/commands/create/app.js +226 -57
  26. package/dist/commands/create/content.d.ts +4 -0
  27. package/dist/commands/create/content.js +83 -15
  28. package/dist/commands/create/function.js +27 -5
  29. package/dist/commands/create/model.d.ts +4 -0
  30. package/dist/commands/create/model.js +120 -25
  31. package/dist/commands/inspect/content.d.ts +25 -0
  32. package/dist/commands/inspect/content.js +159 -0
  33. package/dist/commands/inspect/index.d.ts +7 -0
  34. package/dist/commands/inspect/index.js +16 -0
  35. package/dist/commands/inspect/models.d.ts +27 -0
  36. package/dist/commands/inspect/models.js +197 -0
  37. package/dist/commands/schema.d.ts +27 -0
  38. package/dist/commands/schema.js +231 -0
  39. package/dist/commands/theme/dev.d.ts +1 -1
  40. package/dist/commands/theme/dev.js +2 -2
  41. package/dist/commands/theme/init.d.ts +1 -1
  42. package/dist/commands/theme/init.js +1 -1
  43. package/dist/commands/theme/pull.d.ts +2 -2
  44. package/dist/commands/theme/pull.js +2 -2
  45. package/dist/commands/theme/push.d.ts +1 -1
  46. package/dist/commands/theme/push.js +1 -1
  47. package/dist/create-app-command.d.ts +12 -2
  48. package/dist/create-app-command.js +103 -80
  49. package/dist/create-collection-command.js +4 -1
  50. package/dist/create-config-command.js +4 -0
  51. package/dist/env/local.d.ts +1 -0
  52. package/dist/env/local.js +1 -0
  53. package/dist/env/production.d.ts +1 -0
  54. package/dist/env/production.js +1 -0
  55. package/dist/env/review.d.ts +1 -0
  56. package/dist/env/review.js +1 -0
  57. package/dist/env/staging.d.ts +1 -0
  58. package/dist/env/staging.js +1 -0
  59. package/dist/lib/api.d.ts +16 -5
  60. package/dist/lib/api.js +67 -25
  61. package/dist/lib/app-config.js +1 -0
  62. package/dist/lib/apps/index.d.ts +2 -0
  63. package/dist/lib/apps/index.js +20 -15
  64. package/dist/lib/config.d.ts +1 -1
  65. package/dist/lib/constants.d.ts +1 -0
  66. package/dist/lib/constants.js +1 -0
  67. package/dist/lib/create/content.d.ts +5 -1
  68. package/dist/lib/create/content.js +8 -1
  69. package/dist/lib/create/function.d.ts +2 -1
  70. package/dist/lib/create/function.js +7 -3
  71. package/dist/lib/create/model.d.ts +1 -0
  72. package/dist/lib/create/schemas.d.ts +6 -0
  73. package/dist/lib/create/schemas.js +6 -0
  74. package/dist/lib/sessions.js +4 -3
  75. package/dist/lib/style.js +0 -1
  76. package/dist/lib/theme-sync.d.ts +6 -6
  77. package/dist/lib/theme-sync.js +49 -39
  78. package/dist/push-app-command.d.ts +7 -7
  79. package/dist/push-app-command.js +38 -36
  80. package/dist/remote-app-command.d.ts +1 -1
  81. package/dist/remote-app-command.js +4 -2
  82. package/dist/swell-api-command.d.ts +13 -0
  83. package/dist/swell-api-command.js +75 -0
  84. package/dist/swell-command.d.ts +1 -1
  85. package/dist/swell-command.js +9 -9
  86. package/package.json +8 -1
  87. package/oclif.manifest.json +0 -1936
@@ -7,30 +7,49 @@ import { newConfig, swellConfigFileExists } from '../../lib/app-config.js';
7
7
  import { toAppId, toAppName } from '../../lib/create/index.js';
8
8
  import style from '../../lib/style.js';
9
9
  export default class CreateApp extends CreateAppCommand {
10
- appType = '';
11
10
  static args = {
12
11
  id: Args.string({
13
12
  default: '',
14
- description: `id of the app to create`,
13
+ description: `id of the app to create (required in -y)`,
15
14
  }),
16
15
  };
17
16
  static description = 'Create an app.';
18
17
  static examples = [
19
18
  {
20
19
  command: 'swell create app',
21
- description: 'Create app following command prompts.',
20
+ description: 'Interactive: follow prompts to create an app.',
21
+ },
22
+ {
23
+ command: 'swell create app my_app -t admin -y',
24
+ description: 'Non-interactive: minimal with defaults.',
25
+ },
26
+ {
27
+ command: 'swell create app my_app --type admin --name "My App" --version 1.0.1 --description "" -y',
28
+ description: 'Non-interactive: explicit name, version, description.',
29
+ },
30
+ {
31
+ command: 'swell create app my_app --type admin --frontend astro -y',
32
+ description: 'Non-interactive: initialize an Astro frontend.',
22
33
  },
23
34
  {
24
- command: 'swell create app my_app -y',
25
- description: 'Create app and accept all default values.',
35
+ command: 'swell create app my_theme --type theme --storefront-app proxima -y',
36
+ description: 'Non-interactive: theme targeting a specific storefront app.',
37
+ },
38
+ {
39
+ command: 'swell create app my_payment --type integration --integration-type payment --integration-id card -y',
40
+ description: 'Non-interactive: payment integration for credit cards.',
41
+ },
42
+ {
43
+ command: 'swell create app my_shipping --type integration --integration-type shipping --integration-id fedex -y',
44
+ description: 'Non-interactive: shipping integration for FedEx.',
26
45
  },
27
46
  {
28
47
  command: 'swell create app -p yarn',
29
- description: 'Create app with yarn package manager instead of npm by default.',
48
+ description: 'Package manager: use Yarn instead of npm.',
30
49
  },
31
50
  {
32
51
  command: 'swell create app -p none',
33
- description: 'Create app without installing TypeScript bindings, etc.',
52
+ description: 'Package manager: skip installing default dependencies.',
34
53
  },
35
54
  ];
36
55
  static flags = {
@@ -42,34 +61,66 @@ export default class CreateApp extends CreateAppCommand {
42
61
  }),
43
62
  type: Flags.string({
44
63
  char: 't',
45
- description: `create a specific app type`,
64
+ description: `create a specific app type (required in -y)`,
46
65
  options: ['admin', 'integration', 'storefront', 'theme'],
47
66
  }),
67
+ name: Flags.string({
68
+ char: 'n',
69
+ description: `application name (default: capitalized ID)`,
70
+ }),
71
+ version: Flags.string({
72
+ char: 'v',
73
+ description: `application version (default: 1.0.0)`,
74
+ }),
75
+ description: Flags.string({
76
+ char: 'd',
77
+ description: `application description`,
78
+ }),
48
79
  };
49
- async createSwellConfig({ allowOverwrite = true, inputId = '', inputStorefrontApp, inputType, inputYes, nestedPath = true, }) {
50
- let confirmYes = inputYes;
80
+ appType = '';
81
+ async createSwellConfig({ allowOverwrite = true, inputId = '', inputStorefrontApp, inputType, inputName, inputVersion, inputDescription, inputFrontend, inputIntegrationType, inputIntegrationId, inputYes, nestedPath = true, }) {
82
+ const confirmYes = inputYes;
83
+ let appId = toAppId(inputId || '');
84
+ let storefrontInstalledApps;
85
+ let storefrontAppId;
86
+ let installedStorefrontApp;
87
+ // check missed parameters
88
+ if (confirmYes) {
89
+ if (!appId) {
90
+ this.error('Missing application id for non-interactive mode\n\nYou need to provide an app id value for non-interactive mode\n\nExample: swell create app reviews --type admin -y', {
91
+ exit: 1,
92
+ });
93
+ }
94
+ if (!inputType) {
95
+ this.error('Missing required flag for non-interactive mode: --type\n\nValid types: admin, integration, storefront, theme\n\nExample: swell create app reviews --type admin -y', {
96
+ exit: 1,
97
+ });
98
+ }
99
+ if (!inputName) {
100
+ inputName = toAppName(inputId);
101
+ }
102
+ if (!inputVersion) {
103
+ inputVersion = '1.0.0';
104
+ }
105
+ }
51
106
  const type = inputType ||
52
- (confirmYes
53
- ? 'admin'
54
- : await select({
55
- choices: [
56
- { name: 'Admin', value: 'admin' },
57
- { name: 'Integration', value: 'integration' },
58
- { name: 'Storefront', value: 'storefront' },
59
- { name: 'Theme', value: 'theme' },
60
- ],
61
- message: `What is the primary purpose of the app?`,
62
- }));
107
+ (await select({
108
+ choices: [
109
+ { name: 'Admin', value: 'admin' },
110
+ { name: 'Integration', value: 'integration' },
111
+ { name: 'Storefront', value: 'storefront' },
112
+ { name: 'Theme', value: 'theme' },
113
+ ],
114
+ message: `What is the primary purpose of the app?`,
115
+ }));
63
116
  const typeLabel = type === 'theme' ? 'theme' : 'app';
64
117
  const typeLabelTitle = type === 'theme' ? 'Theme' : 'App';
65
- const name = confirmYes
66
- ? toAppName(inputId)
67
- : await input({
118
+ const name = inputName ||
119
+ (await input({
68
120
  default: inputId ? toAppName(inputId) : '',
69
121
  message: `${typeLabelTitle} name`,
70
122
  validate: (answer) => answer.length > 2,
71
- });
72
- let appId = toAppId(inputId || '');
123
+ }));
73
124
  if (!appId) {
74
125
  appId = await input({
75
126
  default: toAppId(name),
@@ -77,39 +128,135 @@ export default class CreateApp extends CreateAppCommand {
77
128
  validate: (answer) => answer.length > 2,
78
129
  });
79
130
  appId = toAppId(appId);
80
- confirmYes = Boolean(inputYes && appId);
81
131
  }
82
- const version = confirmYes
83
- ? '1.0.0'
84
- : await input({
132
+ const version = inputVersion ||
133
+ (await input({
85
134
  default: '1.0.0',
86
135
  message: `${typeLabelTitle} version`,
87
- });
88
- const description = confirmYes
89
- ? ''
90
- : await input({
91
- default: 'Something special',
92
- message: `Describe your ${typeLabel}`,
93
- });
94
- let storefrontInstalledApps;
95
- let storefrontAppId;
96
- let installedStorefrontApp;
136
+ }));
137
+ const description = inputDescription ||
138
+ (confirmYes
139
+ ? ''
140
+ : await input({
141
+ default: 'Something special',
142
+ message: `Describe your ${typeLabel}`,
143
+ }));
144
+ let integrationType;
145
+ let extensions;
146
+ if (type === 'integration') {
147
+ if (confirmYes && !inputIntegrationType) {
148
+ this.error('Missing required flag for non-interactive mode for integration app: --integration-type\n\nValid types: generic, payment, shipping, tax\n\nExample: swell create app reviews --type integration --integration-type generic -y', {
149
+ exit: 1,
150
+ });
151
+ }
152
+ integrationType =
153
+ inputIntegrationType ||
154
+ (await select({
155
+ choices: [
156
+ { name: 'Generic', value: 'generic' },
157
+ { name: 'Payment gateway', value: 'payment' },
158
+ { name: 'Shipping service', value: 'shipping' },
159
+ { name: 'Tax service', value: 'tax' },
160
+ ],
161
+ message: `What type of integration?`,
162
+ }));
163
+ // Create extensions array based on integration type
164
+ switch (integrationType) {
165
+ case 'generic': {
166
+ // No extension needed for generic integrations
167
+ break;
168
+ }
169
+ case 'payment':
170
+ case 'shipping':
171
+ case 'tax': {
172
+ // Unified validation for all extension-based integrations
173
+ if (confirmYes && !inputIntegrationId) {
174
+ const validValues = integrationType === 'payment'
175
+ ? "use 'card' for credit card integration or unique custom ID for other payment integrations"
176
+ : 'unique custom ID';
177
+ this.error(`Missing required flag for non-interactive mode for ${integrationType} integration: --integration-id\n\nValid values: ${validValues}\n\nExample: swell create app my_app --type integration --integration-type ${integrationType} --integration-id ${integrationType === 'payment' ? 'card' : 'myid'} -y`, {
178
+ exit: 1,
179
+ });
180
+ }
181
+ // Interactive prompting
182
+ let integrationId = inputIntegrationId;
183
+ if (!integrationId) {
184
+ if (integrationType === 'payment') {
185
+ const methodChoice = await select({
186
+ choices: [
187
+ { name: 'Credit card', value: 'card' },
188
+ { name: 'Custom method', value: 'custom' },
189
+ ],
190
+ message: `What type of payment method?`,
191
+ });
192
+ integrationId =
193
+ methodChoice === 'custom'
194
+ ? await input({
195
+ message: `Enter a unique ID for your payment method`,
196
+ validate: (answer) => answer.length > 2,
197
+ })
198
+ : methodChoice; // 'card'
199
+ }
200
+ else {
201
+ // Shipping or tax
202
+ const serviceLabel = integrationType === 'shipping'
203
+ ? 'shipping service'
204
+ : 'tax service';
205
+ integrationId = await input({
206
+ message: `Enter a unique ID for your ${serviceLabel}`,
207
+ validate: (answer) => answer.length > 2,
208
+ });
209
+ }
210
+ }
211
+ // Unified validation
212
+ if (integrationId.length < 3) {
213
+ this.error(`--integration-id must contain at least 3 characters\n\nValid value is a custom ID containing at least 3 characters\n\nExample: swell create app my_app --type integration --integration-type ${integrationType} --integration-id ${integrationType === 'payment' ? 'card' : 'myid'} -y`, {
214
+ exit: 1,
215
+ });
216
+ }
217
+ // Create extension
218
+ extensions = [
219
+ {
220
+ id: integrationId,
221
+ type: integrationType,
222
+ },
223
+ ];
224
+ break;
225
+ }
226
+ default: {
227
+ this.error('Incorrect value for --integration-type\n\nValid types: generic, payment, shipping, tax\n\nExample: swell create app reviews --type integration --integration-type generic -y', {
228
+ exit: 1,
229
+ });
230
+ }
231
+ }
232
+ }
97
233
  if (type === 'theme') {
98
234
  storefrontInstalledApps = await this.getInstalledStorefrontApps();
99
235
  if (storefrontInstalledApps === false) {
100
- return;
236
+ this.error('There are no installed storefront apps\n\nTo use the theme type, you first need to install the storefront app', {
237
+ exit: 1,
238
+ });
239
+ }
240
+ storefrontAppId = inputStorefrontApp || '';
241
+ if (!storefrontAppId) {
242
+ if (storefrontInstalledApps.length === 1) {
243
+ storefrontAppId = storefrontInstalledApps[0].id;
244
+ }
245
+ else {
246
+ if (confirmYes) {
247
+ this.error('There are several installed storefront apps to choose from\n\nYou should select a specific storefront app in the non-interactive mode using --storefront-app appid\n\nExample: swell create app reviews --type theme --storefront-app proxima -y', {
248
+ exit: 1,
249
+ });
250
+ }
251
+ storefrontAppId = await select({
252
+ choices: storefrontInstalledApps.map(({ app, id }) => ({
253
+ name: app.name,
254
+ value: id,
255
+ })),
256
+ message: `Which storefront app is your theme for?`,
257
+ });
258
+ }
101
259
  }
102
- storefrontAppId =
103
- inputStorefrontApp ||
104
- (storefrontInstalledApps.length > 1
105
- ? await select({
106
- choices: storefrontInstalledApps.map(({ app, id }) => ({
107
- name: app.name,
108
- value: id,
109
- })),
110
- message: `Which storefront app is your theme for?`,
111
- })
112
- : storefrontInstalledApps[0].id);
113
260
  installedStorefrontApp = storefrontInstalledApps.find((installedApp) => inputStorefrontApp
114
261
  ? inputStorefrontApp === installedApp.app_private_id ||
115
262
  inputStorefrontApp === installedApp.app_public_id ||
@@ -117,7 +264,20 @@ export default class CreateApp extends CreateAppCommand {
117
264
  inputStorefrontApp === toAppId(installedApp.app_private_id)
118
265
  : installedApp.id === storefrontAppId);
119
266
  if (!installedStorefrontApp) {
120
- this.error(`Could not find an installed storefront app by id: ${storefrontAppId}`);
267
+ this.error(`Could not find an installed storefront app by id: ${storefrontAppId}\n\nThe selected storefront app is not among the installed storefront apps`, {
268
+ exit: 1,
269
+ });
270
+ }
271
+ }
272
+ // check inputFrontend value
273
+ if (!installedStorefrontApp &&
274
+ confirmYes &&
275
+ inputFrontend &&
276
+ inputFrontend !== 'none') {
277
+ const projectType = this.getProjectType(inputFrontend);
278
+ if (!projectType) {
279
+ // error should be thrown from getProjectType
280
+ return;
121
281
  }
122
282
  }
123
283
  const swellConfigJson = {
@@ -137,6 +297,7 @@ export default class CreateApp extends CreateAppCommand {
137
297
  : {
138
298
  permissions: [],
139
299
  }),
300
+ ...(extensions && { extensions }),
140
301
  };
141
302
  const appPath = path.join(process.cwd(), inputId || appId);
142
303
  if (swellConfigFileExists(appPath)) {
@@ -151,7 +312,9 @@ export default class CreateApp extends CreateAppCommand {
151
312
  }
152
313
  }
153
314
  else {
154
- this.error(`An app already exists in the target path ${appPath}.`);
315
+ this.error(`An app already exists in the target path ${appPath}.\n\nCannot overwrite existing app`, {
316
+ exit: 1,
317
+ });
155
318
  }
156
319
  }
157
320
  this.log(`\nCreating ${this.appType === 'theme' ? 'theme' : 'app'} ${style.appConfigValue(name)} in ${appPath}/swell.json`);
@@ -172,13 +335,19 @@ export default class CreateApp extends CreateAppCommand {
172
335
  async run() {
173
336
  const { args, flags } = await this.parse(CreateApp);
174
337
  const { id } = args;
175
- const { pkg, yes } = flags;
176
- const confirmYes = Boolean(yes && id);
338
+ const { pkg, yes, name, type, version, description, frontend } = flags;
339
+ const confirmYes = Boolean(yes);
177
340
  const createParams = await this.createSwellConfig({
178
341
  inputId: id,
179
342
  inputStorefrontApp: flags['storefront-app'],
180
- inputType: flags.type,
343
+ inputType: type,
181
344
  inputYes: confirmYes,
345
+ inputName: name,
346
+ inputVersion: version,
347
+ inputDescription: description,
348
+ inputFrontend: frontend,
349
+ inputIntegrationType: flags['integration-type'],
350
+ inputIntegrationId: flags['integration-id'],
182
351
  });
183
352
  if (!createParams) {
184
353
  return;
@@ -1,6 +1,9 @@
1
1
  import { CreateCollectionCommand } from '../../create-collection-command.js';
2
2
  import { ConfigType } from '../../lib/apps/index.js';
3
3
  export default class CreateContent extends CreateCollectionCommand {
4
+ static args: {
5
+ collection: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
6
+ };
4
7
  static description: string;
5
8
  static examples: {
6
9
  command: string;
@@ -12,6 +15,7 @@ export default class CreateContent extends CreateCollectionCommand {
12
15
  label: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
13
16
  overwrite: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
14
17
  views: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
18
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
15
19
  };
16
20
  static summary: string;
17
21
  createType: ConfigType;
@@ -1,27 +1,40 @@
1
1
  import { checkbox } from '@inquirer/prompts';
2
- import { Flags } from '@oclif/core';
2
+ import { Args, Flags } from '@oclif/core';
3
3
  import { CreateCollectionCommand } from '../../create-collection-command.js';
4
4
  import { ConfigType } from '../../lib/apps/index.js';
5
- import { parseFields, parseViews, } from '../../lib/create/content.js';
6
- import { toFileName } from '../../lib/create/index.js';
5
+ import { parseFields, parseViews, validateViews, } from '../../lib/create/content.js';
6
+ import { SCHEMAS } from '../../lib/create/schemas.js';
7
+ import { toCollectionLabel, toCollectionName, toFileName, } from '../../lib/create/index.js';
7
8
  export default class CreateContent extends CreateCollectionCommand {
8
- static description = `This command assists in generating UI model configuration files. CLI will prompt for values when flags are not present.`;
9
+ static args = {
10
+ collection: Args.string({
11
+ default: '',
12
+ description: 'collection ID to create or apply to, e.g., products (required with -y)',
13
+ }),
14
+ };
15
+ static description = `Interactive mode: choose to create a new collection or extend an existing standard collection. The CLI will propose a list of standard collections you can extend.
16
+
17
+ Non-interactive (-y): provide COLLECTION explicitly. To discover standard collections first, run: swell models.`;
9
18
  static examples = [
10
19
  {
11
20
  command: 'swell create content',
12
21
  description: 'Create content model interactively.',
13
22
  },
14
23
  {
15
- command: 'swell create content visitors -v list,edit,new',
16
- description: 'Specify views delimited by semicolons.',
24
+ command: 'swell create content products -y',
25
+ description: 'Apply configuration to a standard collection (non-interactive)',
17
26
  },
18
27
  {
19
- command: 'swell create content visitors -f name:short_text,description:long_text,verified:boolean,visitor_count:integer,photo:image,orders:collection',
20
- description: 'Specify fields with types delimited by semicolons',
28
+ command: 'swell create content visitors -v list,edit,new -f name:short_text,verified:boolean -l "Visitors" -d "" -y',
29
+ description: 'Create custom collection with explicit fields and views (non-interactive)',
21
30
  },
22
31
  {
23
- command: 'swell create content visitors -o -f verified:short_text -v list,edit,new -d "Visitor Views"',
24
- description: 'Specify fields, views, and description.',
32
+ command: 'swell create content visitors --overwrite -y',
33
+ description: 'Non-interactive: force overwrite existing config file',
34
+ },
35
+ {
36
+ command: 'swell create content visitors -f name:short_text,description:long_text,verified:boolean,visitor_count:integer,photo:image,orders:collection',
37
+ description: 'Specify fields with types delimited by commas (interactive mode)',
25
38
  },
26
39
  ];
27
40
  static flags = {
@@ -33,30 +46,85 @@ export default class CreateContent extends CreateCollectionCommand {
33
46
  fields: Flags.string({
34
47
  char: 'f',
35
48
  default: '',
36
- description: 'list of content model fields delimited by commas',
49
+ description: 'comma-separated id:type pairs (e.g., name:short_text, verified:boolean)',
37
50
  }),
38
51
  label: Flags.string({
39
52
  char: 'l',
40
53
  default: '',
41
- description: 'content model label',
54
+ description: 'display label for the content model (defaults to capitalized collection name)',
42
55
  }),
43
56
  overwrite: Flags.boolean({
44
57
  default: false,
45
- description: 'overwrite existing content model configuration file',
58
+ description: 'overwrite existing content model configuration file (use with -y to force overwrite)',
46
59
  }),
47
60
  views: Flags.string({
48
61
  char: 'v',
49
62
  default: '',
50
- description: 'list of content model views delimited by commas, i.e. list,edit,new',
63
+ description: 'comma-separated views - valid: list, edit, new',
64
+ }),
65
+ yes: Flags.boolean({
66
+ char: 'y',
67
+ description: 'accept all default values; non-interactive, never prompts; errors if required values are missing',
51
68
  }),
52
69
  };
53
70
  static summary = 'Initialize a content UI model config file with properties in the content folder.';
54
71
  createType = ConfigType.CONTENT;
55
72
  async run() {
56
- const { flags } = await this.parse(CreateContent);
73
+ const { args, flags } = await this.parse(CreateContent);
74
+ const confirmYes = Boolean(flags.yes);
75
+ // NON-INTERACTIVE PATH
76
+ if (confirmYes) {
77
+ // Validate required arguments
78
+ const argCollection = args.collection;
79
+ if (!argCollection) {
80
+ this.error('Missing required argument for non-interactive mode: COLLECTION\n\nExample: swell create content products -y\n\nTip: run `swell models` to list standard collections you can extend.', { exit: 1 });
81
+ }
82
+ // Process and normalize collection name
83
+ const collection = toCollectionName(argCollection);
84
+ // Apply defaults for optional values
85
+ const label = flags.label || toCollectionLabel(collection);
86
+ const description = flags.description || '';
87
+ // Validate and parse views
88
+ let viewsList;
89
+ if (flags.views) {
90
+ viewsList = flags.views
91
+ .split(',')
92
+ .map((v) => v.trim())
93
+ .filter(Boolean);
94
+ validateViews(viewsList, this.error.bind(this));
95
+ }
96
+ else {
97
+ // Default views when not specified
98
+ viewsList = ['list', 'edit', 'new'];
99
+ }
100
+ // Parse fields (no validation - trust user input)
101
+ const fieldsRaw = (flags.fields || '');
102
+ const fieldPairs = fieldsRaw
103
+ ? fieldsRaw
104
+ .split(',')
105
+ .map((v) => v.trim())
106
+ .filter(Boolean)
107
+ : [];
108
+ // Build file body
109
+ const fileName = toFileName(collection);
110
+ const fileBody = {
111
+ $schema: SCHEMAS.CONTENT,
112
+ collection,
113
+ description,
114
+ fields: parseFields(fieldPairs),
115
+ label,
116
+ views: parseViews(viewsList),
117
+ };
118
+ // Create file with overwrite handling
119
+ await this.createFile({ fileBody, fileName }, flags.overwrite,
120
+ /* shouldConfirm */ false);
121
+ return;
122
+ }
123
+ // INTERACTIVE PATH (existing logic)
57
124
  const { collection, description, label } = await this.promptCollection();
58
125
  const fileName = toFileName(collection);
59
126
  const fileBody = {
127
+ $schema: SCHEMAS.CONTENT,
60
128
  collection,
61
129
  };
62
130
  const { fields, overwrite, views } = flags;
@@ -70,10 +70,13 @@ export default class CreateFunction extends CreateConfigCommand {
70
70
  let writeFunctionFileParams = {
71
71
  description,
72
72
  events,
73
+ extension: undefined,
73
74
  functionName: functionName || '',
74
- language: (functionName?.includes('.ts')
75
+ language: (functionName?.endsWith('.ts')
75
76
  ? 'ts'
76
- : 'js'),
77
+ : functionName?.endsWith('.js')
78
+ ? 'js'
79
+ : undefined),
77
80
  route,
78
81
  schedule,
79
82
  trigger: trigger,
@@ -85,6 +88,23 @@ export default class CreateFunction extends CreateConfigCommand {
85
88
  await this.writeFunctionFile(writeFunctionFileParams, overwrite);
86
89
  }
87
90
  async gatherInput(params) {
91
+ // Check if there are any extensions in the app configuration
92
+ const extensions = this.swellConfig.get('extensions') || [];
93
+ let { extension } = params;
94
+ const extensionsWithId = extensions.filter((ext) => ext.id);
95
+ if (extensionsWithId.length > 0 && !extension) {
96
+ const extensionChoices = [
97
+ { name: 'Not for an extension', value: '' },
98
+ ...extensionsWithId.map((ext) => ({
99
+ name: ext.id,
100
+ value: ext.id,
101
+ })),
102
+ ];
103
+ extension = await select({
104
+ choices: extensionChoices,
105
+ message: 'Is this function for an app extension?',
106
+ });
107
+ }
88
108
  let { language } = params;
89
109
  if (!language) {
90
110
  language = await select({
@@ -155,9 +175,9 @@ export default class CreateFunction extends CreateConfigCommand {
155
175
  message: 'Name your function',
156
176
  });
157
177
  }
158
- const { description } = params;
178
+ let { description } = params;
159
179
  if (!description) {
160
- await input({
180
+ description = await input({
161
181
  default: 'Does a thing when another other thing happens',
162
182
  message: 'Describe what the function does',
163
183
  });
@@ -165,6 +185,7 @@ export default class CreateFunction extends CreateConfigCommand {
165
185
  return {
166
186
  description,
167
187
  events: events || '',
188
+ extension: extension || undefined,
168
189
  functionName,
169
190
  language,
170
191
  route: route || '',
@@ -172,13 +193,14 @@ export default class CreateFunction extends CreateConfigCommand {
172
193
  trigger,
173
194
  };
174
195
  }
175
- async writeFunctionFile({ description, events, functionName, language, route, schedule, trigger, }, overwrite) {
196
+ async writeFunctionFile({ description, events, extension, functionName, language, route, schedule, trigger, }, overwrite) {
176
197
  const fileName = toFunctionFileName(functionName);
177
198
  await this.createFile({
178
199
  extension: language,
179
200
  fileBody: getFunctionTemplate({
180
201
  description,
181
202
  events,
203
+ extension,
182
204
  functionName,
183
205
  language,
184
206
  route,
@@ -1,6 +1,9 @@
1
1
  import { CreateCollectionCommand } from '../../create-collection-command.js';
2
2
  import { ConfigType } from '../../lib/apps/index.js';
3
3
  export default class CreateModel extends CreateCollectionCommand {
4
+ static args: {
5
+ collection: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
6
+ };
4
7
  static description: string;
5
8
  static examples: {
6
9
  command: string;
@@ -12,6 +15,7 @@ export default class CreateModel extends CreateCollectionCommand {
12
15
  fields: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
13
16
  label: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
14
17
  overwrite: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
18
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
15
19
  };
16
20
  static summary: string;
17
21
  createType: ConfigType;