@sanity/runtime-cli 5.1.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +23 -22
  2. package/dist/actions/blueprints/blueprint.d.ts +9 -5
  3. package/dist/actions/blueprints/index.d.ts +1 -1
  4. package/dist/actions/blueprints/index.js +1 -1
  5. package/dist/baseCommands.d.ts +10 -0
  6. package/dist/baseCommands.js +23 -11
  7. package/dist/commands/blueprints/add.d.ts +0 -8
  8. package/dist/commands/blueprints/add.js +12 -93
  9. package/dist/commands/blueprints/config.d.ts +2 -24
  10. package/dist/commands/blueprints/config.js +12 -179
  11. package/dist/commands/blueprints/deploy.js +12 -69
  12. package/dist/commands/blueprints/destroy.d.ts +5 -4
  13. package/dist/commands/blueprints/destroy.js +21 -61
  14. package/dist/commands/blueprints/info.js +11 -19
  15. package/dist/commands/blueprints/init.d.ts +0 -16
  16. package/dist/commands/blueprints/init.js +10 -167
  17. package/dist/commands/blueprints/logs.js +14 -67
  18. package/dist/commands/blueprints/plan.js +8 -13
  19. package/dist/commands/blueprints/stacks.js +10 -19
  20. package/dist/cores/blueprints/add.d.ts +13 -0
  21. package/dist/cores/blueprints/add.js +107 -0
  22. package/dist/cores/blueprints/config.d.ts +13 -0
  23. package/dist/cores/blueprints/config.js +222 -0
  24. package/dist/cores/blueprints/deploy.d.ts +14 -0
  25. package/dist/cores/blueprints/deploy.js +81 -0
  26. package/dist/cores/blueprints/destroy.d.ts +13 -0
  27. package/dist/cores/blueprints/destroy.js +106 -0
  28. package/dist/cores/blueprints/index.d.ts +18 -0
  29. package/dist/cores/blueprints/index.js +9 -0
  30. package/dist/cores/blueprints/info.d.ts +11 -0
  31. package/dist/cores/blueprints/info.js +33 -0
  32. package/dist/cores/blueprints/init.d.ts +15 -0
  33. package/dist/cores/blueprints/init.js +190 -0
  34. package/dist/cores/blueprints/logs.d.ts +11 -0
  35. package/dist/cores/blueprints/logs.js +74 -0
  36. package/dist/cores/blueprints/plan.d.ts +6 -0
  37. package/dist/cores/blueprints/plan.js +11 -0
  38. package/dist/cores/blueprints/stacks.d.ts +10 -0
  39. package/dist/cores/blueprints/stacks.js +30 -0
  40. package/dist/cores/index.d.ts +20 -0
  41. package/dist/cores/index.js +1 -0
  42. package/dist/utils/display/blueprints-formatting.js +12 -11
  43. package/dist/utils/display/colors.d.ts +3 -1
  44. package/dist/utils/display/colors.js +8 -2
  45. package/oclif.manifest.json +29 -15
  46. package/package.json +5 -1
@@ -1,14 +1,6 @@
1
- import { join } from 'node:path';
2
- import { cwd } from 'node:process';
3
1
  import { Args, Command, Flags } from '@oclif/core';
4
- import chalk from 'chalk';
5
- import inquirer from 'inquirer';
6
- import { findBlueprintFile, writeBlueprintToDisk, writeConfigFile, } from '../../actions/blueprints/blueprint.js';
7
- import { getProject, listProjects } from '../../actions/blueprints/projects.js';
8
- import { createStack, getStack, listStacks } from '../../actions/blueprints/stacks.js';
9
- import { niceId } from '../../utils/display/colors.js';
2
+ import { blueprintInitCore } from '../../cores/blueprints/init.js';
10
3
  import { validTokenOrErrorMessage } from '../../utils/validated-token.js';
11
- const LAUNCH_LIMIT_STACK_PER_PROJECT = true;
12
4
  export default class InitCommand extends Command {
13
5
  static description = 'Initialize a new Blueprint';
14
6
  static examples = [
@@ -52,168 +44,19 @@ export default class InitCommand extends Command {
52
44
  hidden: true, // LAUNCH LIMIT: 1 Stack per Project
53
45
  }),
54
46
  };
