@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
@@ -1,16 +1,17 @@
1
1
  import { setTimeout } from 'node:timers/promises';
2
- import { inspect } from 'node:util';
3
- import { Command, Flags } from '@oclif/core';
2
+ import { Flags } from '@oclif/core';
4
3
  import Spinner from 'yocto-spinner';
5
4
  import { stashAsset } from '../../actions/blueprints/assets.js';
6
- import { readBlueprintOnDisk } from '../../actions/blueprints/blueprint.js';
7
5
  import { getStack, updateStack } from '../../actions/blueprints/stacks.js';
6
+ import { DeployedBlueprintCommand } from '../../baseCommands.js';
8
7
  import { bold, niceId, red } from '../../utils/display/colors.js';
9
8
  import { isLocalFunctionResource } from '../../utils/types.js';
10
- import { validTokenOrErrorMessage } from '../../utils/validated-token.js';
11
- export default class Deploy extends Command {
9
+ export default class DeployCommand extends DeployedBlueprintCommand {
12
10
  static description = 'Deploy a Blueprint';
13
- static examples = ['<%= config.bin %> <%= command.id %>'];
11
+ static examples = [
12
+ '<%= config.bin %> <%= command.id %>',
13
+ '<%= config.bin %> <%= command.id %> --no-wait',
14
+ ];
14
15
  static flags = {
15
16
  'no-wait': Flags.boolean({
16
17
  description: 'Do not wait for deployment to complete',
@@ -18,28 +19,15 @@ export default class Deploy extends Command {
18
19
  }),
19
20
  };
20
21
  async run() {
21
- const { token, error: tokenErr } = await validTokenOrErrorMessage();
22
- if (tokenErr)
23
- this.error(tokenErr.message);
24
- const { flags } = await this.parse(Deploy);
25
- const { errors, projectId, stackId, parsedBlueprint: { resources }, deployedStack, } = await readBlueprintOnDisk({ getStack: true, token });
26
- if (errors.length > 0) {
27
- // printErrors(errors) // TODO: error printer in formatting
28
- this.error(`Blueprint parse errors:\n${inspect(errors, { depth: null })}`);
29
- }
30
- if (!deployedStack || !stackId || !projectId) {
31
- this.error('Before deploying, run `sanity blueprints init`');
32
- }
33
- if (stackId !== deployedStack.id)
34
- this.error('Stack ID mismatch');
35
- const auth = { token, projectId };
22
+ const flags = this.flags;
23
+ const { resources } = this.blueprint.parsedBlueprint;
36
24
  const validResources = resources?.filter((r) => r.type);
37
25
  const functionResources = validResources?.filter(isLocalFunctionResource);
38
26
  // First stash all function assets
39
27
  if (functionResources?.length) {
40
28
  for (const resource of functionResources) {
41
29
  const fnSpinner = Spinner({ text: `Processing ${resource.name}...` }).start();
42
- const result = await stashAsset({ resource, auth });
30
+ const result = await stashAsset({ resource, auth: this.auth });
43
31
  if (result.success && result.assetId) {
44
32
  const src = resource.src;
45
33
  resource.src = result.assetId; // TODO: properly reference asset - for now, the API expects the assetId
@@ -54,21 +42,19 @@ export default class Deploy extends Command {
54
42
  }
55
43
  }
56
44
  const stackPayload = {
57
- projectId,
58
- name: deployedStack.name,
45
+ projectId: this.projectId,
46
+ name: this.deployedStack.name,
59
47
  document: { resources: validResources },
60
48
  };
61
- this.debug('BLUEPRINT DOCUMENT:', stackPayload);
62
- const spinner = Spinner({ text: 'Deploying stack...' }).start();
63
- const { ok: deployOk, stack, error: deployError, } = await updateStack({ stackId: deployedStack.id, stackPayload, auth });
64
- this.debug('STACK RESPONSE:', stack);
49
+ const spinner = Spinner({ text: 'Deploying...' }).start();
50
+ const { ok: deployOk, stack, error: deployError, } = await updateStack({ stackId: this.stackId, stackPayload, auth: this.auth });
65
51
  if (deployOk) {
66
- spinner.success(`Stack "${bold(stack.name)}" ${niceId(stack.id)} deployment started!`);
52
+ spinner.success(`Deployment "${bold(stack.name)}" ${niceId(stack.id)} started!`);
67
53
  if (!flags['no-wait']) {
68
54
  const waitSpinner = Spinner({ text: 'Waiting for deployment to complete...' }).start();
69
55
  while (true) {
70
56
  // TODO: watch logs and print those while polling
71
- const { ok, stack: currentStack } = await getStack({ stackId: stack.id, auth });
57
+ const { ok, stack: currentStack } = await getStack({ stackId: stack.id, auth: this.auth });
72
58
  if (!ok) {
73
59
  waitSpinner.error('Failed to check deployment status');
74
60
  break;
@@ -90,12 +76,11 @@ export default class Deploy extends Command {
90
76
  }
91
77
  }
92
78
  else {
93
- this.log('Use `sanity blueprints info` to check deployment status');
79
+ this.log('Use `sanity-run blueprints info` to check status');
94
80
  }
95
81
  }
96
82
  else {
97
- this.debug('STACK ERROR RESPONSE:', stack);
98
- spinner.error(`${red('Failed')} to update stack`);
83
+ spinner.error(`${red('Failed')} to update deployment`);
99
84
  this.log(`Error: ${deployError || JSON.stringify(stack, null, 2) || 'Unknown error'}`);
100
85
  }
101
86
  }
@@ -1,10 +1,11 @@
1
- import { Command } from '@oclif/core';
2
- export default class Destroy extends Command {
1
+ import { DeployedBlueprintCommand } from '../../baseCommands.js';
2
+ export default class DestroyCommand extends DeployedBlueprintCommand<typeof DestroyCommand> {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
- id: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
6
  force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ projectId: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ id: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
9
  };
9
10
  run(): Promise<void>;
10
11
  }
@@ -1,54 +1,51 @@
1
1
  import { setTimeout } from 'node:timers/promises';
2
- import { Command, Flags } from '@oclif/core';
2
+ import { Flags } from '@oclif/core';
3
3
  import inquirer from 'inquirer';
4
4
  import Spinner from 'yocto-spinner';
5
- import { readBlueprintOnDisk } from '../../actions/blueprints/blueprint.js';
6
5
  import { destroyStack, getStack } from '../../actions/blueprints/stacks.js';
6
+ import { DeployedBlueprintCommand } from '../../baseCommands.js';
7
7
  import { bold, niceId } from '../../utils/display/colors.js';
8
- import { validTokenOrErrorMessage } from '../../utils/validated-token.js';
9
- export default class Destroy extends Command {
10
- static description = 'Destroy a deployed Blueprint Stack';
8
+ export default class DestroyCommand extends DeployedBlueprintCommand {
9
+ static description = 'Destroy a Blueprint deployment (will not delete local files)';
11
10
  static examples = [
12
11
  '<%= config.bin %> <%= command.id %>',
13
- '<%= config.bin %> <%= command.id %> --id ST-a1b2c3',
12
+ // LAUNCH LIMIT: 1 Stack per Project - do not allow Stack ID to be set
13
+ // '<%= config.bin %> <%= command.id %> --id ST-a1b2c3 --projectId a1b2c3 --force',
14
14
  ];
15
15
  static flags = {
16
- id: Flags.string({
17
- description: 'Stack ID to destroy (defaults to current Stack)',
18
- required: false,
19
- }),
20
16
  force: Flags.boolean({
21
17
  description: 'Force destroy (skip confirmation)',
22
18
  default: false,
23
19
  }),
20
+ projectId: Flags.string({
21
+ description: 'Project associated with the Stack (defaults to current Project)',
22
+ dependsOn: ['id', 'force'],
23
+ hidden: true, // LAUNCH LIMIT: 1 Stack per Project
24
+ }),
25
+ id: Flags.string({
26
+ description: 'Stack ID to destroy (defaults to current Stack)',
27
+ dependsOn: ['projectId', 'force'],
28
+ hidden: true, // LAUNCH LIMIT: 1 Stack per Project
29
+ }),
24
30
  };
25
31
  async run() {
26
- const { token, error: tokenErr } = await validTokenOrErrorMessage();
27
- if (tokenErr)
28
- this.error(tokenErr.message);
29
- const { flags } = await this.parse(Destroy);
30
- const { errors, deployedStack, projectId } = await readBlueprintOnDisk({ getStack: true, token });
31
- if (errors.length > 0) {
32
- // printErrors(errors)
33
- this.warn('Blueprint parse errors:');
34
- console.dir(errors, { depth: null });
32
+ const flags = this.flags;
33
+ if (flags.projectId && flags.id && flags.force) {
34
+ // just try to destroy it
35
+ const auth = { token: this.sanityToken, projectId: flags.projectId };
36
+ const { ok, error, stack } = await destroyStack({ stackId: flags.id, auth });
37
+ if (!ok)
38
+ this.error(error || 'Failed to destroy deployment');
39
+ this.log(`Deployment "${stack.name}" ${niceId(stack.id)} destroyed`);
35
40
  return;
36
41
  }
37
- if (!projectId)
38
- this.error('Project resource not found in blueprint');
39
- const auth = { token, projectId };
40
- let stack = deployedStack;
42
+ let stack = this.deployedStack;
41
43
  if (flags.id) {
42
- const { ok, stack: foundStack, error } = await getStack({ stackId: flags.id, auth });
44
+ const { ok, stack: foundStack, error } = await getStack({ stackId: flags.id, auth: this.auth });
43
45
  if (!ok)
44
46
  this.error(error || 'Failed to get stack');
45
47
  stack = foundStack;
46
48
  }
47
- else if (!stack) {
48
- this.error('No stack found');
49
- }
50
- if (!stack)
51
- this.error('Stack not found. Is it deployed?');
52
49
  const destroySpinner = Spinner({
53
50
  text: `Destroying ${bold(stack.name)} ${niceId(stack.id)}...`,
54
51
  color: 'red',
@@ -63,25 +60,25 @@ export default class Destroy extends Command {
63
60
  },
64
61
  ]);
65
62
  if (!confirm) {
66
- this.log('Stack destruction cancelled');
63
+ this.log('Deployment destruction cancelled');
67
64
  return;
68
65
  }
69
66
  destroySpinner.start();
70
67
  let i = 5;
71
68
  while (i >= 0) {
72
- destroySpinner.text = `Destroying stack in ${bold((i--).toString())} seconds...`;
69
+ destroySpinner.text = `Destroying deployment in ${bold((i--).toString())} seconds...`;
73
70
  await setTimeout(1000);
74
71
  }
75
- destroySpinner.text = 'Destroying stack 💥';
72
+ destroySpinner.text = 'Destroying deployment 💥';
76
73
  await setTimeout(500);
77
74
  }
78
75
  else {
79
76
  destroySpinner.start();
80
77
  }
81
- const { ok, error } = await destroyStack({ stackId: stack.id, auth });
78
+ const { ok, error } = await destroyStack({ stackId: stack.id, auth: this.auth });
82
79
  if (!ok)
83
- this.error(error || 'Failed to destroy stack');
80
+ this.error(error || 'Failed to destroy deployment');
84
81
  // TODO: update local config
85
- destroySpinner.success(`Stack "${stack.name}" ${niceId(stack.id)} destroyed`);
82
+ destroySpinner.success(`Deployment "${stack.name}" ${niceId(stack.id)} destroyed`);
86
83
  }
87
84
  }
@@ -1,5 +1,5 @@
1
- import { Command } from '@oclif/core';
2
- export default class Info extends Command {
1
+ import { DeployedBlueprintCommand } from '../../baseCommands.js';
2
+ export default class InfoCommand extends DeployedBlueprintCommand<typeof InfoCommand> {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
@@ -1,53 +1,33 @@
1
- import { Command, Flags } from '@oclif/core';
2
- import { readBlueprintOnDisk } from '../../actions/blueprints/blueprint.js';
1
+ import { Flags } from '@oclif/core';
3
2
  import { getStack } from '../../actions/blueprints/stacks.js';
3
+ import { DeployedBlueprintCommand } from '../../baseCommands.js';
4
4
  import { formatResourceTree, formatStackInfo } from '../../utils/display/blueprints-formatting.js';
5
5
  import { niceId } from '../../utils/display/colors.js';
6
- import { validTokenOrErrorMessage } from '../../utils/validated-token.js';
7
- export default class Info extends Command {
8
- static description = 'Show information about a deployed Blueprint Stack';
6
+ export default class InfoCommand extends DeployedBlueprintCommand {
7
+ static description = 'Show information about a Blueprint deployment';
9
8
  static examples = [
10
9
  '<%= config.bin %> <%= command.id %>',
11
- '<%= config.bin %> <%= command.id %> --id ST-a1b2c3',
10
+ // LAUNCH LIMIT: 1 Stack per Project - do not allow Stack ID to be set
11
+ // '<%= config.bin %> <%= command.id %> --id ST-a1b2c3',
12
12
  ];
13
13
  static flags = {
14
14
  id: Flags.string({
15
15
  description: 'Stack ID to show info for (defaults to current stack)',
16
- required: false,
16
+ hidden: true, // LAUNCH LIMIT: 1 Stack per Project
17
17
  }),
18
18
  };
19
19
  async run() {
20
- const { token, error: tokenErr } = await validTokenOrErrorMessage();
21
- if (tokenErr)
22
- this.error(tokenErr.message);
23
- const { flags } = await this.parse(Info);
24
- const { errors, deployedStack, projectId, stackId } = await readBlueprintOnDisk({
25
- getStack: true,
26
- token,
27
- });
28
- if (errors.length > 0) {
29
- // printErrors(errors)
30
- this.warn('Blueprint parse errors:');
31
- console.dir(errors, { depth: null });
32
- return;
33
- }
34
- if (!stackId && !flags.id)
35
- this.error('No Stack ID provided');
36
- if (!projectId)
37
- this.error('Missing Project ID for Blueprint');
38
- const auth = { token, projectId };
39
- let stack = deployedStack;
20
+ const flags = this.flags;
21
+ const stackId = flags.id || this.stackId;
22
+ let stack = this.deployedStack;
40
23
  if (flags.id) {
41
- const { ok, stack: foundStack, error } = await getStack({ stackId: flags.id, auth });
42
- if (!ok)
43
- this.error(`Failed to get Stack ${niceId(flags.id)}: ${error}`);
44
- stack = foundStack;
45
- }
46
- else if (!stack) {
47
- this.error(`Stack ${niceId(stackId)} not found`);
24
+ const existingStackResponse = await getStack({ stackId, auth: this.auth });
25
+ if (!existingStackResponse.ok) {
26
+ this.error(existingStackResponse.error ??
27
+ `Could not retrieve deployment info for ${niceId(stackId)}`);
28
+ }
29
+ stack = existingStackResponse.stack;
48
30
  }
49
- if (!stack)
50
- this.error('Stack not found. Is it deployed?');
51
31
  this.log(formatStackInfo(stack, true));
52
32
  if (stack.resources) {
53
33
  this.log('');
@@ -1,9 +1,13 @@
1
1
  import { Command } from '@oclif/core';
2
- import type { AuthParams, Stack, StackPayload } from '../../utils/types.js';
3
- export default class Init extends Command {
2
+ import type { AuthParams, Stack } from '../../utils/types.js';
3
+ export default class InitCommand extends Command {
4
4
  static description: string;
5
5
  static examples: string[];
6
+ static args: {
7
+ dir: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
8
+ };
6
9
  static flags: {
10
+ dir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
11
  'blueprint-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
12
  'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
13
  'stack-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -12,12 +16,17 @@ export default class Init extends Command {
12
16
  sanityToken: string | undefined;
13
17
  run(): Promise<void>;
14
18
  promptForBlueprintType(): Promise<string>;
15
- promptForProjectId(): Promise<string>;
19
+ promptForProject(): Promise<{
20
+ projectId: string;
21
+ displayName: string;
22
+ }>;
16
23
  promptForStackId({ projectId }: {
17
24
  projectId: string;
18
25
  }): Promise<string>;
19
- createEmptyStack({ stackPayload, auth, }: {
20
- stackPayload: StackPayload;
21
- auth: AuthParams;
26
+ createEmptyStack({ projectId, name, projectBased, }: {
27
+ projectId: string;
28
+ name: string;
29
+ projectBased?: boolean;
22
30
  }): Promise<Stack>;
31
+ createProjectBasedStack(auth: AuthParams): Promise<Stack>;
23
32
  }
@@ -1,21 +1,34 @@
1
1
  import { join } from 'node:path';
2
2
  import { cwd } from 'node:process';
3
- import { Command, Flags } from '@oclif/core';
3
+ import { Args, Command, Flags } from '@oclif/core';
4
+ import chalk from 'chalk';
4
5
  import inquirer from 'inquirer';
5
6
  import { findBlueprintFile, writeBlueprintToDisk, writeConfigFile, } from '../../actions/blueprints/blueprint.js';
6
- import { listProjects } from '../../actions/blueprints/projects.js';
7
- import { createStack, listStacks } from '../../actions/blueprints/stacks.js';
8
- import { bold, dim, niceId, underline } from '../../utils/display/colors.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';
9
10
  import { validTokenOrErrorMessage } from '../../utils/validated-token.js';
10
- export default class Init extends Command {
11
+ const LAUNCH_LIMIT_STACK_PER_PROJECT = true;
12
+ export default class InitCommand extends Command {
11
13
  static description = 'Initialize a new Blueprint';
12
14
  static examples = [
13
15
  '<%= config.bin %> <%= command.id %>',
16
+ '<%= config.bin %> <%= command.id %> [directory]',
14
17
  '<%= config.bin %> <%= command.id %> --blueprint-type <json|js|ts>',
15
- '<%= config.bin %> <%= command.id %> --blueprint-type <json|js|ts> --project-id <projectId> --stack-id <stackId>',
16
- '<%= config.bin %> <%= command.id %> --blueprint-type <json|js|ts> --project-id <projectId> --stack-name <stackName>',
18
+ '<%= config.bin %> <%= command.id %> --blueprint-type <json|js|ts> --project-id <projectId>',
19
+ // LAUNCH LIMIT: 1 Stack per Project - do not prompt for Stack, just create one
20
+ // '<%= config.bin %> <%= command.id %> --blueprint-type <json|js|ts> --project-id <projectId> --stack-id <stackId>',
21
+ // '<%= config.bin %> <%= command.id %> --blueprint-type <json|js|ts> --project-id <projectId> --stack-name <stackName>',
17
22
  ];
23
+ static args = {
24
+ dir: Args.string({
25
+ description: 'Directory to create the Blueprint in',
26
+ }),
27
+ };
18
28
  static flags = {
29
+ dir: Flags.string({
30
+ description: 'Directory to create the Blueprint in',
31
+ }),
19
32
  'blueprint-type': Flags.string({
20
33
  description: 'Blueprint manifest type to use for the Blueprint',
21
34
  options: ['json', 'js', 'ts'],
@@ -30,52 +43,62 @@ export default class Init extends Command {
30
43
  aliases: ['stack', 'stackId'],
31
44
  dependsOn: ['project-id'],
32
45
  exclusive: ['stack-name'],
46
+ hidden: true, // LAUNCH LIMIT: 1 Stack per Project
33
47
  }),
34
48
  'stack-name': Flags.string({
35
- char: 'n',
36
49
  description: 'Name to use for a NEW Stack',
37
50
  aliases: ['name'],
38
- dependsOn: ['project-id'],
39
51
  exclusive: ['stack-id'],
52
+ hidden: true, // LAUNCH LIMIT: 1 Stack per Project
40
53
  }),
41
54
  };
42
55
  sanityToken;
43
56
  async run() {
44
- const { flags } = await this.parse(Init);
45
- const { 'blueprint-type': flagBlueprintType, 'project-id': flagProjectId, 'stack-id': flagStackId, 'stack-name': flagStackName, } = flags;
46
- const existingBlueprint = findBlueprintFile();
47
- if (existingBlueprint) {
48
- this.error(`A blueprint file already exists: ${existingBlueprint.fileName}`);
49
- }
57
+ const { args, flags } = await this.parse(InitCommand);
50
58
  const { token, error: tokenErr } = await validTokenOrErrorMessage();
51
59
  if (tokenErr)
52
60
  this.error(tokenErr.message);
53
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.');
54
70
  const blueprintExtension = flagBlueprintType || (await this.promptForBlueprintType());
55
71
  if (!blueprintExtension)
56
72
  this.error('Blueprint type is required.');
57
- const projectId = flagProjectId || (await this.promptForProjectId());
58
- if (!projectId)
59
- this.error('Project ID is required.');
73
+ let projectId = flagProjectId;
60
74
  let stackId = flagStackId;
61
- if (!stackId) {
62
- if (flagStackName) {
63
- const stack = await this.createEmptyStack({
64
- stackPayload: { name: flagStackName, projectId, document: { resources: [] } },
65
- auth: { token: this.sanityToken, projectId },
66
- });
67
- stackId = stack.id;
68
- }
69
- else {
70
- stackId = await this.promptForStackId({ projectId });
71
- }
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
72
93
  }
73
94
  const fileName = `blueprint.${blueprintExtension}`;
74
- const filePath = join(cwd(), fileName);
95
+ const filePath = join(dir, fileName);
96
+ if (dirProvided)
97
+ this.log(`New Blueprint created: ${dirProvided}/`);
75
98
  writeBlueprintToDisk({ blueprintFilePath: filePath });
76
- this.log(`Created new blueprint: ./${fileName}`);
99
+ this.log(`Created new blueprint: ${dirProvided ?? '.'}/${fileName}`);
77
100
  writeConfigFile({ blueprintFilePath: filePath, projectId, stackId });
78
- this.log('Created new config file: ./.blueprint/config.json');
101
+ this.log(`Created new config file: ${dirProvided ?? '.'}/.blueprint/config.json`);
79
102
  if (blueprintExtension === 'ts') {
80
103
  this.log('\nNote: TypeScript support requires "tsx" to be installed. Run: npm install -D tsx');
81
104
  }
@@ -96,7 +119,7 @@ export default class Init extends Command {
96
119
  ]);
97
120
  return pickedBlueprintsType;
98
121
  }
99
- async promptForProjectId() {
122
+ async promptForProject() {
100
123
  if (!this.sanityToken)
101
124
  this.error('Unable to list projects. Missing API token.');
102
125
  const { ok: projectsOk, error: projectsErr, projects, } = await listProjects({ token: this.sanityToken });
@@ -107,17 +130,17 @@ export default class Init extends Command {
107
130
  }
108
131
  const projectChoices = projects.map(({ displayName, id: projectId }) => ({
109
132
  name: `"${displayName}" ${niceId(projectId)}`,
110
- value: projectId,
133
+ value: { projectId, displayName },
111
134
  }));
112
- const { pickedProjectId } = await inquirer.prompt([
135
+ const { pickedProject } = await inquirer.prompt([
113
136
  {
114
137
  type: 'list',
115
- name: 'pickedProjectId',
138
+ name: 'pickedProject',
116
139
  message: 'Select your Sanity project:',
117
140
  choices: projectChoices,
118
141
  },
119
142
  ]);
120
- return pickedProjectId;
143
+ return pickedProject;
121
144
  }
122
145
  async promptForStackId({ projectId }) {
123
146
  if (!this.sanityToken)
@@ -125,11 +148,13 @@ export default class Init extends Command {
125
148
  const { ok: stacksOk, error: stacksErr, stacks, } = await listStacks({ token: this.sanityToken, projectId });
126
149
  if (!stacksOk)
127
150
  this.error(stacksErr || 'Failed to list Stacks');
128
- const stackChoices = [{ name: bold('✨ Create a new Stack'), value: 'new' }];
151
+ const stackChoices = [
152
+ { name: chalk.bold('✨ Create a new Stack'), value: 'new' },
153
+ ];
129
154
  if (stacks.length > 0) {
130
- stackChoices.push(new inquirer.Separator(underline('Use an existing Stack:')));
155
+ stackChoices.push(new inquirer.Separator(chalk.underline('Use an existing Stack:')));
131
156
  stackChoices.push(...stacks.map((s) => ({
132
- name: `"${s.name}" ${niceId(s.id)} ${dim(`(${s.resources.length} res)`)}`,
157
+ name: `"${s.name}" ${niceId(s.id)} ${chalk.dim(`(${s.resources.length} res)`)}`,
133
158
  value: s.id,
134
159
  })));
135
160
  }
@@ -137,7 +162,7 @@ export default class Init extends Command {
137
162
  {
138
163
  type: 'list',
139
164
  name: 'pickedStackId',
140
- message: 'Select your Blueprint Stack:',
165
+ message: 'Select an existing deployment or create a new one:',
141
166
  choices: stackChoices,
142
167
  },
143
168
  ]);
@@ -150,18 +175,45 @@ export default class Init extends Command {
150
175
  validate: (input) => input.length > 0 || 'Stack name is required',
151
176
  },
152
177
  ]);
153
- const stack = await this.createEmptyStack({
154
- stackPayload: { name: stackName, projectId, document: { resources: [] } },
155
- auth: { token: this.sanityToken, projectId },
156
- });
178
+ const stack = await this.createEmptyStack({ projectId, name: stackName });
157
179
  return stack.id;
158
180
  }
159
181
  return pickedStackId;
160
182
  }
161
- async createEmptyStack({ stackPayload, auth, }) {
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 };
162
193
  const response = await createStack({ stackPayload, auth });
163
194
  if (!response.ok)
164
- this.error(response.error || 'Failed to create Stack');
195
+ this.error(response.error || 'Failed to create new Stack');
165
196
  return response.stack;
166
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;
218
+ }
167
219
  }
@@ -1,5 +1,5 @@
1
- import { Command } from '@oclif/core';
2
- export default class Logs extends Command {
1
+ import { DeployedBlueprintCommand } from '../../baseCommands.js';
2
+ export default class LogsCommand extends DeployedBlueprintCommand<typeof LogsCommand> {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {