@sanity/runtime-cli 17.2.0 → 17.3.0-blueprinit.g6a63623

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.
Files changed (57) hide show
  1. package/README.md +36 -27
  2. package/SKILL.md +21 -0
  3. package/dist/actions/blueprinit/manifest.d.ts +13 -0
  4. package/dist/actions/blueprinit/manifest.js +57 -0
  5. package/dist/actions/blueprinit/studio.d.ts +6 -0
  6. package/dist/actions/blueprinit/studio.js +43 -0
  7. package/dist/actions/blueprinit/templates.d.ts +34 -0
  8. package/dist/actions/blueprinit/templates.js +83 -0
  9. package/dist/actions/blueprinit/workspace.d.ts +15 -0
  10. package/dist/actions/blueprinit/workspace.js +142 -0
  11. package/dist/actions/blueprints/assets.js +15 -24
  12. package/dist/actions/blueprints/pre-deploy.d.ts +29 -0
  13. package/dist/actions/blueprints/pre-deploy.js +164 -0
  14. package/dist/actions/sanity/datasets.d.ts +15 -0
  15. package/dist/actions/sanity/datasets.js +26 -0
  16. package/dist/commands/blueprinit.d.ts +17 -0
  17. package/dist/commands/blueprinit.js +59 -0
  18. package/dist/commands/functions/env/add.d.ts +3 -0
  19. package/dist/commands/functions/env/add.js +6 -2
  20. package/dist/commands/functions/env/list.d.ts +3 -0
  21. package/dist/commands/functions/env/list.js +9 -3
  22. package/dist/commands/functions/env/remove.d.ts +3 -0
  23. package/dist/commands/functions/env/remove.js +9 -3
  24. package/dist/constants.d.ts +1 -0
  25. package/dist/constants.js +1 -0
  26. package/dist/cores/blueprinit/adopt.d.ts +6 -0
  27. package/dist/cores/blueprinit/adopt.js +108 -0
  28. package/dist/cores/blueprinit/finish.d.ts +14 -0
  29. package/dist/cores/blueprinit/finish.js +64 -0
  30. package/dist/cores/blueprinit/fresh.d.ts +3 -0
  31. package/dist/cores/blueprinit/fresh.js +108 -0
  32. package/dist/cores/blueprinit/preview.d.ts +14 -0
  33. package/dist/cores/blueprinit/preview.js +13 -0
  34. package/dist/cores/blueprinit/prompts.d.ts +4 -0
  35. package/dist/cores/blueprinit/prompts.js +16 -0
  36. package/dist/cores/blueprinit/selection.d.ts +15 -0
  37. package/dist/cores/blueprinit/selection.js +90 -0
  38. package/dist/cores/blueprinit/types.d.ts +16 -0
  39. package/dist/cores/blueprinit/types.js +1 -0
  40. package/dist/cores/blueprinit.d.ts +7 -0
  41. package/dist/cores/blueprinit.js +48 -0
  42. package/dist/cores/blueprints/deploy.js +16 -64
  43. package/dist/server/static/vendor/vendor.bundle.js +10 -14
  44. package/dist/utils/functions/child-process-wrapper.js +13 -171
  45. package/dist/utils/functions/child-runtime/console.d.ts +1 -0
  46. package/dist/utils/functions/child-runtime/console.js +50 -0
  47. package/dist/utils/functions/child-runtime/invoke.d.ts +4 -0
  48. package/dist/utils/functions/child-runtime/invoke.js +47 -0
  49. package/dist/utils/functions/child-runtime/resources.d.ts +4 -0
  50. package/dist/utils/functions/child-runtime/resources.js +59 -0
  51. package/dist/utils/studios/prepare-asset.d.ts +6 -0
  52. package/dist/utils/studios/prepare-asset.js +139 -0
  53. package/dist/utils/types.d.ts +4 -7
  54. package/dist/utils/types.js +2 -3
  55. package/oclif.manifest.json +135 -7
  56. package/package.json +26 -16
  57. package/skills/blueprinit.md +148 -0
