@sanity/runtime-cli 4.1.0 → 4.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.
package/README.md CHANGED
@@ -20,7 +20,7 @@ $ npm install -g @sanity/runtime-cli
20
20
  $ sanity-run COMMAND
21
21
  running command...
22
22
  $ sanity-run (--version)
23
- @sanity/runtime-cli/4.1.0 linux-x64 node-v22.14.0
23
+ @sanity/runtime-cli/4.2.0 linux-x64 node-v22.14.0
24
24
  $ sanity-run --help [COMMAND]
25
25
  USAGE
26
26
  $ sanity-run COMMAND
@@ -52,19 +52,28 @@ Add a resource to a Blueprint
52
52
 
53
53
  ```
54
54
  USAGE
55
- $ sanity-run blueprints add TYPE
55
+ $ sanity-run blueprints add TYPE [--function-type document-publish -n <value>]
56
56
 
57
57
  ARGUMENTS
58
58
  TYPE (function) Type of resource to add (e.g. function)
59
59
 
60
+ FLAGS
61
+ -n, --name=<value> Name of the resource to add
62
+ --function-type=<option> Type of function to add (e.g. document-publish)
63
+ <options: document-publish>
64
+
60
65
  DESCRIPTION
61
66
  Add a resource to a Blueprint
62
67
 
63
68
  EXAMPLES
64
69
  $ sanity-run blueprints add function
70
+
71
+ $ sanity-run blueprints add function --name my-function
72
+
73
+ $ sanity-run blueprints add function --name my-function --function-type document-publish
65
74
  ```
66
75
 
67
- _See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/blueprints/add.ts)_
76
+ _See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/blueprints/add.ts)_
68
77
 
69
78
  ## `sanity-run blueprints config`
70
79
 
@@ -72,10 +81,13 @@ View or edit Blueprint configuration
72
81
 
73
82
  ```
74
83
  USAGE
75
- $ sanity-run blueprints config [--edit]
84
+ $ sanity-run blueprints config [-t] [--project-id <value> -e] [--stack-id <value> ]
76
85
 
77
86
  FLAGS
78
- --edit Edit the configuration
87
+ -e, --edit Edit the configuration
88
+ -t, --test-config Validate the configuration
89
+ --project-id=<value> Update the Project ID in the configuration. Requires --edit flag
90
+ --stack-id=<value> Update the Stack ID in the configuration. Requires --edit flag
79
91
 
80
92
  DESCRIPTION
81
93
  View or edit Blueprint configuration
@@ -83,10 +95,14 @@ DESCRIPTION
83
95
  EXAMPLES
84
96
  $ sanity-run blueprints config
85
97
 
98
+ $ sanity-run blueprints config --test-config
99
+
86
100
  $ sanity-run blueprints config --edit
101
+
102
+ $ sanity-run blueprints config --edit --project-id <projectId> --stack-id <stackId>
87
103
  ```
88
104
 
89
- _See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/blueprints/config.ts)_
105
+ _See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/blueprints/config.ts)_
90
106
 
91
107
  ## `sanity-run blueprints deploy`
92
108
 
@@ -106,7 +122,7 @@ EXAMPLES
106
122
  $ sanity-run blueprints deploy
107
123
  ```
108
124
 
109
- _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/blueprints/deploy.ts)_
125
+ _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/blueprints/deploy.ts)_
110
126
 
111
127
  ## `sanity-run blueprints destroy`
112
128
 
@@ -129,7 +145,7 @@ EXAMPLES
129
145
  $ sanity-run blueprints destroy --id ST-a1b2c3
130
146
  ```
131
147
 
132
- _See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/blueprints/destroy.ts)_
148
+ _See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/blueprints/destroy.ts)_
133
149
 
134
150
  ## `sanity-run blueprints info`
135
151
 
@@ -151,7 +167,7 @@ EXAMPLES
151
167
  $ sanity-run blueprints info --id ST-a1b2c3
152
168
  ```
153
169
 
154
- _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/blueprints/info.ts)_
170
+ _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/blueprints/info.ts)_
155
171
 
156
172
  ## `sanity-run blueprints init`
157
173
 
@@ -159,16 +175,29 @@ Initialize a new Blueprint
159
175
 
160
176
  ```
161
177
  USAGE
162
- $ sanity-run blueprints init
178
+ $ sanity-run blueprints init [--blueprint-type json|js|ts] [--stack-id <value> --project-id <value>] [-n <value> ]
179
+
180
+ FLAGS
181
+ -n, --stack-name=<value> Name to use for a NEW Stack
182
+ --blueprint-type=<option> Blueprint manifest type to use for the Blueprint
183
+ <options: json|js|ts>
184
+ --project-id=<value> Sanity Project ID to use for the Blueprint
185
+ --stack-id=<value> Existing Stack ID to use for the Blueprint
163
186
 
164
187
  DESCRIPTION
165
188
  Initialize a new Blueprint
166
189
 
167
190
  EXAMPLES
168
191
  $ sanity-run blueprints init
192
+
193
+ $ sanity-run blueprints init --blueprint-type <json|js|ts>
194
+
195
+ $ sanity-run blueprints init --blueprint-type <json|js|ts> --project-id <projectId> --stack-id <stackId>
196
+
197
+ $ sanity-run blueprints init --blueprint-type <json|js|ts> --project-id <projectId> --stack-name <stackName>
169
198
  ```
170
199
 
171
- _See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/blueprints/init.ts)_
200
+ _See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/blueprints/init.ts)_
172
201
 
173
202
  ## `sanity-run blueprints logs`
174
203
 
@@ -190,7 +219,7 @@ EXAMPLES
190
219
  $ sanity-run blueprints logs --watch
191
220
  ```
192
221
 
193
- _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/blueprints/logs.ts)_
222
+ _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/blueprints/logs.ts)_
194
223
 
195
224
  ## `sanity-run blueprints plan`
196
225
 
@@ -207,7 +236,7 @@ EXAMPLES
207
236
  $ sanity-run blueprints plan
208
237
  ```
209
238
 
210
- _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/blueprints/plan.ts)_
239
+ _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/blueprints/plan.ts)_
211
240
 
212
241
  ## `sanity-run blueprints stacks`
213
242
 
@@ -229,7 +258,7 @@ EXAMPLES
229
258
  $ sanity-run blueprints stacks --projectId a1b2c3
230
259
  ```
231
260
 
232
- _See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/blueprints/stacks.ts)_
261
+ _See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/blueprints/stacks.ts)_
233
262
 
234
263
  ## `sanity-run functions dev`
235
264
 
@@ -249,7 +278,7 @@ EXAMPLES
249
278
  $ sanity-run functions dev --port 8974
250
279
  ```
251
280
 
252
- _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/functions/dev.ts)_
281
+ _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/functions/dev.ts)_
253
282
 
254
283
  ## `sanity-run functions env add NAME KEY VALUE`
255
284
 
@@ -271,7 +300,7 @@ EXAMPLES
271
300
  $ sanity-run functions env add MyFunction API_URL https://api.example.com/
272
301
  ```
273
302
 
274
- _See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/functions/env/add.ts)_
303
+ _See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/functions/env/add.ts)_
275
304
 
276
305
  ## `sanity-run functions env remove NAME KEY`
277
306
 
@@ -292,7 +321,7 @@ EXAMPLES
292
321
  $ sanity-run functions env remove MyFunction API_URL
293
322
  ```
294
323
 
295
- _See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/functions/env/remove.ts)_
324
+ _See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/functions/env/remove.ts)_
296
325
 
297
326
  ## `sanity-run functions invoke NAME`
298
327
 
@@ -318,7 +347,7 @@ EXAMPLES
318
347
  $ sanity-run functions invoke <name> --file 'payload.json'
319
348
  ```
320
349
 
321
- _See code: [src/commands/functions/invoke.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/functions/invoke.ts)_
350
+ _See code: [src/commands/functions/invoke.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/functions/invoke.ts)_
322
351
 
323
352
  ## `sanity-run functions logs NAME`
324
353
 
@@ -350,7 +379,7 @@ EXAMPLES
350
379
  $ sanity-run functions logs <name> --delete
351
380
  ```
352
381
 
353
- _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/functions/logs.ts)_
382
+ _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/functions/logs.ts)_
354
383
 
355
384
  ## `sanity-run functions test NAME`
356
385
 
@@ -383,7 +412,7 @@ EXAMPLES
383
412
  $ sanity-run functions test <name> --data '{ "id": 1 }' --timeout 60
384
413
  ```
385
414
 
386
- _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v4.1.0/src/commands/functions/test.ts)_
415
+ _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v4.2.0/src/commands/functions/test.ts)_
387
416
 
388
417
  ## `sanity-run help [COMMAND]`
389
418
 
@@ -5,6 +5,15 @@ export default class Add extends Command {
5
5
  static args: {
6
6
  type: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
7
  };
8
+ static flags: {
9
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'function-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ };
8
12
  run(): Promise<void>;
9
- private addFunction;
13
+ promptForFunctionName(): Promise<string>;
14
+ promptForFunctionType(): Promise<string>;
15
+ addFunction({ name, type }: {
16
+ name?: string;
17
+ type?: string;
18
+ }): Promise<void>;
10
19
  }
@@ -1,11 +1,16 @@
1
- import { Args, Command } from '@oclif/core';
1
+ import { Args, Command, Flags } from '@oclif/core';
2
2
  import highlight from 'color-json';
3
3
  import inquirer from 'inquirer';
4
4
  import { findBlueprintFile } from '../../actions/blueprints/blueprint.js';
5
5
  import { createFunctionResource } from '../../actions/blueprints/resources.js';
6
+ import { validateFunctionName, validateFunctionType } from '../../utils/validate/resource.js';
6
7
  export default class Add extends Command {
7
8
  static description = 'Add a resource to a Blueprint';
8
- static examples = ['<%= config.bin %> <%= command.id %> function'];
9
+ static examples = [
10
+ '<%= config.bin %> <%= command.id %> function',
11
+ '<%= config.bin %> <%= command.id %> function --name my-function',
12
+ '<%= config.bin %> <%= command.id %> function --name my-function --function-type document-publish',
13
+ ];
9
14
  static args = {
10
15
  type: Args.string({
11
16
  description: 'Type of resource to add (e.g. function)',
@@ -13,19 +18,30 @@ export default class Add extends Command {
13
18
  required: true,
14
19
  }),
15
20
  };
21
+ static flags = {
22
+ name: Flags.string({
23
+ description: 'Name of the resource to add',
24
+ char: 'n',
25
+ }),
26
+ 'function-type': Flags.string({
27
+ description: 'Type of function to add (e.g. document-publish)',
28
+ options: ['document-publish' /*, 'document-create', 'document-delete'*/],
29
+ dependsOn: ['name'],
30
+ }),
31
+ };
16
32
  async run() {
17
- const { args } = await this.parse(Add);
33
+ const { args, flags } = await this.parse(Add);
18
34
  const existingBlueprint = findBlueprintFile();
19
35
  if (!existingBlueprint) {
20
36
  this.error('No blueprint file found. Run `sanity blueprints init` first.');
21
37
  }
22
- if (args.type === 'function') {
23
- await this.addFunction();
24
- return;
25
- }
26
- this.error(`Unsupported resource type: ${args.type}`);
38
+ if (args.type !== 'function')
39
+ this.error(`Unsupported resource type: ${args.type}`);
40
+ const resourceName = flags.name;
41
+ const functionType = flags['function-type'];
42
+ await this.addFunction({ name: resourceName, type: functionType });
27
43
  }
28
- async addFunction() {
44
+ async promptForFunctionName() {
29
45
  const { functionName } = await inquirer.prompt([
30
46
  {
31
47
  type: 'input',
@@ -34,6 +50,9 @@ export default class Add extends Command {
34
50
  validate: (input) => input.length > 0 || 'Function name is required',
35
51
  },
36
52
  ]);
53
+ return functionName;
54
+ }
55
+ async promptForFunctionType() {
37
56
  const { functionType } = await inquirer.prompt([
38
57
  {
39
58
  type: 'list',
@@ -47,6 +66,17 @@ export default class Add extends Command {
47
66
  default: 'document-publish',
48
67
  },
49
68
  ]);
69
+ return functionType;
70
+ }
71
+ async addFunction({ name, type }) {
72
+ const functionName = name || (await this.promptForFunctionName());
73
+ if (!validateFunctionName(functionName)) {
74
+ this.error('Invalid function name. Must be 6+ characters, no special characters, no spaces');
75
+ }
76
+ const functionType = type || (await this.promptForFunctionType());
77
+ if (!validateFunctionType(functionType)) {
78
+ this.error('Invalid function type. Must be one of: document-publish, document-create, document-delete');
79
+ }
50
80
  const { filePath, resourceAdded, resource } = createFunctionResource({
51
81
  name: functionName,
52
82
  type: functionType,
@@ -3,7 +3,24 @@ export default class Config extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
+ 'test-config': import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
7
  edit: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ 'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ 'stack-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
10
  };
8
11
  run(): Promise<void>;
12
+ promptForProjectId({ knownProjectId }: {
13
+ knownProjectId?: string;
14
+ }): Promise<string>;
15
+ promptForStackId({ projectId, knownStackId, }: {
16
+ projectId: string;
17
+ knownStackId?: string;
18
+ }): Promise<string | undefined>;
19
+ testConfigAndReport({ stackId, projectId }: {
20
+ stackId: string;
21
+ projectId: string;
22
+ }): Promise<{
23
+ ok: boolean;
24
+ error: string | null;
25
+ }>;
9
26
  }
@@ -1,9 +1,10 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
2
  import highlight from 'color-json';
3
3
  import inquirer from 'inquirer';
4
+ import Spinner from 'yocto-spinner';
4
5
  import { readBlueprintOnDisk, writeConfigFile } from '../../actions/blueprints/blueprint.js';
5
6
  import { listProjects } from '../../actions/blueprints/projects.js';
6
- import { listStacks } from '../../actions/blueprints/stacks.js';
7
+ import { getStack, listStacks } from '../../actions/blueprints/stacks.js';
7
8
  import config from '../../config.js';
8
9
  import { bold, dim, niceId } from '../../utils/display/colors.js';
9
10
  const { token } = config;
@@ -11,27 +12,86 @@ export default class Config extends Command {
11
12
  static description = 'View or edit Blueprint configuration';
12
13
  static examples = [
13
14
  '<%= config.bin %> <%= command.id %>',
15
+ '<%= config.bin %> <%= command.id %> --test-config',
14
16
  '<%= config.bin %> <%= command.id %> --edit',
17
+ '<%= config.bin %> <%= command.id %> --edit --project-id <projectId> --stack-id <stackId>',
15
18
  ];
16
19
  static flags = {
20
+ 'test-config': Flags.boolean({
21
+ char: 't',
22
+ aliases: ['test', 'validate'],
23
+ description: 'Validate the configuration',
24
+ default: false,
25
+ }),
17
26
  edit: Flags.boolean({
27
+ char: 'e',
18
28
  description: 'Edit the configuration',
19
29
  default: false,
20
30
  }),
31
+ 'project-id': Flags.string({
32
+ description: 'Update the Project ID in the configuration. Requires --edit flag',
33
+ aliases: ['project', 'projectId'],
34
+ dependsOn: ['edit'],
35
+ }),
36
+ 'stack-id': Flags.string({
37
+ description: 'Update the Stack ID in the configuration. Requires --edit flag',
38
+ aliases: ['stack', 'stackId'],
39
+ dependsOn: ['edit'],
40
+ }),
21
41
  };
22
42
  async run() {
23
43
  const { flags } = await this.parse(Config);
44
+ const { edit: editConfig, 'project-id': editProjectId, 'stack-id': editStackId, 'test-config': testConfig, } = flags;
24
45
  const blueprint = await readBlueprintOnDisk();
25
- const { stackId: configStackId, projectId: configProjectId, configPath, fileInfo } = blueprint;
26
- const { extension: blueprintExtension, blueprintFilePath } = fileInfo;
46
+ const { stackId: configStackId, projectId: configProjectId } = blueprint;
27
47
  if (!configStackId && !configProjectId) {
28
- this.log('Project and Stack configuration is missing.');
48
+ this.error('Project and Stack configuration is missing! Use `blueprints init` to create a configuration.');
29
49
  }
30
50
  this.log(bold('Current configuration:'));
31
- this.log(`Sanity Project: ${niceId(configProjectId)}`);
32
- this.log(`Blueprint Stack: ${niceId(configStackId)}`);
33
- if (!flags.edit)
51
+ this.log(` Sanity Project: ${niceId(configProjectId)}`);
52
+ this.log(` Blueprint Stack: ${niceId(configStackId)}`);
53
+ if ((editProjectId || editStackId) && !editConfig) {
54
+ this.log('To update the configuration, use the --edit flag.');
55
+ return;
56
+ }
57
+ if (!editConfig && !testConfig)
58
+ return;
59
+ if (testConfig && !editConfig) {
60
+ if (configStackId && configProjectId) {
61
+ await this.testConfigAndReport({ stackId: configStackId, projectId: configProjectId });
62
+ }
63
+ else {
64
+ this.log('Unable to test the configuration. Both Project and Stack IDs must be set.');
65
+ }
66
+ }
67
+ if (!editConfig)
34
68
  return;
69
+ const projectId = editProjectId || (await this.promptForProjectId({ knownProjectId: configProjectId }));
70
+ if (!projectId)
71
+ this.error('Project ID is required.');
72
+ const stackId = editStackId || (await this.promptForStackId({ projectId, knownStackId: configStackId }));
73
+ if (testConfig) {
74
+ if (projectId && stackId) {
75
+ const { ok: newConfigOk } = await this.testConfigAndReport({ stackId, projectId });
76
+ if (!newConfigOk) {
77
+ this.error('Updated configuration has not been saved.');
78
+ }
79
+ }
80
+ else {
81
+ this.error('Unable to test the configuration. Both Project and Stack IDs must be set.');
82
+ }
83
+ }
84
+ try {
85
+ // update or create .blueprint/config.json
86
+ writeConfigFile({ projectId, stackId });
87
+ this.log('Configuration updated successfully.');
88
+ }
89
+ catch (error) {
90
+ this.log('Unable to dynamically update config. Use these values in your blueprint:');
91
+ this.log(highlight(JSON.stringify({ metadata: { projectId, stackId } }, null, 2)));
92
+ }
93
+ }
94
+ async promptForProjectId({ knownProjectId }) {
35
95
  const { ok, projects, error } = await listProjects({ token });
36
96
  if (!ok)
37
97
  this.error(error ?? 'Unknown error listing projects');
@@ -42,46 +102,55 @@ export default class Config extends Command {
42
102
  name: `"${displayName}" ${niceId(projectId)}`,
43
103
  value: projectId,
44
104
  }));
45
- const { projectId } = await inquirer.prompt([
105
+ const { pickedProjectId } = await inquirer.prompt([
46
106
  {
47
107
  type: 'list',
48
- name: 'projectId',
108
+ name: 'pickedProjectId',
49
109
  message: 'Select your Sanity project:',
50
110
  choices: projectChoices,
51
- default: configProjectId,
111
+ default: knownProjectId,
52
112
  },
53
113
  ]);
114
+ return pickedProjectId;
115
+ }
116
+ async promptForStackId({ projectId, knownStackId, }) {
54
117
  const auth = { token, projectId };
55
118
  // get stacks for selected project
56
119
  const { ok: stacksOk, stacks, error: stacksError } = await listStacks(auth);
57
120
  if (!stacksOk)
58
121
  this.error(stacksError ?? 'Unknown error listing stacks');
59
- let stackId;
60
122
  if (stacks.length > 0) {
61
123
  const stackChoices = stacks.map((s) => ({
62
124
  name: `"${s.name}" ${niceId(s.id)} ${dim(`(${s.resources.length} res)`)}`,
63
125
  value: s.id,
64
126
  }));
65
127
  stackChoices.push({ name: 'Unset Stack association', value: 'unset' });
66
- const { stackId: selectedStackId } = await inquirer.prompt([
128
+ const { pickedStackId } = await inquirer.prompt([
67
129
  {
68
130
  type: 'list',
69
- name: 'stackId',
131
+ name: 'pickedStackId',
70
132
  message: 'Select a stack:',
71
133
  choices: stackChoices,
72
- default: configStackId,
134
+ default: knownStackId,
73
135
  },
74
136
  ]);
75
- stackId = selectedStackId === 'unset' ? undefined : selectedStackId;
137
+ return pickedStackId === 'unset' ? undefined : pickedStackId;
76
138
  }
77
- try {
78
- // update or create .blueprint/config.json
79
- writeConfigFile({ projectId, stackId });
80
- this.log('Configuration updated successfully.');
139
+ return undefined;
140
+ }
141
+ async testConfigAndReport({ stackId, projectId }) {
142
+ const spinner = Spinner({ text: 'Testing the configuration...' }).start();
143
+ const { ok, error } = await getStack({
144
+ stackId,
145
+ auth: { token, projectId },
146
+ });
147
+ if (!ok) {
148
+ spinner.error(error ?? 'Unknown error testing the configuration');
149
+ this.log('Use the --edit flag to interactively update the configuration.');
81
150
  }
82
- catch (error) {
83
- this.log('Unable to dynamically update config. Use these values in your blueprint:');
84
- this.log(highlight(JSON.stringify({ metadata: { projectId, stackId } }, null, 2)));
151
+ else {
152
+ spinner.success('Configuration is valid.');
85
153
  }
154
+ return { ok, error };
86
155
  }
87
156
  }
@@ -1,6 +1,22 @@
1
1
  import { Command } from '@oclif/core';
2
+ import type { AuthParams, Stack, StackPayload } from '../../utils/types.js';
2
3
  export default class Init extends Command {
3
4
  static description: string;
4
5
  static examples: string[];
6
+ static flags: {
7
+ 'blueprint-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ 'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ 'stack-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'stack-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ };
5
12
  run(): Promise<void>;
13
+ promptForBlueprintType(): Promise<string>;
14
+ promptForProjectId(): Promise<string>;
15
+ promptForStackId({ projectId }: {
16
+ projectId: string;
17
+ }): Promise<string>;
18
+ createEmptyStack({ stackPayload, auth, }: {
19
+ stackPayload: StackPayload;
20
+ auth: AuthParams;
21
+ }): Promise<Stack>;
6
22
  }
@@ -1,6 +1,6 @@
1
1
  import { join } from 'node:path';
2
2
  import { cwd } from 'node:process';
3
- import { Command } from '@oclif/core';
3
+ import { Command, Flags } from '@oclif/core';
4
4
  import inquirer from 'inquirer';
5
5
  import { findBlueprintFile, writeBlueprintToDisk, writeConfigFile, } from '../../actions/blueprints/blueprint.js';
6
6
  import { listProjects } from '../../actions/blueprints/projects.js';
@@ -10,16 +10,77 @@ import { bold, dim, niceId, underline } from '../../utils/display/colors.js';
10
10
  const { token } = config;
11
11
  export default class Init extends Command {
12
12
  static description = 'Initialize a new Blueprint';
13
- static examples = ['<%= config.bin %> <%= command.id %>'];
13
+ static examples = [
14
+ '<%= config.bin %> <%= command.id %>',
15
+ '<%= config.bin %> <%= command.id %> --blueprint-type <json|js|ts>',
16
+ '<%= config.bin %> <%= command.id %> --blueprint-type <json|js|ts> --project-id <projectId> --stack-id <stackId>',
17
+ '<%= config.bin %> <%= command.id %> --blueprint-type <json|js|ts> --project-id <projectId> --stack-name <stackName>',
18
+ ];
19
+ static flags = {
20
+ 'blueprint-type': Flags.string({
21
+ description: 'Blueprint manifest type to use for the Blueprint',
22
+ options: ['json', 'js', 'ts'],
23
+ aliases: ['type'],
24
+ }),
25
+ 'project-id': Flags.string({
26
+ description: 'Sanity Project ID to use for the Blueprint',
27
+ aliases: ['project', 'projectId'],
28
+ }),
29
+ 'stack-id': Flags.string({
30
+ description: 'Existing Stack ID to use for the Blueprint',
31
+ aliases: ['stack', 'stackId'],
32
+ dependsOn: ['project-id'],
33
+ exclusive: ['stack-name'],
34
+ }),
35
+ 'stack-name': Flags.string({
36
+ char: 'n',
37
+ description: 'Name to use for a NEW Stack',
38
+ aliases: ['name'],
39
+ dependsOn: ['project-id'],
40
+ exclusive: ['stack-id'],
41
+ }),
42
+ };
14
43
  async run() {
44
+ const { flags } = await this.parse(Init);
45
+ const { 'blueprint-type': flagBlueprintType, 'project-id': flagProjectId, 'stack-id': flagStackId, 'stack-name': flagStackName, } = flags;
15
46
  const existingBlueprint = findBlueprintFile();
16
47
  if (existingBlueprint) {
17
48
  this.error(`A blueprint file already exists: ${existingBlueprint.fileName}`);
18
49
  }
19
- const { blueprintExtension } = await inquirer.prompt([
50
+ const blueprintExtension = flagBlueprintType || (await this.promptForBlueprintType());
51
+ if (!blueprintExtension)
52
+ this.error('Blueprint type is required.');
53
+ const projectId = flagProjectId || (await this.promptForProjectId());
54
+ if (!projectId)
55
+ this.error('Project ID is required.');
56
+ let stackId = flagStackId;
57
+ if (!stackId) {
58
+ if (flagStackName) {
59
+ const stack = await this.createEmptyStack({
60
+ stackPayload: { name: flagStackName, projectId, document: { resources: [] } },
61
+ auth: { token, projectId },
62
+ });
63
+ stackId = stack.id;
64
+ }
65
+ else {
66
+ stackId = await this.promptForStackId({ projectId });
67
+ }
68
+ }
69
+ const fileName = `blueprint.${blueprintExtension}`;
70
+ const filePath = join(cwd(), fileName);
71
+ writeBlueprintToDisk({ blueprintFilePath: filePath });
72
+ this.log(`Created new blueprint: ./${fileName}`);
73
+ writeConfigFile({ blueprintFilePath: filePath, projectId, stackId });
74
+ this.log(`Created new config file: ./${fileName}.config.json`);
75
+ if (blueprintExtension === 'ts') {
76
+ this.log('\nNote: TypeScript support requires "tsx" to be installed. Run: npm install -D tsx');
77
+ }
78
+ }
79
+ async promptForBlueprintType() {
80
+ const { pickedBlueprintsType } = await inquirer.prompt([
20
81
  {
21
82
  type: 'list',
22
- name: 'blueprintExtension',
83
+ name: 'pickedBlueprintsType',
23
84
  message: 'Choose a blueprint type:',
24
85
  choices: [
25
86
  { name: 'JSON (Recommended)', value: 'json' },
@@ -29,6 +90,9 @@ export default class Init extends Command {
29
90
  default: 'json',
30
91
  },
31
92
  ]);
93
+ return pickedBlueprintsType;
94
+ }
95
+ async promptForProjectId() {
32
96
  const { ok: projectsOk, error: projectsErr, projects } = await listProjects({ token });
33
97
  if (!projectsOk)
34
98
  this.error(projectsErr ?? 'Unknown error listing projects');
@@ -39,14 +103,17 @@ export default class Init extends Command {
39
103
  name: `"${displayName}" ${niceId(projectId)}`,
40
104
  value: projectId,
41
105
  }));
42
- const { projectId } = await inquirer.prompt([
106
+ const { pickedProjectId } = await inquirer.prompt([
43
107
  {
44
108
  type: 'list',
45
- name: 'projectId',
109
+ name: 'pickedProjectId',
46
110
  message: 'Select your Sanity project:',
47
111
  choices: projectChoices,
48
112
  },
49
113
  ]);
114
+ return pickedProjectId;
115
+ }
116
+ async promptForStackId({ projectId }) {
50
117
  const { ok: stacksOk, error: stacksErr, stacks } = await listStacks({ token, projectId });
51
118
  if (!stacksOk)
52
119
  this.error(stacksErr || 'Failed to list Stacks');
@@ -58,16 +125,15 @@ export default class Init extends Command {
58
125
  value: s.id,
59
126
  })));
60
127
  }
61
- let stackId;
62
- const { stackId: stackIdChoice } = await inquirer.prompt([
128
+ const { pickedStackId } = await inquirer.prompt([
63
129
  {
64
130
  type: 'list',
65
- name: 'stackId',
131
+ name: 'pickedStackId',
66
132
  message: 'Select your Blueprint Stack:',
67
133
  choices: stackChoices,
68
134
  },
69
135
  ]);
70
- if (stackIdChoice === 'new') {
136
+ if (pickedStackId === 'new') {
71
137
  const { stackName } = await inquirer.prompt([
72
138
  {
73
139
  type: 'input',
@@ -76,25 +142,18 @@ export default class Init extends Command {
76
142
  validate: (input) => input.length > 0 || 'Stack name is required',
77
143
  },
78
144
  ]);
79
- const { ok: stackOk, error: stackErr, stack, } = await createStack({
145
+ const stack = await this.createEmptyStack({
80
146
  stackPayload: { name: stackName, projectId, document: { resources: [] } },
81
147
  auth: { token, projectId },
82
148
  });
83
- if (!stackOk)
84
- this.error(stackErr || 'Failed to create Stack');
85
- stackId = stack.id;
86
- }
87
- else {
88
- stackId = stackIdChoice;
89
- }
90
- const fileName = `blueprint.${blueprintExtension}`;
91
- const filePath = join(cwd(), fileName);
92
- writeBlueprintToDisk({ blueprintFilePath: filePath });
93
- this.log(`Created new blueprint: ./${fileName}`);
94
- writeConfigFile({ blueprintFilePath: filePath, projectId, stackId });
95
- this.log(`Created new config file: ./${fileName}.config.json`);
96
- if (blueprintExtension === 'ts') {
97
- this.log('\nNote: TypeScript support requires "tsx" to be installed. Run: npm install -D tsx');
149
+ return stack.id;
98
150
  }
151
+ return pickedStackId;
152
+ }
153
+ async createEmptyStack({ stackPayload, auth, }) {
154
+ const response = await createStack({ stackPayload, auth });
155
+ if (!response.ok)
156
+ this.error(response.error || 'Failed to create Stack');
157
+ return response.stack;
99
158
  }
100
159
  }
@@ -1,4 +1,5 @@
1
1
  export * as display from './display/index.js';
2
2
  export * as findFunction from './find-function.js';
3
3
  export * as types from './types.js';
4
+ export * as validate from './validate/index.js';
4
5
  export * as vendor from './vendor/index.js';
@@ -1,4 +1,5 @@
1
1
  export * as display from './display/index.js';
2
2
  export * as findFunction from './find-function.js';
3
3
  export * as types from './types.js';
4
+ export * as validate from './validate/index.js';
4
5
  export * as vendor from './vendor/index.js';
@@ -0,0 +1 @@
1
+ export * as validate from './resource.js';
@@ -0,0 +1 @@
1
+ export * as validate from './resource.js';
@@ -0,0 +1,2 @@
1
+ export declare function validateFunctionName(name: string): boolean;
2
+ export declare function validateFunctionType(type: string): boolean;
@@ -0,0 +1,8 @@
1
+ export function validateFunctionName(name) {
2
+ // must be 6+ characters, no special characters, no spaces, allow _ and -
3
+ return /^[a-zA-Z0-9][a-zA-Z0-9_-]{5,}$/.test(name);
4
+ }
5
+ export function validateFunctionType(type) {
6
+ const functionType = type.replace('sanity.function.', '');
7
+ return ['document-publish', 'document-create', 'document-delete'].includes(functionType);
8
+ }
@@ -14,9 +14,33 @@
14
14
  },
15
15
  "description": "Add a resource to a Blueprint",
16
16
  "examples": [
17
- "<%= config.bin %> <%= command.id %> function"
17
+ "<%= config.bin %> <%= command.id %> function",
18
+ "<%= config.bin %> <%= command.id %> function --name my-function",
19
+ "<%= config.bin %> <%= command.id %> function --name my-function --function-type document-publish"
18
20
  ],
19
- "flags": {},
21
+ "flags": {
22
+ "name": {
23
+ "char": "n",
24
+ "description": "Name of the resource to add",
25
+ "name": "name",
26
+ "hasDynamicHelp": false,
27
+ "multiple": false,
28
+ "type": "option"
29
+ },
30
+ "function-type": {
31
+ "dependsOn": [
32
+ "name"
33
+ ],
34
+ "description": "Type of function to add (e.g. document-publish)",
35
+ "name": "function-type",
36
+ "hasDynamicHelp": false,
37
+ "multiple": false,
38
+ "options": [
39
+ "document-publish"
40
+ ],
41
+ "type": "option"
42
+ }
43
+ },
20
44
  "hasDynamicHelp": false,
21
45
  "hiddenAliases": [],
22
46
  "id": "blueprints:add",
@@ -39,14 +63,56 @@
39
63
  "description": "View or edit Blueprint configuration",
40
64
  "examples": [
41
65
  "<%= config.bin %> <%= command.id %>",
42
- "<%= config.bin %> <%= command.id %> --edit"
66
+ "<%= config.bin %> <%= command.id %> --test-config",
67
+ "<%= config.bin %> <%= command.id %> --edit",
68
+ "<%= config.bin %> <%= command.id %> --edit --project-id <projectId> --stack-id <stackId>"
43
69
  ],
44
70
  "flags": {
71
+ "test-config": {
72
+ "aliases": [
73
+ "test",
74
+ "validate"
75
+ ],
76
+ "char": "t",
77
+ "description": "Validate the configuration",
78
+ "name": "test-config",
79
+ "allowNo": false,
80
+ "type": "boolean"
81
+ },
45
82
  "edit": {
83
+ "char": "e",
46
84
  "description": "Edit the configuration",
47
85
  "name": "edit",
48
86
  "allowNo": false,
49
87
  "type": "boolean"
88
+ },
89
+ "project-id": {
90
+ "aliases": [
91
+ "project",
92
+ "projectId"
93
+ ],
94
+ "dependsOn": [
95
+ "edit"
96
+ ],
97
+ "description": "Update the Project ID in the configuration. Requires --edit flag",
98
+ "name": "project-id",
99
+ "hasDynamicHelp": false,
100
+ "multiple": false,
101
+ "type": "option"
102
+ },
103
+ "stack-id": {
104
+ "aliases": [
105
+ "stack",
106
+ "stackId"
107
+ ],
108
+ "dependsOn": [
109
+ "edit"
110
+ ],
111
+ "description": "Update the Stack ID in the configuration. Requires --edit flag",
112
+ "name": "stack-id",
113
+ "hasDynamicHelp": false,
114
+ "multiple": false,
115
+ "type": "option"
50
116
  }
51
117
  },
52
118
  "hasDynamicHelp": false,
@@ -175,9 +241,73 @@
175
241
  "args": {},
176
242
  "description": "Initialize a new Blueprint",
177
243
  "examples": [
178
- "<%= config.bin %> <%= command.id %>"
244
+ "<%= config.bin %> <%= command.id %>",
245
+ "<%= config.bin %> <%= command.id %> --blueprint-type <json|js|ts>",
246
+ "<%= config.bin %> <%= command.id %> --blueprint-type <json|js|ts> --project-id <projectId> --stack-id <stackId>",
247
+ "<%= config.bin %> <%= command.id %> --blueprint-type <json|js|ts> --project-id <projectId> --stack-name <stackName>"
179
248
  ],
180
- "flags": {},
249
+ "flags": {
250
+ "blueprint-type": {
251
+ "aliases": [
252
+ "type"
253
+ ],
254
+ "description": "Blueprint manifest type to use for the Blueprint",
255
+ "name": "blueprint-type",
256
+ "hasDynamicHelp": false,
257
+ "multiple": false,
258
+ "options": [
259
+ "json",
260
+ "js",
261
+ "ts"
262
+ ],
263
+ "type": "option"
264
+ },
265
+ "project-id": {
266
+ "aliases": [
267
+ "project",
268
+ "projectId"
269
+ ],
270
+ "description": "Sanity Project ID to use for the Blueprint",
271
+ "name": "project-id",
272
+ "hasDynamicHelp": false,
273
+ "multiple": false,
274
+ "type": "option"
275
+ },
276
+ "stack-id": {
277
+ "aliases": [
278
+ "stack",
279
+ "stackId"
280
+ ],
281
+ "dependsOn": [
282
+ "project-id"
283
+ ],
284
+ "description": "Existing Stack ID to use for the Blueprint",
285
+ "exclusive": [
286
+ "stack-name"
287
+ ],
288
+ "name": "stack-id",
289
+ "hasDynamicHelp": false,
290
+ "multiple": false,
291
+ "type": "option"
292
+ },
293
+ "stack-name": {
294
+ "aliases": [
295
+ "name"
296
+ ],
297
+ "char": "n",
298
+ "dependsOn": [
299
+ "project-id"
300
+ ],
301
+ "description": "Name to use for a NEW Stack",
302
+ "exclusive": [
303
+ "stack-id"
304
+ ],
305
+ "name": "stack-name",
306
+ "hasDynamicHelp": false,
307
+ "multiple": false,
308
+ "type": "option"
309
+ }
310
+ },
181
311
  "hasDynamicHelp": false,
182
312
  "hiddenAliases": [],
183
313
  "id": "blueprints:init",
@@ -608,5 +738,5 @@
608
738
  ]
609
739
  }
610
740
  },
611
- "version": "4.1.0"
741
+ "version": "4.2.0"
612
742
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sanity/runtime-cli",
3
3
  "description": "Sanity's Runtime CLI for Blueprints and Functions",
4
- "version": "4.1.0",
4
+ "version": "4.2.0",
5
5
  "author": "Sanity Runtime Team",
6
6
  "type": "module",
7
7
  "license": "MIT",