@sanity/cli 6.0.0 → 6.1.0
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 +78 -111
- package/dist/actions/mcp/detectAvailableEditors.js +19 -10
- package/dist/actions/mcp/detectAvailableEditors.js.map +1 -1
- package/dist/actions/mcp/editorConfigs.js +222 -158
- package/dist/actions/mcp/editorConfigs.js.map +1 -1
- package/dist/actions/mcp/promptForMCPSetup.js +16 -7
- package/dist/actions/mcp/promptForMCPSetup.js.map +1 -1
- package/dist/actions/mcp/setupMCP.js +62 -23
- package/dist/actions/mcp/setupMCP.js.map +1 -1
- package/dist/actions/mcp/types.js.map +1 -1
- package/dist/actions/mcp/validateEditorTokens.js +56 -0
- package/dist/actions/mcp/validateEditorTokens.js.map +1 -0
- package/dist/actions/telemetry/telemetryDebug.js +2 -2
- package/dist/actions/telemetry/telemetryDebug.js.map +1 -1
- package/dist/commands/init.js +9 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mcp/configure.js +3 -1
- package/dist/commands/mcp/configure.js.map +1 -1
- package/dist/commands/preview.js +3 -4
- package/dist/commands/preview.js.map +1 -1
- package/dist/hooks/prerun/setupTelemetry.js +7 -7
- package/dist/hooks/prerun/setupTelemetry.js.map +1 -1
- package/dist/services/mcp.js +55 -1
- package/dist/services/mcp.js.map +1 -1
- package/dist/util/getLocalPackageVersion.js +4 -2
- package/dist/util/getLocalPackageVersion.js.map +1 -1
- package/dist/util/getProjectDefaults.js +22 -28
- package/dist/util/getProjectDefaults.js.map +1 -1
- package/dist/util/gitConfig.js +45 -0
- package/dist/util/gitConfig.js.map +1 -0
- package/dist/util/telemetry/telemetryStoreDebug.js +2 -2
- package/dist/util/telemetry/telemetryStoreDebug.js.map +1 -1
- package/oclif.manifest.json +454 -454
- package/package.json +3 -5
|
@@ -4,17 +4,20 @@ import { logSymbols } from '@sanity/cli-core/ux';
|
|
|
4
4
|
import { createMCPToken, MCP_SERVER_URL } from '../../services/mcp.js';
|
|
5
5
|
import { detectAvailableEditors } from './detectAvailableEditors.js';
|
|
6
6
|
import { promptForMCPSetup } from './promptForMCPSetup.js';
|
|
7
|
+
import { validateEditorTokens } from './validateEditorTokens.js';
|
|
7
8
|
import { writeMCPConfig } from './writeMCPConfig.js';
|
|
8
9
|
const mcpDebug = subdebug('mcp:setup');
|
|
9
10
|
const NO_EDITORS_DETECTED_MESSAGE = `Couldn't auto-configure Sanity MCP server for your editor. Visit ${MCP_SERVER_URL} for setup instructions.`;
|
|
10
11
|
/**
|
|
11
12
|
* Main MCP setup orchestration
|
|
12
|
-
* Opt-out by default: runs automatically unless
|
|
13
|
-
*/ export async function setupMCP(
|
|
13
|
+
* Opt-out by default: runs automatically unless skip option is set
|
|
14
|
+
*/ export async function setupMCP(options) {
|
|
15
|
+
const { explicit = false, skip = false } = options ?? {};
|
|
14
16
|
// 1. Check for explicit opt-out
|
|
15
|
-
if (
|
|
17
|
+
if (skip) {
|
|
16
18
|
ux.warn('Skipping MCP configuration due to --no-mcp flag');
|
|
17
19
|
return {
|
|
20
|
+
alreadyConfiguredEditors: [],
|
|
18
21
|
configuredEditors: [],
|
|
19
22
|
detectedEditors: [],
|
|
20
23
|
skipped: true
|
|
@@ -25,44 +28,79 @@ const NO_EDITORS_DETECTED_MESSAGE = `Couldn't auto-configure Sanity MCP server f
|
|
|
25
28
|
const detectedEditors = editors.map((e)=>e.name);
|
|
26
29
|
mcpDebug('Detected %d editors: %s', detectedEditors.length, detectedEditors);
|
|
27
30
|
if (editors.length === 0) {
|
|
28
|
-
|
|
31
|
+
if (explicit) {
|
|
32
|
+
ux.warn(NO_EDITORS_DETECTED_MESSAGE);
|
|
33
|
+
}
|
|
29
34
|
return {
|
|
35
|
+
alreadyConfiguredEditors: [],
|
|
30
36
|
configuredEditors: [],
|
|
31
37
|
detectedEditors,
|
|
32
38
|
skipped: true
|
|
33
39
|
};
|
|
34
40
|
}
|
|
35
|
-
// 3.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
// 3. Validate existing tokens against the Sanity API
|
|
42
|
+
await validateEditorTokens(editors);
|
|
43
|
+
// 4. Check if there's anything actionable
|
|
44
|
+
const actionable = editors.filter((e)=>!e.configured || e.authStatus !== 'valid');
|
|
45
|
+
if (actionable.length === 0) {
|
|
46
|
+
mcpDebug('All editors configured with valid credentials');
|
|
47
|
+
const alreadyConfiguredEditors = editors.filter((e)=>e.configured && e.authStatus === 'valid').map((e)=>e.name);
|
|
48
|
+
if (explicit) {
|
|
49
|
+
ux.stdout(`${logSymbols.success} All detected editors are already configured`);
|
|
50
|
+
}
|
|
40
51
|
return {
|
|
52
|
+
alreadyConfiguredEditors,
|
|
41
53
|
configuredEditors: [],
|
|
42
54
|
detectedEditors,
|
|
43
55
|
skipped: true
|
|
44
56
|
};
|
|
45
57
|
}
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
ux.warn(`Could not configure MCP: ${err.message}`);
|
|
54
|
-
ux.warn('You can set up MCP manually later using https://mcp.sanity.io');
|
|
58
|
+
// Non-actionable editors are already configured with valid credentials
|
|
59
|
+
const alreadyConfiguredEditors = editors.filter((e)=>!actionable.includes(e)).map((e)=>e.name);
|
|
60
|
+
// 5. Prompt user (shows only actionable editors, annotates auth issues)
|
|
61
|
+
const selected = await promptForMCPSetup(actionable);
|
|
62
|
+
if (!selected || selected.length === 0) {
|
|
63
|
+
// User deselected all editors
|
|
64
|
+
ux.stdout('MCP configuration skipped');
|
|
55
65
|
return {
|
|
66
|
+
alreadyConfiguredEditors,
|
|
56
67
|
configuredEditors: [],
|
|
57
68
|
detectedEditors,
|
|
58
|
-
|
|
59
|
-
skipped: false
|
|
69
|
+
skipped: true
|
|
60
70
|
};
|
|
61
71
|
}
|
|
62
|
-
//
|
|
72
|
+
// 6. Get a token — reuse a valid existing one or create a new one
|
|
73
|
+
let token;
|
|
74
|
+
// Look for an existing valid token we can reuse
|
|
75
|
+
const validEditor = editors.find((e)=>e.authStatus === 'valid' && e.existingToken);
|
|
76
|
+
if (validEditor?.existingToken) {
|
|
77
|
+
mcpDebug('Reusing valid token from %s', validEditor.name);
|
|
78
|
+
token = validEditor.existingToken;
|
|
79
|
+
}
|
|
80
|
+
// Fall back to creating a new token
|
|
81
|
+
if (!token) {
|
|
82
|
+
try {
|
|
83
|
+
token = await createMCPToken();
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
86
|
+
mcpDebug('Error creating MCP token', error);
|
|
87
|
+
ux.warn(`Could not configure MCP: ${err.message}`);
|
|
88
|
+
ux.warn('You can set up MCP manually later using https://mcp.sanity.io');
|
|
89
|
+
return {
|
|
90
|
+
alreadyConfiguredEditors,
|
|
91
|
+
configuredEditors: [],
|
|
92
|
+
detectedEditors,
|
|
93
|
+
error: err,
|
|
94
|
+
skipped: false
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// 7. Write configs for each selected editor
|
|
99
|
+
const configuredEditors = [];
|
|
63
100
|
try {
|
|
64
101
|
for (const editor of selected){
|
|
65
102
|
await writeMCPConfig(editor, token);
|
|
103
|
+
configuredEditors.push(editor.name);
|
|
66
104
|
}
|
|
67
105
|
} catch (error) {
|
|
68
106
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
@@ -70,15 +108,16 @@ const NO_EDITORS_DETECTED_MESSAGE = `Couldn't auto-configure Sanity MCP server f
|
|
|
70
108
|
ux.warn(`Could not configure MCP: ${err.message}`);
|
|
71
109
|
ux.warn('You can set up MCP manually later using https://mcp.sanity.io');
|
|
72
110
|
return {
|
|
73
|
-
|
|
111
|
+
alreadyConfiguredEditors,
|
|
112
|
+
configuredEditors,
|
|
74
113
|
detectedEditors,
|
|
75
114
|
error: err,
|
|
76
115
|
skipped: false
|
|
77
116
|
};
|
|
78
117
|
}
|
|
79
|
-
const configuredEditors = selected.map((e)=>e.name);
|
|
80
118
|
ux.stdout(`${logSymbols.success} MCP configured for ${configuredEditors.join(', ')}`);
|
|
81
119
|
return {
|
|
120
|
+
alreadyConfiguredEditors,
|
|
82
121
|
configuredEditors,
|
|
83
122
|
detectedEditors,
|
|
84
123
|
skipped: false
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/mcp/setupMCP.ts"],"sourcesContent":["import {ux} from '@oclif/core'\nimport {subdebug} from '@sanity/cli-core'\nimport {logSymbols} from '@sanity/cli-core/ux'\n\nimport {createMCPToken, MCP_SERVER_URL} from '../../services/mcp.js'\nimport {detectAvailableEditors} from './detectAvailableEditors.js'\nimport {type EditorName} from './editorConfigs.js'\nimport {promptForMCPSetup} from './promptForMCPSetup.js'\nimport {writeMCPConfig} from './writeMCPConfig.js'\n\nconst mcpDebug = subdebug('mcp:setup')\n\nconst NO_EDITORS_DETECTED_MESSAGE = `Couldn't auto-configure Sanity MCP server for your editor. Visit ${MCP_SERVER_URL} for setup instructions.`\n\ninterface MCPSetupResult {\n configuredEditors: EditorName[]\n detectedEditors: EditorName[]\n skipped: boolean\n\n error?: Error\n}\n\n/**\n * Main MCP setup orchestration\n * Opt-out by default: runs automatically unless
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/mcp/setupMCP.ts"],"sourcesContent":["import {ux} from '@oclif/core'\nimport {subdebug} from '@sanity/cli-core'\nimport {logSymbols} from '@sanity/cli-core/ux'\n\nimport {createMCPToken, MCP_SERVER_URL} from '../../services/mcp.js'\nimport {detectAvailableEditors} from './detectAvailableEditors.js'\nimport {type EditorName} from './editorConfigs.js'\nimport {promptForMCPSetup} from './promptForMCPSetup.js'\nimport {validateEditorTokens} from './validateEditorTokens.js'\nimport {writeMCPConfig} from './writeMCPConfig.js'\n\nconst mcpDebug = subdebug('mcp:setup')\n\nconst NO_EDITORS_DETECTED_MESSAGE = `Couldn't auto-configure Sanity MCP server for your editor. Visit ${MCP_SERVER_URL} for setup instructions.`\n\ninterface MCPSetupOptions {\n /**\n * Whether the user explicitly requested MCP configuration (e.g. `sanity mcp configure`).\n * When true, shows status messages even when there's nothing to do.\n * When false/undefined (e.g. called from `sanity init`), stays quiet.\n */\n explicit?: boolean\n\n /**\n * If true, skip MCP configuration entirely (e.g. --no-mcp flag).\n */\n skip?: boolean\n}\n\ninterface MCPSetupResult {\n /** Editors that were already configured with valid credentials (nothing to do) */\n alreadyConfiguredEditors: EditorName[]\n configuredEditors: EditorName[]\n detectedEditors: EditorName[]\n skipped: boolean\n\n error?: Error\n}\n\n/**\n * Main MCP setup orchestration\n * Opt-out by default: runs automatically unless skip option is set\n */\nexport async function setupMCP(options?: MCPSetupOptions): Promise<MCPSetupResult> {\n const {explicit = false, skip = false} = options ?? {}\n\n // 1. Check for explicit opt-out\n if (skip) {\n ux.warn('Skipping MCP configuration due to --no-mcp flag')\n return {\n alreadyConfiguredEditors: [],\n configuredEditors: [],\n detectedEditors: [],\n skipped: true,\n }\n }\n\n // 2. Detect available editors (filters out unparseable configs)\n const editors = await detectAvailableEditors()\n const detectedEditors = editors.map((e) => e.name)\n\n mcpDebug('Detected %d editors: %s', detectedEditors.length, detectedEditors)\n\n if (editors.length === 0) {\n if (explicit) {\n ux.warn(NO_EDITORS_DETECTED_MESSAGE)\n }\n return {\n alreadyConfiguredEditors: [],\n configuredEditors: [],\n detectedEditors,\n skipped: true,\n }\n }\n\n // 3. Validate existing tokens against the Sanity API\n await validateEditorTokens(editors)\n\n // 4. Check if there's anything actionable\n const actionable = editors.filter((e) => !e.configured || e.authStatus !== 'valid')\n\n if (actionable.length === 0) {\n mcpDebug('All editors configured with valid credentials')\n const alreadyConfiguredEditors = editors\n .filter((e) => e.configured && e.authStatus === 'valid')\n .map((e) => e.name)\n if (explicit) {\n ux.stdout(`${logSymbols.success} All detected editors are already configured`)\n }\n return {\n alreadyConfiguredEditors,\n configuredEditors: [],\n detectedEditors,\n skipped: true,\n }\n }\n\n // Non-actionable editors are already configured with valid credentials\n const alreadyConfiguredEditors = editors.filter((e) => !actionable.includes(e)).map((e) => e.name)\n\n // 5. Prompt user (shows only actionable editors, annotates auth issues)\n const selected = await promptForMCPSetup(actionable)\n\n if (!selected || selected.length === 0) {\n // User deselected all editors\n ux.stdout('MCP configuration skipped')\n return {\n alreadyConfiguredEditors,\n configuredEditors: [],\n detectedEditors,\n skipped: true,\n }\n }\n\n // 6. Get a token — reuse a valid existing one or create a new one\n let token: string | undefined\n\n // Look for an existing valid token we can reuse\n const validEditor = editors.find((e) => e.authStatus === 'valid' && e.existingToken)\n if (validEditor?.existingToken) {\n mcpDebug('Reusing valid token from %s', validEditor.name)\n token = validEditor.existingToken\n }\n\n // Fall back to creating a new token\n if (!token) {\n try {\n token = await createMCPToken()\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n mcpDebug('Error creating MCP token', error)\n ux.warn(`Could not configure MCP: ${err.message}`)\n ux.warn('You can set up MCP manually later using https://mcp.sanity.io')\n return {\n alreadyConfiguredEditors,\n configuredEditors: [],\n detectedEditors,\n error: err,\n skipped: false,\n }\n }\n }\n\n // 7. Write configs for each selected editor\n const configuredEditors: EditorName[] = []\n try {\n for (const editor of selected) {\n await writeMCPConfig(editor, token)\n configuredEditors.push(editor.name)\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n mcpDebug('Error writing MCP config', error)\n ux.warn(`Could not configure MCP: ${err.message}`)\n ux.warn('You can set up MCP manually later using https://mcp.sanity.io')\n return {\n alreadyConfiguredEditors,\n configuredEditors,\n detectedEditors,\n error: err,\n skipped: false,\n }\n }\n\n ux.stdout(`${logSymbols.success} MCP configured for ${configuredEditors.join(', ')}`)\n\n return {\n alreadyConfiguredEditors,\n configuredEditors,\n detectedEditors,\n skipped: false,\n }\n}\n"],"names":["ux","subdebug","logSymbols","createMCPToken","MCP_SERVER_URL","detectAvailableEditors","promptForMCPSetup","validateEditorTokens","writeMCPConfig","mcpDebug","NO_EDITORS_DETECTED_MESSAGE","setupMCP","options","explicit","skip","warn","alreadyConfiguredEditors","configuredEditors","detectedEditors","skipped","editors","map","e","name","length","actionable","filter","configured","authStatus","stdout","success","includes","selected","token","validEditor","find","existingToken","error","err","Error","String","message","editor","push","join"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/mcp/types.ts"],"sourcesContent":["import {type EditorName} from './editorConfigs.js'\n\nexport interface Editor {\n configPath: string\n /** Whether Sanity MCP is already configured for this editor */\n configured: boolean\n name: EditorName\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/mcp/types.ts"],"sourcesContent":["import {type EditorName} from './editorConfigs.js'\n\n/** Auth credential status for a configured editor */\nexport type AuthStatus = 'unauthorized' | 'valid'\n\nexport interface Editor {\n configPath: string\n /** Whether Sanity MCP is already configured for this editor */\n configured: boolean\n name: EditorName\n\n /**\n * Auth status of the existing token. Only set for editors that have\n * a Sanity MCP config with a token that has been validated against the API.\n */\n authStatus?: AuthStatus\n /** The existing auth token found in the editor config, if any */\n existingToken?: string\n}\n"],"names":[],"mappings":"AAKA,WAaC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { subdebug } from '@sanity/cli-core';
|
|
2
|
+
import { validateMCPToken } from '../../services/mcp.js';
|
|
3
|
+
const debug = subdebug('mcp:validateEditorTokens');
|
|
4
|
+
/**
|
|
5
|
+
* Validate existing MCP tokens for all configured editors.
|
|
6
|
+
*
|
|
7
|
+
* Collects unique tokens, validates each once against the Sanity API,
|
|
8
|
+
* and sets `authStatus` on each editor that has a token.
|
|
9
|
+
* Editors without a config or token are left unchanged.
|
|
10
|
+
*/ export async function validateEditorTokens(editors) {
|
|
11
|
+
// Collect unique tokens and map them to their editors
|
|
12
|
+
const tokenToEditors = new Map();
|
|
13
|
+
for (const editor of editors){
|
|
14
|
+
if (editor.existingToken) {
|
|
15
|
+
const existing = tokenToEditors.get(editor.existingToken);
|
|
16
|
+
if (existing) {
|
|
17
|
+
existing.push(editor);
|
|
18
|
+
} else {
|
|
19
|
+
tokenToEditors.set(editor.existingToken, [
|
|
20
|
+
editor
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (tokenToEditors.size === 0) {
|
|
26
|
+
debug('No existing tokens to validate');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const editorCount = [
|
|
30
|
+
...tokenToEditors.values()
|
|
31
|
+
].reduce((sum, eds)=>sum + eds.length, 0);
|
|
32
|
+
debug('Validating %d unique token(s) across %d editor(s)', tokenToEditors.size, editorCount);
|
|
33
|
+
// Validate all unique tokens in parallel to avoid stacking timeouts
|
|
34
|
+
await Promise.all([
|
|
35
|
+
...tokenToEditors.entries()
|
|
36
|
+
].map(async ([token, tokenEditors])=>{
|
|
37
|
+
let status;
|
|
38
|
+
try {
|
|
39
|
+
const valid = await validateMCPToken(token);
|
|
40
|
+
status = valid ? 'valid' : 'unauthorized';
|
|
41
|
+
} catch (err) {
|
|
42
|
+
// Network errors, timeouts, or unexpected failures — assume the token
|
|
43
|
+
// is valid rather than falsely marking it as expired. We only mark
|
|
44
|
+
// tokens as unauthorized when the server explicitly says so (401/403).
|
|
45
|
+
debug('Token validation error (assuming valid): %s', err);
|
|
46
|
+
status = 'valid';
|
|
47
|
+
}
|
|
48
|
+
debug('Token ending ...%s is %s (used by %s)', token.slice(-4), status, tokenEditors.map((e)=>e.name).join(', '));
|
|
49
|
+
// Apply status to all editors sharing this token
|
|
50
|
+
for (const editor of tokenEditors){
|
|
51
|
+
editor.authStatus = status;
|
|
52
|
+
}
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=validateEditorTokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/mcp/validateEditorTokens.ts"],"sourcesContent":["import {subdebug} from '@sanity/cli-core'\n\nimport {validateMCPToken} from '../../services/mcp.js'\nimport {type AuthStatus, type Editor} from './types.js'\n\nconst debug = subdebug('mcp:validateEditorTokens')\n\n/**\n * Validate existing MCP tokens for all configured editors.\n *\n * Collects unique tokens, validates each once against the Sanity API,\n * and sets `authStatus` on each editor that has a token.\n * Editors without a config or token are left unchanged.\n */\nexport async function validateEditorTokens(editors: Editor[]): Promise<void> {\n // Collect unique tokens and map them to their editors\n const tokenToEditors = new Map<string, Editor[]>()\n for (const editor of editors) {\n if (editor.existingToken) {\n const existing = tokenToEditors.get(editor.existingToken)\n if (existing) {\n existing.push(editor)\n } else {\n tokenToEditors.set(editor.existingToken, [editor])\n }\n }\n }\n\n if (tokenToEditors.size === 0) {\n debug('No existing tokens to validate')\n return\n }\n\n const editorCount = [...tokenToEditors.values()].reduce((sum, eds) => sum + eds.length, 0)\n debug('Validating %d unique token(s) across %d editor(s)', tokenToEditors.size, editorCount)\n\n // Validate all unique tokens in parallel to avoid stacking timeouts\n await Promise.all(\n [...tokenToEditors.entries()].map(async ([token, tokenEditors]) => {\n let status: AuthStatus\n try {\n const valid = await validateMCPToken(token)\n status = valid ? 'valid' : 'unauthorized'\n } catch (err) {\n // Network errors, timeouts, or unexpected failures — assume the token\n // is valid rather than falsely marking it as expired. We only mark\n // tokens as unauthorized when the server explicitly says so (401/403).\n debug('Token validation error (assuming valid): %s', err)\n status = 'valid'\n }\n\n debug(\n 'Token ending ...%s is %s (used by %s)',\n token.slice(-4),\n status,\n tokenEditors.map((e) => e.name).join(', '),\n )\n\n // Apply status to all editors sharing this token\n for (const editor of tokenEditors) {\n editor.authStatus = status\n }\n }),\n )\n}\n"],"names":["subdebug","validateMCPToken","debug","validateEditorTokens","editors","tokenToEditors","Map","editor","existingToken","existing","get","push","set","size","editorCount","values","reduce","sum","eds","length","Promise","all","entries","map","token","tokenEditors","status","valid","err","slice","e","name","join","authStatus"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,mBAAkB;AAEzC,SAAQC,gBAAgB,QAAO,wBAAuB;AAGtD,MAAMC,QAAQF,SAAS;AAEvB;;;;;;CAMC,GACD,OAAO,eAAeG,qBAAqBC,OAAiB;IAC1D,sDAAsD;IACtD,MAAMC,iBAAiB,IAAIC;IAC3B,KAAK,MAAMC,UAAUH,QAAS;QAC5B,IAAIG,OAAOC,aAAa,EAAE;YACxB,MAAMC,WAAWJ,eAAeK,GAAG,CAACH,OAAOC,aAAa;YACxD,IAAIC,UAAU;gBACZA,SAASE,IAAI,CAACJ;YAChB,OAAO;gBACLF,eAAeO,GAAG,CAACL,OAAOC,aAAa,EAAE;oBAACD;iBAAO;YACnD;QACF;IACF;IAEA,IAAIF,eAAeQ,IAAI,KAAK,GAAG;QAC7BX,MAAM;QACN;IACF;IAEA,MAAMY,cAAc;WAAIT,eAAeU,MAAM;KAAG,CAACC,MAAM,CAAC,CAACC,KAAKC,MAAQD,MAAMC,IAAIC,MAAM,EAAE;IACxFjB,MAAM,qDAAqDG,eAAeQ,IAAI,EAAEC;IAEhF,oEAAoE;IACpE,MAAMM,QAAQC,GAAG,CACf;WAAIhB,eAAeiB,OAAO;KAAG,CAACC,GAAG,CAAC,OAAO,CAACC,OAAOC,aAAa;QAC5D,IAAIC;QACJ,IAAI;YACF,MAAMC,QAAQ,MAAM1B,iBAAiBuB;YACrCE,SAASC,QAAQ,UAAU;QAC7B,EAAE,OAAOC,KAAK;YACZ,sEAAsE;YACtE,mEAAmE;YACnE,uEAAuE;YACvE1B,MAAM,+CAA+C0B;YACrDF,SAAS;QACX;QAEAxB,MACE,yCACAsB,MAAMK,KAAK,CAAC,CAAC,IACbH,QACAD,aAAaF,GAAG,CAAC,CAACO,IAAMA,EAAEC,IAAI,EAAEC,IAAI,CAAC;QAGvC,iDAAiD;QACjD,KAAK,MAAMzB,UAAUkB,aAAc;YACjClB,OAAO0B,UAAU,GAAGP;QACtB;IACF;AAEJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/telemetry/telemetryDebug.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/telemetry/telemetryDebug.ts"],"sourcesContent":["import debugIt from 'debug'\n\nexport const telemetryDebug = debugIt('telemetry:sanity:cli')\n"],"names":["debugIt","telemetryDebug"],"mappings":"AAAA,OAAOA,aAAa,QAAO;AAE3B,OAAO,MAAMC,iBAAiBD,QAAQ,wBAAuB"}
|
package/dist/commands/init.js
CHANGED
|
@@ -399,7 +399,9 @@ export class InitCommand extends SanityCommand {
|
|
|
399
399
|
workDir
|
|
400
400
|
});
|
|
401
401
|
// Set up MCP integration
|
|
402
|
-
const mcpResult = await setupMCP(
|
|
402
|
+
const mcpResult = await setupMCP({
|
|
403
|
+
skip: !this.flags.mcp
|
|
404
|
+
});
|
|
403
405
|
this._trace.log({
|
|
404
406
|
configuredEditors: mcpResult.configuredEditors,
|
|
405
407
|
detectedEditors: mcpResult.detectedEditors,
|
|
@@ -410,6 +412,12 @@ export class InitCommand extends SanityCommand {
|
|
|
410
412
|
this._trace.error(mcpResult.error);
|
|
411
413
|
}
|
|
412
414
|
const mcpConfigured = mcpResult.configuredEditors;
|
|
415
|
+
// Show checkmark for editors that were already configured
|
|
416
|
+
const { alreadyConfiguredEditors } = mcpResult;
|
|
417
|
+
if (alreadyConfiguredEditors.length > 0) {
|
|
418
|
+
const label = alreadyConfiguredEditors.length === 1 ? `${alreadyConfiguredEditors[0]} already configured for Sanity MCP` : `${alreadyConfiguredEditors.length} editors already configured for Sanity MCP`;
|
|
419
|
+
spinner(label).start().succeed();
|
|
420
|
+
}
|
|
413
421
|
if (isNextJs) {
|
|
414
422
|
await checkNextJsReactCompatibility({
|
|
415
423
|
detectedFramework,
|
|
@@ -751,11 +759,9 @@ export class InitCommand extends SanityCommand {
|
|
|
751
759
|
userAction: 'none'
|
|
752
760
|
};
|
|
753
761
|
}
|
|
754
|
-
const datasetInfo = 'Your content will be stored in a dataset that can be public or private, depending on\nwhether you want to query your content with or without authentication.\nThe default dataset configuration has a public dataset named "production".';
|
|
755
762
|
if (datasets.length === 0) {
|
|
756
763
|
debug('No datasets found for project, prompting for name');
|
|
757
764
|
if (opts.showDefaultConfigPrompt) {
|
|
758
|
-
this.log(datasetInfo);
|
|
759
765
|
defaultConfig = await promptForDefaultConfig();
|
|
760
766
|
}
|
|
761
767
|
const name = defaultConfig ? 'production' : await promptForDatasetName({
|
|
@@ -793,7 +799,6 @@ export class InitCommand extends SanityCommand {
|
|
|
793
799
|
const existingDatasetNames = datasets.map((ds)=>ds.name);
|
|
794
800
|
debug('User wants to create a new dataset, prompting for name');
|
|
795
801
|
if (opts.showDefaultConfigPrompt && !existingDatasetNames.includes('production')) {
|
|
796
|
-
this.log(datasetInfo);
|
|
797
802
|
defaultConfig = await promptForDefaultConfig();
|
|
798
803
|
}
|
|
799
804
|
const newDatasetName = defaultConfig ? 'production' : await promptForDatasetName({
|