55
- sanityToken;
56
47
  async run() {
57
48
  const { args, flags } = await this.parse(InitCommand);
58
49
  const { token, error: tokenErr } = await validTokenOrErrorMessage();
59
50
  if (tokenErr)
60
51
  this.error(tokenErr.message);
61
- this.sanityToken = token;
62
- const { 'blueprint-type': flagBlueprintType, 'project-id': flagProjectId, 'stack-id': flagStackId, 'stack-name': flagStackName, dir: flagDir, } = flags;
63
- const { dir: argDir } = args;
64
- const dirProvided = argDir || flagDir;
65
- const here = cwd();
66
- const dir = argDir || flagDir || here;
67
- const existingBlueprint = findBlueprintFile(dir);
68
- if (existingBlueprint)
69
- this.error('Existing Blueprint found.');
70
- const blueprintExtension = flagBlueprintType || (await this.promptForBlueprintType());
71
- if (!blueprintExtension)
72
- this.error('Blueprint type is required.');
73
- let projectId = flagProjectId;
74
- let stackId = flagStackId;
75
- if (!projectId) {
76
- const pickedProject = await this.promptForProject();
77
- projectId = pickedProject.projectId;
78
- }
79
- const auth = { token: this.sanityToken, projectId };
80
- if (flagStackName) {
81
- // using --stack-name gets around "LAUNCH LIMIT: 1 Stack per Project"
82
- const stack = await this.createEmptyStack({
83
- projectId,
84
- name: flagStackName,
85
- projectBased: false,
86
- });
87
- stackId = stack.id;
88
- }
89
- // LAUNCH LIMIT: 1 Stack per Project - do not prompt for Stack, just create one
90
- if (!stackId && LAUNCH_LIMIT_STACK_PER_PROJECT) {
91
- const stack = await this.createProjectBasedStack(auth);
92
- // stackId = stack.id
93
- }
94
- const fileName = `blueprint.${blueprintExtension}`;
95
- const filePath = join(dir, fileName);
96
- if (dirProvided)
97
- this.log(`New Blueprint created: ${dirProvided}/`);
98
- writeBlueprintToDisk({ blueprintFilePath: filePath });
99
- this.log(`Created new blueprint: ${dirProvided ?? '.'}/${fileName}`);
100
- writeConfigFile({ blueprintFilePath: filePath, projectId, stackId });
101
- this.log(`Created new config file: ${dirProvided ?? '.'}/.blueprint/config.json`);
102
- if (blueprintExtension === 'ts') {
103
- this.log('\nNote: TypeScript support requires "tsx" to be installed. Run: npm install -D tsx');
104
- }
105
- }
106
- async promptForBlueprintType() {
107
- const { pickedBlueprintsType } = await inquirer.prompt([
108
- {
109
- type: 'list',
110
- name: 'pickedBlueprintsType',
111
- message: 'Choose a blueprint type:',
112
- choices: [
113
- { name: 'JSON (Recommended)', value: 'json' },
114
- { name: 'JavaScript', value: 'js', disabled: true },
115
- { name: 'TypeScript', value: 'ts', disabled: true },
116
- ],
117
- default: 'json',
118
- },
119
- ]);
120
- return pickedBlueprintsType;
121
- }
122
- async promptForProject() {
123
- if (!this.sanityToken)
124
- this.error('Unable to list projects. Missing API token.');
125
- const { ok: projectsOk, error: projectsErr, projects, } = await listProjects({ token: this.sanityToken });
126
- if (!projectsOk)
127
- this.error(projectsErr ?? 'Unknown error listing projects');
128
- if (projects.length === 0) {
129
- this.error('No Sanity projects found. Use `npx sanity init` to create one.');
130
- }
131
- const projectChoices = projects.map(({ displayName, id: projectId }) => ({
132
- name: `"${displayName}" ${niceId(projectId)}`,
133
- value: { projectId, displayName },
134
- }));
135
- const { pickedProject } = await inquirer.prompt([
136
- {
137
- type: 'list',
138
- name: 'pickedProject',
139
- message: 'Select your Sanity project:',
140
- choices: projectChoices,
141
- },
142
- ]);
143
- return pickedProject;
144
- }
145
- async promptForStackId({ projectId }) {
146
- if (!this.sanityToken)
147
- this.error('Unable to list Stacks. Missing API token.');
148
- const { ok: stacksOk, error: stacksErr, stacks, } = await listStacks({ token: this.sanityToken, projectId });
149
- if (!stacksOk)
150
- this.error(stacksErr || 'Failed to list Stacks');
151
- const stackChoices = [
152
- { name: chalk.bold('✨ Create a new Stack'), value: 'new' },
153
- ];
154
- if (stacks.length > 0) {
155
- stackChoices.push(new inquirer.Separator(chalk.underline('Use an existing Stack:')));
156
- stackChoices.push(...stacks.map((s) => ({
157
- name: `"${s.name}" ${niceId(s.id)} ${chalk.dim(`(${s.resources.length} res)`)}`,
158
- value: s.id,
159
- })));
160
- }
161
- const { pickedStackId } = await inquirer.prompt([
162
- {
163
- type: 'list',
164
- name: 'pickedStackId',
165
- message: 'Select an existing deployment or create a new one:',
166
- choices: stackChoices,
167
- },
168
- ]);
169
- if (pickedStackId === 'new') {
170
- const { stackName } = await inquirer.prompt([
171
- {
172
- type: 'input',
173
- name: 'stackName',
174
- message: 'Enter a name for your Stack:',
175
- validate: (input) => input.length > 0 || 'Stack name is required',
176
- },
177
- ]);
178
- const stack = await this.createEmptyStack({ projectId, name: stackName });
179
- return stack.id;
180
- }
181
- return pickedStackId;
182
- }
183
- async createEmptyStack({ projectId, name, projectBased = true, }) {
184
- if (!this.sanityToken)
185
- this.error('Unable to create Stack. Missing API token.');
186
- const stackPayload = {
187
- name,
188
- projectId,
189
- useProjectBasedId: projectBased,
190
- document: { resources: [] },
191
- };
192
- const auth = { token: this.sanityToken, projectId };
193
- const response = await createStack({ stackPayload, auth });
194
- if (!response.ok)
195
- this.error(response.error || 'Failed to create new Stack');
196
- return response.stack;
197
- }
198
- // LAUNCH LIMIT: 1 Stack per Project - create exclusive stack for project
199
- async createProjectBasedStack(auth) {
200
- const { projectId } = auth;
201
- // get project
202
- const { ok: projectOk, project } = await getProject(auth);
203
- if (!projectOk)
204
- this.error('Failed to find Project while creating Stack');
205
- const projectDisplayName = project.displayName;
206
- // check if project has a stack
207
- const inferredStackId = `ST-${projectId}`;
208
- const { stack: existingStack, ok: stackOk } = await getStack({ stackId: inferredStackId, auth });
209
- // if existing stack, return stack
210
- if (stackOk && existingStack) {
211
- this.log(chalk.red(`Found existing deployment for "${projectDisplayName}" Blueprint`));
212
- this.log(chalk.red('Deploying an empty Blueprint will override the existing deployment.'));
213
- return existingStack;
214
- }
215
- // if not, create a stack
216
- const stack = await this.createEmptyStack({ projectId, name: projectDisplayName });
217
- return stack;
52
+ const { success, error } = await blueprintInitCore({
53
+ bin: this.config.bin,
54
+ log: (message) => this.log(message),
55
+ token,
56
+ args,
57
+ flags,
58
+ });
59
+ if (!success)
60
+ this.error(error);
218
61
  }
