@sanity/runtime-cli 17.2.0-next.ga4b9ae7 → 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 (40) 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/sanity/datasets.d.ts +15 -0
  12. package/dist/actions/sanity/datasets.js +26 -0
  13. package/dist/commands/blueprinit.d.ts +17 -0
  14. package/dist/commands/blueprinit.js +59 -0
  15. package/dist/commands/functions/env/add.d.ts +3 -0
  16. package/dist/commands/functions/env/add.js +6 -2
  17. package/dist/commands/functions/env/list.d.ts +3 -0
  18. package/dist/commands/functions/env/list.js +9 -3
  19. package/dist/commands/functions/env/remove.d.ts +3 -0
  20. package/dist/commands/functions/env/remove.js +9 -3
  21. package/dist/cores/blueprinit/adopt.d.ts +6 -0
  22. package/dist/cores/blueprinit/adopt.js +108 -0
  23. package/dist/cores/blueprinit/finish.d.ts +14 -0
  24. package/dist/cores/blueprinit/finish.js +64 -0
  25. package/dist/cores/blueprinit/fresh.d.ts +3 -0
  26. package/dist/cores/blueprinit/fresh.js +108 -0
  27. package/dist/cores/blueprinit/preview.d.ts +14 -0
  28. package/dist/cores/blueprinit/preview.js +13 -0
  29. package/dist/cores/blueprinit/prompts.d.ts +4 -0
  30. package/dist/cores/blueprinit/prompts.js +16 -0
  31. package/dist/cores/blueprinit/selection.d.ts +15 -0
  32. package/dist/cores/blueprinit/selection.js +90 -0
  33. package/dist/cores/blueprinit/types.d.ts +16 -0
  34. package/dist/cores/blueprinit/types.js +1 -0
  35. package/dist/cores/blueprinit.d.ts +7 -0
  36. package/dist/cores/blueprinit.js +48 -0
  37. package/dist/server/static/vendor/vendor.bundle.js +10 -14
  38. package/oclif.manifest.json +135 -7
  39. package/package.json +19 -15
  40. package/skills/blueprinit.md +148 -0
