@sanity/runtime-cli 17.3.2 → 17.4.0-blueprinit.g596ac51
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 +50 -47
- package/SKILL.md +59 -0
- package/dist/actions/blueprinit/manifest.d.ts +15 -0
- package/dist/actions/blueprinit/manifest.js +82 -0
- package/dist/actions/blueprinit/studio.d.ts +6 -0
- package/dist/actions/blueprinit/studio.js +43 -0
- package/dist/actions/blueprinit/templates.d.ts +34 -0
- package/dist/actions/blueprinit/templates.js +83 -0
- package/dist/actions/blueprinit/workspace.d.ts +16 -0
- package/dist/actions/blueprinit/workspace.js +143 -0
- package/dist/actions/blueprints/pre-deploy.d.ts +15 -6
- package/dist/actions/blueprints/pre-deploy.js +29 -70
- package/dist/actions/blueprints/process-assets.d.ts +29 -0
- package/dist/actions/blueprints/process-assets.js +80 -0
- package/dist/actions/functions/list.d.ts +34 -0
- package/dist/actions/functions/list.js +33 -0
- package/dist/actions/sanity/datasets.d.ts +15 -0
- package/dist/actions/sanity/datasets.js +26 -0
- package/dist/baseCommands.d.ts +2 -0
- package/dist/baseCommands.js +3 -0
- package/dist/commands/blueprinit.d.ts +17 -0
- package/dist/commands/blueprinit.js +59 -0
- package/dist/commands/blueprints/deploy.d.ts +2 -0
- package/dist/commands/blueprints/deploy.js +22 -1
- package/dist/commands/blueprints/destroy.js +1 -1
- package/dist/commands/blueprints/info.d.ts +1 -0
- package/dist/commands/blueprints/info.js +5 -0
- package/dist/commands/blueprints/init.js +1 -2
- package/dist/commands/blueprints/logs.js +1 -0
- package/dist/commands/functions/add.js +3 -3
- package/dist/commands/functions/dev.js +2 -2
- package/dist/commands/functions/list.d.ts +14 -0
- package/dist/commands/functions/list.js +38 -0
- package/dist/commands/functions/logs.d.ts +1 -1
- package/dist/commands/functions/logs.js +3 -0
- package/dist/cores/blueprinit/adopt.d.ts +6 -0
- package/dist/cores/blueprinit/adopt.js +119 -0
- package/dist/cores/blueprinit/finish.d.ts +14 -0
- package/dist/cores/blueprinit/finish.js +64 -0
- package/dist/cores/blueprinit/fresh.d.ts +3 -0
- package/dist/cores/blueprinit/fresh.js +120 -0
- package/dist/cores/blueprinit/preview.d.ts +15 -0
- package/dist/cores/blueprinit/preview.js +14 -0
- package/dist/cores/blueprinit/prompts.d.ts +5 -0
- package/dist/cores/blueprinit/prompts.js +22 -0
- package/dist/cores/blueprinit/selection.d.ts +15 -0
- package/dist/cores/blueprinit/selection.js +90 -0
- package/dist/cores/blueprinit/types.d.ts +16 -0
- package/dist/cores/blueprinit/types.js +1 -0
- package/dist/cores/blueprinit.d.ts +7 -0
- package/dist/cores/blueprinit.js +48 -0
- package/dist/cores/blueprints/deploy.js +69 -19
- package/dist/cores/functions/add.js +1 -1
- package/dist/cores/functions/list.d.ts +10 -0
- package/dist/cores/functions/list.js +34 -0
- package/dist/utils/display/blueprints-formatting.js +3 -3
- package/dist/utils/display/functions-formatting.d.ts +7 -0
- package/dist/utils/display/functions-formatting.js +95 -0
- package/dist/utils/style-text.js +1 -0
- package/oclif.manifest.json +271 -15
- package/package.json +31 -17
- package/skills/_functions-build.md +23 -0
- package/skills/_functions-list.md +28 -0
- package/skills/blueprinit.md +155 -0
- package/skills/blueprints-add.md +8 -0
- package/skills/blueprints-config.md +33 -0
- package/skills/blueprints-deploy.md +36 -0
- package/skills/blueprints-destroy.md +30 -0
- package/skills/blueprints-doctor.md +31 -0
- package/skills/blueprints-info.md +23 -0
- package/skills/blueprints-init.md +48 -0
- package/skills/blueprints-logs.md +33 -0
- package/skills/blueprints-mint-deploy-token.md +25 -0
- package/skills/blueprints-plan.md +28 -0
- package/skills/blueprints-promote.md +31 -0
- package/skills/blueprints-stacks.md +28 -0
- package/skills/functions-add.md +35 -0
- package/skills/functions-dev.md +21 -0
- package/skills/functions-env-add.md +29 -0
- package/skills/functions-env-list.md +21 -0
- package/skills/functions-env-remove.md +22 -0
- package/skills/functions-logs.md +39 -0
- package/skills/functions-test.md +31 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { select } from '@inquirer/prompts';
|
|
2
|
+
export async function chooseOrganization(groups, organizationId, interactive) {
|
|
3
|
+
if (groups.length === 0) {
|
|
4
|
+
return failure('No organizations found for this account.');
|
|
5
|
+
}
|
|
6
|
+
if (organizationId) {
|
|
7
|
+
const organization = groups.find((group) => group.organization.id === organizationId);
|
|
8
|
+
return organization
|
|
9
|
+
? { ok: true, value: organization }
|
|
10
|
+
: failure(`No organization "${organizationId}" found for this account.`);
|
|
11
|
+
}
|
|
12
|
+
if (groups.length === 1)
|
|
13
|
+
return { ok: true, value: groups[0] };
|
|
14
|
+
if (!interactive) {
|
|
15
|
+
return failure('Multiple organizations found.', ['Pass --organization-id <id> to choose one.']);
|
|
16
|
+
}
|
|
17
|
+
const selectedId = await select({
|
|
18
|
+
message: 'Choose a Sanity organization:',
|
|
19
|
+
choices: groups.map(({ organization, projects }) => ({
|
|
20
|
+
name: organization.name,
|
|
21
|
+
value: organization.id,
|
|
22
|
+
description: `${projects?.length ?? 0} projects`,
|
|
23
|
+
})),
|
|
24
|
+
});
|
|
25
|
+
const selected = groups.find(({ organization }) => organization.id === selectedId);
|
|
26
|
+
return selected ? { ok: true, value: selected } : failure('Organization not found.');
|
|
27
|
+
}
|
|
28
|
+
export async function chooseProject(projects, projectId, organizationName, interactive) {
|
|
29
|
+
if (projects.length === 0) {
|
|
30
|
+
return failure(`No projects found in ${organizationName}.`);
|
|
31
|
+
}
|
|
32
|
+
if (projectId) {
|
|
33
|
+
const project = projects.find(({ id }) => id === projectId);
|
|
34
|
+
return project
|
|
35
|
+
? { ok: true, value: project }
|
|
36
|
+
: failure(`No project "${projectId}" found in ${organizationName}.`);
|
|
37
|
+
}
|
|
38
|
+
if (projects.length === 1)
|
|
39
|
+
return { ok: true, value: projects[0] };
|
|
40
|
+
if (!interactive) {
|
|
41
|
+
return failure('Multiple projects found.', ['Pass --project-id <id> to choose one.']);
|
|
42
|
+
}
|
|
43
|
+
const selectedId = await select({
|
|
44
|
+
message: 'Choose a Sanity project:',
|
|
45
|
+
choices: projects.map((project) => ({ name: project.displayName, value: project.id })),
|
|
46
|
+
});
|
|
47
|
+
const selected = projects.find(({ id }) => id === selectedId);
|
|
48
|
+
return selected ? { ok: true, value: selected } : failure('Project not found.');
|
|
49
|
+
}
|
|
50
|
+
export async function chooseDataset(datasets, datasetName, projectName, interactive) {
|
|
51
|
+
if (datasets.length === 0) {
|
|
52
|
+
return failure(`No datasets found in ${projectName}.`);
|
|
53
|
+
}
|
|
54
|
+
if (datasetName) {
|
|
55
|
+
const dataset = datasets.find(({ name }) => name === datasetName);
|
|
56
|
+
return dataset
|
|
57
|
+
? { ok: true, value: dataset }
|
|
58
|
+
: failure(`No dataset "${datasetName}" found in ${projectName}.`);
|
|
59
|
+
}
|
|
60
|
+
if (datasets.length === 1)
|
|
61
|
+
return { ok: true, value: datasets[0] };
|
|
62
|
+
if (!interactive) {
|
|
63
|
+
return failure('Multiple datasets found.', ['Pass --dataset <name> to choose one.']);
|
|
64
|
+
}
|
|
65
|
+
const production = datasets.find(({ name }) => name === 'production');
|
|
66
|
+
const orderedDatasets = production
|
|
67
|
+
? [production, ...datasets.filter(({ name }) => name !== 'production')]
|
|
68
|
+
: datasets;
|
|
69
|
+
const selectedName = await select({
|
|
70
|
+
message: 'Choose a Sanity dataset:',
|
|
71
|
+
choices: orderedDatasets.map((dataset) => ({
|
|
72
|
+
name: dataset.name,
|
|
73
|
+
value: dataset.name,
|
|
74
|
+
description: dataset.aclMode,
|
|
75
|
+
})),
|
|
76
|
+
});
|
|
77
|
+
const selected = datasets.find(({ name }) => name === selectedName);
|
|
78
|
+
return selected ? { ok: true, value: selected } : failure('Dataset not found.');
|
|
79
|
+
}
|
|
80
|
+
export function findProject(groups, projectId) {
|
|
81
|
+
for (const { organization, projects = [] } of groups) {
|
|
82
|
+
const project = projects.find(({ id }) => id === projectId);
|
|
83
|
+
if (project)
|
|
84
|
+
return { organization, project };
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
function failure(message, suggestions) {
|
|
89
|
+
return { ok: false, error: { message, suggestions } };
|
|
90
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Logger } from '../../utils/logger.js';
|
|
2
|
+
export interface BlueprinitFlags {
|
|
3
|
+
yes?: boolean;
|
|
4
|
+
organizationId?: string;
|
|
5
|
+
projectId?: string;
|
|
6
|
+
datasetName?: string;
|
|
7
|
+
stackName?: string;
|
|
8
|
+
studioName?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface BlueprinitFlowOptions {
|
|
11
|
+
bin: string;
|
|
12
|
+
dir: string;
|
|
13
|
+
flags: BlueprinitFlags;
|
|
14
|
+
log: Logger;
|
|
15
|
+
token: string;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BlueprinitFlags } from './blueprinit/types.js';
|
|
2
|
+
import type { CoreConfig, CoreResult } from './index.js';
|
|
3
|
+
export interface BlueprinitOptions extends CoreConfig {
|
|
4
|
+
dir?: string;
|
|
5
|
+
flags?: BlueprinitFlags;
|
|
6
|
+
}
|
|
7
|
+
export declare function blueprinitCore(options: BlueprinitOptions): Promise<CoreResult>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { cwd } from 'node:process';
|
|
2
|
+
import { detectStudio } from '../actions/blueprinit/studio.js';
|
|
3
|
+
import { findBlueprintFile } from '../actions/blueprints/blueprint.js';
|
|
4
|
+
import { readConfigFile } from '../actions/blueprints/config.js';
|
|
5
|
+
import { validTokenOrErrorMessage } from '../utils/validated-token.js';
|
|
6
|
+
import { adoptFlow } from './blueprinit/adopt.js';
|
|
7
|
+
import { freshFlow } from './blueprinit/fresh.js';
|
|
8
|
+
export async function blueprinitCore(options) {
|
|
9
|
+
const { bin, log, flags = {}, dir = cwd() } = options;
|
|
10
|
+
try {
|
|
11
|
+
const existingBlueprint = findBlueprintFile(dir);
|
|
12
|
+
if (existingBlueprint) {
|
|
13
|
+
const blueprintConfig = readConfigFile(existingBlueprint.blueprintFilePath);
|
|
14
|
+
if (!blueprintConfig?.stackId) {
|
|
15
|
+
return {
|
|
16
|
+
success: false,
|
|
17
|
+
error: `A Blueprint exists here (${existingBlueprint.fileName}), but it is not connected to a Stack.`,
|
|
18
|
+
suggestions: [`Finish setup with: npx ${bin} blueprints init`],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
log(`A blueprint already exists here (${existingBlueprint.fileName}).`);
|
|
22
|
+
log('');
|
|
23
|
+
log('Use these instead:');
|
|
24
|
+
log(` npx ${bin} blueprints config --edit`);
|
|
25
|
+
log(` npx ${bin} blueprints doctor`);
|
|
26
|
+
return {
|
|
27
|
+
success: true,
|
|
28
|
+
json: { alreadyInitialized: true, blueprintFile: existingBlueprint.fileName },
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const tokenCheck = await validTokenOrErrorMessage(log);
|
|
32
|
+
if (!tokenCheck.ok) {
|
|
33
|
+
return {
|
|
34
|
+
success: false,
|
|
35
|
+
error: 'No Sanity session found.',
|
|
36
|
+
suggestions: ['Sign in first: npx sanity login', 'Then run blueprinit again.'],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
log('Setting up your Sanity workspace.');
|
|
40
|
+
log('');
|
|
41
|
+
const flowOptions = { bin, dir, flags, log, token: tokenCheck.value };
|
|
42
|
+
const studio = detectStudio(dir);
|
|
43
|
+
return studio ? await adoptFlow({ ...flowOptions, studio }) : await freshFlow(flowOptions);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -4,21 +4,21 @@ import { getStack, updateStack } from '../../actions/blueprints/stacks.js';
|
|
|
4
4
|
import { waitForOperation } from '../../actions/blueprints/wait-for-operation.js';
|
|
5
5
|
import { checkUserPermission } from '../../actions/sanity/access.js';
|
|
6
6
|
import { createHintCollector } from '../../utils/blueprints/hints.js';
|
|
7
|
-
import { niceId } from '../../utils/display/presenters.js';
|
|
7
|
+
import { check, niceId, severe } from '../../utils/display/presenters.js';
|
|
8
8
|
import { CODE_OPERATION_UNCONFIRMED, EXIT_OPERATION_UNCONFIRMED } from '../../utils/exit-codes.js';
|
|
9
9
|
import { styleText } from '../../utils/style-text.js';
|
|
10
10
|
import { getWarnings } from '../../utils/warnings.js';
|
|
11
11
|
export async function blueprintDeployCore(options) {
|
|
12
|
-
const { bin = 'sanity', log, auth, stackId, scopeType, scopeId, deployedStack, blueprint, flags, } = options;
|
|
12
|
+
const { bin = 'sanity', log: logger, auth, stackId, scopeType, scopeId, deployedStack, blueprint, flags, } = options;
|
|
13
13
|
const { verbose } = flags;
|
|
14
14
|
const noWait = flags['no-wait'] || false;
|
|
15
15
|
const stackName = flags['new-stack-name'] ?? deployedStack.name;
|
|
16
16
|
const installer = flags['fn-installer'] || undefined;
|
|
17
17
|
const blueprintFileDir = dirname(blueprint.fileInfo.blueprintFilePath);
|
|
18
18
|
const workspaceWarnings = getWarnings({ dir: blueprintFileDir });
|
|
19
|
-
|
|
19
|
+
logger(`Deploying "${stackName}" ${niceId(deployedStack.id)}...`);
|
|
20
20
|
try {
|
|
21
|
-
const { ok: checkOk, stack: currentStack } = await getStack({ stackId, auth, logger
|
|
21
|
+
const { ok: checkOk, stack: currentStack } = await getStack({ stackId, auth, logger });
|
|
22
22
|
if (!checkOk) {
|
|
23
23
|
return { success: false, error: 'Failed to check current Stack status' };
|
|
24
24
|
}
|
|
@@ -37,20 +37,20 @@ export async function blueprintDeployCore(options) {
|
|
|
37
37
|
const preDeployResult = await preDeploy({
|
|
38
38
|
scopeType,
|
|
39
39
|
scopeId,
|
|
40
|
-
resources,
|
|
40
|
+
resources, // mutated by preDeploy
|
|
41
41
|
stackId,
|
|
42
42
|
stackName,
|
|
43
43
|
auth,
|
|
44
44
|
blueprintFileDir,
|
|
45
45
|
installer,
|
|
46
|
-
|
|
46
|
+
logger,
|
|
47
47
|
bin,
|
|
48
48
|
verbose,
|
|
49
|
+
onAssetEvent: createAssetEventHandler(logger),
|
|
49
50
|
});
|
|
50
|
-
if (!preDeployResult?.success)
|
|
51
|
+
if (!preDeployResult?.success)
|
|
51
52
|
return preDeployResult;
|
|
52
|
-
|
|
53
|
-
const spinner = log.ora('Deploying...').start();
|
|
53
|
+
const deploySpinner = logger.ora('Deploying...').start();
|
|
54
54
|
const { ok: deployOk, stack, error: deployError, } = await updateStack({
|
|
55
55
|
stackId,
|
|
56
56
|
stackMutation: {
|
|
@@ -61,10 +61,10 @@ export async function blueprintDeployCore(options) {
|
|
|
61
61
|
userMessage: flags.message,
|
|
62
62
|
},
|
|
63
63
|
auth,
|
|
64
|
-
logger
|
|
64
|
+
logger,
|
|
65
65
|
});
|
|
66
66
|
if (!deployOk) {
|
|
67
|
-
|
|
67
|
+
deploySpinner.fail(`${styleText('red', 'Failed')} to update Stack deployment`);
|
|
68
68
|
const hints = createHintCollector(bin);
|
|
69
69
|
if (deployError) {
|
|
70
70
|
hints.inspectMessage(deployError);
|
|
@@ -79,18 +79,18 @@ export async function blueprintDeployCore(options) {
|
|
|
79
79
|
],
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
-
|
|
82
|
+
deploySpinner.stop().clear();
|
|
83
83
|
const legacyPermissionsNoticePromise = CHECK_LEGACY_DEPLOY_PERMISSION
|
|
84
84
|
? checkLegacyDeployPermission({
|
|
85
85
|
auth,
|
|
86
86
|
scopeType,
|
|
87
87
|
scopeId,
|
|
88
|
-
log,
|
|
88
|
+
log: logger,
|
|
89
89
|
})
|
|
90
90
|
: Promise.resolve(null);
|
|
91
91
|
if (noWait) {
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
logger(styleText(['bold', 'green'], 'Stack deployment started!'));
|
|
93
|
+
logger(`Use \`npx ${bin} blueprints info\` to check status`);
|
|
94
94
|
const legacyWarning = await legacyPermissionsNoticePromise;
|
|
95
95
|
const warnings = [...(legacyWarning ? [legacyWarning] : []), ...workspaceWarnings];
|
|
96
96
|
return {
|
|
@@ -100,19 +100,19 @@ export async function blueprintDeployCore(options) {
|
|
|
100
100
|
warnings: warnings.length ? warnings : undefined,
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
logger(styleText('dim', 'Stack deployment progress:'));
|
|
104
|
+
logger('');
|
|
105
105
|
const outcome = await waitForOperation({
|
|
106
106
|
stackId,
|
|
107
107
|
operationId: stack.operationId,
|
|
108
108
|
auth,
|
|
109
|
-
log,
|
|
109
|
+
log: logger,
|
|
110
110
|
bin,
|
|
111
111
|
verbose,
|
|
112
112
|
progressNoun: 'deployment',
|
|
113
113
|
});
|
|
114
114
|
if (outcome.type === 'completed') {
|
|
115
|
-
|
|
115
|
+
logger(styleText(['bold', 'green'], 'Stack deployment completed!'));
|
|
116
116
|
const legacyWarning = await legacyPermissionsNoticePromise;
|
|
117
117
|
const warnings = [...(legacyWarning ? [legacyWarning] : []), ...workspaceWarnings];
|
|
118
118
|
return {
|
|
@@ -151,6 +151,56 @@ export async function blueprintDeployCore(options) {
|
|
|
151
151
|
return { success: false, error: errorMessage };
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
+
function createAssetEventHandler(logger) {
|
|
155
|
+
let spinner;
|
|
156
|
+
const results = [];
|
|
157
|
+
return (event) => {
|
|
158
|
+
switch (event.type) {
|
|
159
|
+
case 'progress': {
|
|
160
|
+
const { completed, total, slow } = event.progress;
|
|
161
|
+
const text = [
|
|
162
|
+
`Processing assets (${completed}/${total})`,
|
|
163
|
+
slow ? ', this can take a moment' : '',
|
|
164
|
+
].join('');
|
|
165
|
+
if (spinner)
|
|
166
|
+
spinner.text = text;
|
|
167
|
+
else
|
|
168
|
+
spinner = logger.ora(text).start();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
case 'result': {
|
|
172
|
+
results.push(event.result);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
case 'complete': {
|
|
176
|
+
spinner?.stop();
|
|
177
|
+
spinner = undefined;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// done. print report
|
|
182
|
+
for (const result of results) {
|
|
183
|
+
const { resource } = result;
|
|
184
|
+
if (!result.success) {
|
|
185
|
+
const summary = result.failureKind === 'upload'
|
|
186
|
+
? `Failed uploading ${resource.name} asset`
|
|
187
|
+
: `Failed processing ${resource.name} asset`;
|
|
188
|
+
logger(` ${severe(summary)}`);
|
|
189
|
+
logger.error(` ${result.error}`);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
logger(` ${check(`${resource.name} ${niceId(resource.src)}`)}`);
|
|
193
|
+
logger(` Source: ${resource.src}`);
|
|
194
|
+
if (result.hash) {
|
|
195
|
+
const hash = result.hash;
|
|
196
|
+
const h = hash.length > 24 ? `${hash.slice(0, 8)}...${hash.slice(-12)}` : hash;
|
|
197
|
+
logger.verbose(` Hash: ${h}`);
|
|
198
|
+
}
|
|
199
|
+
if (result.exists)
|
|
200
|
+
logger.verbose(' Asset unchanged');
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
154
204
|
const CHECK_LEGACY_DEPLOY_PERMISSION = false;
|
|
155
205
|
const BLUEPRINTS_PERMISSION_ENFORCEMENT_DATE = 'July 1, 2026';
|
|
156
206
|
async function checkLegacyDeployPermission({ auth, scopeType, scopeId, log, }) {
|
|
@@ -224,7 +224,7 @@ async function promptForFunctionName() {
|
|
|
224
224
|
const functionName = await input({
|
|
225
225
|
message: 'Enter function name:',
|
|
226
226
|
validate: (input) => validateFunctionName(input) ||
|
|
227
|
-
'Invalid function name. Must be
|
|
227
|
+
'Invalid function name. Must be 3+ characters, no special characters, no spaces',
|
|
228
228
|
});
|
|
229
229
|
return functionName;
|
|
230
230
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AuthParams } from '../../utils/types.js';
|
|
2
|
+
import type { CoreConfig, CoreResult } from '../index.js';
|
|
3
|
+
export interface FunctionListOptions extends Pick<CoreConfig, 'log'> {
|
|
4
|
+
auth: AuthParams;
|
|
5
|
+
flags: {
|
|
6
|
+
json?: boolean;
|
|
7
|
+
verbose?: boolean;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export declare function functionListCore(options: FunctionListOptions): Promise<CoreResult>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { listFunctions } from '../../actions/functions/list.js';
|
|
2
|
+
import { formatFunctionsList } from '../../utils/display/functions-formatting.js';
|
|
3
|
+
import { capitalize, niceId } from '../../utils/display/presenters.js';
|
|
4
|
+
import { styleText } from '../../utils/style-text.js';
|
|
5
|
+
export async function functionListCore(options) {
|
|
6
|
+
const { auth, flags, log } = options;
|
|
7
|
+
const { scopeId, scopeType } = auth;
|
|
8
|
+
const spinner = flags.json ? undefined : log.ora('Fetching functions...').start();
|
|
9
|
+
const result = await listFunctions(auth, log);
|
|
10
|
+
if (!result.ok) {
|
|
11
|
+
spinner?.fail('Failed to list functions');
|
|
12
|
+
const suggestions = /access|permission|forbidden/i.test(result.error)
|
|
13
|
+
? [
|
|
14
|
+
`Verify you have access to ${scopeType} "${scopeId}".`,
|
|
15
|
+
'Try a different scope with --project-id or --organization-id.',
|
|
16
|
+
]
|
|
17
|
+
: undefined;
|
|
18
|
+
return { success: false, error: result.error, suggestions };
|
|
19
|
+
}
|
|
20
|
+
spinner?.stop().clear();
|
|
21
|
+
if (!flags.json) {
|
|
22
|
+
if (result.functions.length === 0) {
|
|
23
|
+
log(`No functions found for ${scopeType} ${niceId(scopeId)}`);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
log(`${styleText('bold', capitalize(scopeType))} ${niceId(scopeId)} ${styleText('bold', 'Functions')} [${result.functions.length}]:`);
|
|
27
|
+
log(formatFunctionsList(result.functions, { verbose: flags.verbose }));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
success: true,
|
|
32
|
+
json: { functions: result.functions, scopeType, scopeId },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -72,15 +72,15 @@ function resourceName(res, displayNameAttribute) {
|
|
|
72
72
|
nameParts.push(styleText('green', `"${displayName}"`));
|
|
73
73
|
return nameParts.join(' ');
|
|
74
74
|
}
|
|
75
|
-
function deployedResourceName(res, displayNameAttribute,
|
|
75
|
+
function deployedResourceName(res, displayNameAttribute, verbose = false) {
|
|
76
76
|
const nameParts = [styleText(['bold', 'green'], res.name)];
|
|
77
77
|
const displayName = displayNameAttribute &&
|
|
78
78
|
typeof res.parameters[displayNameAttribute] === 'string' &&
|
|
79
79
|
res.parameters[displayNameAttribute];
|
|
80
80
|
if (displayName && displayName !== res.name)
|
|
81
81
|
nameParts.push(styleText('green', `"${displayName}"`));
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
if (verbose)
|
|
83
|
+
nameParts.push(idList(res));
|
|
84
84
|
return nameParts.join(' ');
|
|
85
85
|
}
|
|
86
86
|
function idList(res) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DeployedFunction } from '../../actions/functions/list.js';
|
|
2
|
+
export declare function formatFunctionsList(functions: DeployedFunction[], options?: {
|
|
3
|
+
verbose?: boolean;
|
|
4
|
+
}): string;
|
|
5
|
+
export declare function formatFunction(fn: DeployedFunction, options?: {
|
|
6
|
+
verbose?: boolean;
|
|
7
|
+
}): string;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { SANITY_FUNCTION_PREFIX } from '../../constants.js';
|
|
2
|
+
import { styleText } from '../style-text.js';
|
|
3
|
+
import { formatDateTime } from './dates.js';
|
|
4
|
+
import { niceId, renderSection } from './presenters.js';
|
|
5
|
+
export function formatFunctionsList(functions, options = {}) {
|
|
6
|
+
if (!options.verbose)
|
|
7
|
+
return formatCompactList(functions);
|
|
8
|
+
return functions.map((fn) => formatFunction(fn, options)).join('\n');
|
|
9
|
+
}
|
|
10
|
+
export function formatFunction(fn, options = {}) {
|
|
11
|
+
if (!options.verbose)
|
|
12
|
+
return formatCompactList([fn]);
|
|
13
|
+
const rows = [];
|
|
14
|
+
if (fn.type)
|
|
15
|
+
rows.push(['Type', shortType(fn.type)]);
|
|
16
|
+
if (fn.updatedAt) {
|
|
17
|
+
const updatedAt = formatDateTime(fn.updatedAt);
|
|
18
|
+
if (updatedAt)
|
|
19
|
+
rows.push(['Updated', updatedAt]);
|
|
20
|
+
}
|
|
21
|
+
if (typeof fn.memory === 'number')
|
|
22
|
+
rows.push(['Memory', `${fn.memory} MB`]);
|
|
23
|
+
if (typeof fn.timeout === 'number')
|
|
24
|
+
rows.push(['Timeout', `${fn.timeout}s`]);
|
|
25
|
+
const rule = formatEventRule(fn.eventRule);
|
|
26
|
+
if (rule)
|
|
27
|
+
rows.push(['Rule', rule]);
|
|
28
|
+
return renderSection(formatTitle(fn), rows);
|
|
29
|
+
}
|
|
30
|
+
function formatCompactList(functions) {
|
|
31
|
+
const titles = functions.map(plainTitle);
|
|
32
|
+
const ids = functions.map((fn) => `<${fn.id}>`);
|
|
33
|
+
const types = functions.map((fn) => (fn.type ? shortType(fn.type) : '-'));
|
|
34
|
+
const updated = functions.map((fn) => (fn.updatedAt ? formatDateTime(fn.updatedAt) || '-' : '-'));
|
|
35
|
+
const titlePad = Math.max(...titles.map((title) => title.length));
|
|
36
|
+
const idPad = Math.max(...ids.map((id) => id.length));
|
|
37
|
+
const typePad = Math.max(...types.map((type) => type.length));
|
|
38
|
+
return functions
|
|
39
|
+
.map((fn, index) => {
|
|
40
|
+
const title = formatIdentity(fn) + ' '.repeat(titlePad - titles[index].length);
|
|
41
|
+
const id = niceId(fn.id) + ' '.repeat(idPad - ids[index].length);
|
|
42
|
+
const type = styleText('dim', types[index].padEnd(typePad));
|
|
43
|
+
const timestamp = styleText('dim', updated[index]);
|
|
44
|
+
return ` ${title} ${id} ${type} ${timestamp}`;
|
|
45
|
+
})
|
|
46
|
+
.join('\n');
|
|
47
|
+
}
|
|
48
|
+
function formatTitle(fn) {
|
|
49
|
+
return `${formatIdentity(fn)} ${niceId(fn.id)}`;
|
|
50
|
+
}
|
|
51
|
+
function formatIdentity(fn) {
|
|
52
|
+
const parts = [];
|
|
53
|
+
if (fn.name)
|
|
54
|
+
parts.push(styleText('bold', fn.name));
|
|
55
|
+
if (fn.displayName && fn.displayName !== fn.name) {
|
|
56
|
+
parts.push(styleText('green', `"${fn.displayName}"`));
|
|
57
|
+
}
|
|
58
|
+
if (parts.length === 0)
|
|
59
|
+
parts.push(styleText('bold', 'Unnamed'));
|
|
60
|
+
return parts.join(' ');
|
|
61
|
+
}
|
|
62
|
+
function plainTitle(fn) {
|
|
63
|
+
const parts = [];
|
|
64
|
+
if (fn.name)
|
|
65
|
+
parts.push(fn.name);
|
|
66
|
+
if (fn.displayName && fn.displayName !== fn.name)
|
|
67
|
+
parts.push(`"${fn.displayName}"`);
|
|
68
|
+
return parts.length > 0 ? parts.join(' ') : 'Unnamed';
|
|
69
|
+
}
|
|
70
|
+
function shortType(type) {
|
|
71
|
+
return type.startsWith(SANITY_FUNCTION_PREFIX) ? type.slice(SANITY_FUNCTION_PREFIX.length) : type;
|
|
72
|
+
}
|
|
73
|
+
function formatEventRule(rule) {
|
|
74
|
+
if (rule === null || rule === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
if (typeof rule !== 'object')
|
|
77
|
+
return String(rule);
|
|
78
|
+
if (Array.isArray(rule))
|
|
79
|
+
return rule.map(formatRuleValue).join(', ');
|
|
80
|
+
const entries = Object.entries(rule);
|
|
81
|
+
if (entries.length === 0)
|
|
82
|
+
return undefined;
|
|
83
|
+
return entries.map(([key, value]) => `${key}: ${formatRuleValue(value)}`).join(' · ');
|
|
84
|
+
}
|
|
85
|
+
function formatRuleValue(value) {
|
|
86
|
+
if (typeof value === 'string')
|
|
87
|
+
return value;
|
|
88
|
+
if (value === null || value === undefined)
|
|
89
|
+
return String(value);
|
|
90
|
+
if (Array.isArray(value))
|
|
91
|
+
return value.map(formatRuleValue).join(', ');
|
|
92
|
+
if (typeof value === 'object')
|
|
93
|
+
return JSON.stringify(value);
|
|
94
|
+
return String(value);
|
|
95
|
+
}
|
package/dist/utils/style-text.js
CHANGED