@sanity/cli 7.13.0 → 7.14.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.
Files changed (36) hide show
  1. package/dist/actions/auth/getProviderName.js +13 -0
  2. package/dist/actions/auth/getProviderName.js.map +1 -1
  3. package/dist/actions/debug/types.js.map +1 -1
  4. package/dist/actions/deploy/deployApp.js +1 -0
  5. package/dist/actions/deploy/deployApp.js.map +1 -1
  6. package/dist/actions/deploy/deployChecks.js +25 -4
  7. package/dist/actions/deploy/deployChecks.js.map +1 -1
  8. package/dist/actions/deploy/resolveDeployTarget.js +23 -4
  9. package/dist/actions/deploy/resolveDeployTarget.js.map +1 -1
  10. package/dist/actions/init/initAction.js +3 -3
  11. package/dist/actions/init/initAction.js.map +1 -1
  12. package/dist/commands/cors/add.js +13 -7
  13. package/dist/commands/cors/add.js.map +1 -1
  14. package/dist/commands/datasets/copy.js +6 -3
  15. package/dist/commands/datasets/copy.js.map +1 -1
  16. package/dist/commands/debug.js +1 -1
  17. package/dist/commands/debug.js.map +1 -1
  18. package/dist/exports/invokeSanityCli/commandPolicies/index.js +6 -0
  19. package/dist/exports/invokeSanityCli/commandPolicies/index.js.map +1 -0
  20. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +167 -0
  21. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -0
  22. package/dist/exports/invokeSanityCli/commandPolicies/policy.js +46 -0
  23. package/dist/exports/invokeSanityCli/commandPolicies/policy.js.map +1 -0
  24. package/dist/exports/invokeSanityCli/help.js +146 -0
  25. package/dist/exports/invokeSanityCli/help.js.map +1 -0
  26. package/dist/exports/invokeSanityCli/index.d.ts +60 -0
  27. package/dist/exports/invokeSanityCli/index.js +177 -0
  28. package/dist/exports/invokeSanityCli/index.js.map +1 -0
  29. package/dist/server/devServer.js +16 -2
  30. package/dist/server/devServer.js.map +1 -1
  31. package/dist/util/getProjectDefaults.js +4 -1
  32. package/dist/util/getProjectDefaults.js.map +1 -1
  33. package/dist/util/packageManager/preferredPm.js +45 -6
  34. package/dist/util/packageManager/preferredPm.js.map +1 -1
  35. package/oclif.manifest.json +1 -1
  36. package/package.json +9 -4
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/exports/invokeSanityCli/index.ts"],"sourcesContent":["/**\n * Programmatic (in-process) invocation of CLI commands, e.g. from an MCP\n * server. The invokable surface is governed by a per-source command policy\n * (see ./commandPolicies): every CLI command is explicitly allowed, denied,\n * or allowed conditionally on the parsed invocation.\n *\n * {@link invokeSanityCli} handles arg parsing, policy enforcement, command\n * dispatch, per-invocation auth, and output capture.\n * ```ts\n * import {invokeSanityCli} from '@sanity/cli/invokeSanityCli'\n *\n * const {exitCode, output} = await invokeSanityCli({\n * args: 'cors list --project-id abc123',\n * source: 'mcp',\n * token: extra.authInfo.token,\n * })\n * ```\n *\n * Help works like the regular CLI, scoped to the source's policy: `--help`\n * (or `help`) renders root help listing the invokable topics, and a subject\n * (`cors --help`, `cors list --help`) renders topic or command help.\n */\nimport {Config, Parser} from '@oclif/core'\nimport {getHelpFlagAdditions, normalizeArgv} from '@oclif/core/help'\nimport {CLI_TELEMETRY_SYMBOL, exitCodes, noopLogger, setCliTelemetry} from '@sanity/cli-core'\nimport {runWithCliExecutionContext} from '@sanity/cli-core/executionContext'\nimport {parseArgsStringToArgv} from 'string-argv'\n\nimport {commandPolicies} from './commandPolicies/index.js'\nimport {\n type CommandPolicySet,\n deny,\n type InvocationSource,\n isConditionalInvocationPolicy,\n} from './commandPolicies/policy.js'\nimport {isHelpRequest, renderInvokableHelp} from './help.js'\n\n/**\n * Load the oclif `Config` for this package, needed to resolve, load, and run\n * commands. Loading it once and reusing it across invocations avoids\n * re-reading the command manifest per call.\n */\nfunction loadCliCommandConfig(): Promise<Config> {\n return Config.load(import.meta.url)\n}\n\nfunction unknownCommandResult(argv: string[], policySet: CommandPolicySet): InvokeSanityCliResult {\n const available = Object.entries(policySet)\n .filter(([, policy]) => policy.kind !== 'deny')\n .map(([id]) => id.replaceAll(':', ' '))\n .toSorted()\n return {\n exitCode: exitCodes.USAGE_ERROR,\n output: [\n `Unknown or unsupported command: ${argv.slice(0, 2).join(' ') || '(none)'}`,\n `Available commands: ${available.join(', ')}`,\n ].join('\\n'),\n }\n}\n\n/**\n * @internal\n */\nexport interface InvokeSanityCliOptions {\n /**\n * Arguments after `sanity` (a leading `sanity` token is tolerated), either\n * as a single string — shell-style quoting is supported, but no shell is\n * ever executed — or as a pre-split argv array.\n */\n args: string | string[]\n\n /**\n * Where this invocation originates. Selects the command policy to enforce:\n * which commands are invokable and which invocations of them are permitted.\n */\n source: InvocationSource\n\n /**\n * Auth token for this invocation. Scoped to this call via the CLI execution\n * context: it never touches process env or the process-wide token cache, so\n * concurrent invocations with different tokens are fully isolated.\n */\n token: string\n\n /**\n * Optional oclif config override (mainly for tests). Defaults to this\n * package's config, loaded once and cached across invocations.\n */\n config?: Config\n}\n\n/**\n * @internal\n */\nexport interface InvokeSanityCliResult {\n /** `0` on success, the command's exit code otherwise. */\n exitCode: number\n\n /** Combined stdout and stderr output, in emission order. */\n output: string\n}\n\nlet cachedConfig: Promise<Config> | undefined\n\n/**\n * Unlike a shell, string-argv keeps quotes that are glued to unquoted text:\n * `--name=\"my project\"` tokenizes with the quotes intact. Strip a matching\n * wrapping quote pair from the value side of `--flag=`-shaped tokens so the\n * common shell-style form yields the value the caller intended.\n */\nfunction stripFlagQuotes(rawToken: string): string {\n const match = /^(-{1,2}[^\\s=]+=)(['\"])([\\s\\S]*)\\2$/.exec(rawToken)\n return match ? match[1] + match[3] : rawToken\n}\n\n/**\n * Run a policy-permitted CLI command in-process and capture its result.\n *\n * Command-level failures (unknown command, bad flags, API errors) are\n * reported through `exitCode`/`output` rather than thrown, so callers can\n * relay them verbatim.\n *\n * @internal\n */\nexport async function invokeSanityCli({\n args,\n config,\n source,\n token,\n}: InvokeSanityCliOptions): Promise<InvokeSanityCliResult> {\n const resolvedConfig = config ?? (await (cachedConfig ??= loadCliCommandConfig()))\n const policySet = commandPolicies[source]\n\n // Commands log through the global telemetry store; default it to a noop\n // store so embedding hosts need no telemetry wiring (and see no warnings),\n // without clobbering a store the host may have installed itself.\n if (!(globalThis as Record<symbol, unknown>)[CLI_TELEMETRY_SYMBOL]) {\n setCliTelemetry(noopLogger)\n }\n\n // Pre-split argv arrays are taken verbatim; only string input goes through\n // shell-style tokenization and quote normalization.\n let argv =\n typeof args === 'string'\n ? parseArgsStringToArgv(args).map((t) => stripFlagQuotes(t))\n : [...args]\n if (argv[0] === 'sanity') argv = argv.slice(1)\n\n // Help requests are routed through oclif's help system, scoped to the\n // source's policy: root help for a bare request, topic/command help when a\n // subject is given. Denied subjects get the standard unknown-command\n // response (identical to a truly unknown command, so hosts can't probe the\n // full CLI surface through help), and a help request never executes a\n // command.\n if (isHelpRequest(argv, resolvedConfig)) {\n try {\n // Drop a leading `help` so the rest is the subject, mirroring how\n // oclif's dispatch consumes the token before the help command sees argv\n const helpArgv = argv[0] === 'help' ? argv.slice(1) : argv\n const output = await renderInvokableHelp(resolvedConfig, helpArgv, policySet)\n if (output !== undefined) return {exitCode: exitCodes.SUCCESS, output}\n const helpFlags = getHelpFlagAdditions(resolvedConfig)\n return unknownCommandResult(\n helpArgv.filter((token) => !helpFlags.includes(token)),\n policySet,\n )\n } catch (err) {\n return {\n exitCode: exitCodes.RUNTIME_ERROR,\n output: err instanceof Error ? err.message : String(err),\n }\n }\n }\n\n // Resolve the command id the same way oclif's dispatch would (collating\n // space-separated topics, accepting colon-separated ids as-is), then apply\n // the policy. Denied and uncategorized ids fail closed, indistinguishable\n // from commands that don't exist.\n const [commandId = '', ...commandArgv] = normalizeArgv(resolvedConfig, argv)\n const policy = policySet[commandId] ?? deny\n const commandDefinition =\n policy.kind === 'deny' ? undefined : resolvedConfig.findCommand(commandId)\n if (!commandDefinition) return unknownCommandResult(argv, policySet)\n\n const CommandClass = await commandDefinition.load()\n\n // Parse with the command's real definitions (without executing anything) so\n // conditional policies are evaluated against typed args/flags, not tokens.\n let invocation: {args: Record<string, unknown>; flags: Record<string, unknown>}\n try {\n const parsed = await Parser.parse(commandArgv, {\n args: CommandClass.args,\n baseFlags: CommandClass.baseFlags,\n enableJsonFlag: CommandClass.enableJsonFlag,\n flags: CommandClass.flags,\n strict: CommandClass.strict,\n })\n invocation = {\n args: parsed.args as Record<string, unknown>,\n flags: parsed.flags as Record<string, unknown>,\n }\n } catch (err) {\n return {\n exitCode: exitCodes.USAGE_ERROR,\n output: err instanceof Error ? err.message : String(err),\n }\n }\n\n if (!policy.validate(invocation)) {\n const displayId = commandId.replaceAll(':', ' ')\n let output = `This invocation of \\`${displayId}\\` is not supported here`\n\n if (isConditionalInvocationPolicy(policy)) {\n const usedDeniedFlags = policy.deniedFlags.filter(\n (name) => invocation.flags[name] !== undefined && invocation.flags[name] !== false,\n )\n output = `\\nThe ${usedDeniedFlags.map((name) => `--${name}`).join(', ')} flag is not supported here for \\`${displayId}\\``\n }\n\n return {\n exitCode: exitCodes.USAGE_ERROR,\n output,\n }\n }\n\n const output: string[] = []\n const sink = (line: string) => output.push(line)\n\n try {\n await runWithCliExecutionContext({stderr: sink, stdout: sink, token}, () =>\n CommandClass.run(commandArgv, resolvedConfig),\n )\n return {exitCode: exitCodes.SUCCESS, output: output.join('\\n')}\n } catch (err) {\n const exit = err.oclif?.exit\n\n // `this.exit(0)` throws an ExitError but is a successful outcome\n if (exit === exitCodes.SUCCESS) {\n return {exitCode: exitCodes.SUCCESS, output: output.join('\\n')}\n }\n\n const message = err instanceof Error ? err.message : String(err)\n if (message) output.push(message)\n return {\n exitCode: typeof exit === 'number' ? exit : exitCodes.RUNTIME_ERROR,\n output: output.join('\\n'),\n }\n }\n}\n"],"names":["Config","Parser","getHelpFlagAdditions","normalizeArgv","CLI_TELEMETRY_SYMBOL","exitCodes","noopLogger","setCliTelemetry","runWithCliExecutionContext","parseArgsStringToArgv","commandPolicies","deny","isConditionalInvocationPolicy","isHelpRequest","renderInvokableHelp","loadCliCommandConfig","load","url","unknownCommandResult","argv","policySet","available","Object","entries","filter","policy","kind","map","id","replaceAll","toSorted","exitCode","USAGE_ERROR","output","slice","join","cachedConfig","stripFlagQuotes","rawToken","match","exec","invokeSanityCli","args","config","source","token","resolvedConfig","globalThis","t","helpArgv","undefined","SUCCESS","helpFlags","includes","err","RUNTIME_ERROR","Error","message","String","commandId","commandArgv","commandDefinition","findCommand","CommandClass","invocation","parsed","parse","baseFlags","enableJsonFlag","flags","strict","validate","displayId","usedDeniedFlags","deniedFlags","name","sink","line","push","stderr","stdout","run","exit","oclif"],"mappings":"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"}
@@ -64,14 +64,28 @@ export async function startDevServer(options) {
64
64
  // `<root>/index.html`. Sanity has no such file — it serves a virtual document
65
65
  // rewritten to `.sanity/runtime/index.html` — so point the bundler at the real
66
66
  // runtime HTML, otherwise the build fails with UNRESOLVED_ENTRY.
67
+ //
68
+ // Also force `strictExecutionOrder` so shared chunks cannot evaluate before the
69
+ // entry chunk's react-refresh preamble (which otherwise throws
70
+ // "@vitejs/plugin-react can't detect preamble").
71
+ // See https://github.com/vitejs/vite-plugin-react/issues/1191
67
72
  if (bundledDev) {
73
+ const existingRolldown = viteConfig.build?.rolldownOptions;
74
+ const existingOutput = existingRolldown?.output;
68
75
  viteConfig = {
69
76
  ...viteConfig,
70
77
  build: {
71
78
  ...viteConfig.build,
72
79
  rolldownOptions: {
73
- ...viteConfig.build?.rolldownOptions,
74
- input: path.join(cwd, '.sanity', 'runtime', 'index.html')
80
+ ...existingRolldown,
81
+ input: path.join(cwd, '.sanity', 'runtime', 'index.html'),
82
+ output: Array.isArray(existingOutput) ? existingOutput.map((entry)=>({
83
+ ...entry,
84
+ strictExecutionOrder: true
85
+ })) : {
86
+ ...existingOutput,
87
+ strictExecutionOrder: true
88
+ }
75
89
  }
76
90
  },
77
91
  experimental: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/devServer.ts"],"sourcesContent":["import path from 'node:path'\n\nimport {\n extendViteConfigWithUserConfig,\n getViteConfig,\n writeSanityRuntime,\n} from '@sanity/cli-build/_internal/build'\nimport {\n getAppEnvironmentVariables,\n getStudioEnvironmentVariables,\n} from '@sanity/cli-build/_internal/env'\nimport {CliConfig, getCliTelemetry, type UserViteConfig} from '@sanity/cli-core'\nimport {type WorkbenchExposes} from '@sanity/workbench-cli/build'\nimport {type PluginOptions as ReactCompilerConfig} from 'babel-plugin-react-compiler'\nimport {type FSWatcher} from 'chokidar'\nimport {createServer, type InlineConfig, type ViteDevServer} from 'vite'\n\nimport {serverDebug} from './serverDebug.js'\nimport {sanityTypegenPlugin} from './vite/plugin-typegen.js'\n\nconst debug = serverDebug.extend('dev')\n\nexport interface DevServerOptions {\n basePath: string\n cwd: string\n httpPort: number\n\n reactCompiler: boolean | ReactCompilerConfig | undefined\n reactStrictMode: boolean | undefined\n\n staticPath: string\n\n appTitle?: string\n /** Enable Vite's experimental bundled dev mode (`experimental.bundledDev`). */\n bundledDev?: boolean\n entry?: string\n exposes?: WorkbenchExposes\n httpHost?: string\n isApp?: boolean\n isWorkbenchApp?: boolean\n projectName?: string\n schemaExtraction?: CliConfig['schemaExtraction']\n typegen?: CliConfig['typegen']\n vite?: UserViteConfig\n /** The workbench app's bus identity (`__SANITY_APP_ID__`). */\n workbenchAppId?: string\n}\n\ninterface DevServer {\n close(): Promise<void>\n server: ViteDevServer\n\n watcher?: FSWatcher\n}\n\nexport async function startDevServer(options: DevServerOptions): Promise<DevServer> {\n const {\n appTitle,\n basePath,\n bundledDev,\n cwd,\n entry,\n exposes,\n httpHost,\n httpPort,\n isApp,\n isWorkbenchApp,\n reactCompiler,\n reactStrictMode,\n schemaExtraction,\n typegen,\n vite: extendViteConfig,\n workbenchAppId,\n } = options\n\n debug('Writing Sanity runtime files')\n const {entries, watcher} = await writeSanityRuntime({\n appTitle,\n basePath,\n cwd,\n entry,\n isApp,\n isWorkbenchApp,\n reactStrictMode,\n watch: true,\n })\n\n debug('Resolving vite config')\n const mode = 'development'\n\n function getEnvironmentVariables() {\n return isApp\n ? getAppEnvironmentVariables({jsonEncode: true, prefix: 'process.env.'})\n : getStudioEnvironmentVariables({jsonEncode: true, prefix: 'process.env.'})\n }\n\n let viteConfig: InlineConfig = await getViteConfig({\n additionalPlugins: [\n // Add typegen when enabled\n ...(typegen?.enabled\n ? [\n sanityTypegenPlugin({\n config: typegen,\n telemetryLogger: getCliTelemetry(),\n workDir: cwd,\n }),\n ]\n : []),\n ],\n basePath,\n cwd,\n entries,\n exposes,\n getEnvironmentVariables,\n isApp,\n isWorkbenchApp,\n mode: 'development',\n reactCompiler,\n schemaExtraction,\n server: {host: httpHost, port: httpPort},\n workbenchAppId,\n })\n\n // Opt into Vite's experimental bundled dev mode. Set before the user-config\n // extension below so a `vite` override in sanity.cli.ts still has final say.\n //\n // Bundled mode bundles the app up front from an HTML entry, defaulting to\n // `<root>/index.html`. Sanity has no such file — it serves a virtual document\n // rewritten to `.sanity/runtime/index.html` — so point the bundler at the real\n // runtime HTML, otherwise the build fails with UNRESOLVED_ENTRY.\n if (bundledDev) {\n viteConfig = {\n ...viteConfig,\n build: {\n ...viteConfig.build,\n rolldownOptions: {\n ...viteConfig.build?.rolldownOptions,\n input: path.join(cwd, '.sanity', 'runtime', 'index.html'),\n },\n },\n experimental: {...viteConfig.experimental, bundledDev: true},\n }\n }\n\n // Extend Vite configuration with user-provided config\n if (extendViteConfig) {\n viteConfig = await extendViteConfigWithUserConfig(\n {command: 'serve', mode},\n viteConfig,\n extendViteConfig,\n )\n }\n\n debug('Creating vite server')\n const server = await createServer(viteConfig)\n\n debug('Listening on specified port')\n await server.listen()\n\n return {\n close: async () => {\n if (watcher) {\n await watcher.close()\n }\n await server.close()\n },\n server,\n watcher,\n }\n}\n"],"names":["path","extendViteConfigWithUserConfig","getViteConfig","writeSanityRuntime","getAppEnvironmentVariables","getStudioEnvironmentVariables","getCliTelemetry","createServer","serverDebug","sanityTypegenPlugin","debug","extend","startDevServer","options","appTitle","basePath","bundledDev","cwd","entry","exposes","httpHost","httpPort","isApp","isWorkbenchApp","reactCompiler","reactStrictMode","schemaExtraction","typegen","vite","extendViteConfig","workbenchAppId","entries","watcher","watch","mode","getEnvironmentVariables","jsonEncode","prefix","viteConfig","additionalPlugins","enabled","config","telemetryLogger","workDir","server","host","port","build","rolldownOptions","input","join","experimental","command","listen","close"],"mappings":"AAAA,OAAOA,UAAU,YAAW;AAE5B,SACEC,8BAA8B,EAC9BC,aAAa,EACbC,kBAAkB,QACb,oCAAmC;AAC1C,SACEC,0BAA0B,EAC1BC,6BAA6B,QACxB,kCAAiC;AACxC,SAAmBC,eAAe,QAA4B,mBAAkB;AAIhF,SAAQC,YAAY,QAA8C,OAAM;AAExE,SAAQC,WAAW,QAAO,mBAAkB;AAC5C,SAAQC,mBAAmB,QAAO,2BAA0B;AAE5D,MAAMC,QAAQF,YAAYG,MAAM,CAAC;AAmCjC,OAAO,eAAeC,eAAeC,OAAyB;IAC5D,MAAM,EACJC,QAAQ,EACRC,QAAQ,EACRC,UAAU,EACVC,GAAG,EACHC,KAAK,EACLC,OAAO,EACPC,QAAQ,EACRC,QAAQ,EACRC,KAAK,EACLC,cAAc,EACdC,aAAa,EACbC,eAAe,EACfC,gBAAgB,EAChBC,OAAO,EACPC,MAAMC,gBAAgB,EACtBC,cAAc,EACf,GAAGjB;IAEJH,MAAM;IACN,MAAM,EAACqB,OAAO,EAAEC,OAAO,EAAC,GAAG,MAAM7B,mBAAmB;QAClDW;QACAC;QACAE;QACAC;QACAI;QACAC;QACAE;QACAQ,OAAO;IACT;IAEAvB,MAAM;IACN,MAAMwB,OAAO;IAEb,SAASC;QACP,OAAOb,QACHlB,2BAA2B;YAACgC,YAAY;YAAMC,QAAQ;QAAc,KACpEhC,8BAA8B;YAAC+B,YAAY;YAAMC,QAAQ;QAAc;IAC7E;IAEA,IAAIC,aAA2B,MAAMpC,cAAc;QACjDqC,mBAAmB;YACjB,2BAA2B;eACvBZ,SAASa,UACT;gBACE/B,oBAAoB;oBAClBgC,QAAQd;oBACRe,iBAAiBpC;oBACjBqC,SAAS1B;gBACX;aACD,GACD,EAAE;SACP;QACDF;QACAE;QACAc;QACAZ;QACAgB;QACAb;QACAC;QACAW,MAAM;QACNV;QACAE;QACAkB,QAAQ;YAACC,MAAMzB;YAAU0B,MAAMzB;QAAQ;QACvCS;IACF;IAEA,4EAA4E;IAC5E,6EAA6E;IAC7E,EAAE;IACF,0EAA0E;IAC1E,8EAA8E;IAC9E,+EAA+E;IAC/E,iEAAiE;IACjE,IAAId,YAAY;QACdsB,aAAa;YACX,GAAGA,UAAU;YACbS,OAAO;gBACL,GAAGT,WAAWS,KAAK;gBACnBC,iBAAiB;oBACf,GAAGV,WAAWS,KAAK,EAAEC,eAAe;oBACpCC,OAAOjD,KAAKkD,IAAI,CAACjC,KAAK,WAAW,WAAW;gBAC9C;YACF;YACAkC,cAAc;gBAAC,GAAGb,WAAWa,YAAY;gBAAEnC,YAAY;YAAI;QAC7D;IACF;IAEA,sDAAsD;IACtD,IAAIa,kBAAkB;QACpBS,aAAa,MAAMrC,+BACjB;YAACmD,SAAS;YAASlB;QAAI,GACvBI,YACAT;IAEJ;IAEAnB,MAAM;IACN,MAAMkC,SAAS,MAAMrC,aAAa+B;IAElC5B,MAAM;IACN,MAAMkC,OAAOS,MAAM;IAEnB,OAAO;QACLC,OAAO;YACL,IAAItB,SAAS;gBACX,MAAMA,QAAQsB,KAAK;YACrB;YACA,MAAMV,OAAOU,KAAK;QACpB;QACAV;QACAZ;IACF;AACF"}
1
+ {"version":3,"sources":["../../src/server/devServer.ts"],"sourcesContent":["import path from 'node:path'\n\nimport {\n extendViteConfigWithUserConfig,\n getViteConfig,\n writeSanityRuntime,\n} from '@sanity/cli-build/_internal/build'\nimport {\n getAppEnvironmentVariables,\n getStudioEnvironmentVariables,\n} from '@sanity/cli-build/_internal/env'\nimport {CliConfig, getCliTelemetry, type UserViteConfig} from '@sanity/cli-core'\nimport {type WorkbenchExposes} from '@sanity/workbench-cli/build'\nimport {type PluginOptions as ReactCompilerConfig} from 'babel-plugin-react-compiler'\nimport {type FSWatcher} from 'chokidar'\nimport {createServer, type InlineConfig, type ViteDevServer} from 'vite'\n\nimport {serverDebug} from './serverDebug.js'\nimport {sanityTypegenPlugin} from './vite/plugin-typegen.js'\n\nconst debug = serverDebug.extend('dev')\n\nexport interface DevServerOptions {\n basePath: string\n cwd: string\n httpPort: number\n\n reactCompiler: boolean | ReactCompilerConfig | undefined\n reactStrictMode: boolean | undefined\n\n staticPath: string\n\n appTitle?: string\n /** Enable Vite's experimental bundled dev mode (`experimental.bundledDev`). */\n bundledDev?: boolean\n entry?: string\n exposes?: WorkbenchExposes\n httpHost?: string\n isApp?: boolean\n isWorkbenchApp?: boolean\n projectName?: string\n schemaExtraction?: CliConfig['schemaExtraction']\n typegen?: CliConfig['typegen']\n vite?: UserViteConfig\n /** The workbench app's bus identity (`__SANITY_APP_ID__`). */\n workbenchAppId?: string\n}\n\ninterface DevServer {\n close(): Promise<void>\n server: ViteDevServer\n\n watcher?: FSWatcher\n}\n\nexport async function startDevServer(options: DevServerOptions): Promise<DevServer> {\n const {\n appTitle,\n basePath,\n bundledDev,\n cwd,\n entry,\n exposes,\n httpHost,\n httpPort,\n isApp,\n isWorkbenchApp,\n reactCompiler,\n reactStrictMode,\n schemaExtraction,\n typegen,\n vite: extendViteConfig,\n workbenchAppId,\n } = options\n\n debug('Writing Sanity runtime files')\n const {entries, watcher} = await writeSanityRuntime({\n appTitle,\n basePath,\n cwd,\n entry,\n isApp,\n isWorkbenchApp,\n reactStrictMode,\n watch: true,\n })\n\n debug('Resolving vite config')\n const mode = 'development'\n\n function getEnvironmentVariables() {\n return isApp\n ? getAppEnvironmentVariables({jsonEncode: true, prefix: 'process.env.'})\n : getStudioEnvironmentVariables({jsonEncode: true, prefix: 'process.env.'})\n }\n\n let viteConfig: InlineConfig = await getViteConfig({\n additionalPlugins: [\n // Add typegen when enabled\n ...(typegen?.enabled\n ? [\n sanityTypegenPlugin({\n config: typegen,\n telemetryLogger: getCliTelemetry(),\n workDir: cwd,\n }),\n ]\n : []),\n ],\n basePath,\n cwd,\n entries,\n exposes,\n getEnvironmentVariables,\n isApp,\n isWorkbenchApp,\n mode: 'development',\n reactCompiler,\n schemaExtraction,\n server: {host: httpHost, port: httpPort},\n workbenchAppId,\n })\n\n // Opt into Vite's experimental bundled dev mode. Set before the user-config\n // extension below so a `vite` override in sanity.cli.ts still has final say.\n //\n // Bundled mode bundles the app up front from an HTML entry, defaulting to\n // `<root>/index.html`. Sanity has no such file — it serves a virtual document\n // rewritten to `.sanity/runtime/index.html` — so point the bundler at the real\n // runtime HTML, otherwise the build fails with UNRESOLVED_ENTRY.\n //\n // Also force `strictExecutionOrder` so shared chunks cannot evaluate before the\n // entry chunk's react-refresh preamble (which otherwise throws\n // \"@vitejs/plugin-react can't detect preamble\").\n // See https://github.com/vitejs/vite-plugin-react/issues/1191\n if (bundledDev) {\n const existingRolldown = viteConfig.build?.rolldownOptions\n const existingOutput = existingRolldown?.output\n viteConfig = {\n ...viteConfig,\n build: {\n ...viteConfig.build,\n rolldownOptions: {\n ...existingRolldown,\n input: path.join(cwd, '.sanity', 'runtime', 'index.html'),\n output: Array.isArray(existingOutput)\n ? existingOutput.map((entry) => ({...entry, strictExecutionOrder: true}))\n : {...existingOutput, strictExecutionOrder: true},\n },\n },\n experimental: {...viteConfig.experimental, bundledDev: true},\n }\n }\n\n // Extend Vite configuration with user-provided config\n if (extendViteConfig) {\n viteConfig = await extendViteConfigWithUserConfig(\n {command: 'serve', mode},\n viteConfig,\n extendViteConfig,\n )\n }\n\n debug('Creating vite server')\n const server = await createServer(viteConfig)\n\n debug('Listening on specified port')\n await server.listen()\n\n return {\n close: async () => {\n if (watcher) {\n await watcher.close()\n }\n await server.close()\n },\n server,\n watcher,\n }\n}\n"],"names":["path","extendViteConfigWithUserConfig","getViteConfig","writeSanityRuntime","getAppEnvironmentVariables","getStudioEnvironmentVariables","getCliTelemetry","createServer","serverDebug","sanityTypegenPlugin","debug","extend","startDevServer","options","appTitle","basePath","bundledDev","cwd","entry","exposes","httpHost","httpPort","isApp","isWorkbenchApp","reactCompiler","reactStrictMode","schemaExtraction","typegen","vite","extendViteConfig","workbenchAppId","entries","watcher","watch","mode","getEnvironmentVariables","jsonEncode","prefix","viteConfig","additionalPlugins","enabled","config","telemetryLogger","workDir","server","host","port","existingRolldown","build","rolldownOptions","existingOutput","output","input","join","Array","isArray","map","strictExecutionOrder","experimental","command","listen","close"],"mappings":"AAAA,OAAOA,UAAU,YAAW;AAE5B,SACEC,8BAA8B,EAC9BC,aAAa,EACbC,kBAAkB,QACb,oCAAmC;AAC1C,SACEC,0BAA0B,EAC1BC,6BAA6B,QACxB,kCAAiC;AACxC,SAAmBC,eAAe,QAA4B,mBAAkB;AAIhF,SAAQC,YAAY,QAA8C,OAAM;AAExE,SAAQC,WAAW,QAAO,mBAAkB;AAC5C,SAAQC,mBAAmB,QAAO,2BAA0B;AAE5D,MAAMC,QAAQF,YAAYG,MAAM,CAAC;AAmCjC,OAAO,eAAeC,eAAeC,OAAyB;IAC5D,MAAM,EACJC,QAAQ,EACRC,QAAQ,EACRC,UAAU,EACVC,GAAG,EACHC,KAAK,EACLC,OAAO,EACPC,QAAQ,EACRC,QAAQ,EACRC,KAAK,EACLC,cAAc,EACdC,aAAa,EACbC,eAAe,EACfC,gBAAgB,EAChBC,OAAO,EACPC,MAAMC,gBAAgB,EACtBC,cAAc,EACf,GAAGjB;IAEJH,MAAM;IACN,MAAM,EAACqB,OAAO,EAAEC,OAAO,EAAC,GAAG,MAAM7B,mBAAmB;QAClDW;QACAC;QACAE;QACAC;QACAI;QACAC;QACAE;QACAQ,OAAO;IACT;IAEAvB,MAAM;IACN,MAAMwB,OAAO;IAEb,SAASC;QACP,OAAOb,QACHlB,2BAA2B;YAACgC,YAAY;YAAMC,QAAQ;QAAc,KACpEhC,8BAA8B;YAAC+B,YAAY;YAAMC,QAAQ;QAAc;IAC7E;IAEA,IAAIC,aAA2B,MAAMpC,cAAc;QACjDqC,mBAAmB;YACjB,2BAA2B;eACvBZ,SAASa,UACT;gBACE/B,oBAAoB;oBAClBgC,QAAQd;oBACRe,iBAAiBpC;oBACjBqC,SAAS1B;gBACX;aACD,GACD,EAAE;SACP;QACDF;QACAE;QACAc;QACAZ;QACAgB;QACAb;QACAC;QACAW,MAAM;QACNV;QACAE;QACAkB,QAAQ;YAACC,MAAMzB;YAAU0B,MAAMzB;QAAQ;QACvCS;IACF;IAEA,4EAA4E;IAC5E,6EAA6E;IAC7E,EAAE;IACF,0EAA0E;IAC1E,8EAA8E;IAC9E,+EAA+E;IAC/E,iEAAiE;IACjE,EAAE;IACF,gFAAgF;IAChF,+DAA+D;IAC/D,iDAAiD;IACjD,8DAA8D;IAC9D,IAAId,YAAY;QACd,MAAM+B,mBAAmBT,WAAWU,KAAK,EAAEC;QAC3C,MAAMC,iBAAiBH,kBAAkBI;QACzCb,aAAa;YACX,GAAGA,UAAU;YACbU,OAAO;gBACL,GAAGV,WAAWU,KAAK;gBACnBC,iBAAiB;oBACf,GAAGF,gBAAgB;oBACnBK,OAAOpD,KAAKqD,IAAI,CAACpC,KAAK,WAAW,WAAW;oBAC5CkC,QAAQG,MAAMC,OAAO,CAACL,kBAClBA,eAAeM,GAAG,CAAC,CAACtC,QAAW,CAAA;4BAAC,GAAGA,KAAK;4BAAEuC,sBAAsB;wBAAI,CAAA,KACpE;wBAAC,GAAGP,cAAc;wBAAEO,sBAAsB;oBAAI;gBACpD;YACF;YACAC,cAAc;gBAAC,GAAGpB,WAAWoB,YAAY;gBAAE1C,YAAY;YAAI;QAC7D;IACF;IAEA,sDAAsD;IACtD,IAAIa,kBAAkB;QACpBS,aAAa,MAAMrC,+BACjB;YAAC0D,SAAS;YAASzB;QAAI,GACvBI,YACAT;IAEJ;IAEAnB,MAAM;IACN,MAAMkC,SAAS,MAAMrC,aAAa+B;IAElC5B,MAAM;IACN,MAAMkC,OAAOgB,MAAM;IAEnB,OAAO;QACLC,OAAO;YACL,IAAI7B,SAAS;gBACX,MAAMA,QAAQ6B,KAAK;YACrB;YACA,MAAMjB,OAAOiB,KAAK;QACpB;QACAjB;QACAZ;IACF;AACF"}
@@ -44,7 +44,10 @@ async function getSanityUserInfo() {
44
44
  }
45
45
  try {
46
46
  const user = await getCliUser();
47
- return user ? `${user.name} <${user.email}>` : undefined;
47
+ if (!user) return undefined;
48
+ // API tokens resolve to a user with no email; `Name <null>` isn't valid
49
+ // npm author syntax, so emit the bare name instead.
50
+ return user.email ? `${user.name} <${user.email}>` : user.name;
48
51
  } catch {
49
52
  return undefined;
50
53
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/util/getProjectDefaults.ts"],"sourcesContent":["import fs from 'node:fs/promises'\nimport path from 'node:path'\n\nimport {getCliToken, subdebug} from '@sanity/cli-core'\n\nimport {getCliUser} from '../services/user.js'\nimport {getGitRemoteOriginUrl, getGitUserInfo} from './gitConfig.js'\n\nconst debug = subdebug('getProjectDefaults')\n\ninterface ProjectDefaults {\n author: string | undefined\n description: string\n gitRemote: string | undefined\n license: string\n projectName: string\n}\n\n/**\n * Gathers sensible defaults for a new Sanity project by reading git config,\n * the current Sanity user, and the local directory/README. Used to pre-fill\n * prompts during `sanity init`.\n *\n * @internal\n */\nexport async function getProjectDefaults({\n isPlugin,\n workDir,\n}: {\n isPlugin: boolean\n workDir: string\n}): Promise<ProjectDefaults> {\n const cwd = process.cwd()\n const isSanityRoot = workDir === cwd\n\n const [author, gitRemote, description] = await Promise.all([\n getUserInfo(),\n isPlugin && isSanityRoot ? undefined : getGitRemoteOriginUrl(cwd),\n getProjectDescription({isPlugin, isSanityRoot, outputDir: cwd}),\n ])\n\n return {\n author,\n description,\n gitRemote,\n license: 'UNLICENSED',\n projectName: isPlugin && isSanityRoot ? '' : path.basename(cwd),\n }\n}\n\nasync function getUserInfo(): Promise<string | undefined> {\n const user = await getGitUserInfo()\n if (user) {\n return `${user.name} <${user.email}>`\n }\n\n return getSanityUserInfo()\n}\n\nasync function getSanityUserInfo(): Promise<string | undefined> {\n const hasToken = Boolean(getCliToken())\n if (!hasToken) {\n return undefined\n }\n\n try {\n const user = await getCliUser()\n return user ? `${user.name} <${user.email}>` : undefined\n } catch {\n return undefined\n }\n}\n\nasync function getProjectDescription({\n isPlugin,\n isSanityRoot,\n outputDir,\n}: {\n isPlugin: boolean\n isSanityRoot: boolean\n outputDir: string\n}): Promise<string> {\n const tryResolve = isSanityRoot && !isPlugin\n if (!tryResolve) {\n return ''\n }\n\n // Try to grab a project description from a standard GitHub-generated readme\n try {\n const readmePath = path.join(outputDir, 'README.md')\n const readme = await fs.readFile(readmePath, {encoding: 'utf8'})\n const match = readme.match(/^# .*?\\n+(\\w.*?)(?:$|\\n)/)\n return ((match && match[1]) || '').replace(/\\.$/, '') || ''\n } catch (err) {\n debug(`Error getting project description: ${err}`)\n return ''\n }\n}\n"],"names":["fs","path","getCliToken","subdebug","getCliUser","getGitRemoteOriginUrl","getGitUserInfo","debug","getProjectDefaults","isPlugin","workDir","cwd","process","isSanityRoot","author","gitRemote","description","Promise","all","getUserInfo","undefined","getProjectDescription","outputDir","license","projectName","basename","user","name","email","getSanityUserInfo","hasToken","Boolean","tryResolve","readmePath","join","readme","readFile","encoding","match","replace","err"],"mappings":"AAAA,OAAOA,QAAQ,mBAAkB;AACjC,OAAOC,UAAU,YAAW;AAE5B,SAAQC,WAAW,EAAEC,QAAQ,QAAO,mBAAkB;AAEtD,SAAQC,UAAU,QAAO,sBAAqB;AAC9C,SAAQC,qBAAqB,EAAEC,cAAc,QAAO,iBAAgB;AAEpE,MAAMC,QAAQJ,SAAS;AAUvB;;;;;;CAMC,GACD,OAAO,eAAeK,mBAAmB,EACvCC,QAAQ,EACRC,OAAO,EAIR;IACC,MAAMC,MAAMC,QAAQD,GAAG;IACvB,MAAME,eAAeH,YAAYC;IAEjC,MAAM,CAACG,QAAQC,WAAWC,YAAY,GAAG,MAAMC,QAAQC,GAAG,CAAC;QACzDC;QACAV,YAAYI,eAAeO,YAAYf,sBAAsBM;QAC7DU,sBAAsB;YAACZ;YAAUI;YAAcS,WAAWX;QAAG;KAC9D;IAED,OAAO;QACLG;QACAE;QACAD;QACAQ,SAAS;QACTC,aAAaf,YAAYI,eAAe,KAAKZ,KAAKwB,QAAQ,CAACd;IAC7D;AACF;AAEA,eAAeQ;IACb,MAAMO,OAAO,MAAMpB;IACnB,IAAIoB,MAAM;QACR,OAAO,GAAGA,KAAKC,IAAI,CAAC,EAAE,EAAED,KAAKE,KAAK,CAAC,CAAC,CAAC;IACvC;IAEA,OAAOC;AACT;AAEA,eAAeA;IACb,MAAMC,WAAWC,QAAQ7B;IACzB,IAAI,CAAC4B,UAAU;QACb,OAAOV;IACT;IAEA,IAAI;QACF,MAAMM,OAAO,MAAMtB;QACnB,OAAOsB,OAAO,GAAGA,KAAKC,IAAI,CAAC,EAAE,EAAED,KAAKE,KAAK,CAAC,CAAC,CAAC,GAAGR;IACjD,EAAE,OAAM;QACN,OAAOA;IACT;AACF;AAEA,eAAeC,sBAAsB,EACnCZ,QAAQ,EACRI,YAAY,EACZS,SAAS,EAKV;IACC,MAAMU,aAAanB,gBAAgB,CAACJ;IACpC,IAAI,CAACuB,YAAY;QACf,OAAO;IACT;IAEA,4EAA4E;IAC5E,IAAI;QACF,MAAMC,aAAahC,KAAKiC,IAAI,CAACZ,WAAW;QACxC,MAAMa,SAAS,MAAMnC,GAAGoC,QAAQ,CAACH,YAAY;YAACI,UAAU;QAAM;QAC9D,MAAMC,QAAQH,OAAOG,KAAK,CAAC;QAC3B,OAAO,AAAC,CAAA,AAACA,SAASA,KAAK,CAAC,EAAE,IAAK,EAAC,EAAGC,OAAO,CAAC,OAAO,OAAO;IAC3D,EAAE,OAAOC,KAAK;QACZjC,MAAM,CAAC,mCAAmC,EAAEiC,KAAK;QACjD,OAAO;IACT;AACF"}
1
+ {"version":3,"sources":["../../src/util/getProjectDefaults.ts"],"sourcesContent":["import fs from 'node:fs/promises'\nimport path from 'node:path'\n\nimport {getCliToken, subdebug} from '@sanity/cli-core'\n\nimport {getCliUser} from '../services/user.js'\nimport {getGitRemoteOriginUrl, getGitUserInfo} from './gitConfig.js'\n\nconst debug = subdebug('getProjectDefaults')\n\ninterface ProjectDefaults {\n author: string | undefined\n description: string\n gitRemote: string | undefined\n license: string\n projectName: string\n}\n\n/**\n * Gathers sensible defaults for a new Sanity project by reading git config,\n * the current Sanity user, and the local directory/README. Used to pre-fill\n * prompts during `sanity init`.\n *\n * @internal\n */\nexport async function getProjectDefaults({\n isPlugin,\n workDir,\n}: {\n isPlugin: boolean\n workDir: string\n}): Promise<ProjectDefaults> {\n const cwd = process.cwd()\n const isSanityRoot = workDir === cwd\n\n const [author, gitRemote, description] = await Promise.all([\n getUserInfo(),\n isPlugin && isSanityRoot ? undefined : getGitRemoteOriginUrl(cwd),\n getProjectDescription({isPlugin, isSanityRoot, outputDir: cwd}),\n ])\n\n return {\n author,\n description,\n gitRemote,\n license: 'UNLICENSED',\n projectName: isPlugin && isSanityRoot ? '' : path.basename(cwd),\n }\n}\n\nasync function getUserInfo(): Promise<string | undefined> {\n const user = await getGitUserInfo()\n if (user) {\n return `${user.name} <${user.email}>`\n }\n\n return getSanityUserInfo()\n}\n\nasync function getSanityUserInfo(): Promise<string | undefined> {\n const hasToken = Boolean(getCliToken())\n if (!hasToken) {\n return undefined\n }\n\n try {\n const user = await getCliUser()\n if (!user) return undefined\n // API tokens resolve to a user with no email; `Name <null>` isn't valid\n // npm author syntax, so emit the bare name instead.\n return user.email ? `${user.name} <${user.email}>` : user.name\n } catch {\n return undefined\n }\n}\n\nasync function getProjectDescription({\n isPlugin,\n isSanityRoot,\n outputDir,\n}: {\n isPlugin: boolean\n isSanityRoot: boolean\n outputDir: string\n}): Promise<string> {\n const tryResolve = isSanityRoot && !isPlugin\n if (!tryResolve) {\n return ''\n }\n\n // Try to grab a project description from a standard GitHub-generated readme\n try {\n const readmePath = path.join(outputDir, 'README.md')\n const readme = await fs.readFile(readmePath, {encoding: 'utf8'})\n const match = readme.match(/^# .*?\\n+(\\w.*?)(?:$|\\n)/)\n return ((match && match[1]) || '').replace(/\\.$/, '') || ''\n } catch (err) {\n debug(`Error getting project description: ${err}`)\n return ''\n }\n}\n"],"names":["fs","path","getCliToken","subdebug","getCliUser","getGitRemoteOriginUrl","getGitUserInfo","debug","getProjectDefaults","isPlugin","workDir","cwd","process","isSanityRoot","author","gitRemote","description","Promise","all","getUserInfo","undefined","getProjectDescription","outputDir","license","projectName","basename","user","name","email","getSanityUserInfo","hasToken","Boolean","tryResolve","readmePath","join","readme","readFile","encoding","match","replace","err"],"mappings":"AAAA,OAAOA,QAAQ,mBAAkB;AACjC,OAAOC,UAAU,YAAW;AAE5B,SAAQC,WAAW,EAAEC,QAAQ,QAAO,mBAAkB;AAEtD,SAAQC,UAAU,QAAO,sBAAqB;AAC9C,SAAQC,qBAAqB,EAAEC,cAAc,QAAO,iBAAgB;AAEpE,MAAMC,QAAQJ,SAAS;AAUvB;;;;;;CAMC,GACD,OAAO,eAAeK,mBAAmB,EACvCC,QAAQ,EACRC,OAAO,EAIR;IACC,MAAMC,MAAMC,QAAQD,GAAG;IACvB,MAAME,eAAeH,YAAYC;IAEjC,MAAM,CAACG,QAAQC,WAAWC,YAAY,GAAG,MAAMC,QAAQC,GAAG,CAAC;QACzDC;QACAV,YAAYI,eAAeO,YAAYf,sBAAsBM;QAC7DU,sBAAsB;YAACZ;YAAUI;YAAcS,WAAWX;QAAG;KAC9D;IAED,OAAO;QACLG;QACAE;QACAD;QACAQ,SAAS;QACTC,aAAaf,YAAYI,eAAe,KAAKZ,KAAKwB,QAAQ,CAACd;IAC7D;AACF;AAEA,eAAeQ;IACb,MAAMO,OAAO,MAAMpB;IACnB,IAAIoB,MAAM;QACR,OAAO,GAAGA,KAAKC,IAAI,CAAC,EAAE,EAAED,KAAKE,KAAK,CAAC,CAAC,CAAC;IACvC;IAEA,OAAOC;AACT;AAEA,eAAeA;IACb,MAAMC,WAAWC,QAAQ7B;IACzB,IAAI,CAAC4B,UAAU;QACb,OAAOV;IACT;IAEA,IAAI;QACF,MAAMM,OAAO,MAAMtB;QACnB,IAAI,CAACsB,MAAM,OAAON;QAClB,wEAAwE;QACxE,oDAAoD;QACpD,OAAOM,KAAKE,KAAK,GAAG,GAAGF,KAAKC,IAAI,CAAC,EAAE,EAAED,KAAKE,KAAK,CAAC,CAAC,CAAC,GAAGF,KAAKC,IAAI;IAChE,EAAE,OAAM;QACN,OAAOP;IACT;AACF;AAEA,eAAeC,sBAAsB,EACnCZ,QAAQ,EACRI,YAAY,EACZS,SAAS,EAKV;IACC,MAAMU,aAAanB,gBAAgB,CAACJ;IACpC,IAAI,CAACuB,YAAY;QACf,OAAO;IACT;IAEA,4EAA4E;IAC5E,IAAI;QACF,MAAMC,aAAahC,KAAKiC,IAAI,CAACZ,WAAW;QACxC,MAAMa,SAAS,MAAMnC,GAAGoC,QAAQ,CAACH,YAAY;YAACI,UAAU;QAAM;QAC9D,MAAMC,QAAQH,OAAOG,KAAK,CAAC;QAC3B,OAAO,AAAC,CAAA,AAACA,SAASA,KAAK,CAAC,EAAE,IAAK,EAAC,EAAGC,OAAO,CAAC,OAAO,OAAO;IAC3D,EAAE,OAAOC,KAAK;QACZjC,MAAM,CAAC,mCAAmC,EAAEiC,KAAK;QACjD,OAAO;IACT;AACF"}
@@ -5,23 +5,62 @@ import path from 'node:path';
5
5
  import picomatch from 'picomatch';
6
6
  import { toForwardSlashes } from '../toForwardSlashes.js';
7
7
  /**
8
- * Detects the preferred package manager for a project by examining lock files,
8
+ * Detects the preferred package manager for a project by examining the corepack
9
+ * `packageManager` field, the `devEngines.packageManager` declaration, lock files,
9
10
  * workspace configurations, and node_modules markers.
10
11
  */ export function preferredPm(pkgPath) {
12
+ const fromPackageManagerField = detectFromPackageManagerField(pkgPath);
13
+ if (fromPackageManagerField) return fromPackageManagerField;
11
14
  const fromLockFile = detectFromLockFile(pkgPath);
12
15
  if (fromLockFile) return fromLockFile;
13
16
  const fromParentPnpm = findUp('pnpm-lock.yaml', pkgPath) || findUp('pnpm-workspace.yaml', pkgPath);
14
- if (fromParentPnpm) return 'pnpm';
17
+ if (fromParentPnpm) {
18
+ return detectFromPackageManagerField(path.dirname(fromParentPnpm)) ?? 'pnpm';
19
+ }
15
20
  const fromWorkspace = detectFromWorkspaceRoot(pkgPath);
16
21
  if (fromWorkspace) return fromWorkspace;
17
22
  return detectFromNodeModules(pkgPath);
18
23
  }
24
+ /**
25
+ * Detects the package manager from the corepack `packageManager` field in the
26
+ * package.json at `dir`, e.g. `"pnpm@9.1.2"` or `"yarn@4.1.0+sha224.…"`, falling
27
+ * back to the `devEngines.packageManager` declaration.
28
+ */ function detectFromPackageManagerField(dir) {
29
+ try {
30
+ const manifest = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
31
+ const packageManager = manifest?.packageManager;
32
+ if (typeof packageManager === 'string') {
33
+ const [tool] = packageManager.split('@', 1);
34
+ if (tool === 'npm' || tool === 'pnpm' || tool === 'yarn' || tool === 'bun') return tool;
35
+ }
36
+ return detectFromDevEngines(manifest?.devEngines);
37
+ } catch {
38
+ // missing or malformed package.json — fall through to other detection strategies
39
+ return null;
40
+ }
41
+ }
42
+ /**
43
+ * Detects the package manager from a `devEngines.packageManager` declaration
44
+ * (https://docs.npmjs.com/cli/configuring-npm/package-json#devengines).
45
+ * The field is an object (`{name: 'pnpm', …}`) or an array of such objects,
46
+ * where arrays represent alternatives rather than an order of preference. A
47
+ * single-entry array is unambiguous; arrays with multiple entries are ignored.
48
+ * Malformed shapes and unknown names are also ignored.
49
+ */ function detectFromDevEngines(devEngines) {
50
+ if (!devEngines || typeof devEngines !== 'object') return null;
51
+ const packageManager = devEngines.packageManager;
52
+ if (Array.isArray(packageManager) && packageManager.length !== 1) return null;
53
+ const entry = Array.isArray(packageManager) ? packageManager[0] : packageManager;
54
+ if (!entry || typeof entry !== 'object') return null;
55
+ const name = entry.name;
56
+ if (name === 'npm' || name === 'pnpm' || name === 'yarn' || name === 'bun') return name;
57
+ return null;
58
+ }
19
59
  function detectFromLockFile(dir) {
20
- if (fs.existsSync(path.join(dir, 'package-lock.json'))) return 'npm';
21
60
  if (fs.existsSync(path.join(dir, 'yarn.lock'))) return 'yarn';
22
- if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) return 'pnpm';
23
- if (fs.existsSync(path.join(dir, 'shrinkwrap.yaml'))) return 'pnpm';
61
+ if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml')) || fs.existsSync(path.join(dir, 'shrinkwrap.yaml'))) return 'pnpm';
24
62
  if (fs.existsSync(path.join(dir, 'bun.lockb')) || fs.existsSync(path.join(dir, 'bun.lock'))) return 'bun';
63
+ if (fs.existsSync(path.join(dir, 'package-lock.json')) || fs.existsSync(path.join(dir, 'npm-shrinkwrap.json'))) return 'npm';
25
64
  return null;
26
65
  }
27
66
  function findUp(filename, startDir) {
@@ -59,7 +98,7 @@ function detectFromWorkspaceRoot(pkgPath) {
59
98
  const matchDir = packageDir === dir ? resolvedPkgPath : packageDir;
60
99
  const relativePath = toForwardSlashes(path.relative(dir, matchDir));
61
100
  if (relativePath === '' || picomatch.isMatch(relativePath, workspaces)) {
62
- return detectFromLockFile(dir) ?? 'yarn';
101
+ return detectFromPackageManagerField(dir) ?? detectFromLockFile(dir) ?? 'yarn';
63
102
  }
64
103
  }
65
104
  } catch {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/util/packageManager/preferredPm.ts"],"sourcesContent":["// Based on preferred-pm (MIT) by Zoltan Kochan — https://github.com/zkochan/packages\n// Based on which-pm (MIT) by pnpm — https://github.com/pnpm/which-pm\n\nimport fs from 'node:fs'\nimport path from 'node:path'\n\nimport picomatch from 'picomatch'\n\nimport {toForwardSlashes} from '../toForwardSlashes.js'\n\ntype DetectablePackageManager = 'bun' | 'npm' | 'pnpm' | 'yarn'\n\n/**\n * Detects the preferred package manager for a project by examining lock files,\n * workspace configurations, and node_modules markers.\n */\nexport function preferredPm(pkgPath: string): DetectablePackageManager | null {\n const fromLockFile = detectFromLockFile(pkgPath)\n if (fromLockFile) return fromLockFile\n\n const fromParentPnpm = findUp('pnpm-lock.yaml', pkgPath) || findUp('pnpm-workspace.yaml', pkgPath)\n if (fromParentPnpm) return 'pnpm'\n\n const fromWorkspace = detectFromWorkspaceRoot(pkgPath)\n if (fromWorkspace) return fromWorkspace\n\n return detectFromNodeModules(pkgPath)\n}\n\nfunction detectFromLockFile(dir: string): DetectablePackageManager | null {\n if (fs.existsSync(path.join(dir, 'package-lock.json'))) return 'npm'\n if (fs.existsSync(path.join(dir, 'yarn.lock'))) return 'yarn'\n if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) return 'pnpm'\n if (fs.existsSync(path.join(dir, 'shrinkwrap.yaml'))) return 'pnpm'\n if (fs.existsSync(path.join(dir, 'bun.lockb')) || fs.existsSync(path.join(dir, 'bun.lock')))\n return 'bun'\n return null\n}\n\nfunction findUp(filename: string, startDir: string): string | undefined {\n let dir = path.resolve(startDir)\n const {root} = path.parse(dir)\n\n while (dir) {\n const filePath = path.join(dir, filename)\n if (fs.existsSync(filePath)) return filePath\n if (dir === root) break\n dir = path.dirname(dir)\n }\n\n return undefined\n}\n\nfunction findNearestPackageDir(startDir: string): string {\n let dir = path.resolve(startDir)\n const {root} = path.parse(dir)\n\n while (dir !== root) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return dir\n dir = path.dirname(dir)\n }\n\n return startDir\n}\n\nfunction detectFromWorkspaceRoot(pkgPath: string): DetectablePackageManager | null {\n const resolvedPkgPath = path.resolve(pkgPath)\n const packageDir = findNearestPackageDir(resolvedPkgPath)\n let dir = resolvedPkgPath\n const {root} = path.parse(dir)\n\n while (true) {\n const manifestPath = path.join(dir, 'package.json')\n if (fs.existsSync(manifestPath)) {\n try {\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))\n const workspaces = extractWorkspaces(manifest)\n\n if (workspaces) {\n const matchDir = packageDir === dir ? resolvedPkgPath : packageDir\n const relativePath = toForwardSlashes(path.relative(dir, matchDir))\n if (relativePath === '' || picomatch.isMatch(relativePath, workspaces)) {\n return detectFromLockFile(dir) ?? 'yarn'\n }\n }\n } catch {\n // malformed package.json, skip\n }\n }\n if (dir === root) break\n dir = path.dirname(dir)\n }\n\n return null\n}\n\nfunction extractWorkspaces(manifest: Record<string, unknown>): string[] | null {\n const workspaces = manifest?.workspaces\n if (Array.isArray(workspaces)) return workspaces\n if (\n workspaces &&\n typeof workspaces === 'object' &&\n 'packages' in workspaces &&\n Array.isArray((workspaces as Record<string, unknown>).packages)\n ) {\n return (workspaces as Record<string, unknown>).packages as string[]\n }\n return null\n}\n\nfunction detectFromNodeModules(pkgPath: string): DetectablePackageManager | null {\n const modulesPath = path.join(pkgPath, 'node_modules')\n\n if (fs.existsSync(path.join(modulesPath, '.yarn-integrity'))) return 'yarn'\n\n try {\n const modulesYaml = fs.readFileSync(path.join(modulesPath, '.modules.yaml'), 'utf8')\n const pmLine = modulesYaml\n .split('\\n')\n .find((line) => /^\\s*\"?packageManager\"?\\s*[:=]/.test(line))\n if (pmLine) {\n const valueMatch = pmLine.match(/[:=]\\s*['\"]?([^'\",\\s]+)/)\n if (valueMatch) {\n const pmSpec = valueMatch[1]\n const name = pmSpec.startsWith('@')\n ? `@${pmSpec.slice(1).split('@')[0]}`\n : pmSpec.split('@')[0]\n if (name === 'pnpm') return 'pnpm'\n if (name === 'yarn') return 'yarn'\n if (name === 'npm') return 'npm'\n if (name === 'bun') return 'bun'\n }\n }\n } catch {\n // best-effort detection — swallow all read errors\n }\n\n if (fs.existsSync(modulesPath)) return 'npm'\n\n return null\n}\n"],"names":["fs","path","picomatch","toForwardSlashes","preferredPm","pkgPath","fromLockFile","detectFromLockFile","fromParentPnpm","findUp","fromWorkspace","detectFromWorkspaceRoot","detectFromNodeModules","dir","existsSync","join","filename","startDir","resolve","root","parse","filePath","dirname","undefined","findNearestPackageDir","resolvedPkgPath","packageDir","manifestPath","manifest","JSON","readFileSync","workspaces","extractWorkspaces","matchDir","relativePath","relative","isMatch","Array","isArray","packages","modulesPath","modulesYaml","pmLine","split","find","line","test","valueMatch","match","pmSpec","name","startsWith","slice"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/util/packageManager/preferredPm.ts"],"sourcesContent":["// Based on preferred-pm (MIT) by Zoltan Kochan — https://github.com/zkochan/packages\n// Based on which-pm (MIT) by pnpm — https://github.com/pnpm/which-pm\n\nimport fs from 'node:fs'\nimport path from 'node:path'\n\nimport picomatch from 'picomatch'\n\nimport {toForwardSlashes} from '../toForwardSlashes.js'\n\ntype DetectablePackageManager = 'bun' | 'npm' | 'pnpm' | 'yarn'\n\n/**\n * Detects the preferred package manager for a project by examining the corepack\n * `packageManager` field, the `devEngines.packageManager` declaration, lock files,\n * workspace configurations, and node_modules markers.\n */\nexport function preferredPm(pkgPath: string): DetectablePackageManager | null {\n const fromPackageManagerField = detectFromPackageManagerField(pkgPath)\n if (fromPackageManagerField) return fromPackageManagerField\n\n const fromLockFile = detectFromLockFile(pkgPath)\n if (fromLockFile) return fromLockFile\n\n const fromParentPnpm = findUp('pnpm-lock.yaml', pkgPath) || findUp('pnpm-workspace.yaml', pkgPath)\n if (fromParentPnpm) {\n return detectFromPackageManagerField(path.dirname(fromParentPnpm)) ?? 'pnpm'\n }\n\n const fromWorkspace = detectFromWorkspaceRoot(pkgPath)\n if (fromWorkspace) return fromWorkspace\n\n return detectFromNodeModules(pkgPath)\n}\n\n/**\n * Detects the package manager from the corepack `packageManager` field in the\n * package.json at `dir`, e.g. `\"pnpm@9.1.2\"` or `\"yarn@4.1.0+sha224.…\"`, falling\n * back to the `devEngines.packageManager` declaration.\n */\nfunction detectFromPackageManagerField(dir: string): DetectablePackageManager | null {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'))\n const packageManager = manifest?.packageManager\n if (typeof packageManager === 'string') {\n const [tool] = packageManager.split('@', 1)\n if (tool === 'npm' || tool === 'pnpm' || tool === 'yarn' || tool === 'bun') return tool\n }\n return detectFromDevEngines(manifest?.devEngines)\n } catch {\n // missing or malformed package.json — fall through to other detection strategies\n return null\n }\n}\n\n/**\n * Detects the package manager from a `devEngines.packageManager` declaration\n * (https://docs.npmjs.com/cli/configuring-npm/package-json#devengines).\n * The field is an object (`{name: 'pnpm', …}`) or an array of such objects,\n * where arrays represent alternatives rather than an order of preference. A\n * single-entry array is unambiguous; arrays with multiple entries are ignored.\n * Malformed shapes and unknown names are also ignored.\n */\nfunction detectFromDevEngines(devEngines: unknown): DetectablePackageManager | null {\n if (!devEngines || typeof devEngines !== 'object') return null\n const packageManager = (devEngines as Record<string, unknown>).packageManager\n if (Array.isArray(packageManager) && packageManager.length !== 1) return null\n const entry = Array.isArray(packageManager) ? packageManager[0] : packageManager\n if (!entry || typeof entry !== 'object') return null\n const name = (entry as Record<string, unknown>).name\n if (name === 'npm' || name === 'pnpm' || name === 'yarn' || name === 'bun') return name\n return null\n}\n\nfunction detectFromLockFile(dir: string): DetectablePackageManager | null {\n if (fs.existsSync(path.join(dir, 'yarn.lock'))) return 'yarn'\n if (\n fs.existsSync(path.join(dir, 'pnpm-lock.yaml')) ||\n fs.existsSync(path.join(dir, 'shrinkwrap.yaml'))\n )\n return 'pnpm'\n if (fs.existsSync(path.join(dir, 'bun.lockb')) || fs.existsSync(path.join(dir, 'bun.lock')))\n return 'bun'\n if (\n fs.existsSync(path.join(dir, 'package-lock.json')) ||\n fs.existsSync(path.join(dir, 'npm-shrinkwrap.json'))\n )\n return 'npm'\n return null\n}\n\nfunction findUp(filename: string, startDir: string): string | undefined {\n let dir = path.resolve(startDir)\n const {root} = path.parse(dir)\n\n while (dir) {\n const filePath = path.join(dir, filename)\n if (fs.existsSync(filePath)) return filePath\n if (dir === root) break\n dir = path.dirname(dir)\n }\n\n return undefined\n}\n\nfunction findNearestPackageDir(startDir: string): string {\n let dir = path.resolve(startDir)\n const {root} = path.parse(dir)\n\n while (dir !== root) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return dir\n dir = path.dirname(dir)\n }\n\n return startDir\n}\n\nfunction detectFromWorkspaceRoot(pkgPath: string): DetectablePackageManager | null {\n const resolvedPkgPath = path.resolve(pkgPath)\n const packageDir = findNearestPackageDir(resolvedPkgPath)\n let dir = resolvedPkgPath\n const {root} = path.parse(dir)\n\n while (true) {\n const manifestPath = path.join(dir, 'package.json')\n if (fs.existsSync(manifestPath)) {\n try {\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))\n const workspaces = extractWorkspaces(manifest)\n\n if (workspaces) {\n const matchDir = packageDir === dir ? resolvedPkgPath : packageDir\n const relativePath = toForwardSlashes(path.relative(dir, matchDir))\n if (relativePath === '' || picomatch.isMatch(relativePath, workspaces)) {\n return detectFromPackageManagerField(dir) ?? detectFromLockFile(dir) ?? 'yarn'\n }\n }\n } catch {\n // malformed package.json, skip\n }\n }\n if (dir === root) break\n dir = path.dirname(dir)\n }\n\n return null\n}\n\nfunction extractWorkspaces(manifest: Record<string, unknown>): string[] | null {\n const workspaces = manifest?.workspaces\n if (Array.isArray(workspaces)) return workspaces\n if (\n workspaces &&\n typeof workspaces === 'object' &&\n 'packages' in workspaces &&\n Array.isArray((workspaces as Record<string, unknown>).packages)\n ) {\n return (workspaces as Record<string, unknown>).packages as string[]\n }\n return null\n}\n\nfunction detectFromNodeModules(pkgPath: string): DetectablePackageManager | null {\n const modulesPath = path.join(pkgPath, 'node_modules')\n\n if (fs.existsSync(path.join(modulesPath, '.yarn-integrity'))) return 'yarn'\n\n try {\n const modulesYaml = fs.readFileSync(path.join(modulesPath, '.modules.yaml'), 'utf8')\n const pmLine = modulesYaml\n .split('\\n')\n .find((line) => /^\\s*\"?packageManager\"?\\s*[:=]/.test(line))\n if (pmLine) {\n const valueMatch = pmLine.match(/[:=]\\s*['\"]?([^'\",\\s]+)/)\n if (valueMatch) {\n const pmSpec = valueMatch[1]\n const name = pmSpec.startsWith('@')\n ? `@${pmSpec.slice(1).split('@')[0]}`\n : pmSpec.split('@')[0]\n if (name === 'pnpm') return 'pnpm'\n if (name === 'yarn') return 'yarn'\n if (name === 'npm') return 'npm'\n if (name === 'bun') return 'bun'\n }\n }\n } catch {\n // best-effort detection — swallow all read errors\n }\n\n if (fs.existsSync(modulesPath)) return 'npm'\n\n return null\n}\n"],"names":["fs","path","picomatch","toForwardSlashes","preferredPm","pkgPath","fromPackageManagerField","detectFromPackageManagerField","fromLockFile","detectFromLockFile","fromParentPnpm","findUp","dirname","fromWorkspace","detectFromWorkspaceRoot","detectFromNodeModules","dir","manifest","JSON","parse","readFileSync","join","packageManager","tool","split","detectFromDevEngines","devEngines","Array","isArray","length","entry","name","existsSync","filename","startDir","resolve","root","filePath","undefined","findNearestPackageDir","resolvedPkgPath","packageDir","manifestPath","workspaces","extractWorkspaces","matchDir","relativePath","relative","isMatch","packages","modulesPath","modulesYaml","pmLine","find","line","test","valueMatch","match","pmSpec","startsWith","slice"],"mappings":"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"}
@@ -5830,5 +5830,5 @@
5830
5830
  ]
5831
5831
  }
5832
5832
  },
5833
- "version": "7.13.0"
5833
+ "version": "7.14.0"
5834
5834
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/cli",
3
- "version": "7.13.0",
3
+ "version": "7.14.0",
4
4
  "description": "Sanity CLI tool for managing Sanity projects and organizations",
5
5
  "keywords": [
6
6
  "cli",
@@ -45,6 +45,10 @@
45
45
  "source": "./src/exports/_internal.ts",
46
46
  "default": "./dist/exports/_internal.js"
47
47
  },
48
+ "./invokeSanityCli": {
49
+ "source": "./src/exports/invokeSanityCli/index.ts",
50
+ "default": "./dist/exports/invokeSanityCli/index.js"
51
+ },
48
52
  "./runtime": {
49
53
  "source": "./src/exports/runtime.ts",
50
54
  "default": "./dist/exports/runtime.js"
@@ -108,6 +112,7 @@
108
112
  "semver": "^7.8.1",
109
113
  "skills": "^1.5.9",
110
114
  "smol-toml": "^1.6.1",
115
+ "string-argv": "^0.3.2",
111
116
  "tar": "^7.5.15",
112
117
  "tar-fs": "^3.1.2",
113
118
  "tar-stream": "^3.2.0",
@@ -119,8 +124,8 @@
119
124
  "yaml": "^2.9.0",
120
125
  "zod": "^4.4.3",
121
126
  "@sanity/cli-build": "^5.0.0",
122
- "@sanity/workbench-cli": "^1.7.1",
123
- "@sanity/cli-core": "^2.5.1"
127
+ "@sanity/cli-core": "^2.6.0",
128
+ "@sanity/workbench-cli": "^1.7.2"
124
129
  },
125
130
  "devDependencies": {
126
131
  "@eslint/compat": "^2.1.0",
@@ -155,7 +160,7 @@
155
160
  "vitest": "^4.1.10",
156
161
  "@repo/tsconfig": "3.70.0",
157
162
  "@repo/package.config": "0.0.1",
158
- "@sanity/cli-test": "6.0.1",
163
+ "@sanity/cli-test": "7.0.0",
159
164
  "@sanity/eslint-config-cli": "1.1.3"
160
165
  },
161
166
  "peerDependencies": {