@sanity/runtime-cli 14.13.4 → 15.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.
- 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 +15 -0
- package/dist/cores/blueprints/mint-deploy-token.js +111 -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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { patchConfigFile, writeConfigFile, } from '../../actions/blueprints/config.js';
|
|
2
2
|
import { resolveStackIdByNameOrId } from '../../actions/blueprints/stacks.js';
|
|
3
3
|
import { filePathRelativeToCwd, labeledId, warn } from '../../utils/display/presenters.js';
|
|
4
|
-
import {
|
|
4
|
+
import { runScopeAndStackWizard } from '../../utils/display/prompt.js';
|
|
5
5
|
import { styleText } from '../../utils/style-text.js';
|
|
6
6
|
export async function blueprintConfigCore(options) {
|
|
7
7
|
const { bin = 'sanity', blueprint, log, token, flags } = options;
|
|
@@ -77,35 +77,29 @@ export async function blueprintConfigCore(options) {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
-
// prompt for values interactively
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return { success: false, error: 'Project ID is required.' };
|
|
93
|
-
let updatedStackId;
|
|
94
|
-
if (flagStack) {
|
|
95
|
-
updatedStackId = await resolveStackIdByNameOrId(flagStack, { token, scopeType: 'project', scopeId: updatedProjectId }, log);
|
|
96
|
-
}
|
|
97
|
-
if (!updatedStackId) {
|
|
98
|
-
const pickedStack = await promptForStack({ projectId: updatedProjectId, token, logger: log });
|
|
99
|
-
updatedStackId = pickedStack.stackId;
|
|
100
|
-
}
|
|
101
|
-
if (!updatedStackId)
|
|
102
|
-
return { success: false, error: 'Stack is required.' };
|
|
80
|
+
// prompt for values interactively via wizard: org -> scope -> project (if needed) -> stack
|
|
81
|
+
log('');
|
|
82
|
+
const knownOrganizationId = configScopeType === 'organization' ? configScopeId : undefined;
|
|
83
|
+
const knownProjectId = configScopeType === 'project' ? configScopeId : undefined;
|
|
84
|
+
const wizardResult = await runScopeAndStackWizard({
|
|
85
|
+
token,
|
|
86
|
+
knownOrganizationId,
|
|
87
|
+
knownProjectId,
|
|
88
|
+
logger: log,
|
|
89
|
+
});
|
|
90
|
+
if (!wizardResult.scopeId)
|
|
91
|
+
return { success: false, error: 'Scope selection is required.' };
|
|
103
92
|
try {
|
|
104
|
-
// update or create config JSON
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
93
|
+
// update or create config JSON with correct scope fields
|
|
94
|
+
const configUpdate = { stackId: wizardResult.stackId };
|
|
95
|
+
if (wizardResult.scopeType === 'organization') {
|
|
96
|
+
configUpdate.organizationId = wizardResult.scopeId;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
configUpdate.projectId = wizardResult.scopeId;
|
|
100
|
+
}
|
|
101
|
+
const newConfig = writeConfigFile(blueprintFilePath, configUpdate);
|
|
102
|
+
log('');
|
|
109
103
|
printConfig({ configLabel: hasConfigFile ? 'Updated' : 'New', log, config: newConfig });
|
|
110
104
|
return { success: true, json: { config: newConfig } };
|
|
111
105
|
}
|
|
@@ -3,18 +3,24 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { confirm } from '@inquirer/prompts';
|
|
4
4
|
import { findBlueprintFile, readLocalBlueprint, writeBlueprintToDisk, } from '../../actions/blueprints/blueprint.js';
|
|
5
5
|
import { writeConfigFile } from '../../actions/blueprints/config.js';
|
|
6
|
-
import { createEmptyStack } from '../../actions/blueprints/stacks.js';
|
|
6
|
+
import { createEmptyStack, getStack } from '../../actions/blueprints/stacks.js';
|
|
7
7
|
import { writeGitignoreFile } from '../../actions/git.js';
|
|
8
8
|
import { writeOrUpdateNodeDependency } from '../../actions/node.js';
|
|
9
9
|
import { verifyExampleExists, writeExample } from '../../actions/sanity/examples.js';
|
|
10
10
|
import { getProject } from '../../actions/sanity/projects.js';
|
|
11
11
|
import { BLUEPRINT_CONFIG_DIR, BLUEPRINT_CONFIG_FILE } from '../../config.js';
|
|
12
12
|
import { check, filePathRelativeToCwd, labeledId, warn } from '../../utils/display/presenters.js';
|
|
13
|
-
import { promptForBlueprintType,
|
|
13
|
+
import { promptForBlueprintType, promptForOrganization, promptForProjectInOrg, promptForStack, runScopeAndStackWizard, } from '../../utils/display/prompt.js';
|
|
14
14
|
import { styleText } from '../../utils/style-text.js';
|
|
15
15
|
import { blueprintConfigCore } from './config.js';
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/** Derive scope type and ID from mutually exclusive flag values. */
|
|
17
|
+
function resolveScopeFromFlags(projectId, organizationId) {
|
|
18
|
+
if (projectId)
|
|
19
|
+
return { scopeType: 'project', scopeId: projectId };
|
|
20
|
+
if (organizationId)
|
|
21
|
+
return { scopeType: 'organization', scopeId: organizationId };
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
18
24
|
export async function blueprintInitCore(options) {
|
|
19
25
|
const { bin = 'sanity', log, token, knownProjectId, args, flags, validateResources } = options;
|
|
20
26
|
const { dir: flagDir, example: flagExample, 'blueprint-type': flagBlueprintType, 'project-id': flagProjectId, 'organization-id': flagOrganizationId, 'stack-id': flagStackId, 'stack-name': flagStackName, verbose = false, } = flags;
|
|
@@ -87,23 +93,15 @@ async function handleExistingBlueprint(options) {
|
|
|
87
93
|
error: 'Flag --example cannot be used with an existing Blueprint.',
|
|
88
94
|
};
|
|
89
95
|
}
|
|
90
|
-
//
|
|
91
|
-
// --stack-name means they want a new stack created
|
|
96
|
+
// --stack-name with a scope flag means create a new stack before configuring
|
|
92
97
|
let resolvedStackId = flagStackId;
|
|
93
98
|
if (!resolvedStackId && flagStackName) {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
scopeType = SCOPE_PROJECT;
|
|
98
|
-
else if (flagOrganizationId)
|
|
99
|
-
scopeType = SCOPE_ORGANIZATION;
|
|
100
|
-
if (scopeType && scopeId) {
|
|
101
|
-
// have what we need to create a new stack
|
|
102
|
-
log(`\nCreating new Stack "${flagStackName}" scoped to ${labeledId(scopeType, scopeId)}`);
|
|
99
|
+
const scope = resolveScopeFromFlags(flagProjectId, flagOrganizationId);
|
|
100
|
+
if (scope) {
|
|
101
|
+
log(`\nCreating new Stack "${flagStackName}" scoped to ${labeledId(scope.scopeType, scope.scopeId)}`);
|
|
103
102
|
const stack = await createEmptyStack({
|
|
104
103
|
token,
|
|
105
|
-
|
|
106
|
-
scopeId,
|
|
104
|
+
...scope,
|
|
107
105
|
name: flagStackName,
|
|
108
106
|
logger: log,
|
|
109
107
|
});
|
|
@@ -168,8 +166,11 @@ export function validateFlags(flags) {
|
|
|
168
166
|
if (stackId && stackName) {
|
|
169
167
|
return { success: false, error: 'Cannot specify both --stack-id and --stack-name' };
|
|
170
168
|
}
|
|
171
|
-
if (organizationId && projectId) {
|
|
172
|
-
return {
|
|
169
|
+
if ((stackId || stackName) && !organizationId && !projectId) {
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
error: `${stackId ? '--stack-id' : '--stack-name'} requires --project-id or --organization-id`,
|
|
173
|
+
};
|
|
173
174
|
}
|
|
174
175
|
return null;
|
|
175
176
|
}
|
|
@@ -184,10 +185,20 @@ async function handleExampleInitialization(options) {
|
|
|
184
185
|
if (!exampleExists) {
|
|
185
186
|
return { success: false, error: `Blueprint example "${exampleName}" does not exist.` };
|
|
186
187
|
}
|
|
187
|
-
|
|
188
|
+
let resolvedProjectId = projectId;
|
|
189
|
+
if (!resolvedProjectId) {
|
|
190
|
+
// examples are project-scoped; use the composable prompts to pick an org then a project
|
|
191
|
+
const orgResult = await promptForOrganization({ token, logger: log });
|
|
192
|
+
if (typeof orgResult === 'symbol')
|
|
193
|
+
throw new Error('Unexpected back navigation');
|
|
194
|
+
const picked = await promptForProjectInOrg({ projects: orgResult.projects, logger: log });
|
|
195
|
+
if (typeof picked === 'symbol')
|
|
196
|
+
throw new Error('Unexpected back navigation');
|
|
197
|
+
resolvedProjectId = picked.projectId;
|
|
198
|
+
}
|
|
188
199
|
const stack = await createEmptyStack({
|
|
189
200
|
token,
|
|
190
|
-
scopeType:
|
|
201
|
+
scopeType: 'project',
|
|
191
202
|
scopeId: resolvedProjectId,
|
|
192
203
|
name: `example-${exampleName}`,
|
|
193
204
|
logger: log,
|
|
@@ -225,69 +236,81 @@ async function handleExampleInitialization(options) {
|
|
|
225
236
|
}
|
|
226
237
|
export async function resolveScopeAndStack(params) {
|
|
227
238
|
const { projectId, organizationId, stackId, stackName, knownProjectId, log, token } = params;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (
|
|
231
|
-
scopeType =
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
239
|
+
const flagScope = resolveScopeFromFlags(projectId, organizationId);
|
|
240
|
+
// --- Branch 1: Scope provided via flags ---
|
|
241
|
+
if (flagScope) {
|
|
242
|
+
const { scopeType, scopeId } = flagScope;
|
|
243
|
+
let resolvedStackId = stackId;
|
|
244
|
+
// --stack-name: create a new stack
|
|
245
|
+
if (!resolvedStackId && stackName) {
|
|
246
|
+
log(`\nCreating new Stack "${stackName}" scoped to ${labeledId(scopeType, scopeId)}`);
|
|
247
|
+
const stack = await createEmptyStack({
|
|
248
|
+
token,
|
|
249
|
+
scopeType,
|
|
250
|
+
scopeId,
|
|
251
|
+
name: stackName,
|
|
252
|
+
logger: log,
|
|
253
|
+
});
|
|
254
|
+
resolvedStackId = stack.id;
|
|
255
|
+
}
|
|
256
|
+
// --stack-id: validate it exists
|
|
257
|
+
if (resolvedStackId && resolvedStackId === stackId) {
|
|
258
|
+
const spinner = log.ora('Validating Stack...').start();
|
|
259
|
+
const { ok, error } = await getStack({
|
|
260
|
+
stackId: resolvedStackId,
|
|
261
|
+
auth: { token, scopeType, scopeId },
|
|
262
|
+
logger: log,
|
|
263
|
+
});
|
|
264
|
+
spinner.stop();
|
|
265
|
+
if (!ok) {
|
|
266
|
+
throw new Error(error || `Stack "${resolvedStackId}" not found for ${scopeType} "${scopeId}".`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// No stack flag: prompt for stack selection
|
|
270
|
+
if (!resolvedStackId) {
|
|
271
|
+
log('\nBlueprints are deployed to a "Stack".');
|
|
272
|
+
const stackResult = await promptForStack({ scopeType, scopeId, token, logger: log });
|
|
273
|
+
if (typeof stackResult === 'symbol')
|
|
274
|
+
throw new Error('Unexpected back navigation');
|
|
275
|
+
resolvedStackId = stackResult.stackId;
|
|
276
|
+
}
|
|
277
|
+
return { scopeType, scopeId, stackId: resolvedStackId };
|
|
237
278
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
279
|
+
// --- Branch 2: No scope flags -- interactive resolution ---
|
|
280
|
+
log('\nBlueprints are scoped to an organization or project.');
|
|
281
|
+
log(styleText('dim', 'Scope determines which resources your Blueprint can manage.\n' +
|
|
282
|
+
'Organization scope covers all projects; project scope is limited to one.'));
|
|
283
|
+
// Offer the CLI's configured project as a shortcut
|
|
284
|
+
if (knownProjectId) {
|
|
285
|
+
const { ok, project } = await getProject({
|
|
244
286
|
token,
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
name: stackName,
|
|
287
|
+
scopeId: knownProjectId,
|
|
288
|
+
scopeType: 'project',
|
|
248
289
|
logger: log,
|
|
249
290
|
});
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
// If we have a CLI project ID, offer it as a suggestion
|
|
255
|
-
if (knownProjectId) {
|
|
256
|
-
const { ok, project } = await getProject({
|
|
257
|
-
token,
|
|
258
|
-
scopeId: knownProjectId,
|
|
259
|
-
scopeType: 'project',
|
|
260
|
-
logger: log,
|
|
291
|
+
if (ok && project) {
|
|
292
|
+
const useCliProject = await confirm({
|
|
293
|
+
message: `The CLI is configured to use "${project.displayName}" (${knownProjectId}). Use this for the blueprint?`,
|
|
294
|
+
default: true,
|
|
261
295
|
});
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
296
|
+
if (useCliProject) {
|
|
297
|
+
// Scope locked to project; just need a stack
|
|
298
|
+
log('\nBlueprints are deployed to a "Stack".');
|
|
299
|
+
const stackResult = await promptForStack({
|
|
300
|
+
scopeType: 'project',
|
|
301
|
+
scopeId: knownProjectId,
|
|
302
|
+
token,
|
|
303
|
+
logger: log,
|
|
266
304
|
});
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
305
|
+
if (typeof stackResult === 'symbol')
|
|
306
|
+
throw new Error('Unexpected back navigation');
|
|
307
|
+
return { scopeType: 'project', scopeId: knownProjectId, stackId: stackResult.stackId };
|
|
271
308
|
}
|
|
272
309
|
}
|
|
273
|
-
// If still no scope (no knownProjectId, lookup failed, or user declined), prompt for selection
|
|
274
|
-
if (!scopeId) {
|
|
275
|
-
log('Select a project:');
|
|
276
|
-
const pickedProject = await promptForProject({ token, logger: log });
|
|
277
|
-
scopeType = SCOPE_PROJECT;
|
|
278
|
-
scopeId = pickedProject.projectId;
|
|
279
|
-
}
|
|
280
310
|
}
|
|
281
|
-
if
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
resolvedStackId = stackId;
|
|
285
|
-
}
|
|
286
|
-
return {
|
|
287
|
-
scopeType,
|
|
288
|
-
scopeId,
|
|
289
|
-
stackId: resolvedStackId,
|
|
290
|
-
};
|
|
311
|
+
// Full wizard: org -> scope -> project (if needed) -> stack
|
|
312
|
+
const wizard = await runScopeAndStackWizard({ token, logger: log });
|
|
313
|
+
return { scopeType: wizard.scopeType, scopeId: wizard.scopeId, stackId: wizard.stackId };
|
|
291
314
|
}
|
|
292
315
|
export async function determineBlueprintExtension(params) {
|
|
293
316
|
const { requestedType, blueprintDir, log } = params;
|
|
@@ -329,7 +352,7 @@ export async function createBlueprintFiles(params) {
|
|
|
329
352
|
log(check(`${styleText('bold', 'Created Blueprint:')} ${displayPath}/${blueprintFileName}`));
|
|
330
353
|
writeConfigFile(blueprintFilePath, {
|
|
331
354
|
stackId,
|
|
332
|
-
...(scopeType ===
|
|
355
|
+
...(scopeType === 'organization' ? { organizationId: scopeId } : { projectId: scopeId }),
|
|
333
356
|
});
|
|
334
357
|
log(check(`${styleText('bold', 'Added configuration:')} ${displayPath}/${BLUEPRINT_CONFIG_DIR}/${BLUEPRINT_CONFIG_FILE}`));
|
|
335
358
|
const gitignoreResult = writeGitignoreFile(blueprintFilePath);
|
|
@@ -7,6 +7,9 @@ export interface BlueprintLogsOptions extends CoreConfig {
|
|
|
7
7
|
flags: {
|
|
8
8
|
watch?: boolean;
|
|
9
9
|
verbose?: boolean;
|
|
10
|
+
limit?: number;
|
|
11
|
+
since?: string;
|
|
12
|
+
before?: string;
|
|
10
13
|
};
|
|
11
14
|
}
|
|
12
15
|
export declare function blueprintLogsCore(options: BlueprintLogsOptions): Promise<CoreResult>;
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { getLogs
|
|
1
|
+
import { getLogs } from '../../actions/blueprints/logs.js';
|
|
2
2
|
import { setupLogPolling } from '../../actions/blueprints/logs-polling.js';
|
|
3
3
|
import { formatTitle } from '../../utils/display/blueprints-formatting.js';
|
|
4
4
|
import { formatLogEntry, formatLogs } from '../../utils/display/logs-formatting.js';
|
|
5
5
|
import { niceId } from '../../utils/display/presenters.js';
|
|
6
6
|
import { styleText } from '../../utils/style-text.js';
|
|
7
|
+
/** Number of recent logs to show before entering watch mode */
|
|
8
|
+
const WATCH_PREFETCH_LIMIT = 10;
|
|
7
9
|
export async function blueprintLogsCore(options) {
|
|
8
10
|
const { log, auth, stackId, deployedStack, flags } = options;
|
|
9
|
-
const { watch = false, verbose = false } = flags;
|
|
11
|
+
const { watch = false, verbose = false, limit, since, before } = flags;
|
|
10
12
|
const spinner = log.ora(`Fetching recent logs for Stack deployment ${niceId(stackId)}`).start();
|
|
11
13
|
try {
|
|
12
14
|
if (watch) {
|
|
13
|
-
const { ok, logs, error } = await getLogs({ stackId }, auth, log);
|
|
15
|
+
const { ok, logs, error } = await getLogs({ stackId, limit: WATCH_PREFETCH_LIMIT }, auth, log);
|
|
14
16
|
if (!ok) {
|
|
15
17
|
spinner.fail(`${styleText('red', 'Failed')} to retrieve logs`);
|
|
16
18
|
log.error(`Error: ${error || 'Unknown error'}`);
|
|
@@ -20,9 +22,10 @@ export async function blueprintLogsCore(options) {
|
|
|
20
22
|
log(`${formatTitle('Blueprint', deployedStack.name)} ${niceId(stackId)} logs`);
|
|
21
23
|
if (logs.length > 0) {
|
|
22
24
|
log('\nMost recent logs:');
|
|
23
|
-
|
|
25
|
+
// API returns newest first; display in chronological order
|
|
26
|
+
const orderedLogs = [...logs].reverse();
|
|
24
27
|
let previousLog;
|
|
25
|
-
for (const logEntry of
|
|
28
|
+
for (const logEntry of orderedLogs) {
|
|
26
29
|
log(formatLogEntry(logEntry, verbose, previousLog));
|
|
27
30
|
previousLog = logEntry;
|
|
28
31
|
}
|
|
@@ -39,14 +42,14 @@ export async function blueprintLogsCore(options) {
|
|
|
39
42
|
showBanner: true,
|
|
40
43
|
verbose,
|
|
41
44
|
});
|
|
42
|
-
// Return a
|
|
45
|
+
// Return a never-resolving promise so the polling loop keeps running
|
|
43
46
|
return {
|
|
44
47
|
success: true,
|
|
45
48
|
streaming: new Promise(() => { }),
|
|
46
49
|
};
|
|
47
50
|
}
|
|
48
|
-
//
|
|
49
|
-
const { ok, logs, error } = await getLogs({ stackId }, auth, log);
|
|
51
|
+
// One-shot fetch (no watch)
|
|
52
|
+
const { ok, logs, error, hasMore } = await getLogs({ stackId, limit, before, after: since }, auth, log);
|
|
50
53
|
if (!ok) {
|
|
51
54
|
spinner.fail(`${styleText('red', 'Failed')} to retrieve Stack deployment logs`);
|
|
52
55
|
log.error(`Error: ${error || 'Unknown error'}`);
|
|
@@ -59,7 +62,10 @@ export async function blueprintLogsCore(options) {
|
|
|
59
62
|
spinner.succeed(`${formatTitle('Blueprint', deployedStack.name)} Logs`);
|
|
60
63
|
log(`Found ${styleText('bold', logs.length.toString())} log entries for Stack deployment ${niceId(stackId)}\n`);
|
|
61
64
|
log(formatLogs(logs, verbose));
|
|
62
|
-
|
|
65
|
+
if (hasMore) {
|
|
66
|
+
log(`\n${styleText('dim', 'More logs available. Narrow the range with --before <timestamp> or raise --limit (max 500).')}`);
|
|
67
|
+
}
|
|
68
|
+
return { success: true, json: { logs, hasMore } };
|
|
63
69
|
}
|
|
64
70
|
catch (err) {
|
|
65
71
|
spinner.fail('Failed to retrieve Stack deployment logs');
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AuthParams, ScopeType } from '../../utils/types.js';
|
|
2
|
+
import type { CoreConfig, CoreResult } from '../index.js';
|
|
3
|
+
export interface BlueprintMintDeployTokenOptions extends CoreConfig {
|
|
4
|
+
auth: AuthParams;
|
|
5
|
+
scopeType: ScopeType;
|
|
6
|
+
scopeId: string;
|
|
7
|
+
flags: {
|
|
8
|
+
label?: string;
|
|
9
|
+
print?: boolean;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
verbose?: boolean;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export declare function defaultLabel(scopeType: ScopeType): string;
|
|
15
|
+
export declare function blueprintMintDeployTokenCore(options: BlueprintMintDeployTokenOptions): Promise<CoreResult>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { hostname } from 'node:os';
|
|
2
|
+
import { confirm, input, select } from '@inquirer/prompts';
|
|
3
|
+
import { createRobotToken } from '../../actions/sanity/access.js';
|
|
4
|
+
import { ClipboardUnavailableError, write as clipboardWrite } from '../../utils/clipboard.js';
|
|
5
|
+
import { styleText } from '../../utils/style-text.js';
|
|
6
|
+
const ROLE_BY_SCOPE = {
|
|
7
|
+
project: 'blueprints-deployer',
|
|
8
|
+
organization: 'blueprints-deployer-robot',
|
|
9
|
+
};
|
|
10
|
+
export function defaultLabel(scopeType) {
|
|
11
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
12
|
+
let host = 'unknown-host';
|
|
13
|
+
try {
|
|
14
|
+
host = hostname();
|
|
15
|
+
}
|
|
16
|
+
catch { }
|
|
17
|
+
return `blueprints-deployer @ ${host} ${date} (${scopeType})`;
|
|
18
|
+
}
|
|
19
|
+
export async function blueprintMintDeployTokenCore(options) {
|
|
20
|
+
const { log, auth, scopeType, scopeId, flags } = options;
|
|
21
|
+
const label = flags.label ?? defaultLabel(scopeType);
|
|
22
|
+
const roleName = ROLE_BY_SCOPE[scopeType];
|
|
23
|
+
const spinner = log.ora('Minting deploy token...').start();
|
|
24
|
+
const { ok, error, robot } = await createRobotToken({
|
|
25
|
+
auth,
|
|
26
|
+
resourceType: scopeType,
|
|
27
|
+
resourceId: scopeId,
|
|
28
|
+
body: {
|
|
29
|
+
label,
|
|
30
|
+
memberships: [{ resourceType: scopeType, resourceId: scopeId, roleNames: [roleName] }],
|
|
31
|
+
},
|
|
32
|
+
logger: log,
|
|
33
|
+
});
|
|
34
|
+
if (!ok || !robot) {
|
|
35
|
+
spinner.fail('Failed to mint deploy token');
|
|
36
|
+
const suggestions = [];
|
|
37
|
+
if (error && /access|permission|forbidden|role/i.test(error)) {
|
|
38
|
+
suggestions.push(scopeType === 'organization'
|
|
39
|
+
? 'Verify you have organization admin access.'
|
|
40
|
+
: 'Verify you have project admin access.');
|
|
41
|
+
}
|
|
42
|
+
return { success: false, error: error || 'Failed to mint deploy token', suggestions };
|
|
43
|
+
}
|
|
44
|
+
spinner.stop().clear();
|
|
45
|
+
if (flags.print) {
|
|
46
|
+
log(robot.token);
|
|
47
|
+
return { success: true, json: serializeRobot(robot) };
|
|
48
|
+
}
|
|
49
|
+
if (flags.json) {
|
|
50
|
+
return { success: true, json: serializeRobot(robot) };
|
|
51
|
+
}
|
|
52
|
+
log(styleText('green', `Minted "${robot.label}" (${robot.id})`));
|
|
53
|
+
if (robot.expiresAt) {
|
|
54
|
+
log(styleText('dim', `Expires: ${robot.expiresAt}`));
|
|
55
|
+
}
|
|
56
|
+
const action = await select({
|
|
57
|
+
message: 'What would you like to do with the token?',
|
|
58
|
+
choices: [
|
|
59
|
+
{ name: 'Copy to clipboard', value: 'copy' },
|
|
60
|
+
{ name: 'Print to terminal', value: 'print' },
|
|
61
|
+
{ name: 'Exit (token will be lost)', value: 'exit' },
|
|
62
|
+
],
|
|
63
|
+
default: 'copy',
|
|
64
|
+
});
|
|
65
|
+
if (action === 'copy') {
|
|
66
|
+
try {
|
|
67
|
+
await clipboardWrite(robot.token);
|
|
68
|
+
log(styleText('green', 'Token copied to clipboard.'));
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
const msg = err instanceof ClipboardUnavailableError
|
|
72
|
+
? err.message
|
|
73
|
+
: err instanceof Error
|
|
74
|
+
? err.message
|
|
75
|
+
: String(err);
|
|
76
|
+
log.warn(styleText('yellow', `Could not copy to clipboard: ${msg}`));
|
|
77
|
+
const printInstead = await confirm({
|
|
78
|
+
message: 'Print the token to the terminal instead?',
|
|
79
|
+
default: false,
|
|
80
|
+
});
|
|
81
|
+
if (printInstead) {
|
|
82
|
+
await printAndConfirm(log, robot.token);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
log(styleText('yellow', 'Token discarded. You can mint a new one anytime.'));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (action === 'print') {
|
|
90
|
+
await printAndConfirm(log, robot.token);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
log(styleText('yellow', 'Token discarded. You can mint a new one anytime.'));
|
|
94
|
+
}
|
|
95
|
+
return { success: true, json: serializeRobot(robot) };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Show the token inside an inquirer prompt and rely on `clearPromptOnDone`
|
|
99
|
+
* so inquirer wipes the entire rendered block (token included) on Enter.
|
|
100
|
+
*/
|
|
101
|
+
async function printAndConfirm(log, token) {
|
|
102
|
+
await input({
|
|
103
|
+
message: `\n${token}\n\nPress Enter once you have copied the token (it will be erased):`,
|
|
104
|
+
default: '',
|
|
105
|
+
theme: { prefix: '' },
|
|
106
|
+
}, { clearPromptOnDone: true });
|
|
107
|
+
log(styleText('dim', 'Token erased.'));
|
|
108
|
+
}
|
|
109
|
+
function serializeRobot(robot) {
|
|
110
|
+
return robot;
|
|
111
|
+
}
|
|
@@ -4,10 +4,23 @@ import { promoteStack } from '../../actions/blueprints/stacks.js';
|
|
|
4
4
|
import { niceId } from '../../utils/display/presenters.js';
|
|
5
5
|
export async function blueprintPromoteCore(options) {
|
|
6
6
|
const { bin = 'sanity', log, stackId, auth, flags, deployedStack, blueprint } = options;
|
|
7
|
+
const newStackName = flags['new-stack-name'];
|
|
8
|
+
if (newStackName && deployedStack.scopeType === 'organization') {
|
|
9
|
+
return {
|
|
10
|
+
success: false,
|
|
11
|
+
error: '--new-stack-name can only be used when promoting a project-scoped Stack to organization scope.',
|
|
12
|
+
suggestions: [
|
|
13
|
+
`Stack "${deployedStack.name}" ${niceId(deployedStack.id)} is already org-scoped.`,
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
7
17
|
let message = `"${deployedStack.name}" ${niceId(deployedStack.id)}`;
|
|
8
18
|
if (deployedStack.scopeType === 'organization') {
|
|
9
19
|
message = `Stack ${message} is already org-scoped. Promote again?`;
|
|
10
20
|
}
|
|
21
|
+
else if (newStackName) {
|
|
22
|
+
message = `Promote Stack ${message} from project to organization scope and rename to "${newStackName}"? You will need admin access to your organization.`;
|
|
23
|
+
}
|
|
11
24
|
else {
|
|
12
25
|
message = `Promote Stack ${message} from project to organization scope? You will need admin access to your organization.`;
|
|
13
26
|
}
|
|
@@ -19,10 +32,18 @@ export async function blueprintPromoteCore(options) {
|
|
|
19
32
|
}
|
|
20
33
|
try {
|
|
21
34
|
const spinner = log.ora('Promoting Stack...').start();
|
|
22
|
-
const { ok, error, stack } = await promoteStack({
|
|
35
|
+
const { ok, error, stack, response } = await promoteStack({
|
|
36
|
+
stackId,
|
|
37
|
+
auth,
|
|
38
|
+
logger: log,
|
|
39
|
+
name: newStackName,
|
|
40
|
+
});
|
|
23
41
|
if (!ok) {
|
|
24
42
|
spinner.fail('Failed to promote Stack');
|
|
25
43
|
const suggestions = [];
|
|
44
|
+
if (response?.status === 409) {
|
|
45
|
+
suggestions.push('A Stack with this name already exists in the target organization. Use --new-stack-name <name> to rename while promoting.');
|
|
46
|
+
}
|
|
26
47
|
if (error && /access|permission|forbidden/i.test(error)) {
|
|
27
48
|
suggestions.push('Verify you have organization admin access.');
|
|
28
49
|
}
|
|
@@ -5,9 +5,7 @@ import { checkbox, confirm, input, select } from '@inquirer/prompts';
|
|
|
5
5
|
import { highlight } from 'cardinal';
|
|
6
6
|
import { createFunctionResource } from '../../actions/blueprints/resources.js';
|
|
7
7
|
import { verifyExampleExists, writeExample } from '../../actions/sanity/examples.js';
|
|
8
|
-
import { EVENT_DOCUMENT_CREATE, EVENT_DOCUMENT_DELETE, EVENT_DOCUMENT_UPDATE, EVENT_MEDIA_LIBRARY_ASSET_CREATE, EVENT_MEDIA_LIBRARY_ASSET_DELETE, EVENT_MEDIA_LIBRARY_ASSET_UPDATE, EVENT_SYNC_TAG_INVALIDATE,
|
|
9
|
-
// EVENT_SCHEDULED,
|
|
10
|
-
FUNCTION_TYPES, LABEL_DOCUMENT_CREATE, LABEL_DOCUMENT_DELETE, LABEL_DOCUMENT_UPDATE, LABEL_MEDIA_LIBRARY_ASSET_CREATE, LABEL_MEDIA_LIBRARY_ASSET_DELETE, LABEL_MEDIA_LIBRARY_ASSET_UPDATE, LABEL_SCHEDULED, LABEL_SYNC_TAG_INVALIDATE, MAP_EVENT_TO_FUNCTION_TYPE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, } from '../../constants.js';
|
|
8
|
+
import { EVENT_DOCUMENT_CREATE, EVENT_DOCUMENT_DELETE, EVENT_DOCUMENT_UPDATE, EVENT_MEDIA_LIBRARY_ASSET_CREATE, EVENT_MEDIA_LIBRARY_ASSET_DELETE, EVENT_MEDIA_LIBRARY_ASSET_UPDATE, EVENT_SCHEDULED, EVENT_SYNC_TAG_INVALIDATE, FUNCTION_TYPES, LABEL_DOCUMENT_CREATE, LABEL_DOCUMENT_DELETE, LABEL_DOCUMENT_UPDATE, LABEL_MEDIA_LIBRARY_ASSET_CREATE, LABEL_MEDIA_LIBRARY_ASSET_DELETE, LABEL_MEDIA_LIBRARY_ASSET_UPDATE, LABEL_SCHEDULED, LABEL_SYNC_TAG_INVALIDATE, MAP_EVENT_TO_FUNCTION_TYPE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, } from '../../constants.js';
|
|
11
9
|
import { check, indent, warn } from '../../utils/display/presenters.js';
|
|
12
10
|
import { styleText } from '../../utils/style-text.js';
|
|
13
11
|
import { validateFunctionName } from '../../utils/validate/resource.js';
|
|
@@ -152,7 +150,8 @@ export async function functionAddCore(options) {
|
|
|
152
150
|
if (fnTypes.length === 0) {
|
|
153
151
|
throw new Error('At least one function type must be provided.');
|
|
154
152
|
}
|
|
155
|
-
|
|
153
|
+
const validTypes = new Set(FUNCTION_TYPES);
|
|
154
|
+
if (!fnTypes.every((evt) => validTypes.has(evt))) {
|
|
156
155
|
throw new Error(`Invalid function type. Must be one of: ${FUNCTION_TYPES.join(', ').trim()}`);
|
|
157
156
|
}
|
|
158
157
|
const eventSources = new Set(fnTypes.map((t) => t.substring(0, t.lastIndexOf('-'))));
|
|
@@ -241,8 +240,8 @@ async function promptForFunctionType() {
|
|
|
241
240
|
{ name: LABEL_MEDIA_LIBRARY_ASSET_CREATE, value: EVENT_MEDIA_LIBRARY_ASSET_CREATE },
|
|
242
241
|
{ name: LABEL_MEDIA_LIBRARY_ASSET_UPDATE, value: EVENT_MEDIA_LIBRARY_ASSET_UPDATE },
|
|
243
242
|
{ name: LABEL_MEDIA_LIBRARY_ASSET_DELETE, value: EVENT_MEDIA_LIBRARY_ASSET_DELETE },
|
|
243
|
+
{ name: LABEL_SCHEDULED, value: EVENT_SCHEDULED },
|
|
244
244
|
{ name: LABEL_SYNC_TAG_INVALIDATE, value: EVENT_SYNC_TAG_INVALIDATE },
|
|
245
|
-
// {name: LABEL_SCHEDULED, value: EVENT_SCHEDULED},
|
|
246
245
|
],
|
|
247
246
|
validate(choices) {
|
|
248
247
|
if (choices.length === 0) {
|
package/dist/cores/index.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { type ReadBlueprintResult } from '../actions/blueprints/blueprint.js';
|
|
2
2
|
import type { Logger } from '../utils/logger.js';
|
|
3
3
|
import type { AuthParams, Result, ScopeType, Stack } from '../utils/types.js';
|
|
4
|
-
export * as blueprintsCores from './blueprints/index.js';
|
|
5
|
-
export * as functionsCores from './functions/index.js';
|
|
6
4
|
export interface CoreConfig {
|
|
7
5
|
/** The CLI binary name. */
|
|
8
6
|
bin: string;
|
|
@@ -67,3 +65,4 @@ export declare function initDeployedBlueprintConfig(config: Partial<BlueprintCon
|
|
|
67
65
|
validateToken?: boolean;
|
|
68
66
|
stackOverride?: string;
|
|
69
67
|
}): Promise<Result<DeployedBlueprintConfig>>;
|
|
68
|
+
export {};
|
package/dist/cores/index.js
CHANGED
|
@@ -2,8 +2,6 @@ import { readLocalBlueprint } from '../actions/blueprints/blueprint.js';
|
|
|
2
2
|
import { getStack, resolveStackIdByNameOrId } from '../actions/blueprints/stacks.js';
|
|
3
3
|
import { presentBlueprintParserErrors } from '../utils/display/errors.js';
|
|
4
4
|
import { validTokenOrErrorMessage } from '../utils/validated-token.js';
|
|
5
|
-
export * as blueprintsCores from './blueprints/index.js';
|
|
6
|
-
export * as functionsCores from './functions/index.js';
|
|
7
5
|
export async function initBlueprintConfig({ bin, log, token, validateResources = false, validateToken = true, blueprintPath, }) {
|
|
8
6
|
let checkedToken = token;
|
|
9
7
|
if (!token || (token && validateToken)) {
|
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';
|