@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,143 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, posix } from 'node:path';
|
|
3
|
+
import { renderBlueprint, STARTER_FUNCTION_NAME } from './manifest.js';
|
|
4
|
+
import { FUNCTION_SET_SLUG, functionPackageJson, renderStudioConfig, STUDIO_POST_TYPE, STUDIO_SCHEMA_TYPES, studioPackageJson, } from './templates.js';
|
|
5
|
+
const GITIGNORE_ENTRY = '# Sanity Blueprints\n.sanity/';
|
|
6
|
+
const DEFAULT_GITIGNORE = `node_modules
|
|
7
|
+
.env
|
|
8
|
+
|
|
9
|
+
${GITIGNORE_ENTRY}
|
|
10
|
+
`;
|
|
11
|
+
export function scaffoldWorkspace(options) {
|
|
12
|
+
const { dir, studioName } = options;
|
|
13
|
+
assertTargetsAvailable(dir, [
|
|
14
|
+
posix.join('studios', studioName),
|
|
15
|
+
posix.join('functions', STARTER_FUNCTION_NAME),
|
|
16
|
+
]);
|
|
17
|
+
const packageJson = preparePackageJson(options, ['studios/*', 'functions/*']);
|
|
18
|
+
const files = [];
|
|
19
|
+
const write = createWriter(dir, files);
|
|
20
|
+
const blueprintFilePath = write('sanity.blueprint.ts', renderBlueprint({
|
|
21
|
+
...manifestOptions(options),
|
|
22
|
+
studioSrc: `./studios/${studioName}`,
|
|
23
|
+
withFunction: true,
|
|
24
|
+
}));
|
|
25
|
+
write(posix.join('studios', studioName, 'package.json'), formatJson(studioPackageJson(studioName)));
|
|
26
|
+
write(posix.join('studios', studioName, 'sanity.config.ts'), renderStudioConfig(options));
|
|
27
|
+
write(posix.join('studios', studioName, 'schemaTypes', 'index.ts'), STUDIO_SCHEMA_TYPES);
|
|
28
|
+
write(posix.join('studios', studioName, 'schemaTypes', 'post.ts'), STUDIO_POST_TYPE);
|
|
29
|
+
write(posix.join('studios', studioName, 'static', '.gitkeep'), '');
|
|
30
|
+
write(posix.join('functions', STARTER_FUNCTION_NAME, 'package.json'), formatJson(functionPackageJson(STARTER_FUNCTION_NAME)));
|
|
31
|
+
write(posix.join('functions', STARTER_FUNCTION_NAME, 'index.ts'), FUNCTION_SET_SLUG);
|
|
32
|
+
writePreparedRootFiles(dir, packageJson, files);
|
|
33
|
+
return { blueprintFilePath, files };
|
|
34
|
+
}
|
|
35
|
+
export function adoptWorkspace(options) {
|
|
36
|
+
const { dir } = options;
|
|
37
|
+
const packageJson = preparePackageJson(options, []);
|
|
38
|
+
const files = [];
|
|
39
|
+
const write = createWriter(dir, files);
|
|
40
|
+
const blueprintFilePath = write('sanity.blueprint.ts', renderBlueprint({ ...manifestOptions(options), studioSrc: '.', withFunction: false }));
|
|
41
|
+
writePreparedRootFiles(dir, packageJson, files);
|
|
42
|
+
return { blueprintFilePath, files };
|
|
43
|
+
}
|
|
44
|
+
function manifestOptions(options) {
|
|
45
|
+
return {
|
|
46
|
+
projectName: options.stackName,
|
|
47
|
+
displayName: options.displayName,
|
|
48
|
+
projectId: options.projectId,
|
|
49
|
+
datasetName: options.datasetName,
|
|
50
|
+
studioName: options.studioName,
|
|
51
|
+
studioOrigin: options.studioOrigin,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function assertTargetsAvailable(dir, paths) {
|
|
55
|
+
const conflicts = paths.filter((path) => existsSync(join(dir, path)));
|
|
56
|
+
if (conflicts.length > 0) {
|
|
57
|
+
throw new Error(`Refusing to overwrite existing paths: ${conflicts.join(', ')}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function createWriter(dir, files) {
|
|
61
|
+
return (relativePath, content) => {
|
|
62
|
+
const filePath = join(dir, relativePath);
|
|
63
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
64
|
+
writeFileSync(filePath, content);
|
|
65
|
+
files.push(relativePath);
|
|
66
|
+
return filePath;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function writePreparedRootFiles(dir, packageJson, files) {
|
|
70
|
+
if (writeGitignore(dir))
|
|
71
|
+
files.push('.gitignore');
|
|
72
|
+
writeFileSync(join(dir, 'package.json'), formatJson(packageJson));
|
|
73
|
+
files.push('package.json');
|
|
74
|
+
}
|
|
75
|
+
function writeGitignore(dir) {
|
|
76
|
+
const filePath = join(dir, '.gitignore');
|
|
77
|
+
if (!existsSync(filePath)) {
|
|
78
|
+
writeFileSync(filePath, DEFAULT_GITIGNORE);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
const existing = readFileSync(filePath, 'utf8');
|
|
82
|
+
if (existing.split(/\r?\n/).some((line) => line.trim() === '.sanity/'))
|
|
83
|
+
return false;
|
|
84
|
+
writeFileSync(filePath, `${existing.trimEnd()}\n\n${GITIGNORE_ENTRY}\n`);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
function preparePackageJson(options, requiredWorkspaces) {
|
|
88
|
+
const filePath = join(options.dir, 'package.json');
|
|
89
|
+
const parsed = existsSync(filePath)
|
|
90
|
+
? JSON.parse(readFileSync(filePath, 'utf8'))
|
|
91
|
+
: {
|
|
92
|
+
name: options.stackName,
|
|
93
|
+
version: '0.1.0',
|
|
94
|
+
private: true,
|
|
95
|
+
type: 'module',
|
|
96
|
+
};
|
|
97
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
98
|
+
throw new Error('The existing package.json must contain a JSON object.');
|
|
99
|
+
}
|
|
100
|
+
const packageJson = parsed;
|
|
101
|
+
addWorkspaces(packageJson, requiredWorkspaces);
|
|
102
|
+
addBlueprintsDependency(packageJson, options.blueprintsVersion);
|
|
103
|
+
return packageJson;
|
|
104
|
+
}
|
|
105
|
+
function addWorkspaces(packageJson, required) {
|
|
106
|
+
if (required.length === 0)
|
|
107
|
+
return;
|
|
108
|
+
if (!packageJson.workspaces) {
|
|
109
|
+
packageJson.workspaces = required;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (Array.isArray(packageJson.workspaces)) {
|
|
113
|
+
packageJson.workspaces = [...new Set([...packageJson.workspaces, ...required])];
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!packageJson.workspaces || typeof packageJson.workspaces !== 'object') {
|
|
117
|
+
throw new Error('Cannot add generated packages to the existing package.json workspaces.');
|
|
118
|
+
}
|
|
119
|
+
const workspaces = packageJson.workspaces;
|
|
120
|
+
if (Array.isArray(workspaces.packages)) {
|
|
121
|
+
workspaces.packages = [...new Set([...workspaces.packages, ...required])];
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
throw new Error('Cannot add generated packages to the existing package.json workspaces.');
|
|
125
|
+
}
|
|
126
|
+
function addBlueprintsDependency(packageJson, blueprintsVersion) {
|
|
127
|
+
const dependencies = recordOrEmpty(packageJson.dependencies);
|
|
128
|
+
const devDependencies = recordOrEmpty(packageJson.devDependencies);
|
|
129
|
+
if (dependencies['@sanity/blueprints'] || devDependencies['@sanity/blueprints'])
|
|
130
|
+
return;
|
|
131
|
+
packageJson.devDependencies = {
|
|
132
|
+
...devDependencies,
|
|
133
|
+
'@sanity/blueprints': blueprintsVersion,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function recordOrEmpty(value) {
|
|
137
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
138
|
+
? value
|
|
139
|
+
: {};
|
|
140
|
+
}
|
|
141
|
+
function formatJson(value) {
|
|
142
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
143
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BlueprintResource } from '@sanity/blueprints';
|
|
2
2
|
import type { Logger } from '../../utils/logger.js';
|
|
3
3
|
import { type AuthParams, type InstallerType, type ScopeType } from '../../utils/types.js';
|
|
4
|
+
import { type ProcessAssetsProgress, type ProcessedAsset } from './process-assets.js';
|
|
4
5
|
type PreDeployResult = {
|
|
5
6
|
success: false;
|
|
6
7
|
error: string;
|
|
@@ -10,11 +11,14 @@ type PreDeployResult = {
|
|
|
10
11
|
} | {
|
|
11
12
|
success: true;
|
|
12
13
|
};
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
export type PreDeployAssetEvent = {
|
|
15
|
+
type: 'progress';
|
|
16
|
+
progress: ProcessAssetsProgress;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'complete';
|
|
19
|
+
} | {
|
|
20
|
+
type: 'result';
|
|
21
|
+
result: ProcessedAsset;
|
|
18
22
|
};
|
|
19
23
|
type PreDeployOptions = {
|
|
20
24
|
scopeType: ScopeType;
|
|
@@ -24,6 +28,11 @@ type PreDeployOptions = {
|
|
|
24
28
|
stackName: string;
|
|
25
29
|
bin: string;
|
|
26
30
|
verbose: boolean | undefined;
|
|
27
|
-
|
|
31
|
+
auth: AuthParams;
|
|
32
|
+
blueprintFileDir: string;
|
|
33
|
+
installer: InstallerType;
|
|
34
|
+
logger: Logger;
|
|
35
|
+
onAssetEvent?: (event: PreDeployAssetEvent) => void;
|
|
36
|
+
};
|
|
28
37
|
export declare function preDeploy(options: PreDeployOptions): Promise<PreDeployResult>;
|
|
29
38
|
export {};
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
import { env } from 'node:process';
|
|
2
2
|
import { resolveDeployedResources } from '../../utils/blueprints/resolve-deployed-resources.js';
|
|
3
|
-
import { niceId } from '../../utils/display/presenters.js';
|
|
4
3
|
import { CODE_OPERATION_UNCONFIRMED, EXIT_OPERATION_UNCONFIRMED } from '../../utils/exit-codes.js';
|
|
5
4
|
import { styleText } from '../../utils/style-text.js';
|
|
6
5
|
import { isAssetResource, } from '../../utils/types.js';
|
|
7
|
-
import {
|
|
6
|
+
import { processAssets } from './process-assets.js';
|
|
8
7
|
import { updateStack } from './stacks.js';
|
|
9
8
|
import { waitForOperation } from './wait-for-operation.js';
|
|
10
|
-
const DEFAULT_ASSET_TIMEOUT = 180;
|
|
11
|
-
const assetTimeoutS = Number(process.env.SANITY_ASSET_TIMEOUT) || DEFAULT_ASSET_TIMEOUT;
|
|
12
|
-
const assetTimeoutMs = assetTimeoutS * 1000;
|
|
13
|
-
const warnTimeoutMs = assetTimeoutMs / 3;
|
|
14
9
|
export async function preDeploy(options) {
|
|
15
|
-
const { resources, stackId, auth, blueprintFileDir, installer,
|
|
10
|
+
const { resources, stackId, auth, blueprintFileDir, installer, logger } = options;
|
|
16
11
|
const hasAssetResources = resources.some(isAssetResource);
|
|
17
12
|
if (hasAssetResources) {
|
|
18
13
|
let assetResources;
|
|
@@ -26,34 +21,45 @@ export async function preDeploy(options) {
|
|
|
26
21
|
resources,
|
|
27
22
|
stackId,
|
|
28
23
|
auth,
|
|
29
|
-
logger
|
|
24
|
+
logger,
|
|
30
25
|
});
|
|
31
26
|
assetResources = resolvedResources.filter(isAssetResource);
|
|
32
27
|
}
|
|
33
28
|
else {
|
|
34
29
|
assetResources = resources.filter(isAssetResource);
|
|
35
30
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
let processedAssets;
|
|
32
|
+
try {
|
|
33
|
+
processedAssets = await processAssets({
|
|
34
|
+
assetResources,
|
|
39
35
|
auth,
|
|
40
36
|
blueprintFileDir,
|
|
41
37
|
installer,
|
|
42
|
-
|
|
38
|
+
logger,
|
|
39
|
+
onProgress: (progress) => options.onAssetEvent?.({ type: 'progress', progress }),
|
|
40
|
+
onResult: (result) => options.onAssetEvent?.({ type: 'result', result }),
|
|
43
41
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
options.onAssetEvent?.({ type: 'complete' });
|
|
45
|
+
}
|
|
46
|
+
const failure = processedAssets.find((result) => !result.success);
|
|
47
|
+
if (failure)
|
|
48
|
+
return { success: false, error: failure.error };
|
|
49
|
+
// update resources with processed assets
|
|
50
|
+
for (const asset of processedAssets) {
|
|
51
|
+
const idx = resources.findIndex((r) => r.name === asset.resource.name);
|
|
52
|
+
// ! I think this error message is wrong. we somehow have an extra asset
|
|
47
53
|
if (idx === -1)
|
|
48
54
|
throw new Error('Unable to update resource asset');
|
|
49
|
-
resources[idx] =
|
|
55
|
+
resources[idx] = asset.resource;
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
return { success: true };
|
|
53
59
|
}
|
|
54
60
|
async function preDeployStack(options) {
|
|
55
|
-
const { scopeType, scopeId, stackId, stackName, resources, auth,
|
|
56
|
-
const spinner =
|
|
61
|
+
const { scopeType, scopeId, stackId, stackName, resources, auth, logger, bin, verbose } = options;
|
|
62
|
+
const spinner = logger.ora('Starting pre-deploy...').start();
|
|
57
63
|
const stackMutation = {
|
|
58
64
|
scopeType,
|
|
59
65
|
scopeId,
|
|
@@ -61,7 +67,7 @@ async function preDeployStack(options) {
|
|
|
61
67
|
preDeploy: true,
|
|
62
68
|
document: { resources },
|
|
63
69
|
};
|
|
64
|
-
const { ok: deployOk, stack, error: deployError, } = await updateStack({ stackId, stackMutation, auth, logger
|
|
70
|
+
const { ok: deployOk, stack, error: deployError, } = await updateStack({ stackId, stackMutation, auth, logger });
|
|
65
71
|
if (!deployOk) {
|
|
66
72
|
spinner.fail(`${styleText('red', 'Failed')} to complete Stack pre-deployment`);
|
|
67
73
|
return {
|
|
@@ -74,19 +80,19 @@ async function preDeployStack(options) {
|
|
|
74
80
|
};
|
|
75
81
|
}
|
|
76
82
|
spinner.stop().clear();
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
logger(styleText('dim', 'Stack deployment progress:'));
|
|
84
|
+
logger('');
|
|
79
85
|
const outcome = await waitForOperation({
|
|
80
86
|
stackId,
|
|
81
87
|
operationId: stack.operationId,
|
|
82
88
|
auth,
|
|
83
|
-
log,
|
|
89
|
+
log: logger,
|
|
84
90
|
bin,
|
|
85
91
|
verbose,
|
|
86
92
|
progressNoun: 'pre-deployment',
|
|
87
93
|
});
|
|
88
94
|
if (outcome.type === 'completed') {
|
|
89
|
-
|
|
95
|
+
logger(styleText(['bold', 'green'], 'Stack pre-deployment completed!'));
|
|
90
96
|
}
|
|
91
97
|
else if (outcome.type === 'unconfirmed') {
|
|
92
98
|
return {
|
|
@@ -115,50 +121,3 @@ async function preDeployStack(options) {
|
|
|
115
121
|
}
|
|
116
122
|
return { success: true };
|
|
117
123
|
}
|
|
118
|
-
async function preDeployResource(resource, options) {
|
|
119
|
-
const { auth, blueprintFileDir, installer, log } = options;
|
|
120
|
-
const fnSpinner = log.ora({ text: `Processing ${resource.name}...`, prefixText: ' ' }).start();
|
|
121
|
-
const warnTimer = setTimeout(() => {
|
|
122
|
-
fnSpinner.text = `Still processing ${resource.name}, this can take a moment...`;
|
|
123
|
-
}, warnTimeoutMs);
|
|
124
|
-
let assetTimeoutTimer;
|
|
125
|
-
let result;
|
|
126
|
-
try {
|
|
127
|
-
result = await Promise.race([
|
|
128
|
-
stashAsset({ resource, blueprintFileDir, auth, logger: log, installer }),
|
|
129
|
-
new Promise((_, reject) => {
|
|
130
|
-
assetTimeoutTimer = setTimeout(() => {
|
|
131
|
-
reject(new Error(`Processing ${resource.name} timed out after ${assetTimeoutS}s`));
|
|
132
|
-
}, assetTimeoutMs);
|
|
133
|
-
}),
|
|
134
|
-
]);
|
|
135
|
-
}
|
|
136
|
-
catch (err) {
|
|
137
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
138
|
-
fnSpinner.fail(msg);
|
|
139
|
-
return { success: false, error: msg };
|
|
140
|
-
}
|
|
141
|
-
finally {
|
|
142
|
-
clearTimeout(warnTimer);
|
|
143
|
-
clearTimeout(assetTimeoutTimer);
|
|
144
|
-
}
|
|
145
|
-
if (result.success && result.assetId) {
|
|
146
|
-
const updatedResource = { ...resource, src: result.assetId };
|
|
147
|
-
fnSpinner.succeed(`${updatedResource.name} ${niceId(result.assetId)}`);
|
|
148
|
-
log(` Source: ${updatedResource.src}`);
|
|
149
|
-
if (result.hash) {
|
|
150
|
-
if (result.hash.length > 24) {
|
|
151
|
-
log.verbose(` Hash: ${result.hash.slice(0, 8)}...${result.hash.slice(-12)}`);
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
log.verbose(` Hash: ${result.hash}`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (result.exists)
|
|
158
|
-
log.verbose(' Asset unchanged');
|
|
159
|
-
return { success: true, resource: updatedResource };
|
|
160
|
-
}
|
|
161
|
-
const errorMsg = `Failed uploading ${resource.name} asset, deploy has stopped`;
|
|
162
|
-
fnSpinner.fail(errorMsg);
|
|
163
|
-
return { success: false, error: result.error || 'Failed to process asset' };
|
|
164
|
-
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Logger } from '../../utils/logger.js';
|
|
2
|
+
import type { AssetResource, AuthParams, InstallerType } from '../../utils/types.js';
|
|
3
|
+
type ProcessAssetsOptions = {
|
|
4
|
+
assetResources: AssetResource[];
|
|
5
|
+
auth: AuthParams;
|
|
6
|
+
blueprintFileDir: string;
|
|
7
|
+
installer: InstallerType;
|
|
8
|
+
logger: Logger;
|
|
9
|
+
onProgress?: (progress: ProcessAssetsProgress) => void;
|
|
10
|
+
onResult?: (result: ProcessedAsset) => void;
|
|
11
|
+
};
|
|
12
|
+
export type ProcessAssetsProgress = {
|
|
13
|
+
completed: number;
|
|
14
|
+
total: number;
|
|
15
|
+
slow: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type ProcessedAsset = {
|
|
18
|
+
success: false;
|
|
19
|
+
resource: AssetResource;
|
|
20
|
+
error: string;
|
|
21
|
+
failureKind: 'processing' | 'upload';
|
|
22
|
+
} | {
|
|
23
|
+
success: true;
|
|
24
|
+
resource: AssetResource;
|
|
25
|
+
hash?: string;
|
|
26
|
+
exists?: boolean;
|
|
27
|
+
};
|
|
28
|
+
export declare function processAssets(options: ProcessAssetsOptions): Promise<ProcessedAsset[]>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { env } from 'node:process';
|
|
2
|
+
import { stashAsset } from './assets.js';
|
|
3
|
+
const DEFAULT_ASSET_CONCURRENCY = 4;
|
|
4
|
+
const DEFAULT_ASSET_TIMEOUT = 180;
|
|
5
|
+
const assetTimeoutS = Number(env.SANITY_ASSET_TIMEOUT) || DEFAULT_ASSET_TIMEOUT;
|
|
6
|
+
const assetTimeoutMs = assetTimeoutS * 1000;
|
|
7
|
+
const warnTimeoutMs = assetTimeoutMs / 3;
|
|
8
|
+
export async function processAssets(options) {
|
|
9
|
+
const { assetResources, onProgress, onResult } = options;
|
|
10
|
+
const total = assetResources.length;
|
|
11
|
+
const processedAssets = [];
|
|
12
|
+
const concurrency = getAssetConcurrency();
|
|
13
|
+
let completed = 0;
|
|
14
|
+
let slow = false;
|
|
15
|
+
const reportProgress = () => onProgress?.({ completed, total, slow });
|
|
16
|
+
reportProgress();
|
|
17
|
+
const warnTimer = setTimeout(() => {
|
|
18
|
+
slow = true;
|
|
19
|
+
reportProgress();
|
|
20
|
+
}, warnTimeoutMs);
|
|
21
|
+
try {
|
|
22
|
+
for (let start = 0; start < total; start += concurrency) {
|
|
23
|
+
const batch = assetResources.slice(start, start + concurrency);
|
|
24
|
+
const batchResults = await Promise.all(batch.map(async (resource) => {
|
|
25
|
+
const result = await processAsset(resource, options);
|
|
26
|
+
completed += 1;
|
|
27
|
+
reportProgress();
|
|
28
|
+
onResult?.(result);
|
|
29
|
+
return result;
|
|
30
|
+
}));
|
|
31
|
+
processedAssets.push(...batchResults);
|
|
32
|
+
if (batchResults.some((result) => !result.success))
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
clearTimeout(warnTimer);
|
|
38
|
+
}
|
|
39
|
+
return processedAssets;
|
|
40
|
+
}
|
|
41
|
+
function getAssetConcurrency() {
|
|
42
|
+
const configured = Number(env.SANITY_ASSET_CONCURRENCY);
|
|
43
|
+
return Number.isSafeInteger(configured) && configured > 0 ? configured : DEFAULT_ASSET_CONCURRENCY;
|
|
44
|
+
}
|
|
45
|
+
async function processAsset(resource, options) {
|
|
46
|
+
const { auth, blueprintFileDir, installer, logger } = options;
|
|
47
|
+
let assetTimeoutTimer;
|
|
48
|
+
let result;
|
|
49
|
+
try {
|
|
50
|
+
result = await Promise.race([
|
|
51
|
+
stashAsset({ resource, blueprintFileDir, auth, logger, installer }),
|
|
52
|
+
new Promise((_, reject) => {
|
|
53
|
+
assetTimeoutTimer = setTimeout(() => {
|
|
54
|
+
reject(new Error(`Processing ${resource.name} timed out after ${assetTimeoutS}s`));
|
|
55
|
+
}, assetTimeoutMs);
|
|
56
|
+
}),
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
61
|
+
return { success: false, resource, error: msg, failureKind: 'processing' };
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
clearTimeout(assetTimeoutTimer);
|
|
65
|
+
}
|
|
66
|
+
if (result.success && result.assetId) {
|
|
67
|
+
return {
|
|
68
|
+
success: true,
|
|
69
|
+
resource: { ...resource, src: result.assetId },
|
|
70
|
+
hash: result.hash,
|
|
71
|
+
exists: result.exists,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
success: false,
|
|
76
|
+
resource,
|
|
77
|
+
error: result.error || 'Failed to process asset',
|
|
78
|
+
failureKind: 'upload',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Logger } from '../../utils/logger.js';
|
|
2
|
+
import type { AuthParams } from '../../utils/types.js';
|
|
3
|
+
/** A deployed function returned by the Functions API. */
|
|
4
|
+
export interface DeployedFunction {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string | null;
|
|
7
|
+
displayName: string | null;
|
|
8
|
+
type: string | null;
|
|
9
|
+
runtime: string | null;
|
|
10
|
+
status: string | null;
|
|
11
|
+
memory: number | null;
|
|
12
|
+
storage: number | null;
|
|
13
|
+
timeout: number | null;
|
|
14
|
+
resourceType: string | null;
|
|
15
|
+
resourceId: string | null;
|
|
16
|
+
robotId: string | null;
|
|
17
|
+
url: string | null;
|
|
18
|
+
eventRule?: unknown;
|
|
19
|
+
contentSub?: Record<string, unknown> | null;
|
|
20
|
+
createdAt: string | null;
|
|
21
|
+
updatedAt: string | null;
|
|
22
|
+
deletedAt?: string | null;
|
|
23
|
+
}
|
|
24
|
+
type ListFunctionsResponse = {
|
|
25
|
+
ok: true;
|
|
26
|
+
error: null;
|
|
27
|
+
functions: DeployedFunction[];
|
|
28
|
+
} | {
|
|
29
|
+
ok: false;
|
|
30
|
+
error: string;
|
|
31
|
+
functions: [];
|
|
32
|
+
};
|
|
33
|
+
export declare function listFunctions(auth: AuthParams, logger: Logger): Promise<ListFunctionsResponse>;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import config from '../../config.js';
|
|
2
|
+
import getHeaders from '../../utils/get-headers.js';
|
|
3
|
+
import { createTracedFetch } from '../../utils/traced-fetch.js';
|
|
4
|
+
const { apiUrl } = config;
|
|
5
|
+
export async function listFunctions(auth, logger) {
|
|
6
|
+
try {
|
|
7
|
+
const fetchFn = createTracedFetch(logger);
|
|
8
|
+
const response = await fetchFn(`${apiUrl}v2025-07-30/functions/`, {
|
|
9
|
+
headers: getHeaders(auth),
|
|
10
|
+
method: 'GET',
|
|
11
|
+
});
|
|
12
|
+
const payload = (await response.json());
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
return {
|
|
15
|
+
ok: false,
|
|
16
|
+
error: payload.error?.message ?? payload.message ?? 'Failed to list functions',
|
|
17
|
+
functions: [],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
ok: true,
|
|
22
|
+
error: null,
|
|
23
|
+
functions: Array.isArray(payload.functions) ? payload.functions : [],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
error: error instanceof Error ? error.message : String(error),
|
|
30
|
+
functions: [],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Logger } from '../../utils/logger.js';
|
|
2
|
+
import type { ActionResponse } from '../../utils/types.js';
|
|
3
|
+
export interface Dataset {
|
|
4
|
+
name: string;
|
|
5
|
+
aclMode: string;
|
|
6
|
+
}
|
|
7
|
+
interface ListDatasetsResponse extends ActionResponse {
|
|
8
|
+
datasets: Dataset[];
|
|
9
|
+
}
|
|
10
|
+
export declare function listDatasets({ token, projectId, logger, }: {
|
|
11
|
+
token: string;
|
|
12
|
+
projectId: string;
|
|
13
|
+
logger: Logger;
|
|
14
|
+
}): Promise<ListDatasetsResponse>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createTracedFetch } from '../../utils/traced-fetch.js';
|
|
2
|
+
import { projectsApiPath } from './projects.js';
|
|
3
|
+
export async function listDatasets({ token, projectId, logger, }) {
|
|
4
|
+
const fetchFn = createTracedFetch(logger);
|
|
5
|
+
const url = `${projectsApiPath}/${encodeURIComponent(projectId)}/datasets`;
|
|
6
|
+
const response = await fetchFn(url, {
|
|
7
|
+
method: 'GET',
|
|
8
|
+
headers: {
|
|
9
|
+
Authorization: `Bearer ${token}`,
|
|
10
|
+
'Content-Type': 'application/json',
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
const body = await response.json();
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
return {
|
|
16
|
+
ok: false,
|
|
17
|
+
error: body.error?.message ?? response.statusText,
|
|
18
|
+
datasets: [],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
ok: true,
|
|
23
|
+
error: null,
|
|
24
|
+
datasets: body,
|
|
25
|
+
};
|
|
26
|
+
}
|
package/dist/baseCommands.d.ts
CHANGED
|
@@ -138,5 +138,7 @@ export declare abstract class ResolvedCommand<T extends typeof Command> extends
|
|
|
138
138
|
protected blueprintConfig?: LocatedBlueprintsConfig | null;
|
|
139
139
|
protected auth: AuthParams;
|
|
140
140
|
protected deployedStack: Stack;
|
|
141
|
+
/** Runs after flag parsing and before declared command context is resolved. */
|
|
142
|
+
protected preResolve(): Promise<void>;
|
|
141
143
|
init(): Promise<void>;
|
|
142
144
|
}
|
package/dist/baseCommands.js
CHANGED
|
@@ -220,8 +220,11 @@ export class ResolvedCommand extends RuntimeCommand {
|
|
|
220
220
|
blueprintConfig;
|
|
221
221
|
auth;
|
|
222
222
|
deployedStack;
|
|
223
|
+
/** Runs after flag parsing and before declared command context is resolved. */
|
|
224
|
+
async preResolve() { }
|
|
223
225
|
async init() {
|
|
224
226
|
await super.init(); // RuntimeCommand: parse flags + args
|
|
227
|
+
await this.preResolve();
|
|
225
228
|
const needs = new Set(this.constructor.needs);
|
|
226
229
|
if (needs.size === 0)
|
|
227
230
|
return;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RuntimeCommand } from '../baseCommands.js';
|
|
2
|
+
export default class BlueprinitCommand extends RuntimeCommand<typeof BlueprinitCommand> {
|
|
3
|
+
static hidden: boolean;
|
|
4
|
+
static summary: string;
|
|
5
|
+
static description: string;
|
|
6
|
+
static examples: string[];
|
|
7
|
+
static flags: {
|
|
8
|
+
llms: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
'organization-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
dataset: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
'stack-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
'studio-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<Record<string, unknown> | undefined>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { llmsFlagConfig } from '@sanity-labs/oclif-plugin-skills-flag';
|
|
3
|
+
import { RuntimeCommand } from '../baseCommands.js';
|
|
4
|
+
import { blueprinitCore } from '../cores/blueprinit.js';
|
|
5
|
+
import { Logger } from '../utils/logger.js';
|
|
6
|
+
export default class BlueprinitCommand extends RuntimeCommand {
|
|
7
|
+
static hidden = true;
|
|
8
|
+
static summary = 'Experimental blueprints-first project init';
|
|
9
|
+
static description = `Scaffolds a Blueprint, Studio, and starter function around an existing Sanity project.
|
|
10
|
+
|
|
11
|
+
This hidden command is an experiment; its interface and generated files are not stable.`;
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> <%= command.id %>',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> --yes',
|
|
15
|
+
'<%= config.bin %> <%= command.id %> --project-id <id> --dataset <name> --yes',
|
|
16
|
+
];
|
|
17
|
+
static flags = {
|
|
18
|
+
llms: Flags.boolean(llmsFlagConfig),
|
|
19
|
+
yes: Flags.boolean({
|
|
20
|
+
char: 'y',
|
|
21
|
+
description: 'Skip confirmation prompts',
|
|
22
|
+
default: false,
|
|
23
|
+
}),
|
|
24
|
+
'organization-id': Flags.string({
|
|
25
|
+
description: 'Organization to use when the account has several',
|
|
26
|
+
aliases: ['organization', 'organizationId', 'org'],
|
|
27
|
+
}),
|
|
28
|
+
'project-id': Flags.string({
|
|
29
|
+
description: 'Project to use when the organization has several',
|
|
30
|
+
aliases: ['project', 'projectId'],
|
|
31
|
+
}),
|
|
32
|
+
dataset: Flags.string({
|
|
33
|
+
description: 'Dataset to attach',
|
|
34
|
+
}),
|
|
35
|
+
'stack-name': Flags.string({
|
|
36
|
+
description: 'Name for the new organization-scoped Stack',
|
|
37
|
+
}),
|
|
38
|
+
'studio-name': Flags.string({
|
|
39
|
+
description: 'Name and hosted slug for the Studio resource',
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
async run() {
|
|
43
|
+
const result = await blueprinitCore({
|
|
44
|
+
bin: this.config.bin,
|
|
45
|
+
log: Logger(this.log.bind(this), this.flags),
|
|
46
|
+
flags: {
|
|
47
|
+
yes: this.flags.yes,
|
|
48
|
+
organizationId: this.flags['organization-id'],
|
|
49
|
+
projectId: this.flags['project-id'],
|
|
50
|
+
datasetName: this.flags.dataset,
|
|
51
|
+
stackName: this.flags['stack-name'],
|
|
52
|
+
studioName: this.flags['studio-name'],
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
if (!result.success)
|
|
56
|
+
this.coreError(result);
|
|
57
|
+
return result.json;
|
|
58
|
+
}
|
|
59
|
+
}
|