@sanity/runtime-cli 4.5.0 → 5.0.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 (55) hide show
  1. package/README.md +61 -84
  2. package/dist/actions/blueprints/blueprint.d.ts +6 -12
  3. package/dist/actions/blueprints/blueprint.js +55 -45
  4. package/dist/actions/blueprints/index.d.ts +33 -0
  5. package/dist/actions/blueprints/index.js +32 -0
  6. package/dist/actions/blueprints/projects.d.ts +9 -0
  7. package/dist/actions/blueprints/projects.js +12 -0
  8. package/dist/actions/blueprints/resources.d.ts +1 -0
  9. package/dist/actions/blueprints/resources.js +24 -11
  10. package/dist/actions/blueprints/stacks.d.ts +0 -12
  11. package/dist/actions/blueprints/stacks.js +1 -30
  12. package/dist/baseCommands.d.ts +24 -0
  13. package/dist/baseCommands.js +69 -0
  14. package/dist/commands/blueprints/add.d.ts +7 -3
  15. package/dist/commands/blueprints/add.js +51 -14
  16. package/dist/commands/blueprints/config.d.ts +9 -2
  17. package/dist/commands/blueprints/config.js +68 -18
  18. package/dist/commands/blueprints/deploy.d.ts +2 -2
  19. package/dist/commands/blueprints/deploy.js +18 -33
  20. package/dist/commands/blueprints/destroy.d.ts +4 -3
  21. package/dist/commands/blueprints/destroy.js +32 -35
  22. package/dist/commands/blueprints/info.d.ts +2 -2
  23. package/dist/commands/blueprints/info.js +16 -36
  24. package/dist/commands/blueprints/init.d.ts +15 -6
  25. package/dist/commands/blueprints/init.js +99 -47
  26. package/dist/commands/blueprints/logs.d.ts +2 -2
  27. package/dist/commands/blueprints/logs.js +18 -32
  28. package/dist/commands/blueprints/plan.d.ts +2 -2
  29. package/dist/commands/blueprints/plan.js +10 -16
  30. package/dist/commands/blueprints/stacks.d.ts +3 -2
  31. package/dist/commands/blueprints/stacks.js +10 -29
  32. package/dist/commands/functions/env/add.d.ts +2 -2
  33. package/dist/commands/functions/env/add.js +6 -17
  34. package/dist/commands/functions/env/list.d.ts +2 -2
  35. package/dist/commands/functions/env/list.js +10 -17
  36. package/dist/commands/functions/env/remove.d.ts +2 -2
  37. package/dist/commands/functions/env/remove.js +6 -17
  38. package/dist/commands/functions/invoke.d.ts +2 -2
  39. package/dist/commands/functions/invoke.js +7 -14
  40. package/dist/commands/functions/logs.d.ts +3 -7
  41. package/dist/commands/functions/logs.js +21 -37
  42. package/dist/commands/functions/test.d.ts +3 -3
  43. package/dist/commands/functions/test.js +10 -8
  44. package/dist/server/app.js +3 -3
  45. package/dist/server/static/vendor/vendor.bundle.d.ts +2 -2
  46. package/dist/utils/display/blueprints-formatting.js +2 -2
  47. package/dist/utils/display/colors.js +2 -0
  48. package/dist/utils/display/errors.d.ts +4 -0
  49. package/dist/utils/display/errors.js +27 -0
  50. package/dist/utils/display/index.d.ts +1 -0
  51. package/dist/utils/display/index.js +1 -0
  52. package/dist/utils/types.d.ts +15 -3
  53. package/dist/utils/types.js +9 -3
  54. package/oclif.manifest.json +93 -45
  55. package/package.json +2 -1
@@ -2,6 +2,7 @@ import type { LocalFunctionResource } from '../../utils/types.js';
2
2
  interface FunctionResourceOptions {
3
3
  name: string;
4
4
  type: string;
5
+ lang: string;
5
6
  displayName?: string;
6
7
  blueprintFilePath?: string;
7
8
  }
@@ -7,16 +7,16 @@ const DEFAULT_FUNCTION_TEMPLATE = /*js*/ `export async function handler({context
7
7
  const time = new Date().toLocaleTimeString()
8
8
  console.log(\`👋 Your Sanity Function was called at \${time}\`)
9
9
  }`;
