@sanity/cli 7.12.1 → 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 (63) hide show
  1. package/README.md +88 -0
  2. package/dist/actions/api/constants.js +13 -0
  3. package/dist/actions/api/constants.js.map +1 -0
  4. package/dist/actions/api/distillApiRoutes.js +106 -0
  5. package/dist/actions/api/distillApiRoutes.js.map +1 -0
  6. package/dist/actions/api/errors.js +18 -0
  7. package/dist/actions/api/errors.js.map +1 -0
  8. package/dist/actions/api/parseFields.js +183 -0
  9. package/dist/actions/api/parseFields.js.map +1 -0
  10. package/dist/actions/api/resolveEndpoint.js +159 -0
  11. package/dist/actions/api/resolveEndpoint.js.map +1 -0
  12. package/dist/actions/api/types.js +10 -0
  13. package/dist/actions/api/types.js.map +1 -0
  14. package/dist/actions/auth/getProviderName.js +13 -0
  15. package/dist/actions/auth/getProviderName.js.map +1 -1
  16. package/dist/actions/build/buildApp.js +3 -1
  17. package/dist/actions/build/buildApp.js.map +1 -1
  18. package/dist/actions/build/buildStudio.js +104 -13
  19. package/dist/actions/build/buildStudio.js.map +1 -1
  20. package/dist/actions/build/eventListenerFactory.js +70 -0
  21. package/dist/actions/build/eventListenerFactory.js.map +1 -0
  22. package/dist/actions/debug/types.js.map +1 -1
  23. package/dist/actions/deploy/deployApp.js +1 -0
  24. package/dist/actions/deploy/deployApp.js.map +1 -1
  25. package/dist/actions/deploy/deployChecks.js +25 -4
  26. package/dist/actions/deploy/deployChecks.js.map +1 -1
  27. package/dist/actions/deploy/resolveDeployTarget.js +23 -4
  28. package/dist/actions/deploy/resolveDeployTarget.js.map +1 -1
  29. package/dist/actions/dev/servers/startStudioDevServer.js +5 -1
  30. package/dist/actions/dev/servers/startStudioDevServer.js.map +1 -1
  31. package/dist/actions/init/initAction.js +3 -3
  32. package/dist/actions/init/initAction.js.map +1 -1
  33. package/dist/commands/api.js +294 -0
  34. package/dist/commands/api.js.map +1 -0
  35. package/dist/commands/cors/add.js +13 -7
  36. package/dist/commands/cors/add.js.map +1 -1
  37. package/dist/commands/datasets/copy.js +6 -3
  38. package/dist/commands/datasets/copy.js.map +1 -1
  39. package/dist/commands/debug.js +1 -1
  40. package/dist/commands/debug.js.map +1 -1
  41. package/dist/exports/invokeSanityCli/commandPolicies/index.js +6 -0
  42. package/dist/exports/invokeSanityCli/commandPolicies/index.js.map +1 -0
  43. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +167 -0
  44. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -0
  45. package/dist/exports/invokeSanityCli/commandPolicies/policy.js +46 -0
  46. package/dist/exports/invokeSanityCli/commandPolicies/policy.js.map +1 -0
  47. package/dist/exports/invokeSanityCli/help.js +146 -0
  48. package/dist/exports/invokeSanityCli/help.js.map +1 -0
  49. package/dist/exports/invokeSanityCli/index.d.ts +60 -0
  50. package/dist/exports/invokeSanityCli/index.js +177 -0
  51. package/dist/exports/invokeSanityCli/index.js.map +1 -0
  52. package/dist/generated/apiRoutes.js +401 -0
  53. package/dist/generated/apiRoutes.js.map +1 -0
  54. package/dist/server/devServer.js +16 -2
  55. package/dist/server/devServer.js.map +1 -1
  56. package/dist/services/api.js +116 -0
  57. package/dist/services/api.js.map +1 -0
  58. package/dist/util/getProjectDefaults.js +4 -1
  59. package/dist/util/getProjectDefaults.js.map +1 -1
  60. package/dist/util/packageManager/preferredPm.js +45 -6
  61. package/dist/util/packageManager/preferredPm.js.map +1 -1
  62. package/oclif.manifest.json +709 -528
  63. package/package.json +14 -7