@@ -0,0 +1,64 @@
1
+ import { writeConfigFile } from '../../actions/blueprints/config.js';
2
+ import { createEmptyStack } from '../../actions/blueprints/stacks.js';
3
+ import { check } from '../../utils/display/presenters.js';
4
+ export async function createStackAndFinish(options) {
5
+ let stack;
6
+ try {
7
+ stack = await createEmptyStack({
8
+ token: options.token,
9
+ scopeType: 'organization',
10
+ scopeId: options.organizationId,
11
+ name: options.stackName,
12
+ logger: options.log,
13
+ });
14
+ }
15
+ catch (error) {
16
+ const message = error instanceof Error ? error.message : String(error);
17
+ return {
18
+ success: false,
19
+ error: `Local files were written, but Stack creation failed: ${message}`,
20
+ suggestions: [
21
+ 'Resolve the Stack error, then finish setup with:',
22
+ `npx ${options.bin} blueprints init --organization-id ${options.organizationId} --stack-name ${options.stackName}`,
23
+ ],
24
+ };
25
+ }
26
+ try {
27
+ writeConfigFile(options.blueprintFilePath, {
28
+ organizationId: options.organizationId,
29
+ stackId: stack.id,
30
+ });
31
+ }
32
+ catch (error) {
33
+ const message = error instanceof Error ? error.message : String(error);
34
+ return {
35
+ success: false,
36
+ error: `Stack ${stack.id} was created, but the local Blueprint config could not be written: ${message}`,
37
+ suggestions: [
38
+ 'Finish setup with:',
39
+ `npx ${options.bin} blueprints init --organization-id ${options.organizationId} --stack-id ${stack.id}`,
40
+ ],
41
+ };
42
+ }
43
+ options.log(check(`Stack ${stack.name} created (org scope)`));
44
+ options.log('The Stack is empty; Blueprint resources have not been deployed.');
45
+ options.log('');
46
+ options.log('Next steps:');
47
+ for (const step of options.nextSteps)
48
+ options.log(step);
49
+ return {
50
+ success: true,
51
+ json: {
52
+ organizationId: options.organizationId,
53
+ projectId: options.projectId,
54
+ dataset: options.datasetName,
55
+ stackId: stack.id,
56
+ stackName: stack.name,
57
+ files: [...options.files, '.sanity/blueprint.config.json'],
58
+ },
59
+ };
60
+ }
61
+ export function alignSteps(steps) {
62
+ const width = Math.max(...steps.map(([command]) => command.length));
63
+ return steps.map(([command, description]) => description ? ` ${command.padEnd(width + 2)}${description}` : ` ${command}`);
64
+ }
@@ -0,0 +1,3 @@
1
+ import type { CoreResult } from '../index.js';
2
+ import type { BlueprinitFlowOptions } from './types.js';
3
+ export declare function freshFlow(options: BlueprinitFlowOptions): Promise<CoreResult>;
@@ -0,0 +1,108 @@
1
+ import { STARTER_FUNCTION_NAME, slugify } from '../../actions/blueprinit/manifest.js';
2
+ import { scaffoldWorkspace } from '../../actions/blueprinit/workspace.js';
3
+ import { listDatasets } from '../../actions/sanity/datasets.js';
4
+ import { groupProjectsByOrganization } from '../../actions/sanity/projects.js';
5
+ import { check } from '../../utils/display/presenters.js';
6
+ import { getLatestNpmVersion } from '../../utils/external/npmjs.js';
7
+ import { isInteractive } from '../../utils/interactive.js';
8
+ import { alignSteps, createStackAndFinish } from './finish.js';
9
+ import { printBlueprintPreview } from './preview.js';
10
+ import { confirmUnlessSkipped, promptForStudioName } from './prompts.js';
11
+ import { chooseDataset, chooseOrganization, chooseProject } from './selection.js';
12
+ export async function freshFlow(options) {
13
+ const { bin, dir, flags, log, token } = options;
14
+ const grouped = await groupProjectsByOrganization({ token, logger: log });
15
+ if (!grouped.ok) {
16
+ return { success: false, error: `Could not list organizations: ${grouped.error}` };
17
+ }
18
+ const organizationResult = await chooseOrganization(grouped.organizations, flags.organizationId, isInteractive);
19
+ if (!organizationResult.ok) {
20
+ return {
21
+ success: false,
22
+ error: organizationResult.error.message,
23
+ suggestions: organizationResult.error.suggestions,
24
+ };
25
+ }
26
+ const { organization, projects = [] } = organizationResult.value;
27
+ const projectResult = await chooseProject(projects, flags.projectId, organization.name, isInteractive);
28
+ if (!projectResult.ok) {
29
+ return {
30
+ success: false,
31
+ error: projectResult.error.message,
32
+ suggestions: projectResult.error.suggestions,
33
+ };
34
+ }
35
+ const project = projectResult.value;
36
+ const datasets = await listDatasets({ token, projectId: project.id, logger: log });
37
+ if (!datasets.ok) {
38
+ return { success: false, error: `Could not list datasets: ${datasets.error}` };
39
+ }
40
+ const datasetResult = await chooseDataset(datasets.datasets, flags.datasetName, project.displayName, isInteractive);
41
+ if (!datasetResult.ok) {
42
+ return {
43
+ success: false,
44
+ error: datasetResult.error.message,
45
+ suggestions: datasetResult.error.suggestions,
46
+ };
47
+ }
48
+ const dataset = datasetResult.value;
49
+ log(check(`Org: ${organization.name} (${organization.id})`));
50
+ log(check(`Project: ${project.displayName} (${project.id}), dataset: ${dataset.name}`));
51
+ log('');
52
+ const stackName = flags.stackName
53
+ ? slugify(flags.stackName) || project.id
54
+ : slugify(project.displayName) || project.id;
55
+ const studioName = await promptForStudioName(flags, isInteractive);
56
+ printBlueprintPreview({
57
+ log,
58
+ organizationName: organization.name,
59
+ projectId: project.id,
60
+ projectName: project.displayName,
61
+ datasetName: dataset.name,
62
+ studioName,
63
+ studioSource: `studios/${studioName}`,
64
+ stackName,
65
+ functionName: STARTER_FUNCTION_NAME,
66
+ });
67
+ log('Files to write:');
68
+ log(' + sanity.blueprint.ts project + dataset (attached), studio, function');
69
+ log(` + ${`studios/${studioName}/`.padEnd(32)}Studio with a "post" type`);
70
+ log(` + ${`functions/${STARTER_FUNCTION_NAME}/`.padEnd(32)}sets a post slug from its title`);
71
+ log(` + .sanity/blueprint.config.json org scope, stack ${stackName}`);
72
+ log(' + .gitignore');
73
+ log(' + package.json');
74
+ log('');
75
+ const confirmed = await confirmUnlessSkipped(flags, isInteractive, `Create this Blueprint workspace for "${project.displayName}"?`);
76
+ if (!confirmed)
77
+ return { success: false, error: 'Initialization cancelled.' };
78
+ const scaffold = scaffoldWorkspace({
79
+ dir,
80
+ projectId: project.id,
81
+ displayName: project.displayName,
82
+ datasetName: dataset.name,
83
+ stackName,
84
+ studioName,
85
+ blueprintsVersion: await getLatestNpmVersion('@sanity/blueprints', log),
86
+ });
87
+ return await createStackAndFinish({
88
+ ...options,
89
+ organizationId: organization.id,
90
+ projectId: project.id,
91
+ datasetName: dataset.name,
92
+ stackName,
93
+ blueprintFilePath: scaffold.blueprintFilePath,
94
+ files: scaffold.files,
95
+ nextSteps: [
96
+ ...alignSteps([
97
+ ['npm install'],
98
+ [`npm run dev -w studios/${studioName}`, 'open the Studio and add a post'],
99
+ [`npx ${bin} blueprints plan`, 'preview the deployment'],
100
+ [`npx ${bin} blueprints deploy`, 'deploy the resources'],
101
+ ]),
102
+ '',
103
+ 'Test the function on that post:',
104
+ ` npx ${bin} functions test ${STARTER_FUNCTION_NAME} --document-id <id>` +
105
+ ` --project-id ${project.id} --dataset ${dataset.name} --with-user-token`,
106
+ ],
107
+ });
108
+ }
@@ -0,0 +1,14 @@
1
+ import type { Logger } from '../../utils/logger.js';
2
+ interface BlueprintPreviewOptions {
3
+ log: Logger;
4
+ organizationName: string;
5
+ projectId: string;
6
+ projectName: string;
7
+ datasetName: string;
8
+ studioName: string;
9
+ studioSource: string;
10
+ stackName: string;
11
+ functionName?: string;
12
+ }
13
+ export declare function printBlueprintPreview(options: BlueprintPreviewOptions): void;
14
+ export {};
@@ -0,0 +1,13 @@
1
+ export function printBlueprintPreview(options) {
2
+ options.log('Blueprint plan:');
3
+ options.log(` attach Project ${options.projectName} (${options.projectId}) on deploy; retain on Stack removal`);
4
+ options.log(` attach Dataset ${options.datasetName} on deploy; retain on Stack removal`);
5
+ options.log(` create Studio ${options.studioName} from ${options.studioSource} on deploy`);
6
+ if (options.functionName) {
7
+ options.log(` create Function ${options.functionName} on deploy`);
8
+ }
9
+ options.log('');
10
+ options.log('Remote action now:');
11
+ options.log(` create Stack ${options.stackName} in ${options.organizationName} (empty, organization scope)`);
12
+ options.log('');
13
+ }
@@ -0,0 +1,4 @@
1
+ import type { BlueprinitFlags } from './types.js';
2
+ export declare const DEFAULT_STUDIO_NAME = "studio";
3
+ export declare function confirmUnlessSkipped(flags: BlueprinitFlags, interactive: boolean, message: string): Promise<boolean>;
4
+ export declare function promptForStudioName(flags: BlueprinitFlags, interactive: boolean): Promise<string>;
@@ -0,0 +1,16 @@
1
+ import { confirm, input } from '@inquirer/prompts';
2
+ import { slugify } from '../../actions/blueprinit/manifest.js';
3
+ export const DEFAULT_STUDIO_NAME = 'studio';
4
+ export async function confirmUnlessSkipped(flags, interactive, message) {
5
+ if (flags.yes || !interactive)
6
+ return true;
7
+ return await confirm({ message, default: true });
8
+ }
9
+ export async function promptForStudioName(flags, interactive) {
10
+ if (flags.studioName)
11
+ return slugify(flags.studioName) || DEFAULT_STUDIO_NAME;
12
+ if (flags.yes || !interactive)
13
+ return DEFAULT_STUDIO_NAME;
14
+ const answer = await input({ message: 'Name your studio?', default: DEFAULT_STUDIO_NAME });
15
+ return slugify(answer) || DEFAULT_STUDIO_NAME;
16
+ }
@@ -0,0 +1,15 @@
1
+ import type { Dataset } from '../../actions/sanity/datasets.js';
2
+ import type { GroupedProjects, Project } from '../../actions/sanity/projects.js';
3
+ import type { Result } from '../../utils/types.js';
4
+ interface SelectionError {
5
+ message: string;
6
+ suggestions?: string[];
7
+ }
8
+ export declare function chooseOrganization(groups: GroupedProjects[], organizationId: string | undefined, interactive: boolean): Promise<Result<GroupedProjects, SelectionError>>;
9
+ export declare function chooseProject(projects: Project[], projectId: string | undefined, organizationName: string, interactive: boolean): Promise<Result<Project, SelectionError>>;
10
+ export declare function chooseDataset(datasets: Dataset[], datasetName: string | undefined, projectName: string, interactive: boolean): Promise<Result<Dataset, SelectionError>>;
11
+ export declare function findProject(groups: GroupedProjects[], projectId: string): {
12
+ organization: GroupedProjects['organization'];
13
+ project: Project;
14
+ } | undefined;
15
+ export {};
@@ -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
+ }
@@ -1,5 +1,5 @@
1
1
  import { dirname } from 'node:path';
