@sanity/runtime-cli 14.13.4 → 15.0.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.
- package/README.md +215 -118
- package/dist/actions/blueprints/logs.d.ts +2 -1
- package/dist/actions/blueprints/logs.js +4 -5
- package/dist/actions/blueprints/resources.js +1 -0
- package/dist/actions/blueprints/stacks.d.ts +3 -1
- package/dist/actions/blueprints/stacks.js +11 -2
- package/dist/actions/functions/test.js +1 -1
- package/dist/actions/sanity/access.d.ts +38 -0
- package/dist/actions/sanity/access.js +23 -0
- package/dist/actions/sanity/projects.d.ts +1 -1
- package/dist/baseCommands.d.ts +2 -0
- package/dist/baseCommands.js +3 -0
- package/dist/commands/blueprints/add.js +1 -1
- package/dist/commands/blueprints/deploy.d.ts +2 -0
- package/dist/commands/blueprints/deploy.js +6 -2
- package/dist/commands/blueprints/destroy.js +0 -2
- package/dist/commands/blueprints/info.d.ts +1 -0
- package/dist/commands/blueprints/info.js +3 -1
- package/dist/commands/blueprints/init.js +2 -0
- package/dist/commands/blueprints/logs.d.ts +5 -0
- package/dist/commands/blueprints/logs.js +26 -3
- package/dist/commands/blueprints/mint-deploy-token.d.ts +14 -0
- package/dist/commands/blueprints/mint-deploy-token.js +47 -0
- package/dist/commands/blueprints/plan.d.ts +2 -0
- package/dist/commands/blueprints/plan.js +8 -2
- package/dist/commands/blueprints/promote.d.ts +2 -1
- package/dist/commands/blueprints/promote.js +7 -2
- package/dist/commands/blueprints/stacks.js +1 -1
- package/dist/commands/functions/add.js +1 -1
- package/dist/cores/blueprints/config.js +23 -29
- package/dist/cores/blueprints/init.js +99 -76
- package/dist/cores/blueprints/logs.d.ts +3 -0
- package/dist/cores/blueprints/logs.js +15 -9
- package/dist/cores/blueprints/mint-deploy-token.d.ts +16 -0
- package/dist/cores/blueprints/mint-deploy-token.js +123 -0
- package/dist/cores/blueprints/promote.d.ts +1 -0
- package/dist/cores/blueprints/promote.js +22 -1
- package/dist/cores/functions/add.js +4 -5
- package/dist/cores/index.d.ts +1 -2
- package/dist/cores/index.js +0 -2
- package/dist/index.d.ts +0 -18
- package/dist/index.js +0 -20
- package/dist/utils/clipboard.d.ts +14 -0
- package/dist/utils/clipboard.js +73 -0
- package/dist/utils/display/errors.d.ts +5 -1
- package/dist/utils/display/prompt.d.ts +55 -15
- package/dist/utils/display/prompt.js +271 -45
- package/oclif.manifest.json +320 -18
- package/package.json +21 -67
- package/dist/actions/blueprints/index.d.ts +0 -16
- package/dist/actions/blueprints/index.js +0 -10
- package/dist/actions/functions/index.d.ts +0 -4
- package/dist/actions/functions/index.js +0 -4
- package/dist/actions/sanity/index.d.ts +0 -1
- package/dist/actions/sanity/index.js +0 -1
- package/dist/cores/blueprints/index.d.ts +0 -20
- package/dist/cores/blueprints/index.js +0 -10
- package/dist/cores/functions/index.d.ts +0 -16
- package/dist/cores/functions/index.js +0 -8
- package/dist/utils/display/index.d.ts +0 -5
- package/dist/utils/display/index.js +0 -5
- package/dist/utils/index.d.ts +0 -8
- package/dist/utils/index.js +0 -8
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1 @@
|
|
|
1
1
|
export { run } from '@oclif/core';
|
|
2
|
-
export { default as BlueprintsAddCommand } from './commands/blueprints/add.js';
|
|
3
|
-
export { default as BlueprintsConfigCommand } from './commands/blueprints/config.js';
|
|
4
|
-
export { default as BlueprintsDeployCommand } from './commands/blueprints/deploy.js';
|
|
5
|
-
export { default as BlueprintsDestroyCommand } from './commands/blueprints/destroy.js';
|
|
6
|
-
export { default as BlueprintsDoctorCommand } from './commands/blueprints/doctor.js';
|
|
7
|
-
export { default as BlueprintsInfoCommand } from './commands/blueprints/info.js';
|
|
8
|
-
export { default as BlueprintsInitCommand } from './commands/blueprints/init.js';
|
|
9
|
-
export { default as BlueprintsLogsCommand } from './commands/blueprints/logs.js';
|
|
10
|
-
export { default as BlueprintsPlanCommand } from './commands/blueprints/plan.js';
|
|
11
|
-
export { default as BlueprintsPromoteCommand } from './commands/blueprints/promote.js';
|
|
12
|
-
export { default as BlueprintsStacksCommand } from './commands/blueprints/stacks.js';
|
|
13
|
-
export { default as FunctionsAddCommand } from './commands/functions/add.js';
|
|
14
|
-
export { default as FunctionsDevCommand } from './commands/functions/dev.js';
|
|
15
|
-
export { default as FunctionsEnvAddCommand } from './commands/functions/env/add.js';
|
|
16
|
-
export { default as FunctionsEnvListCommand } from './commands/functions/env/list.js';
|
|
17
|
-
export { default as FunctionsEnvRemoveCommand } from './commands/functions/env/remove.js';
|
|
18
|
-
export { default as FunctionsLogsCommand } from './commands/functions/logs.js';
|
|
19
|
-
export { default as FunctionsTestCommand } from './commands/functions/test.js';
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1 @@
|
|
|
1
1
|
export { run } from '@oclif/core';
|
|
2
|
-
// Blueprints command classes
|
|
3
|
-
export { default as BlueprintsAddCommand } from './commands/blueprints/add.js';
|
|
4
|
-
export { default as BlueprintsConfigCommand } from './commands/blueprints/config.js';
|
|
5
|
-
export { default as BlueprintsDeployCommand } from './commands/blueprints/deploy.js';
|
|
6
|
-
export { default as BlueprintsDestroyCommand } from './commands/blueprints/destroy.js';
|
|
7
|
-
export { default as BlueprintsDoctorCommand } from './commands/blueprints/doctor.js';
|
|
8
|
-
export { default as BlueprintsInfoCommand } from './commands/blueprints/info.js';
|
|
9
|
-
export { default as BlueprintsInitCommand } from './commands/blueprints/init.js';
|
|
10
|
-
export { default as BlueprintsLogsCommand } from './commands/blueprints/logs.js';
|
|
11
|
-
export { default as BlueprintsPlanCommand } from './commands/blueprints/plan.js';
|
|
12
|
-
export { default as BlueprintsPromoteCommand } from './commands/blueprints/promote.js';
|
|
13
|
-
export { default as BlueprintsStacksCommand } from './commands/blueprints/stacks.js';
|
|
14
|
-
// Functions command classes
|
|
15
|
-
export { default as FunctionsAddCommand } from './commands/functions/add.js';
|
|
16
|
-
export { default as FunctionsDevCommand } from './commands/functions/dev.js';
|
|
17
|
-
export { default as FunctionsEnvAddCommand } from './commands/functions/env/add.js';
|
|
18
|
-
export { default as FunctionsEnvListCommand } from './commands/functions/env/list.js';
|
|
19
|
-
export { default as FunctionsEnvRemoveCommand } from './commands/functions/env/remove.js';
|
|
20
|
-
export { default as FunctionsLogsCommand } from './commands/functions/logs.js';
|
|
21
|
-
export { default as FunctionsTestCommand } from './commands/functions/test.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface CopyTool {
|
|
2
|
+
cmd: string;
|
|
3
|
+
args: string[];
|
|
4
|
+
}
|
|
5
|
+
export declare class ClipboardUnavailableError extends Error {
|
|
6
|
+
code: string;
|
|
7
|
+
triedTools: string[];
|
|
8
|
+
constructor(message: string, triedTools?: string[]);
|
|
9
|
+
}
|
|
10
|
+
/** Candidate copy tools for the current OS, in fallback order. */
|
|
11
|
+
export declare function pickCopyTools(): CopyTool[];
|
|
12
|
+
/** @throws {ClipboardUnavailableError} when no usable clipboard tool is available. */
|
|
13
|
+
export declare function write(text: string): Promise<void>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { env, platform } from 'node:process';
|
|
3
|
+
export class ClipboardUnavailableError extends Error {
|
|
4
|
+
code = 'CLIPBOARD_UNAVAILABLE';
|
|
5
|
+
triedTools;
|
|
6
|
+
constructor(message, triedTools = []) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'ClipboardUnavailableError';
|
|
9
|
+
this.triedTools = triedTools;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Candidate copy tools for the current OS, in fallback order. */
|
|
13
|
+
export function pickCopyTools() {
|
|
14
|
+
if (platform === 'darwin')
|
|
15
|
+
return [{ cmd: 'pbcopy', args: [] }];
|
|
16
|
+
if (platform === 'win32')
|
|
17
|
+
return [{ cmd: 'clip', args: [] }];
|
|
18
|
+
const tools = [];
|
|
19
|
+
if (env.WAYLAND_DISPLAY)
|
|
20
|
+
tools.push({ cmd: 'wl-copy', args: [] });
|
|
21
|
+
if (env.DISPLAY) {
|
|
22
|
+
tools.push({ cmd: 'xclip', args: ['-selection', 'clipboard'] });
|
|
23
|
+
tools.push({ cmd: 'xsel', args: ['--clipboard', '--input'] });
|
|
24
|
+
}
|
|
25
|
+
if (env.WSL_DISTRO_NAME)
|
|
26
|
+
tools.push({ cmd: 'clip.exe', args: [] });
|
|
27
|
+
return tools;
|
|
28
|
+
}
|
|
29
|
+
function tryWrite(tool, text) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
let child;
|
|
32
|
+
try {
|
|
33
|
+
child = spawn(tool.cmd, tool.args, { stdio: ['pipe', 'ignore', 'pipe'] });
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
let stderr = '';
|
|
40
|
+
child.on('error', reject);
|
|
41
|
+
child.stderr?.on('data', (chunk) => {
|
|
42
|
+
stderr += chunk.toString();
|
|
43
|
+
});
|
|
44
|
+
child.on('close', (code) => {
|
|
45
|
+
if (code === 0) {
|
|
46
|
+
resolve();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
reject(new Error(`${tool.cmd} exited with code ${code}: ${stderr.trim()}`));
|
|
50
|
+
});
|
|
51
|
+
child.stdin?.end(text, 'utf8');
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** @throws {ClipboardUnavailableError} when no usable clipboard tool is available. */
|
|
55
|
+
export async function write(text) {
|
|
56
|
+
const tools = pickCopyTools();
|
|
57
|
+
if (tools.length === 0) {
|
|
58
|
+
throw new ClipboardUnavailableError(`No clipboard tool available for platform "${platform}".`, []);
|
|
59
|
+
}
|
|
60
|
+
const triedTools = tools.map((t) => t.cmd);
|
|
61
|
+
let lastErr;
|
|
62
|
+
for (const tool of tools) {
|
|
63
|
+
try {
|
|
64
|
+
await tryWrite(tool, text);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
lastErr = err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const message = lastErr instanceof Error ? lastErr.message : String(lastErr);
|
|
72
|
+
throw new ClipboardUnavailableError(`Clipboard write failed. Tools tried: ${triedTools.join(', ')}. Last error: ${message}`, triedTools);
|
|
73
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type { BlueprintIssue } from '../../actions/blueprints/index.js';
|
|
2
1
|
import type { BlueprintParserError } from '../types.js';
|
|
2
|
+
export type BlueprintIssue = {
|
|
3
|
+
code: 'NO_STACK_ID' | 'NO_SCOPE_TYPE' | 'NO_SCOPE_ID' | 'NO_STACK' | 'PARSE_ERROR';
|
|
4
|
+
message: string;
|
|
5
|
+
errors?: BlueprintParserError[];
|
|
6
|
+
};
|
|
3
7
|
export declare function presentBlueprintIssues(issues: BlueprintIssue[]): string;
|
|
4
8
|
export declare function presentBlueprintParserErrors(errors: BlueprintParserError[]): string;
|
|
@@ -1,33 +1,73 @@
|
|
|
1
|
+
import type { Project } from '../../actions/sanity/projects.js';
|
|
1
2
|
import type { Logger } from '../logger.js';
|
|
3
|
+
import type { ScopeType } from '../types.js';
|
|
4
|
+
export declare const BACK: unique symbol;
|
|
5
|
+
export type Back = typeof BACK;
|
|
2
6
|
export declare function promptForBlueprintType(): Promise<string>;
|
|
3
7
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* @returns The selected project, with the projectId and displayName
|
|
7
|
-
* @throws {Error} If the user does not have any projects or if the API call fails
|
|
8
|
+
* Fetches organizations and prompts the user to pick one (if multiple).
|
|
9
|
+
* Returns the selected organization along with its projects.
|
|
8
10
|
*/
|
|
9
|
-
export declare function
|
|
11
|
+
export declare function promptForOrganization({ token, knownOrganizationId, logger, allowBack, }: {
|
|
10
12
|
token: string;
|
|
11
13
|
knownOrganizationId?: string;
|
|
12
|
-
knownProjectId?: string;
|
|
13
14
|
logger: Logger;
|
|
15
|
+
allowBack?: boolean;
|
|
16
|
+
}): Promise<{
|
|
17
|
+
organization: {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
};
|
|
21
|
+
projects: Project[];
|
|
22
|
+
wasPrompted: boolean;
|
|
23
|
+
} | Back>;
|
|
24
|
+
/**
|
|
25
|
+
* Asks the user whether to scope a Blueprint to an organization or a project.
|
|
26
|
+
*/
|
|
27
|
+
export declare function promptForScope({ allowBack, hasProjects, }?: {
|
|
28
|
+
allowBack?: boolean;
|
|
29
|
+
hasProjects?: boolean;
|
|
30
|
+
}): Promise<ScopeType | Back>;
|
|
31
|
+
/**
|
|
32
|
+
* Prompts the user to pick a project from a pre-fetched list of projects.
|
|
33
|
+
* Use after promptForOrganization when the user chose project scope.
|
|
34
|
+
*/
|
|
35
|
+
export declare function promptForProjectInOrg({ projects, knownProjectId, logger, allowBack, }: {
|
|
36
|
+
projects: Project[];
|
|
37
|
+
knownProjectId?: string;
|
|
38
|
+
logger?: Logger;
|
|
39
|
+
allowBack?: boolean;
|
|
14
40
|
}): Promise<{
|
|
15
41
|
projectId: string;
|
|
16
42
|
displayName: string;
|
|
17
|
-
}>;
|
|
43
|
+
} | Back>;
|
|
18
44
|
/**
|
|
19
|
-
* Prompt the user for a Stack
|
|
20
|
-
* Can
|
|
21
|
-
* @param projectId - The ID of the Project
|
|
22
|
-
* @param token - The Sanity API token
|
|
23
|
-
* @returns The selected Stack ID
|
|
24
|
-
* @throws {Error} If the user does not have any Stacks or if the API call fails
|
|
45
|
+
* Prompt the user for a Stack after scope has been determined.
|
|
46
|
+
* Can create a new Stack or select an existing one.
|
|
25
47
|
*/
|
|
26
|
-
export declare function promptForStack({
|
|
27
|
-
|
|
48
|
+
export declare function promptForStack({ scopeType, scopeId, token, logger, allowBack, }: {
|
|
49
|
+
scopeType: ScopeType;
|
|
50
|
+
scopeId: string;
|
|
28
51
|
token: string;
|
|
29
52
|
logger: Logger;
|
|
53
|
+
allowBack?: boolean;
|
|
30
54
|
}): Promise<{
|
|
31
55
|
stackId: string;
|
|
32
56
|
name: string;
|
|
57
|
+
} | Back>;
|
|
58
|
+
/**
|
|
59
|
+
* Full wizard: Organization -> Scope -> Project (if project-scoped) -> Stack.
|
|
60
|
+
* Supports back-navigation between steps.
|
|
61
|
+
*/
|
|
62
|
+
export declare function runScopeAndStackWizard({ token, knownOrganizationId, knownProjectId, logger, }: {
|
|
63
|
+
token: string;
|
|
64
|
+
knownOrganizationId?: string;
|
|
65
|
+
knownProjectId?: string;
|
|
66
|
+
logger: Logger;
|
|
67
|
+
}): Promise<{
|
|
68
|
+
scopeType: ScopeType;
|
|
69
|
+
scopeId: string;
|
|
70
|
+
displayName: string;
|
|
71
|
+
stackId: string;
|
|
72
|
+
stackName: string;
|
|
33
73
|
}>;
|
|
@@ -3,6 +3,60 @@ import { createEmptyStack, listStacks } from '../../actions/blueprints/stacks.js
|
|
|
3
3
|
import { groupProjectsByOrganization } from '../../actions/sanity/projects.js';
|
|
4
4
|
import { styleText } from '../style-text.js';
|
|
5
5
|
import { niceId } from './presenters.js';
|
|
6
|
+
// --- Wizard infrastructure ---
|
|
7
|
+
export const BACK = Symbol('back');
|
|
8
|
+
/**
|
|
9
|
+
* Runs a sequence of prompt steps with back-navigation support.
|
|
10
|
+
* Each step can return BACK to go to the previous non-skipped step.
|
|
11
|
+
* When going back, stale results from the current step onward are cleared.
|
|
12
|
+
*/
|
|
13
|
+
async function runWizard(steps) {
|
|
14
|
+
const results = {};
|
|
15
|
+
let i = 0;
|
|
16
|
+
while (i < steps.length) {
|
|
17
|
+
const step = steps[i];
|
|
18
|
+
if (step.shouldSkip?.(results)) {
|
|
19
|
+
i++;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const answer = await step.prompt(results);
|
|
23
|
+
if (answer === BACK) {
|
|
24
|
+
if (i === 0) {
|
|
25
|
+
// Cannot go back from the first step; re-prompt it
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
// Walk back to the previous non-skipped step
|
|
29
|
+
do {
|
|
30
|
+
i--;
|
|
31
|
+
} while (i > 0 && steps[i].shouldSkip?.(results));
|
|
32
|
+
// Erase 2 lines: the "← Back" answer line and the previous step's
|
|
33
|
+
// answered prompt. Back is only offered on steps where the previous
|
|
34
|
+
// step rendered a prompt, so this is always correct.
|
|
35
|
+
process.stdout.write('\x1b[1A\x1b[2K\x1b[1A\x1b[2K');
|
|
36
|
+
// Clear stale results from this step forward
|
|
37
|
+
for (let j = i; j < steps.length; j++) {
|
|
38
|
+
delete results[steps[j].key];
|
|
39
|
+
}
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
results[step.key] = answer;
|
|
43
|
+
i++;
|
|
44
|
+
}
|
|
45
|
+
return results;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Appends a "Back" choice to a list of select choices.
|
|
49
|
+
* Skipped for the first step in a wizard (nowhere to go back to).
|
|
50
|
+
*/
|
|
51
|
+
function withBackChoice(choices, allowBack) {
|
|
52
|
+
if (!allowBack)
|
|
53
|
+
return choices;
|
|
54
|
+
return [
|
|
55
|
+
...choices,
|
|
56
|
+
{ name: styleText('dim', '\u2190 Back'), value: BACK, description: 'Return to previous step' },
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
// --- Standalone prompts ---
|
|
6
60
|
export async function promptForBlueprintType() {
|
|
7
61
|
return await select({
|
|
8
62
|
message: 'Choose a Blueprint file type:',
|
|
@@ -14,60 +68,117 @@ export async function promptForBlueprintType() {
|
|
|
14
68
|
default: 'ts',
|
|
15
69
|
});
|
|
16
70
|
}
|
|
71
|
+
// --- Composable prompt functions for scope selection ---
|
|
17
72
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* @returns The selected project, with the projectId and displayName
|
|
21
|
-
* @throws {Error} If the user does not have any projects or if the API call fails
|
|
73
|
+
* Fetches organizations and prompts the user to pick one (if multiple).
|
|
74
|
+
* Returns the selected organization along with its projects.
|
|
22
75
|
*/
|
|
23
|
-
export async function
|
|
76
|
+
export async function promptForOrganization({ token, knownOrganizationId, logger, allowBack = false, }) {
|
|
77
|
+
const spinner = logger.ora('Loading organizations...').start();
|
|
24
78
|
const { ok, error, organizations } = await groupProjectsByOrganization({ token, logger });
|
|
79
|
+
spinner.stop();
|
|
25
80
|
if (!ok) {
|
|
26
|
-
throw new Error(error ?? 'Unknown error listing
|
|
81
|
+
throw new Error(error ?? 'Unknown error listing organizations');
|
|
27
82
|
}
|
|
28
83
|
if (organizations.length === 0) {
|
|
29
|
-
throw new Error('No Sanity
|
|
84
|
+
throw new Error('No Sanity organizations found. Use `npx sanity init` to create a project.');
|
|
30
85
|
}
|
|
31
|
-
let
|
|
86
|
+
let picked;
|
|
87
|
+
let wasPrompted = false;
|
|
32
88
|
if (organizations.length > 1) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
89
|
+
wasPrompted = true;
|
|
90
|
+
const orgChoices = organizations.map(({ organization, projects }) => {
|
|
91
|
+
const projectCount = projects?.length ?? 0;
|
|
92
|
+
return {
|
|
93
|
+
name: `"${organization.name}" ${niceId(organization.id)}`,
|
|
94
|
+
value: organization.id,
|
|
95
|
+
description: `${projectCount} ${projectCount === 1 ? 'project' : 'projects'}`,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
38
98
|
const pickedOrgId = await select({
|
|
39
|
-
message: 'Which
|
|
40
|
-
choices: orgChoices,
|
|
99
|
+
message: 'Which organization would you like to use?',
|
|
100
|
+
choices: withBackChoice(orgChoices, allowBack),
|
|
41
101
|
default: knownOrganizationId,
|
|
42
102
|
});
|
|
103
|
+
if (pickedOrgId === BACK)
|
|
104
|
+
return BACK;
|
|
43
105
|
const pickedOrg = organizations.find((o) => o.organization.id === pickedOrgId);
|
|
44
|
-
|
|
106
|
+
if (!pickedOrg) {
|
|
107
|
+
throw new Error('No organization found with the given ID');
|
|
108
|
+
}
|
|
109
|
+
picked = pickedOrg;
|
|
45
110
|
}
|
|
46
111
|
else {
|
|
47
|
-
|
|
112
|
+
picked = organizations[0];
|
|
113
|
+
logger(`Using organization "${picked.organization.name}" ${niceId(picked.organization.id)}`);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
organization: picked.organization,
|
|
117
|
+
projects: picked.projects ?? [],
|
|
118
|
+
wasPrompted,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Asks the user whether to scope a Blueprint to an organization or a project.
|
|
123
|
+
*/
|
|
124
|
+
export async function promptForScope({ allowBack = false, hasProjects = true, } = {}) {
|
|
125
|
+
const choices = [
|
|
126
|
+
{
|
|
127
|
+
name: 'Organization',
|
|
128
|
+
value: 'organization',
|
|
129
|
+
description: 'Manage resources across all projects in the organization',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'Project',
|
|
133
|
+
value: 'project',
|
|
134
|
+
description: 'Manage resources within a single project',
|
|
135
|
+
disabled: hasProjects ? false : '(no projects in this organization)',
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
const answer = await select({
|
|
139
|
+
message: 'How would you like to scope your Blueprint?',
|
|
140
|
+
choices: withBackChoice(choices, allowBack),
|
|
141
|
+
default: 'organization',
|
|
142
|
+
});
|
|
143
|
+
return answer;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Prompts the user to pick a project from a pre-fetched list of projects.
|
|
147
|
+
* Use after promptForOrganization when the user chose project scope.
|
|
148
|
+
*/
|
|
149
|
+
export async function promptForProjectInOrg({ projects, knownProjectId, logger, allowBack = false, }) {
|
|
150
|
+
if (projects.length === 0) {
|
|
151
|
+
throw new Error('No projects found in this organization.');
|
|
48
152
|
}
|
|
49
153
|
const projectChoices = projects.map(({ displayName, id: projectId }) => ({
|
|
50
154
|
name: `"${displayName}" ${niceId(projectId)}`,
|
|
51
155
|
value: projectId,
|
|
52
156
|
}));
|
|
53
157
|
let pickedProjectId;
|
|
54
|
-
if (projectChoices.length === 1) {
|
|
158
|
+
if (projectChoices.length === 1 && !allowBack) {
|
|
159
|
+
// Single-project confirm only when not in wizard (wizard has Back for navigation)
|
|
55
160
|
const onlyProject = projectChoices[0];
|
|
56
161
|
const confirmed = await confirm({
|
|
57
|
-
message: `Found 1 project. Use ${onlyProject.name}?`,
|
|
162
|
+
message: `Found 1 project in this organization. Use ${onlyProject.name}?`,
|
|
58
163
|
default: true,
|
|
59
164
|
});
|
|
60
|
-
if (confirmed)
|
|
165
|
+
if (confirmed) {
|
|
61
166
|
pickedProjectId = onlyProject.value;
|
|
62
|
-
|
|
167
|
+
logger?.(`Using project ${onlyProject.name}`);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
63
170
|
throw new Error('No project selected');
|
|
171
|
+
}
|
|
64
172
|
}
|
|
65
173
|
else {
|
|
66
|
-
|
|
67
|
-
message: 'Choose a Sanity
|
|
68
|
-
choices: projectChoices,
|
|
174
|
+
const answer = await select({
|
|
175
|
+
message: 'Choose a Sanity project:',
|
|
176
|
+
choices: withBackChoice(projectChoices, allowBack),
|
|
69
177
|
default: knownProjectId,
|
|
70
178
|
});
|
|
179
|
+
if (answer === BACK)
|
|
180
|
+
return BACK;
|
|
181
|
+
pickedProjectId = answer;
|
|
71
182
|
}
|
|
72
183
|
const pickedProject = projects.find((p) => p.id === pickedProjectId);
|
|
73
184
|
if (!pickedProject)
|
|
@@ -75,44 +186,69 @@ export async function promptForProject({ token, knownOrganizationId, knownProjec
|
|
|
75
186
|
return { projectId: pickedProject.id, displayName: pickedProject.displayName };
|
|
76
187
|
}
|
|
77
188
|
/**
|
|
78
|
-
* Prompt the user for a Stack
|
|
79
|
-
* Can
|
|
80
|
-
* @param projectId - The ID of the Project
|
|
81
|
-
* @param token - The Sanity API token
|
|
82
|
-
* @returns The selected Stack ID
|
|
83
|
-
* @throws {Error} If the user does not have any Stacks or if the API call fails
|
|
189
|
+
* Prompt the user for a Stack after scope has been determined.
|
|
190
|
+
* Can create a new Stack or select an existing one.
|
|
84
191
|
*/
|
|
85
|
-
export async function promptForStack({
|
|
86
|
-
|
|
192
|
+
export async function promptForStack({ scopeType, scopeId, token, logger, allowBack = false, }) {
|
|
193
|
+
if (!scopeId) {
|
|
194
|
+
throw new Error(`Cannot list Stacks: no ${scopeType} ID provided. ` +
|
|
195
|
+
'Provide --project-id or --organization-id, or select a scope in the wizard.');
|
|
196
|
+
}
|
|
197
|
+
const spinner = logger.ora('Loading Stacks...').start();
|
|
198
|
+
const { ok: stacksOk, error: stacksErr, stacks, } = await listStacks({ token, scopeType, scopeId }, logger);
|
|
199
|
+
spinner.stop();
|
|
87
200
|
if (!stacksOk) {
|
|
88
|
-
|
|
201
|
+
const scopeLabel = scopeType === 'organization' ? 'organization' : 'project';
|
|
202
|
+
throw new Error(stacksErr || `Failed to list Stacks for ${scopeLabel} "${scopeId}".`);
|
|
89
203
|
}
|
|
90
204
|
const NEW_STACK_ID = 'new';
|
|
91
205
|
let pickedStackId = NEW_STACK_ID;
|
|
206
|
+
if (stacks.length === 0) {
|
|
207
|
+
logger('No existing Stacks found. Creating a new one.');
|
|
208
|
+
}
|
|
92
209
|
if (stacks.length > 0) {
|
|
93
|
-
const stackChoices = [
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
210
|
+
const stackChoices = [
|
|
211
|
+
new Separator(styleText('underline', 'Create a new Stack:')),
|
|
212
|
+
{ name: styleText('bold', 'New Stack'), value: NEW_STACK_ID },
|
|
213
|
+
new Separator(styleText('underline', 'Use an existing Stack:')),
|
|
214
|
+
...stacks.map((s) => ({
|
|
215
|
+
name: `"${s.name}" ${niceId(s.id)} ${styleText('dim', `(${s.resourceCount} ${s.resourceCount === 1 ? 'resource' : 'resources'})`)}`,
|
|
216
|
+
value: s.id,
|
|
217
|
+
})),
|
|
218
|
+
];
|
|
219
|
+
if (allowBack) {
|
|
220
|
+
stackChoices.push(new Separator(), {
|
|
221
|
+
name: styleText('dim', '\u2190 Back'),
|
|
222
|
+
value: BACK,
|
|
223
|
+
description: 'Return to previous step',
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
const answer = await select({
|
|
102
227
|
message: 'Select a deployment Stack:',
|
|
103
228
|
choices: stackChoices,
|
|
104
229
|
default: NEW_STACK_ID,
|
|
105
230
|
});
|
|
231
|
+
if (answer === BACK)
|
|
232
|
+
return BACK;
|
|
233
|
+
pickedStackId = answer;
|
|
234
|
+
}
|
|
235
|
+
else if (allowBack) {
|
|
236
|
+
const proceed = await select({
|
|
237
|
+
message: 'No existing Stacks. Create a new one?',
|
|
238
|
+
choices: withBackChoice([{ name: 'Yes, create a new Stack', value: 'yes' }], allowBack),
|
|
239
|
+
});
|
|
240
|
+
if (proceed === BACK)
|
|
241
|
+
return BACK;
|
|
106
242
|
}
|
|
107
243
|
if (pickedStackId === NEW_STACK_ID) {
|
|
108
244
|
const stackName = await input({
|
|
109
245
|
message: 'Enter a name for your new Stack:',
|
|
110
|
-
validate: (
|
|
246
|
+
validate: (val) => val.length > 0 || 'Stack name is required',
|
|
111
247
|
});
|
|
112
248
|
const stack = await createEmptyStack({
|
|
113
249
|
token,
|
|
114
|
-
scopeType
|
|
115
|
-
scopeId
|
|
250
|
+
scopeType,
|
|
251
|
+
scopeId,
|
|
116
252
|
name: stackName,
|
|
117
253
|
logger,
|
|
118
254
|
});
|
|
@@ -123,3 +259,93 @@ export async function promptForStack({ projectId, token, logger, }) {
|
|
|
123
259
|
throw new Error('Stack not found');
|
|
124
260
|
return { stackId: pickedStack.id, name: pickedStack.name };
|
|
125
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Full wizard: Organization -> Scope -> Project (if project-scoped) -> Stack.
|
|
264
|
+
* Supports back-navigation between steps.
|
|
265
|
+
*/
|
|
266
|
+
export async function runScopeAndStackWizard({ token, knownOrganizationId, knownProjectId, logger, }) {
|
|
267
|
+
const steps = [
|
|
268
|
+
{
|
|
269
|
+
key: 'organization',
|
|
270
|
+
prompt: async () => {
|
|
271
|
+
return await promptForOrganization({
|
|
272
|
+
token,
|
|
273
|
+
knownOrganizationId,
|
|
274
|
+
logger,
|
|
275
|
+
// First step -- no back
|
|
276
|
+
});
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
key: 'scopeType',
|
|
281
|
+
prompt: async (results) => {
|
|
282
|
+
const orgResult = results.organization;
|
|
283
|
+
return await promptForScope({
|
|
284
|
+
allowBack: orgResult.wasPrompted,
|
|
285
|
+
hasProjects: orgResult.projects.length > 0,
|
|
286
|
+
});
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
key: 'project',
|
|
291
|
+
shouldSkip: (results) => results.scopeType === 'organization',
|
|
292
|
+
prompt: async (results) => {
|
|
293
|
+
const orgResult = results.organization;
|
|
294
|
+
if (!orgResult)
|
|
295
|
+
throw new Error('Organization not resolved');
|
|
296
|
+
return await promptForProjectInOrg({
|
|
297
|
+
projects: orgResult.projects,
|
|
298
|
+
knownProjectId,
|
|
299
|
+
logger,
|
|
300
|
+
allowBack: true,
|
|
301
|
+
});
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
key: 'stack',
|
|
306
|
+
prompt: async (results) => {
|
|
307
|
+
const scopeType = results.scopeType;
|
|
308
|
+
if (!scopeType)
|
|
309
|
+
throw new Error('Scope type not resolved');
|
|
310
|
+
let scopeId;
|
|
311
|
+
if (scopeType === 'organization') {
|
|
312
|
+
const orgResult = results.organization;
|
|
313
|
+
scopeId = orgResult.organization.id;
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
const project = results.project;
|
|
317
|
+
scopeId = project.projectId;
|
|
318
|
+
}
|
|
319
|
+
return await promptForStack({
|
|
320
|
+
scopeType,
|
|
321
|
+
scopeId,
|
|
322
|
+
token,
|
|
323
|
+
logger,
|
|
324
|
+
allowBack: true,
|
|
325
|
+
});
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
];
|
|
329
|
+
const result = await runWizard(steps);
|
|
330
|
+
const orgResult = result.organization;
|
|
331
|
+
const scopeType = result.scopeType;
|
|
332
|
+
const stack = result.stack;
|
|
333
|
+
let scopeId;
|
|
334
|
+
let displayName;
|
|
335
|
+
if (scopeType === 'organization') {
|
|
336
|
+
scopeId = orgResult.organization.id;
|
|
337
|
+
displayName = orgResult.organization.name;
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
const project = result.project;
|
|
341
|
+
scopeId = project.projectId;
|
|
342
|
+
displayName = project.displayName;
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
scopeType,
|
|
346
|
+
scopeId,
|
|
347
|
+
displayName,
|
|
348
|
+
stackId: stack.stackId,
|
|
349
|
+
stackName: stack.name,
|
|
350
|
+
};
|
|
351
|
+
}
|