@sanity/runtime-cli 1.4.1 → 1.5.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 COMMAND
21
21
  running command...
22
22
  $ sanity (--version)
23
- @sanity/runtime-cli/1.4.1 linux-x64 node-v22.14.0
23
+ @sanity/runtime-cli/1.5.0 linux-x64 node-v22.14.0
24
24
  $ sanity --help [COMMAND]
25
25
  USAGE
26
26
  $ sanity COMMAND
@@ -33,6 +33,7 @@ USAGE
33
33
  * [`sanity blueprints info`](#sanity-blueprints-info)
34
34
  * [`sanity blueprints logs`](#sanity-blueprints-logs)
35
35
  * [`sanity blueprints plan`](#sanity-blueprints-plan)
36
+ * [`sanity blueprints stacks`](#sanity-blueprints-stacks)
36
37
  * [`sanity functions dev`](#sanity-functions-dev)
37
38
  * [`sanity functions invoke ID`](#sanity-functions-invoke-id)
38
39
  * [`sanity functions logs ID`](#sanity-functions-logs-id)
@@ -64,7 +65,7 @@ EXAMPLES
64
65
  $ sanity blueprints deploy
65
66
  ```
66
67
 
67
- _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v1.4.1/src/commands/blueprints/deploy.ts)_
68
+ _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v1.5.0/src/commands/blueprints/deploy.ts)_
68
69
 
69
70
  ## `sanity blueprints info`
70
71
 
@@ -72,16 +73,21 @@ Show information about a Blueprint
72
73
 
73
74
  ```
74
75
  USAGE
75
- $ sanity blueprints info
76
+ $ sanity blueprints info [--id <value>]
77
+
78
+ FLAGS
79
+ --id=<value> Stack ID to show info for (defaults to current stack)
76
80
 
77
81
  DESCRIPTION
78
82
  Show information about a Blueprint
79
83
 
80
84
  EXAMPLES
81
85
  $ sanity blueprints info
86
+
87
+ $ sanity blueprints info --id abc123
82
88
  ```
83
89
 
84
- _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v1.4.1/src/commands/blueprints/info.ts)_
90
+ _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v1.5.0/src/commands/blueprints/info.ts)_
85
91
 
86
92
  ## `sanity blueprints logs`
87
93
 
@@ -103,7 +109,7 @@ EXAMPLES
103
109
  $ sanity blueprints logs --watch
104
110
  ```
105
111
 
106
- _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v1.4.1/src/commands/blueprints/logs.ts)_
112
+ _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v1.5.0/src/commands/blueprints/logs.ts)_
107
113
 
108
114
  ## `sanity blueprints plan`
109
115
 
@@ -120,7 +126,24 @@ EXAMPLES
120
126
  $ sanity blueprints plan
121
127
  ```
122
128
 
123
- _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v1.4.1/src/commands/blueprints/plan.ts)_
129
+ _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v1.5.0/src/commands/blueprints/plan.ts)_
130
+
131
+ ## `sanity blueprints stacks`
132
+
133
+ List all Blueprint stacks
134
+
135
+ ```
136
+ USAGE
137
+ $ sanity blueprints stacks
138
+
139
+ DESCRIPTION
140
+ List all Blueprint stacks
141
+
142
+ EXAMPLES
143
+ $ sanity blueprints stacks
144
+ ```
145
+
146
+ _See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v1.5.0/src/commands/blueprints/stacks.ts)_
124
147
 
125
148
  ## `sanity functions dev`
126
149
 
@@ -140,7 +163,7 @@ EXAMPLES
140
163
  $ sanity functions dev --port 8974
141
164
  ```
142
165
 
143
- _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v1.4.1/src/commands/functions/dev.ts)_
166
+ _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v1.5.0/src/commands/functions/dev.ts)_
144
167
 
145
168
  ## `sanity functions invoke ID`
146
169
 
@@ -166,7 +189,7 @@ EXAMPLES
166
189
  $ sanity functions invoke <ID> --file 'payload.json'
167
190
  ```
168
191
 
169
- _See code: [src/commands/functions/invoke.ts](https://github.com/sanity-io/runtime-cli/blob/v1.4.1/src/commands/functions/invoke.ts)_
192
+ _See code: [src/commands/functions/invoke.ts](https://github.com/sanity-io/runtime-cli/blob/v1.5.0/src/commands/functions/invoke.ts)_
170
193
 
171
194
  ## `sanity functions logs ID`
172
195
 
@@ -186,7 +209,7 @@ EXAMPLES
186
209
  $ sanity functions logs <ID>
187
210
  ```
188
211
 
189
- _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v1.4.1/src/commands/functions/logs.ts)_
212
+ _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v1.5.0/src/commands/functions/logs.ts)_
190
213
 
191
214
  ## `sanity functions test PATH`
192
215
 
@@ -215,7 +238,7 @@ EXAMPLES
215
238
  $ sanity functions test ./test.ts --data '{ "id": 1 }' --timeout 60
216
239
  ```
217
240
 
218
- _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v1.4.1/src/commands/functions/test.ts)_
241
+ _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v1.5.0/src/commands/functions/test.ts)_
219
242
 
220
243
  ## `sanity help [COMMAND]`
221
244
 
@@ -0,0 +1,26 @@
1
+ import type { Blueprint, BlueprintError, BlueprintStack } from '../../utils/types.js';
2
+ export declare function readBlueprintOnDisk({ blueprintPath, getStack, }?: {
3
+ blueprintPath?: string;
4
+ getStack?: boolean;
5
+ }): Promise<{
6
+ fileInfo: {
7
+ path: string;
8
+ fileName: string;
9
+ extension: string;
10
+ };
11
+ parsedBlueprint: Blueprint;
12
+ errors: BlueprintError[];
13
+ projectId?: string;
14
+ stackId?: string;
15
+ deployedStack?: BlueprintStack;
16
+ }>;
17
+ export declare function updateBlueprintMetadata({ blueprintPath, projectId, stackId, }: {
18
+ blueprintPath?: string;
19
+ projectId: string;
20
+ stackId: string;
21
+ }): Promise<void>;
22
+ export declare function writeBlueprintConfig({ blueprintPath, projectId, stackId, }: {
23
+ blueprintPath?: string;
24
+ projectId: string;
25
+ stackId: string;
26
+ }): void;
@@ -0,0 +1,164 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, extname, join } from 'node:path';
3
+ import { cwd } from 'node:process';
4
+ import { BlueprintErrorType } from '../../utils/types.js';
5
+ // @ts-ignore - this is currently untyped
6
+ import blueprintParserValidator from '../../utils/vendor/parser-validator.js';
7
+ import { getStack as getStackById } from './stacks.js';
8
+ const SUPPORTED_FILE_NAMES_IN_PRIORITY_ORDER = [
9
+ 'blueprint.json',
10
+ // 'blueprint.js',
11
+ // 'blueprint.mjs',
12
+ // 'blueprint.cjs',
13
+ // 'blueprint.ts',
14
+ ];
15
+ function findBlueprintFile(blueprintPath) {
16
+ if (blueprintPath) {
17
+ if (existsSync(blueprintPath)) {
18
+ return { path: blueprintPath, fileName: blueprintPath, extension: extname(blueprintPath) };
19
+ }
20
+ throw Error(`Blueprint file not found: ${blueprintPath}`);
21
+ }
22
+ for (const fileName of SUPPORTED_FILE_NAMES_IN_PRIORITY_ORDER) {
23
+ const filePath = join(cwd(), fileName);
24
+ if (existsSync(filePath)) {
25
+ return { path: filePath, fileName, extension: extname(filePath) };
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+ function readConfigFile(blueprintPath) {
31
+ if (blueprintPath) {
32
+ const blueprintDir = dirname(blueprintPath);
33
+ const configPath = join(blueprintDir, '.blueprint', 'config.json');
34
+ if (existsSync(configPath)) {
35
+ try {
36
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
37
+ return config || null;
38
+ }
39
+ catch (err) {
40
+ return null;
41
+ }
42
+ }
43
+ }
44
+ const metadataPath = join(cwd(), '.blueprint', 'config.json');
45
+ if (!existsSync(metadataPath))
46
+ return null;
47
+ try {
48
+ const metadata = JSON.parse(readFileSync(metadataPath, 'utf8'));
49
+ return metadata || null;
50
+ }
51
+ catch (err) {
52
+ return null;
53
+ }
54
+ }
55
+ export async function readBlueprintOnDisk({ blueprintPath, getStack, } = {}) {
56
+ try {
57
+ const blueprintFile = findBlueprintFile(blueprintPath);
58
+ if (!blueprintFile)
59
+ throw Error('Could not find Blueprint file! Use the init command.');
60
+ const { path, fileName, extension } = blueprintFile;
61
+ let blueprintString;
62
+ if (extension === '.json') {
63
+ blueprintString = readFileSync(path, 'utf8').toString();
64
+ // } else if (extension === '.js' || extension === '.mjs') {
65
+ // const blueprintModule = require(path)
66
+ // blueprintJson = blueprintModule.default
67
+ }
68
+ else {
69
+ throw Error(`Unsupported blueprint file extension: ${extension}`);
70
+ }
71
+ const parsed = blueprintParserValidator(JSON.parse(blueprintString));
72
+ const { blueprint: parsedBlueprint } = parsed;
73
+ const errors = parsed.errors || [];
74
+ // find project and stack IDs from .blueprint/config.json first,
75
+ // then fallback to metadata in blueprint file,
76
+ // finally fallback to resources array...
77
+ let projectId;
78
+ let stackId;
79
+ const configIds = readConfigFile(blueprintPath);
80
+ const blueprintMetadata = extension === '.json' ? parsedBlueprint.metadata : null;
81
+ // find/create project resource
82
+ if (configIds?.projectId) {
83
+ projectId = configIds.projectId;
84
+ }
85
+ else if (blueprintMetadata?.projectId) {
86
+ projectId = blueprintMetadata.projectId;
87
+ }
88
+ else {
89
+ const projectResource = parsedBlueprint.resources.find((r) => r.type === 'sanity.project');
90
+ if (projectResource)
91
+ projectId = projectResource.id;
92
+ }
93
+ // find/create stack resource
94
+ if (configIds?.stackId) {
95
+ stackId = configIds.stackId;
96
+ }
97
+ else if (blueprintMetadata?.stackId) {
98
+ stackId = blueprintMetadata.stackId;
99
+ }
100
+ else {
101
+ const stackResource = parsedBlueprint.resources.find((r) => r.type === 'sanity.blueprints.stack');
102
+ if (stackResource)
103
+ stackId = stackResource.id;
104
+ }
105
+ let deployedStack;
106
+ if (getStack && projectId && stackId) {
107
+ const { stack } = await getStackById({ stackId, projectId });
108
+ if (!stack) {
109
+ errors.push({
110
+ message: 'Stack not found',
111
+ type: BlueprintErrorType.InvalidStack,
112
+ });
113
+ }
114
+ deployedStack = stack;
115
+ }
116
+ return {
117
+ fileInfo: { path, fileName, extension },
118
+ errors,
119
+ projectId,
120
+ stackId,
121
+ deployedStack,
122
+ parsedBlueprint,
123
+ };
124
+ }
125
+ catch (err) {
126
+ throw Error(`Unable to parse Blueprint file: ${err}`);
127
+ }
128
+ }
129
+ export async function updateBlueprintMetadata({ blueprintPath, projectId, stackId, }) {
130
+ const blueprintFile = findBlueprintFile(blueprintPath);
131
+ if (!blueprintFile)
132
+ throw Error('Could not find Blueprint file');
133
+ const { path } = blueprintFile;
134
+ const blueprintString = readFileSync(path, 'utf8').toString();
135
+ const blueprint = JSON.parse(blueprintString);
136
+ blueprint.metadata = blueprint.metadata || {};
137
+ blueprint.metadata.projectId = projectId;
138
+ blueprint.metadata.stackId = stackId;
139
+ writeFileSync(path, JSON.stringify(blueprint, null, 2));
140
+ }
141
+ export function writeBlueprintConfig({ blueprintPath, projectId, stackId, }) {
142
+ const blueprintFile = findBlueprintFile(blueprintPath);
143
+ if (!blueprintFile)
144
+ throw Error('Could not find Blueprint file');
145
+ const { path } = blueprintFile;
146
+ const blueprintDir = dirname(path);
147
+ const configDir = join(blueprintDir, '.blueprint');
148
+ const configPath = join(configDir, 'config.json');
149
+ if (!existsSync(configDir)) {
150
+ mkdirSync(configDir, { recursive: true });
151
+ }
152
+ let config = {};
153
+ if (existsSync(configPath)) {
154
+ try {
155
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
156
+ }
157
+ catch (err) {
158
+ // config broken, start fresh
159
+ }
160
+ }
161
+ config.projectId = projectId;
162
+ config.stackId = stackId;
163
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
164
+ }
@@ -3,7 +3,9 @@ import config from '../../config.js';
3
3
  const { blueprints } = config.server;
4
4
  export const logsUrl = `${blueprints}vX/blueprints/logs`;
5
5
  export async function getLogs(stackId, projectId, token) {
6
- const response = await fetch(`${logsUrl}?stackId=${stackId}`, {
6
+ const url = new URL(logsUrl);
7
+ url.searchParams.append('stackId', stackId);
8
+ const response = await fetch(url.toString(), {
7
9
  headers: {
8
10
  Accept: 'application/json',
9
11
  'Content-Type': 'application/json',
@@ -1,28 +1,54 @@
1
1
  import { Command } from '@oclif/core';
2
- import readBlueprintOnDisk from '../../actions/blueprints/read-blueprint.js';
2
+ import inquirer from 'inquirer';
3
+ import { readBlueprintOnDisk, writeBlueprintConfig } from '../../actions/blueprints/blueprint.js';
3
4
  import { createStack, updateStack } from '../../actions/blueprints/stacks.js';
4
5
  import { stashAsset } from '../../actions/blueprints/stash-asset.js';
5
- import { green, red, yellow } from '../../utils/display/colors.js';
6
+ import { bold, green, red, yellow } from '../../utils/display/colors.js';
6
7
  import Spinner from '../../utils/spinner.js';
7
8
  export default class Deploy extends Command {
8
9
  static description = 'Deploy a Blueprint';
9
10
  static examples = ['<%= config.bin %> <%= command.id %>'];
10
11
  async run() {
11
- const { errors, projectResource, stackResource, parsedBlueprint: { resources }, deployedStack, } = await readBlueprintOnDisk({ getStack: true });
12
+ const { errors, projectId: configuredProjectId, stackId, parsedBlueprint: { resources }, deployedStack, } = await readBlueprintOnDisk({ getStack: true });
12
13
  if (errors.length > 0) {
13
14
  // printErrors(errors) // TODO: error printer in formatting
14
15
  this.log('Blueprint parse errors:');
15
16
  console.dir(errors, { depth: null });
16
17
  return;
17
18
  }
18
- if (!projectResource)
19
- this.error('Blueprint must contain a project resource'); // returns
20
- if (!stackResource)
21
- this.error('Blueprint must contain a stack resource'); // returns
22
- const { id: projectId } = projectResource;
23
- const { name } = stackResource;
19
+ if (stackId && !deployedStack)
20
+ this.error('Stack ID defined but deployed stack not found');
21
+ let projectId = configuredProjectId;
22
+ if (!projectId) {
23
+ this.log('No Sanity Project context found in Blueprint configuration.');
24
+ const { maybeProjectId } = await inquirer.prompt([
25
+ {
26
+ type: 'input',
27
+ name: 'maybeProjectId',
28
+ message: 'Enter Sanity Project ID:',
29
+ },
30
+ ]);
31
+ projectId = maybeProjectId;
32
+ }
33
+ if (!projectId)
34
+ this.error('Sanity Project context is required');
35
+ let name = deployedStack?.name;
36
+ if (!name) {
37
+ const { stackName } = await inquirer.prompt([
38
+ {
39
+ type: 'input',
40
+ name: 'stackName',
41
+ message: 'Enter stack name:',
42
+ validate: (input) => input.length > 0 || 'Stack name is required',
43
+ },
44
+ ]);
45
+ name = stackName;
46
+ }
47
+ if (!name)
48
+ this.error('Stack name is required');
24
49
  const s = new Spinner();
25
- const functionResources = resources.filter((r) => r.type === 'function');
50
+ const validResources = resources.filter((r) => r.kind);
51
+ const functionResources = validResources.filter((r) => r.type?.startsWith('sanity.function.'));
26
52
  // First stash all function assets
27
53
  if (functionResources.length > 0) {
28
54
  for (const resource of functionResources) {
@@ -45,7 +71,7 @@ export default class Deploy extends Command {
45
71
  const blueprint = {
46
72
  name,
47
73
  projectId,
48
- document: { resources },
74
+ document: { resources: validResources },
49
75
  };
50
76
  this.debug('BLUEPRINT DOCUMENT:', blueprint);
51
77
  const { ok: deployOk, stack, error: deployError, } = deployedStack
@@ -53,13 +79,17 @@ export default class Deploy extends Command {
53
79
  : await createStack({ blueprint, projectId });
54
80
  this.debug('STACK RESPONSE:', stack);
55
81
  if (deployOk) {
56
- s.stop(`${green('Success!')} Stack ${deployedStack ? 'updated' : 'created'} <${yellow(stack.id)}>`);
82
+ s.stop(`${green('Success!')} Stack "${bold(stack.name)}" ${deployedStack ? 'updated' : 'created'} <${yellow(stack.id)}>`);
83
+ writeBlueprintConfig({
84
+ projectId,
85
+ stackId: stack.id,
86
+ });
57
87
  this.log('\nUse `sanity blueprints info` to check deployment status');
58
88
  }
59
89
  else {
60
90
  this.debug('STACK ERROR RESPONSE:', stack);
61
91
  s.stop(`${red('Failed')} to ${deployedStack ? 'update' : 'create'} stack`);
62
- this.log(`Error: ${deployError || 'Unknown error'}`);
92
+ this.log(`Error: ${deployError || JSON.stringify(stack, null, 2) || 'Unknown error'}`);
63
93
  }
64
94
  }
65
95
  }
@@ -2,5 +2,8 @@ import { Command } from '@oclif/core';
2
2
  export default class Info extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
+ static flags: {
6
+ id: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ };
5
8
  run(): Promise<void>;
6
9
  }
@@ -1,32 +1,55 @@
1
- import { Command } from '@oclif/core';
2
- import readBlueprintOnDisk from '../../actions/blueprints/read-blueprint.js';
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { readBlueprintOnDisk } from '../../actions/blueprints/blueprint.js';
3
+ import { getStack } from '../../actions/blueprints/stacks.js';
3
4
  import { formatResourceTree } from '../../utils/display/blueprints-formatting.js';
4
5
  import { bold, green, red, yellow } from '../../utils/display/colors.js';
5
6
  import { formatDate, formatDuration } from '../../utils/display/dates.js';
6
7
  export default class Info extends Command {
7
8
  static description = 'Show information about a Blueprint';
8
- static examples = ['<%= config.bin %> <%= command.id %>'];
9
+ static examples = [
10
+ '<%= config.bin %> <%= command.id %>',
11
+ '<%= config.bin %> <%= command.id %> --id abc123',
12
+ ];
13
+ static flags = {
14
+ id: Flags.string({
15
+ description: 'Stack ID to show info for (defaults to current stack)',
16
+ required: false,
17
+ }),
18
+ };
9
19
  async run() {
10
- const { errors, deployedStack } = await readBlueprintOnDisk({ getStack: true });
20
+ const { flags } = await this.parse(Info);
21
+ const { errors, deployedStack, projectId } = await readBlueprintOnDisk({ getStack: true });
11
22
  if (errors.length > 0) {
12
23
  // printErrors(errors)
13
24
  this.log('Blueprint parse errors:');
14
25
  console.dir(errors, { depth: null });
15
26
  return;
16
27
  }
17
- if (!deployedStack)
18
- this.error('Stack not found'); // returns
28
+ if (!projectId)
29
+ this.error('Project resource not found in blueprint');
30
+ let stack = deployedStack;
31
+ if (flags.id) {
32
+ const { ok, stack: foundStack, error } = await getStack({ stackId: flags.id, projectId });
33
+ if (!ok)
34
+ this.error(error || 'Failed to get stack');
35
+ stack = foundStack;
36
+ }
37
+ else if (!stack) {
38
+ this.error('No stack found');
39
+ }
40
+ if (!stack)
41
+ this.error('Stack not found. Is it deployed?');
19
42
  try {
20
- this.log(`Stack name: ${bold(deployedStack.name)}`);
21
- this.log(`Stack ID: ${yellow(deployedStack.id)}`);
22
- if (deployedStack.createdAt) {
23
- this.log(`Created: ${formatDate(deployedStack.createdAt)}`);
43
+ this.log(`Stack name: ${bold(stack.name)}`);
44
+ this.log(`Stack ID: ${yellow(stack.id)}`);
45
+ if (stack.createdAt) {
46
+ this.log(`Created: ${formatDate(stack.createdAt)}`);
24
47
  }
25
- if (deployedStack.updatedAt) {
26
- this.log(`Updated: ${formatDate(deployedStack.updatedAt)}`);
48
+ if (stack.updatedAt) {
49
+ this.log(`Updated: ${formatDate(stack.updatedAt)}`);
27
50
  }
28
- if (deployedStack.recentOperation) {
29
- const operation = deployedStack.recentOperation;
51
+ if (stack.recentOperation) {
52
+ const operation = stack.recentOperation;
30
53
  if (operation.id) {
31
54
  this.log(`Recent Operation <${yellow(operation.id)}>:`);
32
55
  }
@@ -44,8 +67,8 @@ export default class Info extends Command {
44
67
  }
45
68
  }
46
69
  this.log('');
47
- if (deployedStack.resources) {
48
- formatResourceTree(deployedStack.resources, this.log.bind(this));
70
+ if (stack.resources) {
71
+ formatResourceTree(stack.resources, this.log.bind(this));
49
72
  }
50
73
  }
51
74
  catch (err) {
@@ -1,6 +1,6 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
+ import { readBlueprintOnDisk } from '../../actions/blueprints/blueprint.js';
2
3
  import { getLogs, streamLogs } from '../../actions/blueprints/logs.js';
3
- import readBlueprintOnDisk from '../../actions/blueprints/read-blueprint.js';
4
4
  import config from '../../config.js';
5
5
  import { formatTitle } from '../../utils/display/blueprints-formatting.js';
6
6
  import { blue, bold, green, red, yellow } from '../../utils/display/colors.js';
@@ -1,24 +1,22 @@
1
1
  import { Command } from '@oclif/core';
2
- import readBlueprintOnDisk from '../../actions/blueprints/read-blueprint.js';
2
+ import { readBlueprintOnDisk } from '../../actions/blueprints/blueprint.js';
3
3
  import { formatResourceTree, formatTitle } from '../../utils/display/blueprints-formatting.js';
4
4
  export default class Plan extends Command {
5
5
  static description = 'Enumerate resources to be deployed - will not modify any resources';
6
6
  static examples = ['<%= config.bin %> <%= command.id %>'];
7
7
  async run() {
8
- const { errors, projectResource, stackResource, parsedBlueprint: { resources }, fileInfo, } = await readBlueprintOnDisk();
8
+ const { errors, projectId, stackId, parsedBlueprint: { resources }, fileInfo, } = await readBlueprintOnDisk();
9
9
  if (errors.length > 0) {
10
10
  // printErrors(errors)
11
11
  this.log('Blueprint parse errors:');
12
12
  console.dir(errors, { depth: null });
13
- return;
13
+ // return // don't return, show the plan
14
14
  }
15
- if (!projectResource)
15
+ if (!projectId)
16
16
  this.log('Blueprint must contain a project resource');
17
- if (!stackResource)
18
- this.log('Blueprint must contain a stack resource');
19
- const name = stackResource?.name || 'Unknown';
17
+ const name = stackId || 'Unknown'; // TODO: what is a name, really?
20
18
  this.log(`${formatTitle('Blueprint', name)} Plan\n`);
21
- this.log(`Blueprint document: "${name}" (${fileInfo.fileName})`);
19
+ this.log(`Blueprint document: (${fileInfo.fileName})`);
22
20
  this.log('');
23
21
  formatResourceTree(resources, this.log.bind(this));
24
22
  if (resources.length > 0) {
@@ -0,0 +1,6 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Stacks extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ }
@@ -0,0 +1,40 @@
1
+ import { Command } from '@oclif/core';
2
+ import { readBlueprintOnDisk } from '../../actions/blueprints/blueprint.js';
3
+ import { listStacks } from '../../actions/blueprints/stacks.js';
4
+ import { bold, boldnblue, yellow } from '../../utils/display/colors.js';
5
+ import { formatDate } from '../../utils/display/dates.js';
6
+ export default class Stacks extends Command {
7
+ static description = 'List all Blueprint stacks';
8
+ static examples = ['<%= config.bin %> <%= command.id %>'];
9
+ async run() {
10
+ const { errors, projectId, stackId } = await readBlueprintOnDisk();
11
+ if (errors.length > 0) {
12
+ this.log('Blueprint parse errors:');
13
+ console.dir(errors, { depth: null });
14
+ return;
15
+ }
16
+ if (!projectId)
17
+ this.error('Project resource not found in blueprint');
18
+ const { ok, stacks, error } = await listStacks({ projectId });
19
+ if (!ok)
20
+ this.error(error || 'Failed to list stacks');
21
+ if (!stacks || stacks.length === 0) {
22
+ this.log('No stacks found');
23
+ return;
24
+ }
25
+ this.log(`${bold('Project')} <${yellow(projectId)}> ${bold('Stacks')} :\n`);
26
+ for (const stack of stacks) {
27
+ const isCurrentStack = stackId === stack.id;
28
+ const stackName = isCurrentStack ? boldnblue(stack.name) : bold(stack.name);
29
+ this.log(`${stackName} <${yellow(stack.id)}>${isCurrentStack ? ' (current)' : ''}`);
30
+ if (stack.createdAt) {
31
+ this.log(` Created: ${formatDate(stack.createdAt)}`);
32
+ }
33
+ if (stack.updatedAt) {
34
+ this.log(` Updated: ${formatDate(stack.updatedAt)}`);
35
+ }
36
+ this.log(` ${stack.resources.length} resource${stack.resources.length === 1 ? '' : 's'}`);
37
+ this.log('');
38
+ }
39
+ }
40
+ }
@@ -1,5 +1,5 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
- import readBlueprintOnDisk from '../../actions/blueprints/read-blueprint.js';
2
+ import { readBlueprintOnDisk } from '../../actions/blueprints/blueprint.js';
3
3
  import { invoke } from '../../actions/functions/invoke.js';
4
4
  import config from '../../config.js';
5
5
  import { red } from '../../utils/display/colors.js';
@@ -1,5 +1,5 @@
1
1
  import { Args, Command } from '@oclif/core';
2
- import readBlueprintOnDisk from '../../actions/blueprints/read-blueprint.js';
2
+ import { readBlueprintOnDisk } from '../../actions/blueprints/blueprint.js';
3
3
  import { logs } from '../../actions/functions/logs.js';
4
4
  import config from '../../config.js';
5
5
  import { bold, red, yellow } from '../../utils/display/colors.js';
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { default as mime } from 'mime-types';
3
- import readBlueprintOnDisk from '../actions/blueprints/read-blueprint.js';
3
+ import { readBlueprintOnDisk } from '../actions/blueprints/blueprint.js';
4
4
  import invoke from '../utils/invoke-local.js';
5
5
  import * as http from 'node:http';
6
6
  const host = 'localhost';
@@ -36,7 +36,7 @@ export function formatResourceTree(resources, logger) {
36
36
  logger(` └─ ${bold('Other Resources')} [${otherResources.length}]`);
37
37
  for (const [i, other] of otherResources.entries()) {
38
38
  const isLast = i === otherResources.length - 1;
39
- logger(` ${isLast ? '└─' : '├─'} "${yellow(other.displayName || other.name || other.src)}"`);
39
+ logger(` ${isLast ? '└─' : '├─'} "${yellow(other.displayName || other.name || other.src)}"`);
40
40
  }
41
41
  }
42
42
  }
@@ -75,6 +75,8 @@ export interface BlueprintError {
75
75
  export interface BlueprintLog {
76
76
  timestamp: string;
77
77
  message: string;
78
+ stackId: string;
79
+ level: string;
78
80
  }
79
81
  /** @internal */
80
82
  export interface AuthParams {
@@ -29,9 +29,19 @@
29
29
  "args": {},
30
30
  "description": "Show information about a Blueprint",
31
31
  "examples": [
32
- "<%= config.bin %> <%= command.id %>"
32
+ "<%= config.bin %> <%= command.id %>",
33
+ "<%= config.bin %> <%= command.id %> --id abc123"
33
34
  ],
34
- "flags": {},
35
+ "flags": {
36
+ "id": {
37
+ "description": "Stack ID to show info for (defaults to current stack)",
38
+ "name": "id",
39
+ "required": false,
40
+ "hasDynamicHelp": false,
41
+ "multiple": false,
42
+ "type": "option"
43
+ }
44
+ },
35
45
  "hasDynamicHelp": false,
36
46
  "hiddenAliases": [],
37
47
  "id": "blueprints:info",
@@ -106,6 +116,30 @@
106
116
  "plan.js"
107
117
  ]
108
118
  },
119
+ "blueprints:stacks": {
120
+ "aliases": [],
121
+ "args": {},
122
+ "description": "List all Blueprint stacks",
123
+ "examples": [
124
+ "<%= config.bin %> <%= command.id %>"
125
+ ],
126
+ "flags": {},
127
+ "hasDynamicHelp": false,
128
+ "hiddenAliases": [],
129
+ "id": "blueprints:stacks",
130
+ "pluginAlias": "@sanity/runtime-cli",
131
+ "pluginName": "@sanity/runtime-cli",
132
+ "pluginType": "core",
133
+ "strict": true,
134
+ "enableJsonFlag": false,
135
+ "isESM": true,
136
+ "relativePath": [
137
+ "dist",
138
+ "commands",
139
+ "blueprints",
140
+ "stacks.js"
141
+ ]
142
+ },
109
143
  "functions:dev": {
110
144
  "aliases": [],
111
145
  "args": {},
@@ -281,5 +315,5 @@
281
315
  ]
282
316
  }
283
317
  },
284
- "version": "1.4.1"
318
+ "version": "1.5.0"
285
319
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sanity/runtime-cli",
3
3
  "description": "A new CLI generated with oclif",
4
- "version": "1.4.1",
4
+ "version": "1.5.0",
5
5
  "author": "Sanity Runtime Team",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -41,6 +41,7 @@
41
41
  "@oclif/plugin-help": "^6",
42
42
  "@oclif/plugin-plugins": "^5",
43
43
  "eventsource": "^3.0.5",
44
+ "inquirer": "^12.5.0",
44
45
  "jszip": "^3.10.1",
45
46
  "mime-types": "^2.1.35",
46
47
  "xdg-basedir": "^5.1.0"
@@ -1,16 +0,0 @@
1
- import type { Blueprint, BlueprintError, BlueprintResource, BlueprintStack } from '../../utils/types.js';
2
- export default function readBlueprintOnDisk({ blueprintPath, getStack, }?: {
3
- blueprintPath?: string;
4
- getStack?: boolean;
5
- }): Promise<{
6
- fileInfo: {
7
- path: string;
8
- fileName: string;
9
- extension: string;
10
- };
11
- parsedBlueprint: Blueprint;
12
- errors: BlueprintError[];
13
- projectResource?: BlueprintResource;
14
- stackResource?: BlueprintResource;
15
- deployedStack?: BlueprintStack;
16
- }>;
@@ -1,89 +0,0 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { extname, join } from 'node:path';
3
- import { cwd } from 'node:process';
4
- import { getStackByName } from '../../actions/blueprints/stacks.js';
5
- import { BlueprintErrorType } from '../../utils/types.js';
6
- // @ts-ignore - this is currently untyped
7
- import blueprintParserValidator from '../../utils/vendor/parser-validator.js';
8
- const SUPPORTED_FILE_NAMES_IN_PRIORITY_ORDER = [
9
- 'blueprint.json',
10
- // 'blueprint.js',
11
- // 'blueprint.mjs',
12
- // 'blueprint.cjs',
13
- // 'blueprint.ts',
14
- ];
15
- function findBlueprintFile(blueprintPath) {
16
- if (blueprintPath) {
17
- if (existsSync(blueprintPath)) {
18
- return { path: blueprintPath, fileName: blueprintPath, extension: extname(blueprintPath) };
19
- }
20
- throw Error(`Blueprint file not found: ${blueprintPath}`);
21
- }
22
- for (const fileName of SUPPORTED_FILE_NAMES_IN_PRIORITY_ORDER) {
23
- const filePath = join(cwd(), fileName);
24
- if (existsSync(filePath)) {
25
- return { path: filePath, fileName, extension: extname(filePath) };
26
- }
27
- }
28
- return null;
29
- }
30
- export default async function readBlueprintOnDisk({ blueprintPath, getStack, } = {}) {
31
- try {
32
- const blueprintFile = findBlueprintFile(blueprintPath);
33
- if (!blueprintFile)
34
- throw Error('Could not find Blueprint file');
35
- const { path, fileName, extension } = blueprintFile;
36
- let blueprintString;
37
- if (extension === '.json') {
38
- blueprintString = readFileSync(path, 'utf8').toString();
39
- // } else if (extension === '.js' || extension === '.mjs') {
40
- // const blueprintModule = require(path)
41
- // blueprintJson = blueprintModule.default
42
- }
43
- else {
44
- throw Error(`Unsupported blueprint file extension: ${extension}`);
45
- }
46
- const parsed = blueprintParserValidator(JSON.parse(blueprintString));
47
- const { blueprint: parsedBlueprint } = parsed;
48
- const errors = parsed.errors || [];
49
- const projectResource = parsedBlueprint.resources.find((r) => r.type === 'sanity.project');
50
- const stackResource = parsedBlueprint.resources.find((r) => r.type === 'sanity.blueprints.stack');
51
- if (!projectResource) {
52
- errors.push({
53
- message: 'Blueprint is missing a project resource',
54
- type: BlueprintErrorType.MissingProject,
55
- });
56
- }
57
- if (!stackResource) {
58
- errors.push({
59
- message: 'Blueprint is missing a stack resource',
60
- type: BlueprintErrorType.MissingStack,
61
- });
62
- }
63
- let deployedStack;
64
- if (getStack && projectResource && stackResource) {
65
- const { stack } = await getStackByName({
66
- name: stackResource.name,
67
- projectId: projectResource.id,
68
- });
69
- if (!stack) {
70
- errors.push({
71
- message: 'Stack not found',
72
- type: BlueprintErrorType.InvalidStack,
73
- });
74
- }
75
- deployedStack = stack;
76
- }
77
- return {
78
- fileInfo: { path, fileName, extension },
79
- errors,
80
- projectResource,
81
- stackResource,
82
- deployedStack,
83
- parsedBlueprint,
84
- };
85
- }
86
- catch (err) {
87
- throw Error(`Unable to parse Blueprint file: ${err}`);
88
- }
89
- }