219
62
  }
@@ -1,10 +1,6 @@
1
1
  import { Flags } from '@oclif/core';
2
- import Spinner from 'yocto-spinner';
3
- import { findNewestLogTimestamp, getLogs, getRecentLogs, isNewerLog, streamLogs, } from '../../actions/blueprints/logs.js';
4
2
  import { DeployedBlueprintCommand } from '../../baseCommands.js';
5
- import { formatTitle } from '../../utils/display/blueprints-formatting.js';
6
- import { bold, niceId, red } from '../../utils/display/colors.js';
7
- import { formatLogEntry, formatLogsByDay, organizeLogsByDay, } from '../../utils/display/logs-formatting.js';
3
+ import { blueprintLogsCore } from '../../cores/blueprints/logs.js';
8
4
  export default class LogsCommand extends DeployedBlueprintCommand {
9
5
  static description = 'Display logs for a Blueprint deployment';
10
6
  static examples = [
@@ -15,70 +11,21 @@ export default class LogsCommand extends DeployedBlueprintCommand {
15
11
  watch: Flags.boolean({
16
12
  char: 'w',
17
13
  description: 'Watch for new logs (streaming mode)',
18
- required: false,
19
- default: false,
14
+ aliases: ['follow'],
20
15
  }),
21
16
  };
22
17
  async run() {
23
- const flags = this.flags;
24
- const spinner = Spinner({
25
- text: `Fetching recent logs for deployment ${niceId(this.stackId)}`,
26
- }).start();
27
- try {
28
- if (flags.watch) {
29
- const { ok, logs, error } = await getLogs(this.stackId, this.auth);
30
- if (!ok) {
31
- spinner.error(`${red('Failed')} to retrieve logs`);
32
- this.log(`Error: ${error || 'Unknown error'}`);
33
- return;
34
- }
35
- spinner.stop().clear();
36
- this.log(`${formatTitle('Blueprint', this.deployedStack.name)} ${niceId(this.stackId)} logs`);
37
- if (logs.length > 0) {
38
- this.log('\nMost recent logs:');
39
- const recentLogs = getRecentLogs(logs);
40
- for (const log of recentLogs) {
41
- this.log(` ${formatLogEntry(log)}`);
42
- }
43
- }
44
- else {
45
- this.log(`No recent logs found for deployment ${niceId(this.stackId)}`);
46
- }
47
- const onOpen = () => {
48
- this.log(`Watching for new logs... ${bold('ctrl+c')} to stop`);
49
- };
50
- let newestTimestamp = findNewestLogTimestamp(logs);
51
- const renderLog = (log) => {
52
- if (!isNewerLog(log, newestTimestamp))
53
- return;
54
- newestTimestamp = new Date(log.timestamp).getTime();
55
- this.log(formatLogEntry(log, true));
56
- };
57
- streamLogs(this.stackId, this.auth, renderLog, onOpen, (error) => this.log(`${red('Error:')} ${error}`));
58
- return new Promise(() => {
59
- // hold the line until the user terminates with Ctrl+C
60
- });
61
- }
62
- const { ok, logs, error } = await getLogs(this.stackId, this.auth);
63
- if (!ok) {
64
- spinner.error(`${red('Failed')} to retrieve logs`);
65
- this.log(`Error: ${error || 'Unknown error'}`);
66
- return;
67
- }
68
- if (logs.length === 0) {
69
- spinner.info(`No logs found for deployment ${this.stackId}`);
70
- return;
71
- }
72
- spinner.success(`${formatTitle('Blueprint', this.deployedStack.name)} Logs`);
73
- this.log(`Found ${bold(logs.length.toString())} log entries for deployment ${niceId(this.stackId)}\n`);
74
- // Organize and format logs by day
75
- const logsByDay = organizeLogsByDay(logs);
76
- this.log(formatLogsByDay(logsByDay));
77
- }
78
- catch (err) {
79
- this.warn('Failed to retrieve logs');
80
- if (err instanceof Error)
81
- this.error(`Error: ${err.message}`);
82
- }
18
+ const { success, streaming, error } = await blueprintLogsCore({
19
+ bin: this.config.bin,
20
+ log: (message) => this.log(message),
21
+ auth: this.auth,
22
+ stackId: this.stackId,
23
+ deployedStack: this.deployedStack,
24
+ flags: this.flags,
25
+ });
26
+ if (streaming)
27
+ return streaming;
28
+ if (!success)
29
+ this.error(error);
83
30
  }
84
31
  }
@@ -1,20 +1,15 @@
1
1
  import { BlueprintCommand } from '../../baseCommands.js';
2
- import { formatResourceTree, formatTitle } from '../../utils/display/blueprints-formatting.js';
3
- import { presentBlueprintParserErrors } from '../../utils/display/errors.js';
2
+ import { blueprintPlanCore } from '../../cores/blueprints/plan.js';
4
3
  export default class PlanCommand extends BlueprintCommand {
5
4
  static description = 'Enumerate resources to be deployed - will not modify any resources';
6
5
  static examples = ['<%= config.bin %> <%= command.id %>'];
7
6
  async run() {
8
- const { parsedBlueprint, fileInfo, errors } = this.blueprint;
9
- const { resources } = parsedBlueprint;
10
- if (errors.length > 0) {
11
- this.log(presentBlueprintParserErrors(errors));
12
- // continue to show plan
13
- }
14
- // TODO: compare local to existingStack
15
- this.log(`${formatTitle('Deployment', 'Plan')}`);
16
- this.log(`(${fileInfo.fileName})\n`);
17
- this.log(formatResourceTree(resources));
18
- this.log('\nRun `sanity-run blueprints deploy` to deploy these changes');
7
+ const { success, error } = await blueprintPlanCore({
8
+ bin: this.config.bin,
9
+ log: (message) => this.log(message),
10
+ blueprint: this.blueprint,
11
+ });
12
+ if (!success)
13
+ this.error(error);
19
14
  }
20
15
  }
@@ -1,8 +1,6 @@
1
1
  import { Flags } from '@oclif/core';
2
- import { listStacks } from '../../actions/blueprints/stacks.js';
3
2
  import { BlueprintCommand } from '../../baseCommands.js';
4
- import { formatStacksListing } from '../../utils/display/blueprints-formatting.js';
5
- import { bold, niceId } from '../../utils/display/colors.js';
3
+ import { blueprintStacksCore } from '../../cores/blueprints/stacks.js';
6
4
  export default class StacksCommand extends BlueprintCommand {
7
5
  // LAUNCH LIMIT: 1 Stack per Project - hide stacks command
8
6
  static hidden = true;
@@ -18,21 +16,14 @@ export default class StacksCommand extends BlueprintCommand {
18
16
  }),
19
17
  };