@@ -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"}
@@ -0,0 +1,116 @@
1
+ import { getCliToken, getGlobalCliClient, getProjectCliClient } from '@sanity/cli-core';
2
+ import { createRequester } from '@sanity/cli-core/request';
3
+ const API_REQUEST_TAG = 'sanity.cli.api';
4
+ /**
5
+ * Perform an authenticated raw HTTP request against a Sanity API.
6
+ *
7
+ * URL construction and token resolution reuse the standard CLI client
8
+ * factories; the request itself uses a bare requester so the response
9
+ * (status, headers, non-JSON bodies) can be passed through unfiltered.
10
+ */ export async function performApiRequest(options) {
11
+ const { body, headers = {}, method, query = {}, resolved, token: providedToken, unauthenticated = false } = options;
12
+ const { token, url } = await resolveRequestTarget(resolved, unauthenticated, providedToken);
13
+ // Header names are case-insensitive - keyed lowercase so a user-provided
14
+ // header (eg `-H 'authorization: ...'`) replaces the default regardless of
15
+ // how either side is cased.
16
+ const requestHeaders = {};
17
+ if (token) requestHeaders.authorization = `Bearer ${token}`;
18
+ let requestBody;
19
+ if (typeof body === 'string' || Buffer.isBuffer(body)) {
20
+ // Raw bodies (--input) pass through without a default Content-Type - the
21
+ // caller knows the media type, we don't (gh api behaves the same way)
22
+ requestBody = body;
23
+ } else if (body !== undefined) {
24
+ requestBody = JSON.stringify(body);
25
+ requestHeaders['content-type'] = 'application/json';
26
+ }
27
+ for (const [key, value] of Object.entries(headers)){
28
+ requestHeaders[key.toLowerCase()] = value;
29
+ }
30
+ const request = createRequester({
31
+ middleware: {
32
+ httpErrors: false,
33
+ promise: {
34
+ onlyBody: false
35
+ }
36
+ }
37
+ });
38
+ const mergedQuery = {
39
+ ...resolved.query,
40
+ ...query
41
+ };
42
+ const response = await request({
43
+ ...requestBody === undefined ? {} : {
44
+ body: requestBody
45
+ },
46
+ headers: requestHeaders,
47
+ method,
48
+ query: {
49
+ tag: API_REQUEST_TAG,
50
+ ...mergedQuery
51
+ },
52
+ url
53
+ });
54
+ const rawBody = typeof response.body === 'string' ? response.body : String(response.body ?? '');
55
+ const parsed = parseBody(rawBody, response.headers['content-type']);
56
+ return {
57
+ body: parsed.body,
58
+ headers: response.headers,
59
+ jsonBody: parsed.jsonBody,
60
+ rawBody,
61
+ statusCode: response.statusCode,
62
+ statusMessage: response.statusMessage,
63
+ url: response.url
64
+ };
65
+ }
66
+ async function resolveRequestTarget(resolved, unauthenticated, providedToken) {
67
+ if (resolved.kind === 'url') {
68
+ const token = unauthenticated ? undefined : providedToken || await getCliToken();
69
+ if (!token && !unauthenticated) {
70
+ throw new Error('You must login first - run "sanity login"');
71
+ }
72
+ return {
73
+ token,
74
+ url: resolved.url
75
+ };
76
+ }
77
+ // The client factories handle token resolution, staging/production API
78
+ // hosts and the project subdomain - we only use them to build the URL.
79
+ const client = resolved.host === 'project' ? await getProjectCliClient({
80
+ apiVersion: resolved.apiVersion,
81
+ projectId: resolved.projectId,
82
+ requireUser: !unauthenticated,
83
+ token: providedToken
84
+ }) : await getGlobalCliClient({
85
+ apiVersion: resolved.apiVersion,
86
+ requireUser: !unauthenticated,
87
+ token: providedToken,
88
+ unauthenticated
89
+ });
90
+ const token = unauthenticated ? undefined : client.config().token ?? undefined;
91
+ return {
92
+ token,
93
+ url: client.getUrl(resolved.path)
94
+ };
95
+ }
96
+ function parseBody(rawBody, contentType) {
97
+ if (rawBody === '' || !contentType || !/\bjson\b/.test(contentType)) {
98
+ return {
99
+ body: rawBody,
100
+ jsonBody: false
101
+ };
102
+ }
103
+ try {
104
+ return {
105
+ body: JSON.parse(rawBody),
106
+ jsonBody: true
107
+ };
108
+ } catch {
109
+ return {
110
+ body: rawBody,
111
+ jsonBody: false
112
+ };
113
+ }
114
+ }
115
+
116
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/services/api.ts"],"sourcesContent":["import {getCliToken, getGlobalCliClient, getProjectCliClient} from '@sanity/cli-core'\nimport {createRequester} from '@sanity/cli-core/request'\n\nimport {type ResolvedEndpoint} from '../actions/api/resolveEndpoint.js'\n\nconst API_REQUEST_TAG = 'sanity.cli.api'\n\n/**\n * Options for {@link performApiRequest}.\n */\nexport interface PerformApiRequestOptions {\n /** HTTP method (upper case). */\n method: string\n\n /** The resolved request target. */\n resolved: ResolvedEndpoint\n\n /** Request body: objects are JSON-serialized; strings and raw byte buffers are sent verbatim. */\n body?: Buffer | string | unknown\n\n /** Extra request headers. A user-provided `Authorization` header wins over the CLI token. */\n headers?: Record<string, string>\n\n /** Extra query parameters, merged with those from the endpoint argument. */\n query?: Record<string, string | string[]>\n\n /** Explicit API token, used instead of the logged-in user's token. */\n token?: string\n\n /** Send the request without an authorization token. */\n unauthenticated?: boolean\n}\n\n/**\n * The full HTTP response of an API request. Non-2xx responses are returned,\n * not thrown - the command decides how to surface them.\n */\nexport interface ApiResponse {\n /** Parsed JSON body for JSON responses, raw text otherwise. */\n body: unknown\n\n headers: Record<string, string>\n\n /** Whether `body` was parsed from a JSON response. */\n jsonBody: boolean\n\n /** The raw response body text. */\n rawBody: string\n\n statusCode: number\n\n url: string\n\n statusMessage?: string\n}\n\n/**\n * Perform an authenticated raw HTTP request against a Sanity API.\n *\n * URL construction and token resolution reuse the standard CLI client\n * factories; the request itself uses a bare requester so the response\n * (status, headers, non-JSON bodies) can be passed through unfiltered.\n */\nexport async function performApiRequest(options: PerformApiRequestOptions): Promise<ApiResponse> {\n const {\n body,\n headers = {},\n method,\n query = {},\n resolved,\n token: providedToken,\n unauthenticated = false,\n } = options\n\n const {token, url} = await resolveRequestTarget(resolved, unauthenticated, providedToken)\n\n // Header names are case-insensitive - keyed lowercase so a user-provided\n // header (eg `-H 'authorization: ...'`) replaces the default regardless of\n // how either side is cased.\n const requestHeaders: Record<string, string> = {}\n if (token) requestHeaders.authorization = `Bearer ${token}`\n\n let requestBody: Buffer | string | undefined\n if (typeof body === 'string' || Buffer.isBuffer(body)) {\n // Raw bodies (--input) pass through without a default Content-Type - the\n // caller knows the media type, we don't (gh api behaves the same way)\n requestBody = body\n } else if (body !== undefined) {\n requestBody = JSON.stringify(body)\n requestHeaders['content-type'] = 'application/json'\n }\n\n for (const [key, value] of Object.entries(headers)) {\n requestHeaders[key.toLowerCase()] = value\n }\n\n const request = createRequester({\n middleware: {httpErrors: false, promise: {onlyBody: false}},\n })\n\n const mergedQuery = {...resolved.query, ...query}\n\n const response = await request({\n ...(requestBody === undefined ? {} : {body: requestBody}),\n headers: requestHeaders,\n method,\n query: {tag: API_REQUEST_TAG, ...mergedQuery},\n url,\n })\n\n const rawBody = typeof response.body === 'string' ? response.body : String(response.body ?? '')\n const parsed = parseBody(rawBody, response.headers['content-type'])\n\n return {\n body: parsed.body,\n headers: response.headers,\n jsonBody: parsed.jsonBody,\n rawBody,\n statusCode: response.statusCode,\n statusMessage: response.statusMessage,\n url: response.url,\n }\n}\n\nasync function resolveRequestTarget(\n resolved: ResolvedEndpoint,\n unauthenticated: boolean,\n providedToken?: string,\n): Promise<{token?: string; url: string}> {\n if (resolved.kind === 'url') {\n const token = unauthenticated ? undefined : providedToken || (await getCliToken())\n if (!token && !unauthenticated) {\n throw new Error('You must login first - run \"sanity login\"')\n }\n return {token, url: resolved.url}\n }\n\n // The client factories handle token resolution, staging/production API\n // hosts and the project subdomain - we only use them to build the URL.\n const client =\n resolved.host === 'project'\n ? await getProjectCliClient({\n apiVersion: resolved.apiVersion,\n projectId: resolved.projectId as string,\n requireUser: !unauthenticated,\n token: providedToken,\n })\n : await getGlobalCliClient({\n apiVersion: resolved.apiVersion,\n requireUser: !unauthenticated,\n token: providedToken,\n unauthenticated,\n })\n\n const token = unauthenticated ? undefined : (client.config().token ?? undefined)\n\n return {token, url: client.getUrl(resolved.path)}\n}\n\nfunction parseBody(\n rawBody: string,\n contentType: string | undefined,\n): {body: unknown; jsonBody: boolean} {\n if (rawBody === '' || !contentType || !/\\bjson\\b/.test(contentType)) {\n return {body: rawBody, jsonBody: false}\n }\n try {\n return {body: JSON.parse(rawBody), jsonBody: true}\n } catch {\n return {body: rawBody, jsonBody: false}\n }\n}\n"],"names":["getCliToken","getGlobalCliClient","getProjectCliClient","createRequester","API_REQUEST_TAG","performApiRequest","options","body","headers","method","query","resolved","token","providedToken","unauthenticated","url","resolveRequestTarget","requestHeaders","authorization","requestBody","Buffer","isBuffer","undefined","JSON","stringify","key","value","Object","entries","toLowerCase","request","middleware","httpErrors","promise","onlyBody","mergedQuery","response","tag","rawBody","String","parsed","parseBody","jsonBody","statusCode","statusMessage","kind","Error","client","host","apiVersion","projectId","requireUser","config","getUrl","path","contentType","test","parse"],"mappings":"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"}
@@ -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":"AAAA,qFAAqF;AACrF,qEAAqE;AAErE,OAAOA,QAAQ,UAAS;AACxB,OAAOC,UAAU,YAAW;AAE5B,OAAOC,eAAe,YAAW;AAEjC,SAAQC,gBAAgB,QAAO,yBAAwB;AAIvD;;;CAGC,GACD,OAAO,SAASC,YAAYC,OAAe;IACzC,MAAMC,eAAeC,mBAAmBF;IACxC,IAAIC,cAAc,OAAOA;IAEzB,MAAME,iBAAiBC,OAAO,kBAAkBJ,YAAYI,OAAO,uBAAuBJ;IAC1F,IAAIG,gBAAgB,OAAO;IAE3B,MAAME,gBAAgBC,wBAAwBN;IAC9C,IAAIK,eAAe,OAAOA;IAE1B,OAAOE,sBAAsBP;AAC/B;AAEA,SAASE,mBAAmBM,GAAW;IACrC,IAAIb,GAAGc,UAAU,CAACb,KAAKc,IAAI,CAACF,KAAK,uBAAuB,OAAO;IAC/D,IAAIb,GAAGc,UAAU,CAACb,KAAKc,IAAI,CAACF,KAAK,eAAe,OAAO;IACvD,IAAIb,GAAGc,UAAU,CAACb,KAAKc,IAAI,CAACF,KAAK,oBAAoB,OAAO;IAC5D,IAAIb,GAAGc,UAAU,CAACb,KAAKc,IAAI,CAACF,KAAK,qBAAqB,OAAO;IAC7D,IAAIb,GAAGc,UAAU,CAACb,KAAKc,IAAI,CAACF,KAAK,iBAAiBb,GAAGc,UAAU,CAACb,KAAKc,IAAI,CAACF,KAAK,cAC7E,OAAO;IACT,OAAO;AACT;AAEA,SAASJ,OAAOO,QAAgB,EAAEC,QAAgB;IAChD,IAAIJ,MAAMZ,KAAKiB,OAAO,CAACD;IACvB,MAAM,EAACE,IAAI,EAAC,GAAGlB,KAAKmB,KAAK,CAACP;IAE1B,MAAOA,IAAK;QACV,MAAMQ,WAAWpB,KAAKc,IAAI,CAACF,KAAKG;QAChC,IAAIhB,GAAGc,UAAU,CAACO,WAAW,OAAOA;QACpC,IAAIR,QAAQM,MAAM;QAClBN,MAAMZ,KAAKqB,OAAO,CAACT;IACrB;IAEA,OAAOU;AACT;AAEA,SAASC,sBAAsBP,QAAgB;IAC7C,IAAIJ,MAAMZ,KAAKiB,OAAO,CAACD;IACvB,MAAM,EAACE,IAAI,EAAC,GAAGlB,KAAKmB,KAAK,CAACP;IAE1B,MAAOA,QAAQM,KAAM;QACnB,IAAInB,GAAGc,UAAU,CAACb,KAAKc,IAAI,CAACF,KAAK,kBAAkB,OAAOA;QAC1DA,MAAMZ,KAAKqB,OAAO,CAACT;IACrB;IAEA,OAAOI;AACT;AAEA,SAASN,wBAAwBN,OAAe;IAC9C,MAAMoB,kBAAkBxB,KAAKiB,OAAO,CAACb;IACrC,MAAMqB,aAAaF,sBAAsBC;IACzC,IAAIZ,MAAMY;IACV,MAAM,EAACN,IAAI,EAAC,GAAGlB,KAAKmB,KAAK,CAACP;IAE1B,MAAO,KAAM;QACX,MAAMc,eAAe1B,KAAKc,IAAI,CAACF,KAAK;QACpC,IAAIb,GAAGc,UAAU,CAACa,eAAe;YAC/B,IAAI;gBACF,MAAMC,WAAWC,KAAKT,KAAK,CAACpB,GAAG8B,YAAY,CAACH,cAAc;gBAC1D,MAAMI,aAAaC,kBAAkBJ;gBAErC,IAAIG,YAAY;oBACd,MAAME,WAAWP,eAAeb,MAAMY,kBAAkBC;oBACxD,MAAMQ,eAAe/B,iBAAiBF,KAAKkC,QAAQ,CAACtB,KAAKoB;oBACzD,IAAIC,iBAAiB,MAAMhC,UAAUkC,OAAO,CAACF,cAAcH,aAAa;wBACtE,OAAOxB,mBAAmBM,QAAQ;oBACpC;gBACF;YACF,EAAE,OAAM;YACN,+BAA+B;YACjC;QACF;QACA,IAAIA,QAAQM,MAAM;QAClBN,MAAMZ,KAAKqB,OAAO,CAACT;IACrB;IAEA,OAAO;AACT;AAEA,SAASmB,kBAAkBJ,QAAiC;IAC1D,MAAMG,aAAaH,UAAUG;IAC7B,IAAIM,MAAMC,OAAO,CAACP,aAAa,OAAOA;IACtC,IACEA,cACA,OAAOA,eAAe,YACtB,cAAcA,cACdM,MAAMC,OAAO,CAAC,AAACP,WAAuCQ,QAAQ,GAC9D;QACA,OAAO,AAACR,WAAuCQ,QAAQ;IACzD;IACA,OAAO;AACT;AAEA,SAAS3B,sBAAsBP,OAAe;IAC5C,MAAMmC,cAAcvC,KAAKc,IAAI,CAACV,SAAS;IAEvC,IAAIL,GAAGc,UAAU,CAACb,KAAKc,IAAI,CAACyB,aAAa,qBAAqB,OAAO;IAErE,IAAI;QACF,MAAMC,cAAczC,GAAG8B,YAAY,CAAC7B,KAAKc,IAAI,CAACyB,aAAa,kBAAkB;QAC7E,MAAME,SAASD,YACZE,KAAK,CAAC,MACNC,IAAI,CAAC,CAACC,OAAS,gCAAgCC,IAAI,CAACD;QACvD,IAAIH,QAAQ;YACV,MAAMK,aAAaL,OAAOM,KAAK,CAAC;YAChC,IAAID,YAAY;gBACd,MAAME,SAASF,UAAU,CAAC,EAAE;gBAC5B,MAAMG,OAAOD,OAAOE,UAAU,CAAC,OAC3B,CAAC,CAAC,EAAEF,OAAOG,KAAK,CAAC,GAAGT,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,GACnCM,OAAON,KAAK,CAAC,IAAI,CAAC,EAAE;gBACxB,IAAIO,SAAS,QAAQ,OAAO;gBAC5B,IAAIA,SAAS,QAAQ,OAAO;gBAC5B,IAAIA,SAAS,OAAO,OAAO;gBAC3B,IAAIA,SAAS,OAAO,OAAO;YAC7B;QACF;IACF,EAAE,OAAM;IACN,kDAAkD;IACpD;IAEA,IAAIlD,GAAGc,UAAU,CAAC0B,cAAc,OAAO;IAEvC,OAAO;AACT"}
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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"}