10
- const DEFAULT_PACKAGE_JSON = `{
11
- "name": "<<NAME>>",
12
- "type": "module",
13
- "main": "index.js"
14
- }`;
10
+ const DEFAULT_PACKAGE_JSON = {
11
+ name: '',
12
+ type: 'module',
13
+ main: '',
14
+ };
15
15
  /**
16
16
  * Creates a new function resource file and adds it to the blueprint
17
17
  */
18
18
  export function createFunctionResource(options) {
19
- const { name, type, displayName = name, blueprintFilePath } = options;
19
+ const { name, type, lang, displayName = name, blueprintFilePath } = options;
20
20
  let workingDir = cwd();
21
21
  if (blueprintFilePath) {
22
22
  if (!existsSync(blueprintFilePath)) {
@@ -34,19 +34,32 @@ export function createFunctionResource(options) {
34
34
  if (!existsSync(functionDir)) {
35
35
  mkdirSync(functionDir, { recursive: true });
36
36
  }
37
- // Create index.js with default template
38
- const indexPath = join(functionDir, 'index.js');
37
+ if (!['ts', 'js'].includes(lang))
38
+ throw Error(`Unsupported language: ${lang}`);
39
+ // Create index.<lang> with default template
40
+ const indexPath = join(functionDir, `index.${lang}`);
39
41
  writeFileSync(indexPath, DEFAULT_FUNCTION_TEMPLATE);
40
42
  // Create package.json
41
43
  const packagePath = join(functionDir, 'package.json');
42
- const packageContent = DEFAULT_PACKAGE_JSON.replace('<<NAME>>', name);
43
- writeFileSync(packagePath, packageContent);
44
+ const packageContent = {
45
+ ...DEFAULT_PACKAGE_JSON,
46
+ name,
47
+ main: `index.${lang}`,
48
+ };
49
+ writeFileSync(packagePath, JSON.stringify(packageContent, null, 2));
50
+ // type looks like 'document-publish'
51
+ const typeParts = type.split('-');
52
+ const typeName = typeParts[0];
53
+ const eventOn = typeParts[1];
44
54
  // Create resource definition
45
55
  const resourceJson = {
46
56
  displayName,
47
57
  name,
48
- type: `sanity.function.${type}`,
49
58
  src: `functions/${name}`,
59
+ type: `sanity.function.${typeName}`,
60
+ event: {
61
+ on: [eventOn],
62
+ },
50
63
  };
51
64
  // Add to blueprint or return for manual addition
52
65
  const resource = addResourceToBlueprint({ blueprintFilePath, resource: resourceJson });
@@ -6,18 +6,6 @@ interface ListStacksResponse {
6
6
  stacks: Stack[];
7
7
  }
8
8
  export declare function listStacks(auth: AuthParams): Promise<ListStacksResponse>;
9
- interface GetStackByNameResponse {
10
- ok: boolean;
11
- error: string | null;
12
- stack: Stack | null;
13
- stackId: string | null;
14
- availableStacks?: string[];
15
- }
16
- /** @deprecated Use getStack instead */
17
- export declare function getStackByName({ name, auth, }: {
18
- name: string;
19
- auth: AuthParams;
20
- }): Promise<GetStackByNameResponse>;
21
9
  interface GetStackResponse {
22
10
  ok: boolean;
23
11
  error: string | null;
@@ -14,35 +14,6 @@ export async function listStacks(auth) {
14
14
  stacks,
15
15
  };
16
16
  }
17
- /** @deprecated Use getStack instead */
18
- export async function getStackByName({ name, auth, }) {
19
- const { ok, stacks, error } = await listStacks(auth);
20
- if (!ok || !stacks) {
21
- return {
22
- ok: false,
23
- error: error || 'Failed to retrieve stacks',
24
- stack: null,
25
- stackId: null,
26
- };
27
- }
28
- const foundStack = stacks.find((stack) => stack.name === name);
29
- if (!foundStack) {
30
- return {
31
- ok: true,
32
- error: null,
33
- stack: null,
34
- stackId: null,
35
- availableStacks: stacks.map((s) => s.name),
36
- };
37
- }
38
- const stackResult = await getStack({ stackId: foundStack.id, auth });
39
- return {
40
- ok: stackResult.ok,
41
- error: stackResult.error,
42
- stack: stackResult.stack,
43
- stackId: foundStack.id,
44
- };
45
- }
46
17
  export async function getStack({ stackId, auth, }) {
47
18
  const response = await fetch(`${stacksUrl}/${stackId}`, {
48
19
  method: 'GET',
@@ -64,7 +35,7 @@ export async function createStack({ stackPayload, auth, }) {
64
35
  const stack = await response.json();
65
36
  return {
66
37
  ok: response.ok,
67
- error: response.ok ? null : stack.message,
38
+ error: response.ok ? null : stack.message || stack.error,
68
39
  stack,
69
40
  };
70
41
  }
@@ -0,0 +1,24 @@
1
+ import { Command } from '@oclif/core';
2
+ import type { Interfaces } from '@oclif/core';
3
+ import { readLocalBlueprint } from './actions/blueprints/blueprint.js';
4
+ import type { AuthParams, Stack } from './utils/types.js';
5
+ export type Flags<T extends typeof Command> = Interfaces.InferredFlags<(typeof BlueprintCommand)['baseFlags'] & T['flags']>;
6
+ export type Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>;
7
+ export declare abstract class BlueprintCommand<T extends typeof Command> extends Command {
8
+ protected sanityToken: string;
9
+ protected blueprint: Awaited<ReturnType<typeof readLocalBlueprint>>;
10
+ protected flags: Flags<T>;
11
+ protected args: Args<T>;
12
+ init(): Promise<void>;
13
+ protected catch(err: Error & {
14
+ exitCode?: number;
15
+ }): Promise<unknown>;
16
+ protected finally(_: Error | undefined): Promise<unknown>;
17
+ }
18
+ export declare abstract class DeployedBlueprintCommand<T extends typeof Command> extends BlueprintCommand<T> {
19
+ protected auth: AuthParams;
20
+ protected deployedStack: Stack;
21
+ protected projectId: string;
22
+ protected stackId: string;
23
+ init(): Promise<void>;
24
+ }
@@ -0,0 +1,69 @@
1
+ // * https://oclif.io/docs/base_class
2
+ import { Command } from '@oclif/core';
3
+ import { readLocalBlueprint } from './actions/blueprints/blueprint.js';
4
+ import { getStack } from './actions/blueprints/stacks.js';
5
+ import { presentBlueprintParserErrors } from './utils/display/errors.js';
6
+ import { validTokenOrErrorMessage } from './utils/validated-token.js';
7
+ export class BlueprintCommand extends Command {
8
+ sanityToken;
9
+ blueprint;
10
+ flags;
11
+ args;
12
+ async init() {
13
+ const { args, flags } = await this.parse({
14
+ flags: this.ctor.flags,
15
+ baseFlags: super.ctor.baseFlags,
16
+ enableJsonFlag: this.ctor.enableJsonFlag,
17
+ args: this.ctor.args,
18
+ strict: this.ctor.strict,
19
+ });
20
+ this.flags = flags;
21
+ this.args = args;
22
+ await super.init();
23
+ const { token, error: tokenErr } = await validTokenOrErrorMessage();
24
+ if (tokenErr)
25
+ this.error(tokenErr.message);
26
+ this.sanityToken = token;
27
+ const blueprint = await readLocalBlueprint();
28
+ if (blueprint.errors.length > 0) {
29
+ this.log(presentBlueprintParserErrors(blueprint.errors));
30
+ this.error('Blueprint parse errors.');
31
+ }
32
+ this.blueprint = blueprint;
33
+ }
34
+ async catch(err) {
35
+ // add any custom logic to handle errors from the command
36
+ // or simply return the parent class error handling
37
+ return super.catch(err);
38
+ }
39
+ async finally(_) {
40
+ // called after run and catch regardless of whether or not the command errored
41
+ return super.finally(_);
42
+ }
43
+ }
44
+ export class DeployedBlueprintCommand extends BlueprintCommand {
45
+ auth;
46
+ deployedStack;
47
+ projectId;
48
+ stackId;
49
+ async init() {
50
+ await super.init();
51
+ const { projectId, stackId } = this.blueprint;
52
+ if (!projectId)
53
+ this.error('Missing Project ID for Blueprint');
54
+ if (!stackId)
55
+ this.error('Missing Stack ID for Blueprint');
56
+ this.projectId = projectId;
57
+ this.stackId = stackId;
58
+ this.auth = { token: this.sanityToken, projectId };
59
+ const stackResponse = await getStack({ stackId, auth: this.auth });
60
+ if (!stackResponse.ok) {
61
+ this.error(stackResponse.error ?? 'Could not retrieve deployment info');
62
+ }
63
+ const { stack: deployedStack } = stackResponse;
64
+ if (!deployedStack) {
65
+ this.error('Unable to find deployed Stack. Run `sanity-run blueprints init`');
66
+ }
67
+ this.deployedStack = deployedStack;
68
+ }
69
+ }
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class Add extends Command {
2
+ export default class AddCommand extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
@@ -7,13 +7,17 @@ export default class Add extends Command {
7
7
  };
8
8
  static flags: {
9
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>;
10
+ 'fn-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ language: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ javascript: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
13
  };
12
14
  run(): Promise<void>;
13
15
  promptForFunctionName(): Promise<string>;
14
16
  promptForFunctionType(): Promise<string>;
15
- addFunction({ name, type }: {
17
+ promptForFunctionLang(): Promise<string>;
18
+ addFunction({ name, type, lang, }: {
16
19
  name?: string;
17
20
  type?: string;
21
+ lang?: string;
18
22
  }): Promise<void>;
19
23
  }
@@ -1,15 +1,18 @@
1
+ import { cwd } from 'node:process';
1
2
  import { Args, Command, Flags } from '@oclif/core';
3
+ import chalk from 'chalk';
2
4
  import highlight from 'color-json';
3
5
  import inquirer from 'inquirer';
4
6
  import { findBlueprintFile } from '../../actions/blueprints/blueprint.js';
5
7
  import { createFunctionResource } from '../../actions/blueprints/resources.js';
6
8
  import { validateFunctionName, validateFunctionType } from '../../utils/validate/resource.js';
7
- export default class Add extends Command {
8
- static description = 'Add a resource to a Blueprint';
9
+ export default class AddCommand extends Command {
10
+ static description = 'Add a (function) resource to a Blueprint';
9
11
  static examples = [
10
12
  '<%= config.bin %> <%= command.id %> function',
11
13
  '<%= config.bin %> <%= command.id %> function --name my-function',
12
- '<%= config.bin %> <%= command.id %> function --name my-function --function-type document-publish',
14
+ '<%= config.bin %> <%= command.id %> function --name my-function --fn-type document-publish',
15
+ '<%= config.bin %> <%= command.id %> function --name my-function --fn-type document-publish --lang js',
13
16
  ];
14
17
  static args = {
15
18
  type: Args.string({
@@ -23,23 +26,37 @@ export default class Add extends Command {
23
26
  description: 'Name of the resource to add',
24
27
  char: 'n',
25
28
  }),
26
- 'function-type': Flags.string({
27
- description: 'Type of function to add (e.g. document-publish)',
29
+ 'fn-type': Flags.string({
30
+ description: 'Type of new function',
28
31
  options: ['document-publish' /*, 'document-create', 'document-delete'*/],
32
+ aliases: ['function-type'],
33
+ // default: 'document-publish', // prompting informs user of default
29
34
  dependsOn: ['name'],
30
35
  }),
36
+ language: Flags.string({
37
+ description: 'Language of the new function',
38
+ aliases: ['function-language', 'lang'],
39
+ options: ['ts', 'js'],
40
+ default: 'ts',
41
+ }),
42
+ javascript: Flags.boolean({
43
+ description: 'Use JavaScript instead of TypeScript',
44
+ aliases: ['js'],
45
+ }),
31
46
  };
32
47
  async run() {
33
- const { args, flags } = await this.parse(Add);
48
+ const { args, flags } = await this.parse(AddCommand);
49
+ if (args.type !== 'function')
50
+ this.error(`Unsupported Resource type: ${args.type}`);
34
51
  const existingBlueprint = findBlueprintFile();
35
52
  if (!existingBlueprint) {
36
- this.error('No blueprint file found. Run `sanity blueprints init` first.');
53
+ this.error('No Blueprint file found. Run `sanity-run blueprints init` first.');
37
54
  }
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 });
55
+ const { name: resourceName, 'fn-type': functionType, javascript } = flags;
56
+ let { lang: functionLang } = flags;
57
+ if (javascript)
58
+ functionLang = 'js';
59
+ await this.addFunction({ name: resourceName, type: functionType, lang: functionLang });
43
60
  }
44
61
  async promptForFunctionName() {
45
62
  const { functionName } = await inquirer.prompt([
@@ -68,21 +85,41 @@ export default class Add extends Command {
68
85
  ]);
69
86
  return functionType;
70
87
  }
71
- async addFunction({ name, type }) {
88
+ async promptForFunctionLang() {
89
+ const { functionLang } = await inquirer.prompt([
90
+ {
91
+ type: 'list',
92
+ name: 'functionLang',
93
+ message: 'Choose function language:',
94
+ choices: [
95
+ { name: 'TypeScript', value: 'ts' },
96
+ { name: 'JavaScript', value: 'js' },
97
+ ],
98
+ default: 'ts',
99
+ },
100
+ ]);
101
+ return functionLang;
102
+ }
103
+ async addFunction({ name, type, lang, }) {
72
104
  const functionName = name || (await this.promptForFunctionName());
73
105
  if (!validateFunctionName(functionName)) {
74
106
  this.error('Invalid function name. Must be 6+ characters, no special characters, no spaces');
75
107
  }
76
108
  const functionType = type || (await this.promptForFunctionType());
109
+ const functionLang = lang || (await this.promptForFunctionLang());
77
110
  if (!validateFunctionType(functionType)) {
78
111
  this.error('Invalid function type. Must be one of: document-publish, document-create, document-delete');
79
112
  }
80
113
  const { filePath, resourceAdded, resource } = createFunctionResource({
81
114
  name: functionName,
82
115
  type: functionType,
116
+ lang: functionLang,
83
117
  displayName: functionName,
84
118
  });
85
- this.log(`\nCreated function: ${filePath}`);
119
+ this.log(`\nCreated function: ${filePath.replace(cwd(), '')}`);
120
+ if (functionLang === 'ts') {
121
+ this.log(chalk.dim('To avoid committing build artifacts, it is helpful to .gitignore "functions/**/.build/**"'));
122
+ }
86
123
  if (!resourceAdded) {
87
124
  // print the resource JSON for manual addition
88
125
  this.log('\nAdd this Function resource to your blueprint:');
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class Config extends Command {
2
+ export default class ConfigCommand extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
@@ -9,6 +9,8 @@ export default class Config extends Command {
9
9
  'stack-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
10
  };
11
11
  sanityToken: string | undefined;
12
+ projectId: string | undefined;
13
+ stackId: string | undefined;
12
14
  run(): Promise<void>;
13
15
  promptForProjectId({ knownProjectId }: {
14
16
  knownProjectId?: string;
@@ -17,11 +19,16 @@ export default class Config extends Command {
17
19
  projectId: string;
18
20
  knownStackId?: string;
19
21
  }): Promise<string | undefined>;
20
- testConfigAndReport({ stackId, projectId }: {
22
+ testConfigAndReport({ stackId, projectId, reinit, }: {
21
23
  stackId: string;
22
24
  projectId: string;
25
+ reinit?: boolean;
23
26
  }): Promise<{
24
27
  ok: boolean;
25
28
  error: string | null;
26
29
  }>;
30
+ startReinitializeStack({ projectId, stackId }: {
31
+ projectId: string;
32
+ stackId: string;
33
+ }): Promise<void>;
27
34
  }
@@ -1,19 +1,23 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
2
3
  import highlight from 'color-json';
3
4
  import inquirer from 'inquirer';
4
5
  import Spinner from 'yocto-spinner';
5
- import { readBlueprintOnDisk, writeConfigFile } from '../../actions/blueprints/blueprint.js';
6
- import { listProjects } from '../../actions/blueprints/projects.js';
7
- import { getStack, listStacks } from '../../actions/blueprints/stacks.js';
6
+ import { readLocalBlueprint, 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';
8
9
  import { bold, dim, niceId } from '../../utils/display/colors.js';
10
+ import { presentBlueprintParserErrors } from '../../utils/display/errors.js';
9
11
  import { validTokenOrErrorMessage } from '../../utils/validated-token.js';
10
- export default class Config extends Command {
12
+ export default class ConfigCommand extends Command {
11
13
  static description = 'View or edit Blueprint configuration';
12
14
  static examples = [
13
15
  '<%= config.bin %> <%= command.id %>',
14
16
  '<%= config.bin %> <%= command.id %> --test-config',
15
17
  '<%= config.bin %> <%= command.id %> --edit',
16
- '<%= config.bin %> <%= command.id %> --edit --project-id <projectId> --stack-id <stackId>',
18
+ '<%= config.bin %> <%= command.id %> --edit --project-id <projectId>',
19
+ // LAUNCH LIMIT: 1 Stack per Project - do not allow Stack ID to be set
20
+ // '<%= config.bin %> <%= command.id %> --edit --project-id <projectId> --stack-id <stackId>',
17
21
  ];
18
22
  static flags = {
19
23
  'test-config': Flags.boolean({
@@ -36,20 +40,27 @@ export default class Config extends Command {
36
40
  description: 'Update the Stack ID in the configuration. Requires --edit flag',
37
41
  aliases: ['stack', 'stackId'],
38
42
  dependsOn: ['edit'],
43
+ hidden: true, // LAUNCH LIMIT: 1 Stack per Project
39
44
  }),
40
45
  };
41
46
  sanityToken;
47
+ projectId;
48
+ stackId;
42
49
  async run() {
43
- const { flags } = await this.parse(Config);
50
+ const { flags } = await this.parse(ConfigCommand);
44
51
  const { edit: editConfig, 'project-id': editProjectId, 'stack-id': editStackId, 'test-config': testConfig, } = flags;
45
- const blueprint = await readBlueprintOnDisk();
46
- const { stackId: configStackId, projectId: configProjectId } = blueprint;
52
+ const blueprint = await readLocalBlueprint();
53
+ const { stackId: configStackId, projectId: configProjectId, errors } = blueprint;
54
+ if (errors.length > 0) {
55
+ this.log(presentBlueprintParserErrors(errors));
56
+ this.error('Blueprint parse errors.');
57
+ }
47
58
  if (!configStackId && !configProjectId) {
48
- this.error('Project and Stack configuration is missing! Use `blueprints init` to create a configuration.');
59
+ this.error('Project configuration is missing! Use `sanity-run blueprints init` to create a configuration.');
49
60
  }
50
61
  this.log(bold('Current configuration:'));
51
62
  this.log(` Sanity Project: ${niceId(configProjectId)}`);
52
- this.log(` Blueprint Stack: ${niceId(configStackId)}`);
63
+ this.log(` Blueprint deployment: ${niceId(configStackId)}`);
53
64
  if ((editProjectId || editStackId) && !editConfig) {
54
65
  this.log('To update the configuration, use the --edit flag.');
55
66
  return;
@@ -73,18 +84,27 @@ export default class Config extends Command {
73
84
  const projectId = editProjectId || (await this.promptForProjectId({ knownProjectId: configProjectId }));
74
85
  if (!projectId)
75
86
  this.error('Project ID is required.');
76
- const stackId = editStackId || (await this.promptForStackId({ projectId, knownStackId: configStackId }));
87
+ // LAUNCH LIMIT: 1 Stack per Project - do not allow Stack ID to be set
88
+ // const stackId =
89
+ // editStackId || (await this.promptForStackId({projectId, knownStackId: configStackId}))
90
+ let stackId = configStackId;
77
91
  if (testConfig) {
78
92
  if (projectId && stackId) {
79
- const { ok: newConfigOk } = await this.testConfigAndReport({ stackId, projectId });
80
- if (!newConfigOk) {
93
+ const { ok: newConfigOk } = await this.testConfigAndReport({
94
+ stackId,
95
+ projectId,
96
+ reinit: editConfig,
97
+ });
98
+ if (!newConfigOk)
81
99
  this.error('Updated configuration has not been saved.');
82
- }
83
100
  }
84
101
  else {
85
102
  this.error('Unable to test the configuration. Both Project and Stack IDs must be set.');
86
103
  }
87
104
  }
105
+ // LAUNCH LIMIT: 1 Stack per Project - do not allow Stack ID to be set
106
+ if (stackId?.endsWith(projectId))
107
+ stackId = undefined;
88
108
  try {
89
109
  // update or create .blueprint/config.json
90
110
  writeConfigFile({ projectId, stackId });
@@ -92,7 +112,8 @@ export default class Config extends Command {
92
112
  }
93
113
  catch (error) {
94
114
  this.log('Unable to dynamically update config. Use these values in your blueprint:');
95
- this.log(highlight(JSON.stringify({ metadata: { projectId, stackId } }, null, 2)));
115
+ // LAUNCH LIMIT: 1 Stack per Project - do not allow Stack ID to be set
116
+ this.log(highlight(JSON.stringify({ metadata: { projectId /*, stackId*/ } }, null, 2)));
96
117
  }
97
118
  }
98
119
  async promptForProjectId({ knownProjectId }) {
@@ -146,7 +167,7 @@ export default class Config extends Command {
146
167
  }
147
168
  return undefined;
148
169
  }
149
- async testConfigAndReport({ stackId, projectId }) {
170
+ async testConfigAndReport({ stackId, projectId, reinit = false, }) {
150
171
  if (!this.sanityToken)
151
172
  this.error('Unable to test the configuration. Missing API token.');
152
173
  const spinner = Spinner({ text: 'Testing the configuration...' }).start();
@@ -155,12 +176,41 @@ export default class Config extends Command {
155
176
  auth: { token: this.sanityToken, projectId },
156
177
  });
157
178
  if (!ok) {
158
- spinner.error(error ?? 'Unknown error testing the configuration');
159
- this.log('Use the --edit flag to interactively update the configuration.');
179
+ spinner.error('Blueprint deployment not found');
180
+ // check if configured project and stack can be reinitialized
181
+ if (reinit && stackId === `ST-${projectId}`) {
182
+ await this.startReinitializeStack({ projectId, stackId });
183
+ return { ok: true, error: null };
184
+ }
160
185
  }
161
186
  else {
162
187
  spinner.success('Configuration is valid.');
163
188
  }
164
189
  return { ok, error };
165
190
  }
191
+ async startReinitializeStack({ projectId, stackId }) {
192
+ if (!this.sanityToken)
193
+ this.error('Unable to reinitialize the Stack. Missing API token.');
194
+ const auth = { token: this.sanityToken, projectId };
195
+ // stack id IS ST-${projectId} – it has already been checked and doesn't exist
196
+ this.log(`A new Blueprint deployment can be created with the ${chalk.bold('existing')} configuration.`);
197
+ const { confirm } = await inquirer.prompt([
198
+ {
199
+ type: 'confirm',
200
+ name: 'confirm',
201
+ message: `Do you want to create a ${chalk.blue('new')}, empty Blueprint deployment with the ${chalk.blue('existing')} configuration?`,
202
+ },
203
+ ]);
204
+ if (!confirm)
205
+ this.error('Reinitialization cancelled.');
206
+ const { ok: projectOk, project } = await getProject(auth);
207
+ if (!projectOk)
208
+ this.error('Failed to find Project while creating Stack');
209
+ const projectDisplayName = project.displayName;
210
+ const stackPayload = { name: projectDisplayName, projectId, document: { resources: [] } };
211
+ const response = await createStack({ stackPayload, auth });
212
+ if (!response.ok)
213
+ this.error(response.error || 'Failed to create new Stack');
214
+ this.log(`New Blueprint deployment created for "${projectDisplayName}"`);
215
+ }
166
216
  }
@@ -1,5 +1,5 @@
1
- import { Command } from '@oclif/core';
2
- export default class Deploy extends Command {
1
+ import { DeployedBlueprintCommand } from '../../baseCommands.js';
2
+ export default class DeployCommand extends DeployedBlueprintCommand<typeof DeployCommand> {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {