@sanity/runtime-cli 1.6.0 → 1.8.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 (57) hide show
  1. package/README.md +184 -122
  2. package/dist/actions/blueprints/blueprint.d.ts +24 -7
  3. package/dist/actions/blueprints/blueprint.js +65 -44
  4. package/dist/actions/blueprints/logs.d.ts +3 -0
  5. package/dist/actions/blueprints/logs.js +24 -0
  6. package/dist/actions/blueprints/projects.d.ts +5 -0
  7. package/dist/actions/blueprints/projects.js +21 -0
  8. package/dist/actions/blueprints/resources.d.ts +13 -0
  9. package/dist/actions/blueprints/resources.js +37 -0
  10. package/dist/actions/blueprints/stacks.js +1 -1
  11. package/dist/commands/blueprints/add.d.ts +10 -0
  12. package/dist/commands/blueprints/add.js +67 -0
  13. package/dist/commands/blueprints/config.d.ts +9 -0
  14. package/dist/commands/blueprints/config.js +72 -0
  15. package/dist/commands/blueprints/deploy.js +13 -12
  16. package/dist/commands/blueprints/info.js +1 -1
  17. package/dist/commands/blueprints/init.d.ts +6 -0
  18. package/dist/commands/blueprints/init.js +56 -0
  19. package/dist/commands/blueprints/logs.js +24 -64
  20. package/dist/commands/blueprints/stacks.js +3 -15
  21. package/dist/commands/functions/invoke.d.ts +1 -1
  22. package/dist/commands/functions/invoke.js +9 -7
  23. package/dist/commands/functions/logs.d.ts +1 -1
  24. package/dist/commands/functions/logs.js +21 -12
  25. package/dist/commands/functions/test.d.ts +1 -1
  26. package/dist/commands/functions/test.js +25 -16
  27. package/dist/config.d.ts +1 -0
  28. package/dist/config.js +12 -5
  29. package/dist/server/app.js +2 -2
  30. package/dist/server/static/api.js +43 -38
  31. package/dist/server/static/components/api-base.js +7 -6
  32. package/dist/server/static/components/function-list.js +48 -44
  33. package/dist/server/static/components/network-spinner.js +7 -6
  34. package/dist/server/static/components/payload-panel.js +36 -32
  35. package/dist/server/static/components/response-panel.js +64 -50
  36. package/dist/server/static/vendor/vendor.bundle.js +1029 -913
  37. package/dist/utils/display/blueprints-formatting.d.ts +3 -1
  38. package/dist/utils/display/blueprints-formatting.js +27 -2
  39. package/dist/utils/display/logs-formatting.d.ts +5 -0
  40. package/dist/utils/display/logs-formatting.js +50 -0
  41. package/dist/utils/find-function.d.ts +2 -0
  42. package/dist/utils/find-function.js +6 -0
  43. package/dist/utils/types.d.ts +0 -1
  44. package/oclif.manifest.json +102 -13
  45. package/package.json +10 -10
  46. package/dist/server/static/static/api.js +0 -53
  47. package/dist/server/static/static/components/api-base.js +0 -10
  48. package/dist/server/static/static/components/function-list.js +0 -54
  49. package/dist/server/static/static/components/network-spinner.js +0 -71
  50. package/dist/server/static/static/components/payload-panel.js +0 -45
  51. package/dist/server/static/static/components/response-panel.js +0 -83
  52. package/dist/server/static/static/vendor/vendor.bundle.js +0 -26879
  53. package/dist/utils/spinner.d.ts +0 -9
  54. package/dist/utils/spinner.js +0 -25
  55. /package/dist/server/static/{static/components → components}/app.css +0 -0
  56. /package/dist/server/static/{static/index.html → index.html} +0 -0
  57. /package/dist/server/static/{static/sanity-logo-sm.svg → sanity-logo-sm.svg} +0 -0
@@ -13,7 +13,11 @@ const SUPPORTED_FILE_NAMES_IN_PRIORITY_ORDER = [
13
13
  'blueprint.cjs',
14
14
  'blueprint.ts',
15
15
  ];
16
- function findBlueprintFile(blueprintPath) {
16
+ export const DEFAULT_BLUEPRINT_CONTENT = {
17
+ blueprintVersion: '2024-10-01',
18
+ resources: [],
19
+ };
20
+ export function findBlueprintFile(blueprintPath) {
17
21
  if (blueprintPath) {
18
22
  if (existsSync(blueprintPath)) {
19
23
  return { path: blueprintPath, fileName: blueprintPath, extension: extname(blueprintPath) };
@@ -28,31 +32,6 @@ function findBlueprintFile(blueprintPath) {
28
32
  }
29
33
  return null;
30
34
  }
31
- function readConfigFile(blueprintPath) {
32
- if (blueprintPath) {
33
- const blueprintDir = dirname(blueprintPath);
34
- const configPath = join(blueprintDir, '.blueprint', 'config.json');
35
- if (existsSync(configPath)) {
36
- try {
37
- const config = JSON.parse(readFileSync(configPath, 'utf8'));
38
- return config || null;
39
- }
40
- catch (err) {
41
- return null;
42
- }
43
- }
44
- }
45
- const configFilePath = join(cwd(), '.blueprint', 'config.json');
46
- if (!existsSync(configFilePath))
47
- return null;
48
- try {
49
- const config = JSON.parse(readFileSync(configFilePath, 'utf8'));
50
- return config || null;
51
- }
52
- catch (err) {
53
- return null;
54
- }
55
- }
56
35
  export async function readBlueprintOnDisk({ blueprintPath, getStack, } = {}) {
57
36
  try {
58
37
  const blueprintFile = findBlueprintFile(blueprintPath);
@@ -160,25 +139,51 @@ export async function readBlueprintOnDisk({ blueprintPath, getStack, } = {}) {
160
139
  throw Error(`Unable to parse Blueprint file: ${err}`);
161
140
  }
162
141
  }
163
- /** @experimental */ // no, that's not a real JSDoc pragma
164
- export async function updateBlueprintMetadata({ blueprintPath, projectId, stackId, }) {
165
- const blueprintFile = findBlueprintFile(blueprintPath);
166
- if (!blueprintFile)
167
- throw Error('Could not find Blueprint file');
168
- const { path } = blueprintFile;
169
- const blueprintString = readFileSync(path, 'utf8').toString();
170
- const blueprint = JSON.parse(blueprintString);
171
- blueprint.metadata = blueprint.metadata || {};
172
- blueprint.metadata.projectId = projectId;
173
- blueprint.metadata.stackId = stackId;
174
- writeFileSync(path, JSON.stringify(blueprint, null, 2));
142
+ export function writeBlueprintToDisk({ path, fileType, content = DEFAULT_BLUEPRINT_CONTENT, }) {
143
+ let blueprintContent;
144
+ switch (fileType) {
145
+ case 'json': {
146
+ blueprintContent = JSON.stringify(content, null, 2);
147
+ break;
148
+ }
149
+ case 'js':
150
+ case 'ts': {
151
+ blueprintContent = `export default function() {
152
+ return ${JSON.stringify(content)}
153
+ }`;
154
+ break;
155
+ }
156
+ }
157
+ writeFileSync(path, blueprintContent);
158
+ return blueprintContent;
175
159
  }
176
- export function writeBlueprintConfig({ blueprintPath, projectId, stackId, }) {
177
- const blueprintFile = findBlueprintFile(blueprintPath);
178
- if (!blueprintFile)
179
- throw Error('Could not find Blueprint file');
180
- const { path } = blueprintFile;
181
- const blueprintDir = dirname(path);
160
+ export function readConfigFile(blueprintPath) {
161
+ if (blueprintPath) {
162
+ const blueprintDir = dirname(blueprintPath);
163
+ const configPath = join(blueprintDir, '.blueprint', 'config.json');
164
+ if (existsSync(configPath)) {
165
+ try {
166
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
167
+ return config || null;
168
+ }
169
+ catch (err) {
170
+ return null;
171
+ }
172
+ }
173
+ }
174
+ const configFilePath = join(cwd(), '.blueprint', 'config.json');
175
+ if (!existsSync(configFilePath))
176
+ return null;
177
+ try {
178
+ const config = JSON.parse(readFileSync(configFilePath, 'utf8'));
179
+ return config || null;
180
+ }
181
+ catch (err) {
182
+ return null;
183
+ }
184
+ }
185
+ export function writeConfigFile({ blueprintPath, projectId, stackId, }) {
186
+ const blueprintDir = blueprintPath ? dirname(blueprintPath) : cwd();
182
187
  const configDir = join(blueprintDir, '.blueprint');
183
188
  const configPath = join(configDir, 'config.json');
184
189
  if (!existsSync(configDir)) {
@@ -197,3 +202,19 @@ export function writeBlueprintConfig({ blueprintPath, projectId, stackId, }) {
197
202
  config.stackId = stackId;
198
203
  writeFileSync(configPath, JSON.stringify(config, null, 2));
199
204
  }
205
+ export function addResourceToBlueprint({ blueprintPath, resource, }) {
206
+ const blueprintFile = findBlueprintFile(blueprintPath);
207
+ if (!blueprintFile)
208
+ throw Error('Could not find Blueprint file');
209
+ const { path, extension } = blueprintFile;
210
+ // modify .json files directly
211
+ if (extension === '.json') {
212
+ const blueprintString = readFileSync(path, 'utf8').toString();
213
+ const blueprint = JSON.parse(blueprintString);
214
+ blueprint.resources = blueprint.resources || [];
215
+ blueprint.resources.push(resource);
216
+ writeFileSync(path, JSON.stringify(blueprint, null, 2));
217
+ return;
218
+ }
219
+ return resource;
220
+ }
@@ -5,4 +5,7 @@ export declare function getLogs(stackId: string, projectId: string, token: strin
5
5
  ok: boolean;
6
6
  error: string | null;
7
7
  }>;
8
+ export declare function findNewestLogTimestamp(logs: BlueprintLog[]): number;
9
+ export declare function isNewerLog(log: BlueprintLog, timestamp: number): boolean;
10
+ export declare function getRecentLogs(logs: BlueprintLog[], limit?: number): BlueprintLog[];
8
11
  export declare function streamLogs(stackId: string, projectId: string, token: string, onLog: (log: BlueprintLog) => void, onOpen: () => void, onError: (error: string) => void): () => void;
@@ -22,6 +22,30 @@ export async function getLogs(stackId, projectId, token) {
22
22
  logs: response.ok ? result : [],
23
23
  };
24
24
  }
25
+ // Process logs to find the newest timestamp
26
+ export function findNewestLogTimestamp(logs) {
27
+ let newestTimestamp = 0;
28
+ if (logs.length > 0) {
29
+ for (const log of logs) {
30
+ const timestamp = new Date(log.timestamp).getTime();
31
+ if (timestamp > newestTimestamp) {
32
+ newestTimestamp = timestamp;
33
+ }
34
+ }
35
+ }
36
+ return newestTimestamp;
37
+ }
38
+ // Check if a log is newer than a given timestamp
39
+ export function isNewerLog(log, timestamp) {
40
+ const logTimestamp = new Date(log.timestamp).getTime();
41
+ return logTimestamp > timestamp;
42
+ }
43
+ // Get most recent logs, up to a limit
44
+ export function getRecentLogs(logs, limit = 10) {
45
+ const sortedLogs = [...logs].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
46
+ return sortedLogs.slice(-limit);
47
+ }
48
+ // Create streaming logs connection
25
49
  export function streamLogs(stackId, projectId, token, onLog, onOpen, onError) {
26
50
  const url = new URL(`${logsUrl}/stream`);
27
51
  url.searchParams.append('stackId', stackId);
@@ -0,0 +1,5 @@
1
+ export declare function listProjects(): Promise<{
2
+ ok: boolean;
3
+ error: any;
4
+ projects: any;
5
+ }>;
@@ -0,0 +1,21 @@
1
+ import config from '../../config.js';
2
+ const { apiUrl, token } = config;
3
+ function getHeaders() {
4
+ return {
5
+ Authorization: `Bearer ${token}`,
6
+ 'Content-Type': 'application/json',
7
+ };
8
+ }
9
+ const projectsUrl = `${apiUrl.toString()}v2021-06-07/projects`;
10
+ export async function listProjects() {
11
+ const response = await fetch(projectsUrl, {
12
+ method: 'GET',
13
+ headers: getHeaders(),
14
+ });
15
+ const projects = await response.json();
16
+ return {
17
+ ok: response.ok,
18
+ error: response.ok ? null : projects.error?.message,
19
+ projects,
20
+ };
21
+ }
@@ -0,0 +1,13 @@
1
+ export interface FunctionResourceOptions {
2
+ name: string;
3
+ type: string;
4
+ displayName?: string;
5
+ }
6
+ /**
7
+ * Creates a new function resource file and adds it to the blueprint
8
+ */
9
+ export declare function createFunctionResource(options: FunctionResourceOptions): {
10
+ filePath: string;
11
+ resourceAdded: boolean;
12
+ resource: Record<string, unknown>;
13
+ };
@@ -0,0 +1,37 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { cwd } from 'node:process';
5
+ import { addResourceToBlueprint } from './blueprint.js';
6
+ /**
7
+ * Creates a new function resource file and adds it to the blueprint
8
+ */
9
+ export function createFunctionResource(options) {
10
+ const { name, type, displayName = name } = options;
11
+ // Ensure functions directory exists
12
+ const functionsDir = join(cwd(), 'functions');
13
+ if (!existsSync(functionsDir)) {
14
+ mkdirSync(functionsDir, { recursive: true });
15
+ }
16
+ // Create function file with default template
17
+ const functionPath = join(functionsDir, `${name}.js`);
18
+ const functionContent = `export async function handler (event) {
19
+ console.log(event)
20
+ return {event}
21
+ }`;
22
+ writeFileSync(functionPath, functionContent);
23
+ // Create resource definition
24
+ const resourceJson = {
25
+ displayName,
26
+ name,
27
+ type: `sanity.function.${type}`,
28
+ src: `functions/${name}.js`,
29
+ };
30
+ // Add to blueprint or return for manual addition
31
+ const resource = addResourceToBlueprint({ resource: resourceJson });
32
+ return {
33
+ filePath: functionPath,
34
+ resourceAdded: !resource, // If resource is null, it was added to blueprint
35
+ resource: resource || resourceJson,
36
+ };
37
+ }
@@ -17,7 +17,7 @@ export async function listStacks({ projectId }) {
17
17
  const stacks = await response.json();
18
18
  return {
19
19
  ok: response.ok,
20
- error: response.ok ? null : stacks.message,
20
+ error: response.ok ? null : stacks.error?.message,
21
21
  stacks,
22
22
  };
23
23
  }
@@ -0,0 +1,10 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Add extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ type: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ run(): Promise<void>;
9
+ private addFunction;
10
+ }
@@ -0,0 +1,67 @@
1
+ import { Args, Command } from '@oclif/core';
2
+ import inquirer from 'inquirer';
3
+ import { highlight } from 'tinyhighlight/picocolors';
4
+ import { findBlueprintFile } from '../../actions/blueprints/blueprint.js';
5
+ import { createFunctionResource } from '../../actions/blueprints/resources.js';
6
+ export default class Add extends Command {
7
+ static description = 'Add a resource to a Blueprint';
8
+ static examples = ['<%= config.bin %> <%= command.id %> function'];
9
+ static args = {
10
+ type: Args.string({
11
+ description: 'Type of resource to add (e.g. function)',
12
+ options: ['function'],
13
+ required: true,
14
+ }),
15
+ };
16
+ async run() {
17
+ const { args } = await this.parse(Add);
18
+ const existingBlueprint = findBlueprintFile(undefined);
19
+ if (!existingBlueprint) {
20
+ this.error('No blueprint file found. Run `sanity blueprints init` first.');
21
+ }
22
+ if (args.type === 'function') {
23
+ await this.addFunction();
24
+ return;
25
+ }
26
+ this.error(`Unsupported resource type: ${args.type}`);
27
+ }
28
+ async addFunction() {
29
+ const { functionName } = await inquirer.prompt([
30
+ {
31
+ type: 'input',
32
+ name: 'functionName',
33
+ message: 'Enter function name:',
34
+ validate: (input) => input.length > 0 || 'Function name is required',
35
+ },
36
+ ]);
37
+ const { functionType } = await inquirer.prompt([
38
+ {
39
+ type: 'list',
40
+ name: 'functionType',
41
+ message: 'Choose function type:',
42
+ choices: [
43
+ { name: 'Document Mutation', value: 'document-mutation' },
44
+ { name: 'Document Publish', value: 'document-publish' },
45
+ { name: 'Document Update', value: 'document-update' },
46
+ { name: 'Document Delete', value: 'document-delete' },
47
+ ],
48
+ default: 'document-mutation',
49
+ },
50
+ ]);
51
+ const { filePath, resourceAdded, resource } = createFunctionResource({
52
+ name: functionName,
53
+ type: functionType,
54
+ displayName: functionName,
55
+ });
56
+ this.log(`\nCreated function: ${filePath}`);
57
+ if (!resourceAdded) {
58
+ // print the resource JSON for manual addition
59
+ this.log('\nAdd this Function resource to your blueprint:');
60
+ this.log(highlight(JSON.stringify(resource, null, 2)));
61
+ }
62
+ else {
63
+ // added to blueprint.json
64
+ this.log('Function resource added to blueprint');
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,9 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Config extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ edit: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,72 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import inquirer from 'inquirer';
3
+ import { highlight } from 'tinyhighlight/picocolors';
4
+ import { readConfigFile, writeConfigFile } from '../../actions/blueprints/blueprint.js';
5
+ import { listProjects } from '../../actions/blueprints/projects.js';
6
+ import { listStacks } from '../../actions/blueprints/stacks.js';
7
+ export default class Config extends Command {
8
+ static description = 'View or edit Blueprint configuration';
9
+ static examples = [
10
+ '<%= config.bin %> <%= command.id %>',
11
+ '<%= config.bin %> <%= command.id %> --edit',
12
+ ];
13
+ static flags = {
14
+ edit: Flags.boolean({
15
+ description: 'Edit the configuration',
16
+ default: false,
17
+ }),
18
+ };
19
+ async run() {
20
+ const { flags } = await this.parse(Config);
21
+ const config = readConfigFile();
22
+ if (!config) {
23
+ this.error('No configuration found. Run `sanity blueprints init` first.');
24
+ }
25
+ this.log('\nCurrent configuration:');
26
+ this.log(highlight(JSON.stringify(config, null, 2)));
27
+ if (!flags.edit)
28
+ return;
29
+ const { ok, projects, error } = await listProjects();
30
+ if (!ok)
31
+ this.error(error);
32
+ if (projects.length === 0) {
33
+ this.error('No projects found. Please create a project in Sanity.io first.');
34
+ }
35
+ const projectChoices = projects.map(({ displayName, id }) => ({
36
+ name: `${displayName} <${id}>`,
37
+ value: id,
38
+ }));
39
+ const { projectId } = await inquirer.prompt([
40
+ {
41
+ type: 'list',
42
+ name: 'projectId',
43
+ message: 'Select your Sanity project:',
44
+ choices: projectChoices,
45
+ default: config.projectId,
46
+ },
47
+ ]);
48
+ // get stacks for selected project
49
+ const { ok: stacksOk, stacks, error: stacksError } = await listStacks({ projectId });
50
+ if (!stacksOk)
51
+ this.error(stacksError);
52
+ let stackId;
53
+ if (stacks.length > 0) {
54
+ const stackChoices = stacks.map(({ name, id }) => ({
55
+ name: `${name} <${id}>`,
56
+ value: id,
57
+ }));
58
+ const { stackId: selectedStackId } = await inquirer.prompt([
59
+ {
60
+ type: 'list',
61
+ name: 'stackId',
62
+ message: 'Select a stack:',
63
+ choices: stackChoices,
64
+ default: config.stackId,
65
+ },
66
+ ]);
67
+ stackId = selectedStackId;
68
+ }
69
+ writeConfigFile({ projectId, stackId });
70
+ this.log('\nConfiguration updated successfully.');
71
+ }
72
+ }
@@ -1,10 +1,10 @@
1
1
  import { Command } from '@oclif/core';
2
2
  import inquirer from 'inquirer';
3
- import { readBlueprintOnDisk, writeBlueprintConfig } from '../../actions/blueprints/blueprint.js';
3
+ import { Spinner } from 'picospinner';
4
+ import { readBlueprintOnDisk, writeConfigFile } from '../../actions/blueprints/blueprint.js';
4
5
  import { createStack, updateStack } from '../../actions/blueprints/stacks.js';
5
6
  import { stashAsset } from '../../actions/blueprints/stash-asset.js';
6
7
  import { bold, green, red, yellow } from '../../utils/display/colors.js';
7
- import Spinner from '../../utils/spinner.js';
8
8
  export default class Deploy extends Command {
9
9
  static description = 'Deploy a Blueprint';
10
10
  static examples = ['<%= config.bin %> <%= command.id %>'];
@@ -46,23 +46,22 @@ export default class Deploy extends Command {
46
46
  }
47
47
  if (!name)
48
48
  this.error('Stack name is required');
49
- const s = new Spinner();
50
- const validResources = resources.filter((r) => r.kind);
51
- const functionResources = validResources.filter((r) => r.type?.startsWith('sanity.function.'));
49
+ const validResources = resources.filter((r) => r.type);
50
+ const functionResources = validResources.filter((r) => r.type.startsWith('sanity.function.'));
52
51
  // First stash all function assets
53
52
  if (functionResources.length > 0) {
54
53
  for (const resource of functionResources) {
55
- const fnSpinner = new Spinner();
56
- fnSpinner.start(`Processing ${resource.name}...`);
54
+ const fnSpinner = new Spinner(`Processing ${resource.name}...`);
55
+ fnSpinner.start();
57
56
  const result = await stashAsset({ resource, projectId });
58
57
  if (result.success) {
59
58
  const src = resource.src;
60
59
  resource.src = result.assetId; // TODO: properly reference asset - for now, the API expects the assetId
61
- fnSpinner.stop(`${green('✓')} ${resource.name} <${yellow(result.assetId)}>`);
60
+ fnSpinner.succeed(`${resource.name} <${yellow(result.assetId)}>`);
62
61
  this.log(` Source: ${src}`);
63
62
  }
64
63
  else {
65
- fnSpinner.stop(`${red('✗')} Failed to process ${resource.name}`);
64
+ fnSpinner.fail(`Failed to process ${resource.name}`);
66
65
  this.log(` Error: ${result.error}`);
67
66
  return;
68
67
  }
@@ -74,13 +73,15 @@ export default class Deploy extends Command {
74
73
  document: { resources: validResources },
75
74
  };
76
75
  this.debug('BLUEPRINT DOCUMENT:', blueprint);
76
+ const spinner = new Spinner('Deploying stack...');
77
+ spinner.start();
77
78
  const { ok: deployOk, stack, error: deployError, } = deployedStack
78
79
  ? await updateStack({ stackId: deployedStack.id, blueprint, projectId })
79
80
  : await createStack({ blueprint, projectId });
80
81
  this.debug('STACK RESPONSE:', stack);
81
82
  if (deployOk) {
82
- s.stop(`${green('Success!')} Stack "${bold(stack.name)}" ${deployedStack ? 'updated' : 'created'} <${yellow(stack.id)}>`);
83
- writeBlueprintConfig({
83
+ spinner.succeed(`${green('Success!')} Stack "${bold(stack.name)}" ${deployedStack ? 'updated' : 'created'} <${yellow(stack.id)}>`);
84
+ writeConfigFile({
84
85
  projectId,
85
86
  stackId: stack.id,
86
87
  });
@@ -88,7 +89,7 @@ export default class Deploy extends Command {
88
89
  }
89
90
  else {
90
91
  this.debug('STACK ERROR RESPONSE:', stack);
91
- s.stop(`${red('Failed')} to ${deployedStack ? 'update' : 'create'} stack`);
92
+ spinner.fail(`${red('Failed')} to ${deployedStack ? 'update' : 'create'} stack`);
92
93
  this.log(`Error: ${deployError || JSON.stringify(stack, null, 2) || 'Unknown error'}`);
93
94
  }
94
95
  }
@@ -59,7 +59,7 @@ export default class Info extends Command {
59
59
  this.log(` Status: ${operationColor(status)}`);
60
60
  }
61
61
  if (operation.createdAt) {
62
- this.log(` Started : ${formatDate(operation.createdAt)}`);
62
+ this.log(` Started: ${formatDate(operation.createdAt)}`);
63
63
  }
64
64
  if (operation.status === 'COMPLETED' && operation.completedAt && operation.createdAt) {
65
65
  this.log(` Completed: ${formatDate(operation.completedAt)}`);
@@ -0,0 +1,6 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Init extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ }
@@ -0,0 +1,56 @@
1
+ import { join } from 'node:path';
2
+ import { cwd } from 'node:process';
3
+ import { Command } from '@oclif/core';
4
+ import inquirer from 'inquirer';
5
+ import { findBlueprintFile, writeBlueprintToDisk, writeConfigFile, } from '../../actions/blueprints/blueprint.js';
6
+ import { listProjects } from '../../actions/blueprints/projects.js';
7
+ export default class Init extends Command {
8
+ static description = 'Initialize a new Blueprint';
9
+ static examples = ['<%= config.bin %> <%= command.id %>'];
10
+ async run() {
11
+ const existingBlueprint = findBlueprintFile(undefined);
12
+ if (existingBlueprint) {
13
+ this.error(`A blueprint file already exists: ${existingBlueprint.fileName}`);
14
+ }
15
+ const { blueprintExtension } = await inquirer.prompt([
16
+ {
17
+ type: 'list',
18
+ name: 'blueprintExtension',
19
+ message: 'Choose a blueprint type:',
20
+ choices: [
21
+ { name: 'JSON (Recommended)', value: 'json' },
22
+ { name: 'JavaScript (Beta)', value: 'js' },
23
+ { name: 'TypeScript (Alpha)', value: 'ts' },
24
+ ],
25
+ default: 'json',
26
+ },
27
+ ]);
28
+ const { ok, projects, error } = await listProjects();
29
+ if (!ok) {
30
+ this.error(error);
31
+ }
32
+ if (projects.length === 0) {
33
+ this.error('No projects found. Please create a project in Sanity.io first.');
34
+ }
35
+ const projectChoices = projects.map(({ displayName, id }) => ({
36
+ name: `${displayName} <${id}>`,
37
+ value: id,
38
+ }));
39
+ const { projectId } = await inquirer.prompt([
40
+ {
41
+ type: 'list',
42
+ name: 'projectId',
43
+ message: 'Select your Sanity project:',
44
+ choices: projectChoices,
45
+ },
46
+ ]);
47
+ const fileName = `blueprint.${blueprintExtension}`;
48
+ const filePath = join(cwd(), fileName);
49
+ writeBlueprintToDisk({ path: filePath, fileType: blueprintExtension });
50
+ writeConfigFile({ projectId });
51
+ this.log(`Created new blueprint: ./${fileName}`);
52
+ if (blueprintExtension === 'ts') {
53
+ this.log('\nNote: TypeScript support requires "tsx" to be installed. Run: npm install -D tsx');
54
+ }
55
+ }
56
+ }