@sanity/runtime-cli 17.4.0 → 17.6.0-blueprinit.g77b7a64
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 +55 -52
- 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/blueprint.js +2 -1
- 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/blueprints/resources.js +17 -1
- package/dist/actions/sanity/datasets.d.ts +15 -0
- package/dist/actions/sanity/datasets.js +26 -0
- package/dist/commands/blueprints/deploy.js +3 -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/logs.d.ts +1 -1
- package/dist/commands/functions/logs.js +3 -0
- package/dist/commands/init.d.ts +17 -0
- package/dist/commands/init.js +59 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +6 -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 +71 -19
- package/dist/cores/blueprints/plan.js +2 -0
- package/dist/cores/functions/add.js +11 -8
- package/dist/server/static/components/api-base.js +1 -0
- package/dist/server/static/components/filters.js +4 -2
- package/dist/server/static/components/payload-panel.js +3 -0
- package/dist/utils/blueprints/extract-durable-config.d.ts +8 -0
- package/dist/utils/blueprints/extract-durable-config.js +60 -0
- package/dist/utils/blueprints/validate-resources.js +5 -2
- package/dist/utils/display/blueprints-formatting.js +8 -4
- package/dist/utils/display/resources-formatting.js +2 -2
- package/dist/utils/style-text.js +1 -0
- package/dist/utils/types.d.ts +8 -2
- package/dist/utils/types.js +8 -2
- package/oclif.manifest.json +151 -15
- package/package.json +29 -15
- package/skills/_functions-build.md +23 -0
- package/skills/_functions-list.md +28 -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
- package/skills/init.md +155 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface WorkspaceOptions {
|
|
2
|
+
dir: string;
|
|
3
|
+
projectId: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
datasetName: string;
|
|
6
|
+
stackName: string;
|
|
7
|
+
studioName: string;
|
|
8
|
+
studioOrigin: string;
|
|
9
|
+
blueprintsVersion: string;
|
|
10
|
+
}
|
|
11
|
+
export interface WorkspaceResult {
|
|
12
|
+
blueprintFilePath: string;
|
|
13
|
+
files: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function scaffoldWorkspace(options: WorkspaceOptions): WorkspaceResult;
|
|
16
|
+
export declare function adoptWorkspace(options: WorkspaceOptions): WorkspaceResult;
|
|
@@ -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
|
+
}
|
|
@@ -5,7 +5,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
5
5
|
import blueprintParserValidator from '@sanity/blueprints-parser';
|
|
6
6
|
import * as find from 'empathic/find';
|
|
7
7
|
import { createJiti } from 'jiti';
|
|
8
|
-
import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, } from '../../constants.js';
|
|
8
|
+
import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_DURABLE, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, } from '../../constants.js';
|
|
9
9
|
import { validateResources } from '../../utils/blueprints/validate-resources.js';
|
|
10
10
|
import { backfillProjectBasedStackId, readConfigFile, } from './config.js';
|
|
11
11
|
import { resolveIds } from './resolve.js';
|
|
@@ -122,6 +122,7 @@ export function parseBlueprintContent(rawBlueprint, options) {
|
|
|
122
122
|
SANITY_FUNCTION_DOCUMENT,
|
|
123
123
|
SANITY_FUNCTION_MEDIA_LIBRARY_ASSET,
|
|
124
124
|
SANITY_FUNCTION_SCHEDULED,
|
|
125
|
+
SANITY_FUNCTION_DURABLE,
|
|
125
126
|
],
|
|
126
127
|
});
|
|
127
128
|
const parsedBlueprint = parserResult.result === 'valid' ? parserResult.blueprint : undefined;
|
|
@@ -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
|
+
}
|
|
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { cwd } from 'node:process';
|
|
5
|
-
import { MAP_EVENT_TO_FUNCTION_TYPE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, } from '../../constants.js';
|
|
5
|
+
import { MAP_EVENT_TO_FUNCTION_TYPE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_PUBSUB, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, } from '../../constants.js';
|
|
6
6
|
import { styleText } from '../../utils/style-text.js';
|
|
7
7
|
import { writeOrUpdateNodeDependency } from '../node.js';
|
|
8
8
|
import { addResourceToBlueprint } from './blueprint.js';
|
|
@@ -36,6 +36,12 @@ export const handler = syncTagInvalidateEventHandler(async ({ context, event, do
|
|
|
36
36
|
console.error('Error invoking Sanity invalidation done endpoint!', e)
|
|
37
37
|
}
|
|
38
38
|
})`;
|
|
39
|
+
const DEFAULT_PUBSUB_HELPER_FUNCTION_TEMPLATE = /*ts*/ `import { pubSubEventHandler } from '@sanity/functions'
|
|
40
|
+
|
|
41
|
+
export const handler = pubSubEventHandler(async ({ context, event }) => {
|
|
42
|
+
const time = new Date().toLocaleTimeString()
|
|
43
|
+
console.log(\`Your pubsub Sanity Function was called at \${time}\`)
|
|
44
|
+
})`;
|
|
39
45
|
/**
|
|
40
46
|
* Creates a new function resource file and adds it to the blueprint
|
|
41
47
|
*/
|
|
@@ -68,6 +74,9 @@ export async function createFunctionResource(options, logger) {
|
|
|
68
74
|
let template = DEFAULT_FUNCTION_TEMPLATE;
|
|
69
75
|
if (addHelpers) {
|
|
70
76
|
switch (functionType) {
|
|
77
|
+
case SANITY_FUNCTION_PUBSUB:
|
|
78
|
+
template = DEFAULT_PUBSUB_HELPER_FUNCTION_TEMPLATE;
|
|
79
|
+
break;
|
|
71
80
|
case SANITY_FUNCTION_SCHEDULED:
|
|
72
81
|
template = DEFAULT_SCHEDULED_HELPER_FUNCTION_TEMPLATE;
|
|
73
82
|
break;
|
|
@@ -119,6 +128,13 @@ export async function createFunctionResource(options, logger) {
|
|
|
119
128
|
},
|
|
120
129
|
};
|
|
121
130
|
break;
|
|
131
|
+
case SANITY_FUNCTION_PUBSUB:
|
|
132
|
+
resourceJson = {
|
|
133
|
+
name,
|
|
134
|
+
src: `functions/${name}`,
|
|
135
|
+
type: SANITY_FUNCTION_PUBSUB,
|
|
136
|
+
};
|
|
137
|
+
break;
|
|
122
138
|
case SANITY_FUNCTION_SCHEDULED:
|
|
123
139
|
resourceJson = {
|
|
124
140
|
name,
|
|
@@ -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
|
+
}
|
|
@@ -14,7 +14,9 @@ Use --no-wait to queue the deployment and return immediately without waiting for
|
|
|
14
14
|
|
|
15
15
|
Use --fn-installer to force which package manager to use when deploying functions.
|
|
16
16
|
|
|
17
|
-
Set SANITY_ASSET_TIMEOUT (seconds) to override the
|
|
17
|
+
Set SANITY_ASSET_TIMEOUT (seconds) to override the 180-second timeout for processing resource assets.
|
|
18
|
+
|
|
19
|
+
Set SANITY_ASSET_CONCURRENCY to override how many resource assets are processed at once (default 4).
|
|
18
20
|
|
|
19
21
|
Exit codes: 0 deployed, 2 deployment failed, 75 deployment accepted but completion could not be confirmed (rerun 'blueprints info' to check).`;
|
|
20
22
|
static examples = [
|
|
@@ -19,7 +19,7 @@ Exit codes: 0 destroyed, 2 destruction failed, 75 destruction accepted but compl
|
|
|
19
19
|
static flags = {
|
|
20
20
|
force: Flags.boolean({
|
|
21
21
|
description: 'Force Stack destruction (skip confirmation)',
|
|
22
|
-
|
|
22
|
+
char: 'f',
|
|
23
23
|
default: false,
|
|
24
24
|
}),
|
|
25
25
|
'project-id': Flags.string({
|
|
@@ -8,6 +8,7 @@ export default class InfoCommand extends ResolvedCommand<typeof InfoCommand> {
|
|
|
8
8
|
stack: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
9
|
'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
'organization-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
12
|
};
|
|
12
13
|
run(): Promise<Record<string, unknown> | undefined>;
|
|
13
14
|
}
|
|
@@ -23,6 +23,11 @@ Run 'blueprints stacks' to see all available Stacks in your project or organizat
|
|
|
23
23
|
}),
|
|
24
24
|
'project-id': Flags.string({ ...projectIdFlagConfig }),
|
|
25
25
|
'organization-id': Flags.string({ ...organizationIdFlagConfig }),
|
|
26
|
+
verbose: Flags.boolean({
|
|
27
|
+
char: 'v',
|
|
28
|
+
description: 'Show resource and external IDs',
|
|
29
|
+
default: false,
|
|
30
|
+
}),
|
|
26
31
|
};
|
|
27
32
|
async run() {
|
|
28
33
|
const result = await blueprintInfoCore({
|
|
@@ -6,10 +6,9 @@ import { validTokenOrErrorMessage } from '../../utils/validated-token.js';
|
|
|
6
6
|
export default class InitCommand extends RuntimeCommand {
|
|
7
7
|
static summary = 'Initialize a Blueprint and create a remote Stack';
|
|
8
8
|
static description = `A Blueprint is your local infrastructure-as-code configuration that defines Sanity resources (datasets, functions, etc.). A Stack is the remote deployment target where your Blueprint is applied.
|
|
9
|
-
[NOTE: Currently, accounts are limited to three (3) Stacks per project scope.]
|
|
10
9
|
|
|
11
10
|
This is typically the first command you run in a new project. It creates a local Blueprint manifest file (sanity.blueprint.ts, .js, or .json) and provisions a new remote Stack.
|
|
12
|
-
Additionally, a Blueprint configuration file is created in .sanity/ containing the scope and Stack IDs.
|
|
11
|
+
Additionally, a Blueprint configuration file is created in .sanity/ containing the scope and Stack IDs. A .gitignore covering node_modules, .env, and Function build output is created or updated; the .sanity/ config itself is not ignored.
|
|
13
12
|
|
|
14
13
|
After initialization, use 'blueprints plan' to preview changes, then 'blueprints deploy' to apply them.
|
|
15
14
|
|
|
@@ -23,7 +23,7 @@ After adding, use 'functions dev' to test locally, then 'blueprints deploy' to p
|
|
|
23
23
|
example: Flags.string({
|
|
24
24
|
description: 'Example to use for the Function',
|
|
25
25
|
aliases: ['recipe'],
|
|
26
|
-
exclusive: ['name', '
|
|
26
|
+
exclusive: ['name', 'type', 'language', 'javascript', 'helpers', 'installer', 'install'],
|
|
27
27
|
}),
|
|
28
28
|
name: Flags.string({
|
|
29
29
|
description: 'Name of the Function to add',
|
|
@@ -56,9 +56,9 @@ After adding, use 'functions dev' to test locally, then 'blueprints deploy' to p
|
|
|
56
56
|
options: ['skip', ...INSTALLER_OPTIONS],
|
|
57
57
|
}),
|
|
58
58
|
install: Flags.boolean({
|
|
59
|
-
description: 'Shortcut for --
|
|
59
|
+
description: 'Shortcut for --installer npm',
|
|
60
60
|
char: 'i',
|
|
61
|
-
exclusive: ['
|
|
61
|
+
exclusive: ['installer'],
|
|
62
62
|
}),
|
|
63
63
|
};
|
|
64
64
|
async run() {
|