@swell/cli 2.1.0 → 2.2.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.
Files changed (112) 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 +163 -139
  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/index.js +5 -1
  30. package/dist/commands/create/model.d.ts +4 -0
  31. package/dist/commands/create/model.js +120 -25
  32. package/dist/commands/create/tests.d.ts +15 -0
  33. package/dist/commands/create/tests.js +79 -0
  34. package/dist/commands/inspect/content.d.ts +25 -0
  35. package/dist/commands/inspect/content.js +159 -0
  36. package/dist/commands/inspect/index.d.ts +7 -0
  37. package/dist/commands/inspect/index.js +16 -0
  38. package/dist/commands/inspect/models.d.ts +27 -0
  39. package/dist/commands/inspect/models.js +197 -0
  40. package/dist/commands/schema.d.ts +27 -0
  41. package/dist/commands/schema.js +231 -0
  42. package/dist/commands/theme/dev.d.ts +1 -1
  43. package/dist/commands/theme/dev.js +2 -2
  44. package/dist/commands/theme/init.d.ts +1 -1
  45. package/dist/commands/theme/init.js +1 -1
  46. package/dist/commands/theme/pull.d.ts +2 -2
  47. package/dist/commands/theme/pull.js +2 -2
  48. package/dist/commands/theme/push.d.ts +1 -1
  49. package/dist/commands/theme/push.js +1 -1
  50. package/dist/create-app-command.d.ts +12 -2
  51. package/dist/create-app-command.js +103 -80
  52. package/dist/create-collection-command.js +4 -1
  53. package/dist/create-config-command.js +4 -0
  54. package/dist/env/local.d.ts +1 -0
  55. package/dist/env/local.js +1 -0
  56. package/dist/env/production.d.ts +1 -0
  57. package/dist/env/production.js +1 -0
  58. package/dist/env/review.d.ts +1 -0
  59. package/dist/env/review.js +1 -0
  60. package/dist/env/staging.d.ts +1 -0
  61. package/dist/env/staging.js +1 -0
  62. package/dist/lib/api.d.ts +16 -5
  63. package/dist/lib/api.js +67 -25
  64. package/dist/lib/app-config.js +1 -0
  65. package/dist/lib/apps/index.d.ts +2 -0
  66. package/dist/lib/apps/index.js +20 -14
  67. package/dist/lib/config.d.ts +1 -1
  68. package/dist/lib/constants.d.ts +1 -0
  69. package/dist/lib/constants.js +1 -0
  70. package/dist/lib/create/content.d.ts +5 -1
  71. package/dist/lib/create/content.js +8 -1
  72. package/dist/lib/create/function.d.ts +2 -1
  73. package/dist/lib/create/function.js +7 -3
  74. package/dist/lib/create/model.d.ts +1 -0
  75. package/dist/lib/create/schemas.d.ts +6 -0
  76. package/dist/lib/create/schemas.js +6 -0
  77. package/dist/lib/create/tests/templates/env-dts.d.ts +1 -0
  78. package/dist/lib/create/tests/templates/env-dts.js +14 -0
  79. package/dist/lib/create/tests/templates/index.d.ts +8 -0
  80. package/dist/lib/create/tests/templates/index.js +8 -0
  81. package/dist/lib/create/tests/templates/integration-test.d.ts +1 -0
  82. package/dist/lib/create/tests/templates/integration-test.js +19 -0
  83. package/dist/lib/create/tests/templates/mock-request.d.ts +2 -0
  84. package/dist/lib/create/tests/templates/mock-request.js +112 -0
  85. package/dist/lib/create/tests/templates/setup-globals.d.ts +1 -0
  86. package/dist/lib/create/tests/templates/setup-globals.js +23 -0
  87. package/dist/lib/create/tests/templates/swell-client.d.ts +1 -0
  88. package/dist/lib/create/tests/templates/swell-client.js +126 -0
  89. package/dist/lib/create/tests/templates/tsconfig.d.ts +1 -0
  90. package/dist/lib/create/tests/templates/tsconfig.js +15 -0
  91. package/dist/lib/create/tests/templates/unit-test.d.ts +1 -0
  92. package/dist/lib/create/tests/templates/unit-test.js +42 -0
  93. package/dist/lib/create/tests/templates/vitest-config.d.ts +2 -0
  94. package/dist/lib/create/tests/templates/vitest-config.js +121 -0
  95. package/dist/lib/create/tests/types.d.ts +21 -0
  96. package/dist/lib/create/tests/types.js +1 -0
  97. package/dist/lib/create/tests.d.ts +4 -0
  98. package/dist/lib/create/tests.js +113 -0
  99. package/dist/lib/sessions.js +4 -3
  100. package/dist/lib/style.js +0 -1
  101. package/dist/lib/theme-sync.d.ts +6 -6
  102. package/dist/lib/theme-sync.js +49 -39
  103. package/dist/push-app-command.d.ts +7 -7
  104. package/dist/push-app-command.js +38 -36
  105. package/dist/remote-app-command.d.ts +1 -1
  106. package/dist/remote-app-command.js +4 -2
  107. package/dist/swell-api-command.d.ts +13 -0
  108. package/dist/swell-api-command.js +75 -0
  109. package/dist/swell-command.d.ts +1 -1
  110. package/dist/swell-command.js +9 -9
  111. package/oclif.manifest.json +639 -46
  112. package/package.json +8 -1