@@ -0,0 +1,90 @@
1
+ import { select } from '@inquirer/prompts';
2
+ export async function chooseOrganization(groups, organizationId, interactive) {
3
+ if (groups.length === 0) {
4
+ return failure('No organizations found for this account.');
5
+ }
6
+ if (organizationId) {
7
+ const organization = groups.find((group) => group.organization.id === organizationId);
8
+ return organization
9
+ ? { ok: true, value: organization }
10
+ : failure(`No organization "${organizationId}" found for this account.`);
11
+ }
12
+ if (groups.length === 1)
13
+ return { ok: true, value: groups[0] };
14
+ if (!interactive) {
15
+ return failure('Multiple organizations found.', ['Pass --organization-id <id> to choose one.']);
16
+ }
17
+ const selectedId = await select({
18
+ message: 'Choose a Sanity organization:',
19
+ choices: groups.map(({ organization, projects }) => ({
20
+ name: organization.name,
21
+ value: organization.id,
22
+ description: `${projects?.length ?? 0} projects`,
23
+ })),
24
+ });
25
+ const selected = groups.find(({ organization }) => organization.id === selectedId);
26
+ return selected ? { ok: true, value: selected } : failure('Organization not found.');
27
+ }
28
+ export async function chooseProject(projects, projectId, organizationName, interactive) {
29
+ if (projects.length === 0) {
30
+ return failure(`No projects found in ${organizationName}.`);
31
+ }
32
+ if (projectId) {
33
+ const project = projects.find(({ id }) => id === projectId);
34
+ return project
35
+ ? { ok: true, value: project }
36
+ : failure(`No project "${projectId}" found in ${organizationName}.`);
37
+ }
38
+ if (projects.length === 1)
39
+ return { ok: true, value: projects[0] };
40
+ if (!interactive) {
41
+ return failure('Multiple projects found.', ['Pass --project-id <id> to choose one.']);
42
+ }
43
+ const selectedId = await select({
44
+ message: 'Choose a Sanity project:',
45
+ choices: projects.map((project) => ({ name: project.displayName, value: project.id })),
46
+ });
47
+ const selected = projects.find(({ id }) => id === selectedId);
48
+ return selected ? { ok: true, value: selected } : failure('Project not found.');
49
+ }
50
+ export async function chooseDataset(datasets, datasetName, projectName, interactive) {
51
+ if (datasets.length === 0) {
52
+ return failure(`No datasets found in ${projectName}.`);
53
+ }
54
+ if (datasetName) {
55
+ const dataset = datasets.find(({ name }) => name === datasetName);
56
+ return dataset
57
+ ? { ok: true, value: dataset }
58
+ : failure(`No dataset "${datasetName}" found in ${projectName}.`);
59
+ }
60
+ if (datasets.length === 1)
61
+ return { ok: true, value: datasets[0] };
62
+ if (!interactive) {
63
+ return failure('Multiple datasets found.', ['Pass --dataset <name> to choose one.']);
64
+ }
65
+ const production = datasets.find(({ name }) => name === 'production');
66
+ const orderedDatasets = production
67
+ ? [production, ...datasets.filter(({ name }) => name !== 'production')]
68
+ : datasets;
69
+ const selectedName = await select({
70
+ message: 'Choose a Sanity dataset:',
71
+ choices: orderedDatasets.map((dataset) => ({
72
+ name: dataset.name,
73
+ value: dataset.name,
74
+ description: dataset.aclMode,
75
+ })),
76
+ });
77
+ const selected = datasets.find(({ name }) => name === selectedName);
78
+ return selected ? { ok: true, value: selected } : failure('Dataset not found.');
79
+ }
80
+ export function findProject(groups, projectId) {
81
+ for (const { organization, projects = [] } of groups) {
82
+ const project = projects.find(({ id }) => id === projectId);
83
+ if (project)
84
+ return { organization, project };
85
+ }
86
+ return undefined;
87
+ }
88
+ function failure(message, suggestions) {
89
+ return { ok: false, error: { message, suggestions } };
90
+ }
@@ -0,0 +1,16 @@
1
+ import type { Logger } from '../../utils/logger.js';
2
+ export interface BlueprinitFlags {
3
+ yes?: boolean;
4
+ organizationId?: string;
5
+ projectId?: string;
6
+ datasetName?: string;
7
+ stackName?: string;
8
+ studioName?: string;
9
+ }
10
+ export interface BlueprinitFlowOptions {
11
+ bin: string;
12
+ dir: string;
13
+ flags: BlueprinitFlags;
14
+ log: Logger;
15
+ token: string;
16
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { BlueprinitFlags } from './blueprinit/types.js';
2
+ import type { CoreConfig, CoreResult } from './index.js';
3
+ export interface BlueprinitOptions extends CoreConfig {
4
+ dir?: string;
5
+ flags?: BlueprinitFlags;
6
+ }
7
+ export declare function blueprinitCore(options: BlueprinitOptions): Promise<CoreResult>;
@@ -0,0 +1,48 @@
1
+ import { cwd } from 'node:process';
2
+ import { detectStudio } from '../actions/blueprinit/studio.js';
3
+ import { findBlueprintFile } from '../actions/blueprints/blueprint.js';
4
+ import { readConfigFile } from '../actions/blueprints/config.js';
5
+ import { validTokenOrErrorMessage } from '../utils/validated-token.js';
6
+ import { adoptFlow } from './blueprinit/adopt.js';
7
+ import { freshFlow } from './blueprinit/fresh.js';
8
+ export async function blueprinitCore(options) {
9
+ const { bin, log, flags = {}, dir = cwd() } = options;
10
+ try {
11
+ const existingBlueprint = findBlueprintFile(dir);
12
+ if (existingBlueprint) {
13
+ const blueprintConfig = readConfigFile(existingBlueprint.blueprintFilePath);
14
+ if (!blueprintConfig?.stackId) {
15
+ return {
16
+ success: false,
17
+ error: `A Blueprint exists here (${existingBlueprint.fileName}), but it is not connected to a Stack.`,
18
+ suggestions: [`Finish setup with: npx ${bin} blueprints init`],
19
+ };
20
+ }
21
+ log(`A blueprint already exists here (${existingBlueprint.fileName}).`);
22
+ log('');
23
+ log('Use these instead:');
24
+ log(` npx ${bin} blueprints config --edit`);
25
+ log(` npx ${bin} blueprints doctor`);
26
+ return {
27
+ success: true,
28
+ json: { alreadyInitialized: true, blueprintFile: existingBlueprint.fileName },
29
+ };
30
+ }
31
+ const tokenCheck = await validTokenOrErrorMessage(log);
32
+ if (!tokenCheck.ok) {
33
+ return {
34
+ success: false,
35
+ error: 'No Sanity session found.',
36
+ suggestions: ['Sign in first: npx sanity login', 'Then run blueprinit again.'],
37
+ };
38
+ }
39
+ log('Setting up your Sanity workspace.');
40
+ log('');
41
+ const flowOptions = { bin, dir, flags, log, token: tokenCheck.value };
42
+ const studio = detectStudio(dir);
43
+ return studio ? await adoptFlow({ ...flowOptions, studio }) : await freshFlow(flowOptions);
44
+ }
45
+ catch (error) {
46
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
47
+ }
48
+ }
@@ -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
 
@@ -1,5 +1,133 @@
1
1
  {
2
2
  "commands": {
3
+ "blueprinit": {
4
+ "aliases": [],
5
+ "args": {},
6
+ "description": "Scaffolds a Blueprint, Studio, and starter function around an existing Sanity project.\n\nThis hidden command is an experiment; its interface and generated files are not stable.",
7
+ "examples": [
8
+ "<%= config.bin %> <%= command.id %>",
9
+ "<%= config.bin %> <%= command.id %> --yes",
10
+ "<%= config.bin %> <%= command.id %> --project-id <id> --dataset <name> --yes"
11
+ ],
12
+ "flags": {
13
+ "json": {
14
+ "description": "Format output as json",
15
+ "name": "json",
16
+ "allowNo": false,
17
+ "type": "boolean"
18
+ },
19
+ "path": {
20
+ "aliases": [
21
+ "blueprint-path"
22
+ ],
23
+ "char": "p",
24
+ "description": "Path to a Blueprint file or directory containing one",
25
+ "env": "SANITY_BLUEPRINT_PATH",
26
+ "hidden": true,
27
+ "name": "path",
28
+ "hasDynamicHelp": false,
29
+ "multiple": false,
30
+ "type": "option"
31
+ },
32
+ "trace": {
33
+ "description": "Trace output",
34
+ "hidden": true,
35
+ "name": "trace",
36
+ "allowNo": false,
37
+ "type": "boolean"
38
+ },
39
+ "validate-resources": {
40
+ "description": "Validate resources",
41
+ "hidden": true,
42
+ "name": "validate-resources",
43
+ "allowNo": true,
44
+ "type": "boolean"
45
+ },
46
+ "verbose": {
47
+ "description": "Verbose output",
48
+ "hidden": true,
49
+ "name": "verbose",
50
+ "allowNo": false,
51
+ "type": "boolean"
52
+ },
53
+ "llms": {
54
+ "aliases": [
55
+ "skill"
56
+ ],
57
+ "description": "Show agent-oriented guidance for this command",
58
+ "name": "llms",
59
+ "allowNo": false,
60
+ "type": "boolean"
61
+ },
62
+ "yes": {
63
+ "char": "y",
64
+ "description": "Skip confirmation prompts",
65
+ "name": "yes",
66
+ "allowNo": false,
67
+ "type": "boolean"
68
+ },
69
+ "organization-id": {
70
+ "aliases": [
71
+ "organization",
72
+ "organizationId",
73
+ "org"
74
+ ],
75
+ "description": "Organization to use when the account has several",
76
+ "name": "organization-id",
77
+ "hasDynamicHelp": false,
78
+ "multiple": false,
79
+ "type": "option"
80
+ },
81
+ "project-id": {
82
+ "aliases": [
83
+ "project",
84
+ "projectId"
85
+ ],
86
+ "description": "Project to use when the organization has several",
87
+ "name": "project-id",
88
+ "hasDynamicHelp": false,
89
+ "multiple": false,
90
+ "type": "option"
91
+ },
92
+ "dataset": {
93
+ "description": "Dataset to attach",
94
+ "name": "dataset",
95
+ "hasDynamicHelp": false,
96
+ "multiple": false,
97
+ "type": "option"
98
+ },
99
+ "stack-name": {
100
+ "description": "Name for the new organization-scoped Stack",
101
+ "name": "stack-name",
102
+ "hasDynamicHelp": false,
103
+ "multiple": false,
104
+ "type": "option"
105
+ },
106
+ "studio-name": {
107
+ "description": "Name for the Studio resource",
108
+ "name": "studio-name",
109
+ "hasDynamicHelp": false,
110
+ "multiple": false,
111
+ "type": "option"
112
+ }
113
+ },
114
+ "hasDynamicHelp": false,
115
+ "hidden": true,
116
+ "hiddenAliases": [],
117
+ "id": "blueprinit",
118
+ "pluginAlias": "@sanity/runtime-cli",
119
+ "pluginName": "@sanity/runtime-cli",
120
+ "pluginType": "core",
121
+ "strict": true,
122
+ "summary": "Experimental blueprints-first project init",
123
+ "enableJsonFlag": true,
124
+ "isESM": true,
125
+ "relativePath": [
126
+ "dist",
127
+ "commands",
128
+ "blueprinit.js"
129
+ ]
130
+ },
3
131
  "blueprints:add": {
4
132
  "aliases": [],
5
133
  "args": {
@@ -2655,7 +2783,8 @@
2655
2783
  },
2656
2784
  "description": "Sets an environment variable in a deployed Sanity Function. If the variable already exists, its value is updated.\n\nEnvironment variables are useful for API keys, configuration values, and other secrets that shouldn't be hardcoded. Changes take effect on the next function invocation.",
2657
2785
  "examples": [
2658
- "<%= config.bin %> <%= command.id %> MyFunction API_URL https://api.example.com/"
2786
+ "<%= config.bin %> <%= command.id %> MyFunction API_URL https://api.example.com/",
2787
+ "<%= config.bin %> <%= command.id %> --stack <name-or-id> MyFunction API_URL https://api.example.com/"
2659
2788
  ],
2660
2789
  "flags": {
2661
2790
  "json": {
@@ -2700,7 +2829,6 @@
2700
2829
  },
2701
2830
  "stack": {
2702
2831
  "description": "Stack name or ID to use instead of the locally configured Stack",
2703
- "hidden": true,
2704
2832
  "name": "stack",
2705
2833
  "hasDynamicHelp": false,
2706
2834
  "multiple": false,
@@ -2771,7 +2899,8 @@
2771
2899
  },
2772
2900
  "description": "Displays all environment variables (keys only) configured in a deployed Sanity Function.\n\nUse 'functions env add' to set variables or 'functions env remove' to delete them.",
2773
2901
  "examples": [
2774
- "<%= config.bin %> <%= command.id %> MyFunction"
2902
+ "<%= config.bin %> <%= command.id %> MyFunction",
2903
+ "<%= config.bin %> <%= command.id %> --stack <name-or-id> MyFunction"
2775
2904
  ],
2776
2905
  "flags": {
2777
2906
  "json": {
@@ -2816,7 +2945,6 @@
2816
2945
  },
2817
2946
  "stack": {
2818
2947
  "description": "Stack name or ID to use instead of the locally configured Stack",
2819
- "hidden": true,
2820
2948
  "name": "stack",
2821
2949
  "hasDynamicHelp": false,
2822
2950
  "multiple": false,
@@ -2892,7 +3020,8 @@
2892
3020
  },
2893
3021
  "description": "Deletes an environment variable from a deployed Sanity Function. The change takes effect on the next function invocation.\n\nUse 'functions env list' to see current variables before removing.",
2894
3022
  "examples": [
2895
- "<%= config.bin %> <%= command.id %> MyFunction API_URL"
3023
+ "<%= config.bin %> <%= command.id %> MyFunction API_URL",
3024
+ "<%= config.bin %> <%= command.id %> --stack <name-or-id> MyFunction API_URL"
2896
3025
  ],
2897
3026
  "flags": {
2898
3027
  "json": {
@@ -2937,7 +3066,6 @@
2937
3066
  },
2938
3067
  "stack": {
2939
3068
  "description": "Stack name or ID to use instead of the locally configured Stack",
2940
- "hidden": true,
2941
3069
  "name": "stack",
2942
3070
  "hasDynamicHelp": false,
2943
3071
  "multiple": false,
@@ -2998,5 +3126,5 @@
2998
3126
  ]
2999
3127
  }
3000
3128
  },
3001
- "version": "17.2.0-next.ga4b9ae7"
3129
+ "version": "17.3.0-blueprinit.g6a63623"
3002
3130
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sanity/runtime-cli",
3
3
  "description": "Sanity's Runtime CLI for Blueprints and Functions",
4
- "version": "17.2.0-next.ga4b9ae7",
4
+ "version": "17.3.0-blueprinit.g6a63623",
5
5
  "author": "Sanity Runtime Team",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -26,7 +26,9 @@
26
26
  "files": [
27
27
  "./bin",
28
28
  "./dist",
29
- "./oclif.manifest.json"
29
+ "./oclif.manifest.json",
30
+ "./SKILL.md",
31
+ "./skills"
30
32
  ],
31
33
  "bin": {
32
34
  "sanity-run": "bin/run.js"
@@ -63,20 +65,21 @@
63
65
  "@architect/hydrate": "~6.0.2",
64
66
  "@architect/inventory": "~6.1.0",
65
67
  "@inquirer/prompts": "^8.5.2",
66
- "@oclif/core": "^4.11.14",
67
- "@oclif/plugin-help": "^6.2.53",
68
+ "@oclif/core": "^4.13.2",
69
+ "@oclif/plugin-help": "^6.2.55",
68
70
  "@sanity-labs/design-tokens": "^0.0.2-alpha.2",
69
- "@sanity/blueprints": "^0.23.0",
71
+ "@sanity-labs/oclif-plugin-skills-flag": "^0.1.3",
72
+ "@sanity/blueprints": "^0.23.1",
70
73
  "@sanity/blueprints-parser": "^0.4.0",
71
74
  "@sanity/cli-build": "^5.0.0",
72
75
  "@sanity/cli-core": "^2.5.1",
73
- "@sanity/client": "^7.23.1",
76
+ "@sanity/client": "^7.25.0",
74
77
  "adm-zip": "^0.6.0",
75
78
  "array-treeify": "^0.1.5",
76
79
  "cardinal": "^2.1.1",
77
80
  "empathic": "^2.0.1",
78
81
  "eventsource": "^4.1.0",
79
- "groq-js": "^1.30.3",
82
+ "groq-js": "^2.0.0",
80
83
  "jiti": "^2.7.0",
81
84
  "mime-types": "^3.0.2",
82
85
  "ora": "^9.4.1",
@@ -84,18 +87,18 @@
84
87
  "strip-ansi": "^7.2.0",
85
88
  "tar-fs": "^3.1.3",
86
89
  "tar-stream": "^3.2.0",
87
- "vite": "^8.1.4",
90
+ "vite": "^8.1.5",
88
91
  "ws": "^8.21.1",
89
92
  "xdg-basedir": "^5.1.0"
90
93
  },
91
94
  "devDependencies": {
92
- "@biomejs/biome": "2.5.3",
95
+ "@biomejs/biome": "2.5.5",
93
96
  "@codemirror/lang-json": "^6.0.2",
94
97
  "@codemirror/state": "^6.7.1",
95
98
  "@enhance/store": "^1.0.2",
96
99
  "@lezer/highlight": "^1.2.3",
97
- "@oclif/test": "^4.1.20",
98
- "@playwright/test": "^1.61.1",
100
+ "@oclif/test": "^4.1.21",
101
+ "@playwright/test": "^1.62.0",
99
102
  "@rollup/plugin-node-resolve": "^16.0.3",
100
103
  "@sanity/functions": "^1.4.0",
101
104
  "@types/adm-zip": "^0.5.8",
@@ -108,10 +111,10 @@
108
111
  "@vitest/coverage-v8": "4.1.10",
109
112
  "codemirror": "^6.0.2",
110
113
  "mentoss": "^0.13.0",
111
- "oclif": "^4.23.27",
112
- "pretty-bytes": "^7.1.0",
114
+ "oclif": "^4.23.29",
115
+ "pretty-bytes": "^7.1.1",
113
116
  "pretty-ms": "^9.3.0",
114
- "rollup": "^4.62.2",
117
+ "rollup": "^4.62.3",
115
118
  "shx": "^0.4.0",
116
119
  "typescript": "^7.0.2",
117
120
  "vitest": "4.1.10"
@@ -121,7 +124,8 @@
121
124
  "dirname": "sanity-run",
122
125
  "commands": "./dist/commands",
123
126
  "plugins": [
124
- "@oclif/plugin-help"
127
+ "@oclif/plugin-help",
128
+ "@sanity-labs/oclif-plugin-skills-flag"
125
129
  ],
126
130
  "topicSeparator": " ",
127
131
  "topics": {
@@ -0,0 +1,148 @@
1
+ # Blueprinit
2
+
3
+ Use `blueprinit` to scaffold a Blueprint around an existing Sanity project. The command is
4
+ experimental, hidden from root help, and available from the `blueprinit` npm dist-tag.
5
+
6
+ Use it for demos and evaluation, not as a stable project initializer. Its flags, generated files,
7
+ and resource model may change.
8
+
9
+ ## Know what it does
10
+
11
+ `blueprinit` uses an existing Sanity organization, project, and dataset. It writes local Blueprint
12
+ files, then creates an empty organization-scoped Stack. The generated Blueprint attaches the
13
+ project and dataset and declares resources for a later deployment.
14
+
15
+ It does not create an organization, project, or dataset. It does not deploy the attached resources,
16
+ Studio, or Function. Those changes happen only if `blueprints deploy` is run later.
17
+
18
+ ## Inspect the target directory
19
+
20
+ Run the command from the directory that should contain the Blueprint.
21
+
22
+ Before running:
23
+
24
+ 1. Check the working tree and existing files. Do not discard unrelated user changes.
25
+ 2. Look for a root `sanity.blueprint.*` file. If one exists, use the existing Blueprint instead of
26
+ initializing again.
27
+ 3. Look for a root `sanity.config.*` file. Its presence selects the Studio adoption flow.
28
+ 4. Confirm the user asked to initialize this directory. The command writes files and creates a
29
+ remote Stack.
30
+ 5. Confirm the user is signed in. If needed, have them run:
31
+
32
+ ```sh
33
+ npx sanity login
34
+ ```
35
+
36
+ The account must have at least one organization, one project in the selected organization, and one
37
+ dataset in the selected project.
38
+
39
+ Interactive runs show the Blueprint resources, local changes, and remote Stack action before the
40
+ confirmation prompt. Project and dataset attachment, Studio creation, and Function creation remain
41
+ pending until a later deployment.
42
+
43
+ ## Choose the flow
44
+
45
+ - **Existing Blueprint with a configured Stack:** Exit without changes and use
46
+ `blueprints config --edit` or `blueprints doctor`.
47
+ - **Existing Blueprint without a configured Stack:** Finish setup with `blueprints init`; it can
48
+ connect the existing manifest without rewriting it.
49
+ - **Root Studio config:** Adopt the Studio in place. Read literal `projectId` and `dataset` strings
50
+ from `sanity.config.*`, falling back to `sanity.cli.*`. Do not move the Studio.
51
+ - **Fresh workspace:** Create a Blueprint, an npm-workspace Studio, and a starter `set-slug`
52
+ document function.
53
+
54
+ Do not rewrite a Studio config merely to make adoption work unless the user asks. Dynamic
55
+ `projectId` or `dataset` values built from environment variables or imports cannot be detected.
56
+
57
+ For an existing Blueprint, inspect it with:
58
+
59
+ ```sh
60
+ npx @sanity/runtime-cli@blueprinit blueprints doctor
61
+ ```
62
+
63
+ ## Run unattended
64
+
65
+ When the organization and project IDs are known, run this from the target directory:
66
+
67
+ ```sh
68
+ npx @sanity/runtime-cli@blueprinit blueprinit --yes \
69
+ --organization-id <organization-id> \
70
+ --project-id <project-id> \
71
+ --dataset <dataset-name>
72
+ ```
73
+
74
+ Replace all placeholders with real values. The project must belong to the organization, and the
75
+ dataset must belong to the project. Do not guess any value.
76
+
77
+ `--yes` skips confirmation. Without explicit values, an unattended run stops when the account has
78
+ several organizations, projects, or datasets. Ask the user for the missing value or have them run
79
+ the command interactively.
80
+
81
+ Use `--stack-name <name>` and `--studio-name <name>` when those generated names need to be stable.
82
+ The command normalizes both names to lowercase kebab case.
83
+
84
+ For interactive organization, project, dataset, confirmation, and Studio-name prompts, have the
85
+ user run:
86
+
87
+ ```sh
88
+ npx @sanity/runtime-cli@blueprinit blueprinit
89
+ ```
90
+
91
+ When the project has several datasets, the interactive command prompts for one and lists
92
+ `production` first. Report the selected dataset from the command output.
93
+
94
+ ## Review the result
95
+
96
+ A successful fresh run creates or updates:
97
+
98
+ ```text
99
+ .
100
+ ├── .sanity/blueprint.config.json
101
+ ├── sanity.blueprint.ts
102
+ ├── package.json
103
+ ├── functions/
104
+ │ └── set-slug/
105
+ └── studios/
106
+ └── studio/
107
+ ```
108
+
109
+ The command also creates or updates `.gitignore`. It preserves existing package metadata and adds
110
+ the required npm workspaces and `@sanity/blueprints` dependency.
111
+
112
+ An adopted Studio stays at the repository root. The command adds the Blueprint, Blueprint config,
113
+ dependency, and `.gitignore` entry, but does not generate the starter Function.
114
+
115
+ After the command exits:
116
+
117
+ 1. Confirm the output names the expected organization, project, and dataset.
118
+ 2. Confirm `.sanity/blueprint.config.json` exists. Its presence means Stack creation completed.
119
+ 3. Review `sanity.blueprint.ts`, `package.json`, `.gitignore`, and the working-tree diff.
120
+ 4. Report the Stack ID and every created or modified path.
121
+
122
+ Do not deploy as part of initialization unless the user separately asks for deployment. To continue
123
+ after reviewing the scaffold, install dependencies and preview the deployment:
124
+
125
+ ```sh
126
+ npm install
127
+ npx @sanity/runtime-cli@blueprinit blueprints plan
128
+ ```
129
+
130
+ Treat dependency installation and planning as separate follow-up actions when they were not part of
131
+ the user's request.
132
+
133
+ ## Handle failures
134
+
135
+ - **`No Sanity session found.`** Stop and ask the user to run `npx sanity login`. Authentication
136
+ fails before files are written.
137
+ - **Multiple organizations or projects found.** Rerun with the matching `--organization-id` and
138
+ `--project-id`. Selection fails before files are written.
139
+ - **No datasets found.** Ask the user to create or choose a project with a dataset. The command does
140
+ not create datasets.
141
+ - **Could not read `projectId` and `dataset`.** The detected Studio config does not contain both
142
+ values as string literals. Pass explicit `--project-id` and `--dataset` values or report the config
143
+ file and let the user decide whether to change it.
144
+ - **Refusing to overwrite an existing path.** Inspect the reported Studio or Function directory.
145
+ Do not delete or replace it without the user's direction.
146
+ - **Stack quota or duplicate-name error.** Local scaffold files may already exist while
147
+ `.sanity/blueprint.config.json` does not. Resolve the Stack error, then run the recovery command
148
+ printed by `blueprinit`. It uses `blueprints init` to connect the existing manifest.