2
- import { stashAsset } from '../../actions/blueprints/assets.js';
2
+ import { preDeploy } from '../../actions/blueprints/pre-deploy.js';
3
3
  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';
@@ -7,12 +7,7 @@ import { createHintCollector } from '../../utils/blueprints/hints.js';
7
7
  import { niceId } 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
- import { isAssetResource } from '../../utils/types.js';
11
10
  import { getWarnings } from '../../utils/warnings.js';
12
- const DEFAULT_ASSET_TIMEOUT = 60;
13
- const assetTimeoutS = Number(process.env.SANITY_ASSET_TIMEOUT) || DEFAULT_ASSET_TIMEOUT;
14
- const assetTimeoutMs = assetTimeoutS * 1000;
15
- const warnTimeoutMs = assetTimeoutMs / 2;
16
11
  export async function blueprintDeployCore(options) {
17
12
  const { bin = 'sanity', log, auth, stackId, scopeType, scopeId, deployedStack, blueprint, flags, } = options;
18
13
  const { verbose } = flags;
@@ -39,17 +34,21 @@ export async function blueprintDeployCore(options) {
39
34
  };
40
35
  }
41
36
  const resources = [...blueprint.resources];
42
- const assetResources = resources.filter(isAssetResource);
43
- if (assetResources.length > 0) {
44
- log('Processing assets...');
45
- for (const resource of assetResources) {
46
- const preDeployResult = await preDeploy(resource, { auth, blueprintFileDir, installer, log });
47
- if (!preDeployResult.success)
48
- return preDeployResult;
49
- const idx = resources.indexOf(resource);
50
- if (idx !== -1)
51
- resources[idx] = preDeployResult.resource;
52
- }
37
+ const preDeployResult = await preDeploy({
38
+ scopeType,
39
+ scopeId,
40
+ resources,
41
+ stackId,
42
+ stackName,
43
+ auth,
44
+ blueprintFileDir,
45
+ installer,
46
+ log,
47
+ bin,
48
+ verbose,
49
+ });
50
+ if (!preDeployResult?.success) {
51
+ return preDeployResult;
53
52
  }
54
53
  const spinner = log.ora('Deploying...').start();
55
54
  const { ok: deployOk, stack, error: deployError, } = await updateStack({
@@ -152,53 +151,6 @@ export async function blueprintDeployCore(options) {
152
151
  return { success: false, error: errorMessage };
153
152
  }
154
153
  }
155
- async function preDeploy(resource, options) {
156
- const { auth, blueprintFileDir, installer, log } = options;
157
- const fnSpinner = log.ora({ text: `Processing ${resource.name}...`, prefixText: ' ' }).start();
158
- const warnTimer = setTimeout(() => {
159
- fnSpinner.text = `Still processing ${resource.name}, this can take a moment...`;
160
- }, warnTimeoutMs);
161
- let assetTimeoutTimer;
162
- let result;
163
- try {
164
- result = await Promise.race([
165
- stashAsset({ resource, blueprintFileDir, auth, logger: log, installer }),
166
- new Promise((_, reject) => {
167
- assetTimeoutTimer = setTimeout(() => {
168
- reject(new Error(`Processing ${resource.name} timed out after ${assetTimeoutS}s`));
169
- }, assetTimeoutMs);
170
- }),
171
- ]);
172
- }
173
- catch (err) {
174
- const msg = err instanceof Error ? err.message : String(err);
175
- fnSpinner.fail(msg);
176
- return { success: false, error: msg };
177
- }
178
- finally {
179
- clearTimeout(warnTimer);
180
- clearTimeout(assetTimeoutTimer);
181
- }
182
- if (result.success && result.assetId) {
183
- const updatedResource = { ...resource, src: result.assetId };
184
- fnSpinner.succeed(`${updatedResource.name} ${niceId(result.assetId)}`);
185
- log(` Source: ${updatedResource.src}`);
186
- if (result.hash) {
187
- if (result.hash.length > 24) {
188
- log.verbose(` Hash: ${result.hash.slice(0, 8)}...${result.hash.slice(-12)}`);
189
- }
190
- else {
191
- log.verbose(` Hash: ${result.hash}`);
192
- }
193
- }
194
- if (result.exists)
195
- log.verbose(' Asset unchanged');
196
- return { success: true, resource: updatedResource };
197
- }
198
- const errorMsg = `Failed uploading ${resource.name} asset, deploy has stopped`;
199
- fnSpinner.fail(errorMsg);
200
- return { success: false, error: result.error || 'Failed to process asset' };
201
- }
202
154
  const CHECK_LEGACY_DEPLOY_PERMISSION = false;
203
155
  const BLUEPRINTS_PERMISSION_ENFORCEMENT_DATE = 'July 1, 2026';
204
156
  async function checkLegacyDeployPermission({ auth, scopeType, scopeId, log, }) {
@@ -27598,25 +27598,21 @@ function prettyBytes(number, options) {
27598
27598
  }
27599
27599
 
27600
27600
  const localeOptions = buildLocaleOptions(options);
27601
- let result;
27602
27601
 
27603
- if (number < 1) {
27604
- const numberString = toLocaleString(number, options.locale, localeOptions);
27605
- result = prefix + numberString + separator + UNITS[0];
27606
- } else {
27607
- const exponent = Math.min(Math.floor(options.binary ? log(number) / Math.log(1024) : log10(number) / 3), UNITS.length - 1);
27608
- number = divide(number, (options.binary ? 1024 : 1000) ** exponent);
27602
+ let exponent = 0;
27603
+ if (number >= 1) {
27604
+ exponent = Math.min(Math.floor(options.binary ? log(number) / Math.log(1024) : log10(number) / 3), UNITS.length - 1);
27605
+ }
27609
27606
 
27610
- if (!localeOptions) {
27611
- const minPrecision = Math.max(3, Math.floor(number).toString().length);
27612
- number = number.toPrecision(minPrecision);
27613
- }
27607
+ number = divide(number, (options.binary ? 1024 : 1000) ** exponent);
27614
27608
 
27615
- const numberString = toLocaleString(Number(number), options.locale, localeOptions);
27616
- const unit = UNITS[exponent];
27617
- result = prefix + numberString + separator + unit;
27609
+ if (!localeOptions) {
27610
+ const minPrecision = Math.max(3, Math.floor(number).toString().length);
27611
+ number = Number(number.toPrecision(minPrecision));
27618
27612
  }
27619
27613
 
27614
+ const numberString = toLocaleString(number, options.locale, localeOptions);
27615
+ const result = prefix + numberString + separator + UNITS[exponent];
27620
27616
  return applyFixedWidth(result, options.fixedWidth);
27621
27617
  }
27622
27618