20
18
  async run() {
21
- const flags = this.flags;
22
- const projectId = flags.projectId || this.blueprint.projectId;
23
- if (!projectId) {
24
- this.log('Unable to determine Project ID. Provide one with --projectId');
25
- this.log('Or create a Blueprint with `sanity-run blueprints init`');
26
- return;
27
- }
28
- const { ok, stacks, error } = await listStacks({ token: this.sanityToken, projectId });
29
- if (!ok)
30
- this.error(error || 'Failed to list stacks');
31
- if (!stacks || stacks.length === 0) {
32
- this.warn('No stacks found');
33
- return;
34
- }
35
- this.log(`${bold('Project')} ${niceId(projectId)} ${bold('Stacks')}:\n`);
36
- this.log(formatStacksListing(stacks, this.blueprint.stackId));
19
+ const { success, error } = await blueprintStacksCore({
20
+ bin: this.config.bin,
21
+ log: (message) => this.log(message),
22
+ token: this.sanityToken,
23
+ blueprint: this.blueprint,
24
+ flags: this.flags,
25
+ });
26
+ if (!success)
27
+ this.error(error);
37
28
  }
38
29
  }
@@ -0,0 +1,13 @@
1
+ import type { CoreConfig, CoreResult } from '../index.js';
2
+ export interface BlueprintAddOptions extends CoreConfig {
3
+ args: {
4
+ type: string;
5
+ };
6
+ flags: {
7
+ name?: string;
8
+ 'fn-type'?: string;
9
+ language?: string;
10
+ javascript?: boolean;
11
+ };
12
+ }
13
+ export declare function blueprintAddCore(options: BlueprintAddOptions): Promise<CoreResult>;
@@ -0,0 +1,107 @@
1
+ import { cwd } from 'node:process';
2
+ import chalk from 'chalk';
3
+ import highlight from 'color-json';
4
+ import inquirer from 'inquirer';
5
+ import { findBlueprintFile } from '../../actions/blueprints/blueprint.js';
6
+ import { createFunctionResource } from '../../actions/blueprints/resources.js';
7
+ import { validateFunctionName } from '../../utils/validate/resource.js';
8
+ export async function blueprintAddCore(options) {
9
+ const { bin = 'sanity', log, args, flags } = options;
10
+ const { type: resourceType } = args;
11
+ const { name: flagResourceName, 'fn-type': flagFnType, javascript: flagJs } = flags;
12
+ let { language: flagFnLang } = flags;
13
+ flagFnLang = flagJs ? 'js' : flagFnLang;
14
+ try {
15
+ if (resourceType !== 'function') {
16
+ return {
17
+ success: false,
18
+ error: `Unsupported Resource type: ${resourceType}`,
19
+ };
20
+ }
21
+ const existingBlueprint = findBlueprintFile();
22
+ if (!existingBlueprint) {
23
+ return {
24
+ success: false,
25
+ error: `No Blueprint file found. Run \`${bin} blueprints init\` first.`,
26
+ };
27
+ }
28
+ const fnName = flagResourceName || (await promptForFunctionName());
29
+ if (!validateFunctionName(fnName)) {
30
+ throw new Error('Invalid function name. Must be 6+ characters, no special characters, no spaces');
31
+ }
32
+ const fnType = flagFnType || (await promptForFunctionType());
33
+ const fnLang = flagFnLang || (await promptForFunctionLang());
34
+ if (!['document-publish', 'document-create', 'document-delete'].includes(fnType)) {
35
+ throw new Error('Invalid function type. Must be one of: document-publish, document-create, document-delete');
36
+ }
37
+ const { filePath, resourceAdded, resource } = createFunctionResource({
38
+ name: fnName,
39
+ type: fnType,
40
+ lang: fnLang,
41
+ displayName: fnName,
42
+ });
43
+ log(`\nCreated function: ${filePath.replace(cwd(), '')}`);
44
+ if (!resourceAdded) {
45
+ // print the resource JSON for manual addition
46
+ log('\nAdd this Function resource to your blueprint:');
47
+ log(highlight(JSON.stringify(resource, null, 2)));
48
+ }
49
+ else {
50
+ // added to blueprint.json
51
+ log(`Function "${chalk.bold(fnName)}" added to blueprint file.`);
52
+ }
53
+ if (fnLang === 'ts') {
54
+ log(chalk.dim('Add "functions/**/.build/**" to your .gitignore.'));
55
+ }
56
+ return { success: true };
57
+ }
58
+ catch (err) {
59
+ const errorMessage = err instanceof Error ? err.message : String(err);
60
+ return {
61
+ success: false,
62
+ error: errorMessage,
63
+ };
64
+ }
65
+ }
66
+ async function promptForFunctionName() {
67
+ const { functionName } = await inquirer.prompt([
68
+ {
69
+ type: 'input',
70
+ name: 'functionName',
71
+ message: 'Enter function name:',
72
+ validate: (input) => input.length > 0 || 'Function name is required',
73
+ },
74
+ ]);
75
+ return functionName;
76
+ }
77
+ async function promptForFunctionType() {
78
+ const { functionType } = await inquirer.prompt([
79
+ {
80
+ type: 'list',
81
+ name: 'functionType',
82
+ message: 'Choose function type:',
83
+ choices: [
84
+ { name: 'Document Publish', value: 'document-publish' },
85
+ { name: 'Document Create', value: 'document-create', disabled: true },
86
+ { name: 'Document Delete', value: 'document-delete', disabled: true },
87
+ ],
88
+ default: 'document-publish',
89
+ },
90
+ ]);
91
+ return functionType;
92
+ }
93
+ async function promptForFunctionLang() {
94
+ const { functionLang } = await inquirer.prompt([
95
+ {
96
+ type: 'list',
97
+ name: 'functionLang',
98
+ message: 'Choose function language:',
99
+ choices: [
100
+ { name: 'TypeScript', value: 'ts' },
101
+ { name: 'JavaScript', value: 'js' },
102
+ ],
103
+ default: 'ts',
104
+ },
105
+ ]);
106
+ return functionLang;
107
+ }
@@ -0,0 +1,13 @@
1
+ import type { ReadBlueprintResult } from '../../actions/blueprints/blueprint.js';
2
+ import type { CoreConfig, CoreResult } from '../index.js';
3
+ export interface BlueprintConfigOptions extends CoreConfig {
4
+ token: string;
5
+ blueprint: ReadBlueprintResult;
6
+ flags: {
7
+ 'test-config'?: boolean;
8
+ edit?: boolean;
9
+ 'project-id'?: string;
10
+ 'stack-id'?: string;
11
+ };
12
+ }
13
+ export declare function blueprintConfigCore(options: BlueprintConfigOptions): Promise<CoreResult>;