@@ -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 { toCollectionLabel, toCollectionName, toFileName, } from '../../lib/create/index.js';
7
+ import { SCHEMAS } from '../../lib/create/schemas.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,
@@ -26,9 +26,13 @@ export default class Create extends Command {
26
26
  name: 'Notification',
27
27
  value: 'notification',
28
28
  },
29
+ {
30
+ name: 'Tests',
31
+ value: 'tests',
32
+ },
29
33
  ],
30
34
  message: 'What do you want to create?',
31
35
  });
32
- this.log(JSON.stringify(createTarget));
36
+ await this.config.runCommand(`create:${createTarget}`);
33
37
  }
34
38
  }
@@ -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;
@@ -1,27 +1,36 @@
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 { toFileName } from '../../lib/create/index.js';
5
+ import { toCollectionLabel, toCollectionName, toFileName, } from '../../lib/create/index.js';
6
6
  import { parseEvents, parseFields, } from '../../lib/create/model.js';
7
+ import { SCHEMAS } from '../../lib/create/schemas.js';
7
8
  export default class CreateModel extends CreateCollectionCommand {
8
- static description = `This command assists in generating data 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 inspect models.`;
9
18
  static examples = [
10
19
  {
11
20
  command: 'swell create model',
12
- description: 'Create data model configuration interactively.',
21
+ description: 'Create data model configuration interactively',
13
22
  },
14
23
  {
15
- command: 'swell create model visitors -e created,updated,deleted.',
16
- description: 'Specify events of the data model.',
24
+ command: 'swell create model products -y',
25
+ description: 'Apply configuration to a standard collection (non-interactive)',
17
26
  },
18
27
  {
19
- command: 'swell create model visitors -f first_name:string,last_name:string,photo:file,visit_count:int,visits:link',
20
- description: 'Specify fields of the data model.',
28
+ command: 'swell create model visitors -e created,updated -f title:string,photo:image -l "Visitors" -d "" -y',
29
+ description: 'Create custom collection with explicit fields (non-interactive)',
21
30
  },
22
31
  {
23
- command: 'swell create model visitors -f first_name:string,last_name:string,photo:file,visit_count:int,visits:link -d "Visitors Tracking" -o -e created,updated,deleted',
24
- description: 'Scaffold a data model configuration with args and flags.',
32
+ command: 'swell inspect models',
33
+ description: 'List standard collections available for extending',
25
34
  },
26
35
  ];
27
36
  static flags = {
@@ -33,40 +42,129 @@ export default class CreateModel extends CreateCollectionCommand {
33
42
  events: Flags.string({
34
43
  char: 'e',
35
44
  default: 'created,updated,deleted',
36
- description: 'list of data model events delimited by commas',
45
+ description: 'comma-separated events - valid: created, updated, deleted',
37
46
  }),
38
47
  fields: Flags.string({
39
48
  char: 'f',
40
49
  default: '',
41
- description: 'list of data model fields delimited by commas',
50
+ description: 'comma-separated id:type pairs (e.g., title:string, price:currency). Types: collection, currency, image, int, link, string',
42
51
  }),
43
52
  label: Flags.string({
44
53
  char: 'l',
45
54
  default: '',
46
- description: 'data model label, i.e. Products',
55
+ description: 'display label for the model (defaults to capitalized collection name)',
47
56
  }),
48
57
  overwrite: Flags.boolean({
49
58
  default: false,
50
- description: 'Overwrite existing data model configuration file.',
59
+ description: 'overwrite existing data model configuration file (use with -y to force overwrite)',
60
+ }),
61
+ yes: Flags.boolean({
62
+ char: 'y',
63
+ description: 'accept all default values; non-interactive, never prompts; errors if required values are missing',
51
64
  }),
52
65
  };
53
- static summary = 'Initialize a data model config file with properties in the models folder.';
66
+ static summary = 'Create or extend a data model configuration in the models folder.';
54
67
  createType = ConfigType.MODEL;
55
68
  async run() {
56
- const { flags } = await this.parse(CreateModel);
69
+ const { args, flags } = await this.parse(CreateModel);
70
+ const confirmYes = Boolean(flags.yes);
71
+ // Helper validators
72
+ const validEventValues = ['created', 'updated', 'deleted'];
73
+ const validFieldTypes = [
74
+ 'collection',
75
+ 'currency',
76
+ 'image',
77
+ 'int',
78
+ 'link',
79
+ 'string',
80
+ ];
81
+ const validateEvents = (values) => {
82
+ const invalid = values.filter((v) => !validEventValues.includes(v));
83
+ if (invalid.length > 0) {
84
+ this.error(`Invalid event(s): ${invalid.join(', ')}\n\nValid events: ${validEventValues.join(', ')}\n\nExample: swell create model visitors -e created,updated -y`, { exit: 1 });
85
+ }
86
+ };
87
+ const validateFields = (pairs) => {
88
+ const invalid = [];
89
+ for (const pair of pairs) {
90
+ if (!pair.includes(':')) {
91
+ invalid.push(pair);
92
+ continue;
93
+ }
94
+ const type = pair.split(':')[1]?.trim();
95
+ if (!validFieldTypes.includes(type)) {
96
+ invalid.push(pair);
97
+ }
98
+ }
99
+ if (invalid.length > 0) {
100
+ this.error(`Invalid field(s): ${invalid.join(', ')}\n\nValid types: ${validFieldTypes.join(', ')}\n\nExample: swell create model visitors -f title:string,price:currency -y`, { exit: 1 });
101
+ }
102
+ };
103
+ if (confirmYes) {
104
+ const argCollection = args.collection;
105
+ if (!argCollection) {
106
+ this.error('Missing required argument for non-interactive mode: COLLECTION\n\nExample: swell create model products -y\n\nTip: run `swell inspect models` to list standard collections you can extend.', { exit: 1 });
107
+ }
108
+ const collection = toCollectionName(argCollection);
109
+ const label = flags.label || toCollectionLabel(collection);
110
+ const description = flags.description || '';
111
+ const eventsRaw = (flags.events || 'created,updated,deleted');
112
+ const eventsList = eventsRaw
113
+ .split(',')
114
+ .map((v) => v.trim())
115
+ .filter(Boolean);
116
+ validateEvents(eventsList);
117
+ const fieldsRaw = (flags.fields || '');
118
+ const fieldPairs = fieldsRaw
119
+ ? fieldsRaw
120
+ .split(',')
121
+ .map((v) => v.trim())
122
+ .filter(Boolean)
123
+ : [];
124
+ validateFields(fieldPairs);
125
+ const fileName = toFileName(collection);
126
+ const fileBody = {
127
+ $schema: SCHEMAS.MODEL,
128
+ collection,
129
+ description,
130
+ fields: parseFields(fieldPairs),
131
+ label,
132
+ events: parseEvents(eventsList),
133
+ };
134
+ await this.createFile({ fileBody, fileName }, flags.overwrite,
135
+ /* shouldConfirm */ false);
136
+ return;
137
+ }
138
+ // Interactive flow (prompts)
57
139
  const { collection, description, label } = await this.promptCollection();
58
140
  const fileName = toFileName(collection);
59
141
  const fileBody = {
142
+ $schema: SCHEMAS.MODEL,
60
143
  collection,
61
144
  };
62
145
  const { events, fields, overwrite } = flags;
63
- if (label) {
146
+ if (label)
64
147
  fileBody.label = label;
65
- }
66
148
  fileBody.description = description;
67
- fileBody.fields = fields ? parseFields(fields.split(',')) : {};
68
- if (fileBody.events) {
69
- fileBody.events = events ? parseEvents(events.split(',')) : {};
149
+ if (fields) {
150
+ const pairs = fields
151
+ .split(',')
152
+ .map((v) => v.trim())
153
+ .filter(Boolean);
154
+ validateFields(pairs);
155
+ fileBody.fields = parseFields(pairs);
156
+ }
157
+ else {
158
+ fileBody.fields = {};
159
+ }
160
+ // If events were provided via flag, use them; otherwise prompt
161
+ if (events && events !== 'created,updated,deleted') {
162
+ const eventsList = events
163
+ .split(',')
164
+ .map((v) => v.trim())
165
+ .filter(Boolean);
166
+ validateEvents(eventsList);
167
+ fileBody.events = parseEvents(eventsList);
70
168
  }
71
169
  else {
72
170
  const eventsInput = await checkbox({
@@ -79,9 +177,6 @@ export default class CreateModel extends CreateCollectionCommand {
79
177
  });
80
178
  fileBody.events = parseEvents(eventsInput);
81
179
  }
82
- await this.createFile({
83
- fileBody,
84
- fileName,
85
- }, overwrite);
180
+ await this.createFile({ fileBody, fileName }, overwrite);
86
181
  }
87
182
  }
@@ -0,0 +1,15 @@
1
+ import { AppCommand } from '../../app-command.js';
2
+ export default class CreateTests extends AppCommand {
3
+ static description: string;
4
+ static summary: string;
5
+ static examples: {
6
+ command: string;
7
+ description: string;
8
+ }[];
9
+ static flags: {
10
+ overwrite: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
12
+ };
13
+ run(): Promise<void>;
14
+ private logScaffoldResult;
15
+ }
@@ -0,0 +1,79 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { AppCommand } from '../../app-command.js';
3
+ import { toAppId } from '../../lib/create/index.js';
4
+ import { createTestsScaffold, } from '../../lib/create/tests.js';
5
+ export default class CreateTests extends AppCommand {
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
+ 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
+ },
17
+ ];
18
+ static flags = {
19
+ overwrite: Flags.boolean({
20
+ default: false,
21
+ description: 'overwrite existing test files if they already exist',
22
+ }),
23
+ yes: Flags.boolean({
24
+ char: 'y',
25
+ default: false,
26
+ description: 'accept defaults, skip prompts (no prompts currently, included for consistency)',
27
+ }),
28
+ };
29
+ async run() {
30
+ const { flags } = await this.parse(CreateTests);
31
+ const appIdFromConfig = (this.swellConfig.get('id') ||
32
+ this.swellConfig.get('private_id') ||
33
+ this.swellConfig.get('public_id'));
34
+ const appId = appIdFromConfig ||
35
+ toAppId(this.swellConfig.get('name')) ||
36
+ 'app';
37
+ const result = await createTestsScaffold({
38
+ appPath: this.appPath,
39
+ appId,
40
+ overwrite: Boolean(flags.overwrite),
41
+ });
42
+ this.logScaffoldResult(result);
43
+ }
44
+ logScaffoldResult(result) {
45
+ const hasChanges = result.createdFiles.length > 0 || result.packageJsonUpdated;
46
+ if (hasChanges) {
47
+ this.log('');
48
+ this.log('✓ Created test scaffolding');
49
+ }
50
+ if (result.createdFiles.length > 0) {
51
+ this.log('');
52
+ this.log('Created:');
53
+ for (const file of result.createdFiles) {
54
+ this.log(` ${file}`);
55
+ }
56
+ }
57
+ if (result.skippedFiles.length > 0) {
58
+ this.log('');
59
+ this.log('Skipped (already exist):');
60
+ for (const file of result.skippedFiles) {
61
+ this.log(` ${file}`);
62
+ }
63
+ }
64
+ if (result.packageJsonUpdated) {
65
+ this.log('');
66
+ this.log('Updated package.json with test scripts and devDependencies.');
67
+ }
68
+ for (const warning of result.warnings) {
69
+ this.warn(warning);
70
+ }
71
+ if (hasChanges) {
72
+ this.log('');
73
+ this.log('Next steps:');
74
+ this.log(' 1. npm install');
75
+ this.log(' 2. npm test');
76
+ }
77
+ this.log('');
78
+ }
79
+ }
@@ -0,0 +1,25 @@
1
+ import { SwellCommand } from '../../swell-command.js';
2
+ export default class Content extends SwellCommand {
3
+ static summary: string;
4
+ static description: string;
5
+ static args: {
6
+ 'content-id': import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
10
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
+ };
12
+ static examples: string[];
13
+ run(): Promise<void>;
14
+ protected catch(error: Error): Promise<any>;
15
+ private listContent;
16
+ private showContentViews;
17
+ private showContentView;
18
+ private resolveContentId;
19
+ private showContentDetail;
20
+ private reverseResolveContentId;
21
+ private getAppBySlug;
22
+ private getAppSlugId;
23
+ private throwContentNotFound;
24
+ private showContent;
25
+ }