@sanity/cli 6.3.2 → 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -18
- package/dist/actions/build/buildApp.js +12 -4
- package/dist/actions/build/buildApp.js.map +1 -1
- package/dist/actions/build/buildStaticFiles.js +3 -1
- package/dist/actions/build/buildStaticFiles.js.map +1 -1
- package/dist/actions/build/buildStudio.js +29 -7
- package/dist/actions/build/buildStudio.js.map +1 -1
- package/dist/actions/build/buildVendorDependencies.js +7 -7
- package/dist/actions/build/buildVendorDependencies.js.map +1 -1
- package/dist/actions/build/checkRequiredDependencies.js +4 -4
- package/dist/actions/build/checkRequiredDependencies.js.map +1 -1
- package/dist/actions/build/checkStudioDependencyVersions.js +9 -9
- package/dist/actions/build/checkStudioDependencyVersions.js.map +1 -1
- package/dist/actions/build/getAutoUpdatesImportMap.js +9 -0
- package/dist/actions/build/getAutoUpdatesImportMap.js.map +1 -1
- package/dist/actions/build/getViteConfig.js +2 -1
- package/dist/actions/build/getViteConfig.js.map +1 -1
- package/dist/actions/build/renderDocument.js.map +1 -1
- package/dist/actions/build/renderDocumentWorker/addTimestampImportMapScriptToHtml.js +34 -14
- package/dist/actions/build/renderDocumentWorker/addTimestampImportMapScriptToHtml.js.map +1 -1
- package/dist/actions/build/renderDocumentWorker/getDocumentHtml.js +2 -2
- package/dist/actions/build/renderDocumentWorker/getDocumentHtml.js.map +1 -1
- package/dist/actions/build/renderDocumentWorker/renderDocumentWorker.js +2 -2
- package/dist/actions/build/renderDocumentWorker/renderDocumentWorker.js.map +1 -1
- package/dist/actions/codemods/reactIconsV3.js +2 -2
- package/dist/actions/codemods/reactIconsV3.js.map +1 -1
- package/dist/actions/dev/startStudioDevServer.js +2 -2
- package/dist/actions/dev/startStudioDevServer.js.map +1 -1
- package/dist/actions/doctor/types.js.map +1 -1
- package/dist/actions/graphql/resolveGraphQLApisFromWorkspaces.js.map +1 -1
- package/dist/actions/init/bootstrapLocalTemplate.js +16 -1
- package/dist/actions/init/bootstrapLocalTemplate.js.map +1 -1
- package/dist/actions/init/bootstrapTemplate.js.map +1 -1
- package/dist/actions/init/checkNextJsReactCompatibility.js +3 -3
- package/dist/actions/init/checkNextJsReactCompatibility.js.map +1 -1
- package/dist/actions/init/initAction.js +287 -0
- package/dist/actions/init/initAction.js.map +1 -0
- package/dist/actions/init/initApp.js +63 -0
- package/dist/actions/init/initApp.js.map +1 -0
- package/dist/actions/init/initError.js +10 -0
- package/dist/actions/init/initError.js.map +1 -0
- package/dist/actions/init/initHelpers.js +28 -0
- package/dist/actions/init/initHelpers.js.map +1 -0
- package/dist/actions/init/initNextJs.js +243 -0
- package/dist/actions/init/initNextJs.js.map +1 -0
- package/dist/actions/init/initStudio.js +118 -0
- package/dist/actions/init/initStudio.js.map +1 -0
- package/dist/actions/init/plan/getPlan.js +15 -0
- package/dist/actions/init/plan/getPlan.js.map +1 -0
- package/dist/actions/init/plan/verifyCoupon.js +35 -0
- package/dist/actions/init/plan/verifyCoupon.js.map +1 -0
- package/dist/actions/init/plan/verifyPlan.js +34 -0
- package/dist/actions/init/plan/verifyPlan.js.map +1 -0
- package/dist/actions/init/project/createProjectFromName.js +44 -0
- package/dist/actions/init/project/createProjectFromName.js.map +1 -0
- package/dist/actions/init/project/getOrCreateDataset.js +126 -0
- package/dist/actions/init/project/getOrCreateDataset.js.map +1 -0
- package/dist/actions/init/project/getOrCreateProject.js +128 -0
- package/dist/actions/init/project/getOrCreateProject.js.map +1 -0
- package/dist/actions/init/project/getProjectDetails.js +87 -0
- package/dist/actions/init/project/getProjectDetails.js.map +1 -0
- package/dist/actions/init/project/getProjectOutputPath.js +17 -0
- package/dist/actions/init/project/getProjectOutputPath.js.map +1 -0
- package/dist/actions/init/project/promptForAppTemplateSetup.js +112 -0
- package/dist/actions/init/project/promptForAppTemplateSetup.js.map +1 -0
- package/dist/actions/init/project/promptForProjectCreation.js +40 -0
- package/dist/actions/init/project/promptForProjectCreation.js.map +1 -0
- package/dist/actions/init/project/promptUserForNewOrganization.js +12 -0
- package/dist/actions/init/project/promptUserForNewOrganization.js.map +1 -0
- package/dist/actions/init/project/promptUserForOrganization.js +38 -0
- package/dist/actions/init/project/promptUserForOrganization.js.map +1 -0
- package/dist/actions/init/scaffoldTemplate.js +108 -0
- package/dist/actions/init/scaffoldTemplate.js.map +1 -0
- package/dist/actions/init/templates/appQuickstart.js +2 -1
- package/dist/actions/init/templates/appQuickstart.js.map +1 -1
- package/dist/actions/init/templates/appSanityUi.js +2 -1
- package/dist/actions/init/templates/appSanityUi.js.map +1 -1
- package/dist/actions/init/templates/shopify.js +6 -6
- package/dist/actions/init/templates/shopify.js.map +1 -1
- package/dist/actions/init/templates/shopifyOnline.js +2 -2
- package/dist/actions/init/templates/shopifyOnline.js.map +1 -1
- package/dist/actions/init/types.js +47 -1
- package/dist/actions/init/types.js.map +1 -1
- package/dist/actions/manifest/types.js +0 -1
- package/dist/actions/manifest/types.js.map +1 -1
- package/dist/actions/mcp/detectAvailableEditors.js +16 -3
- package/dist/actions/mcp/detectAvailableEditors.js.map +1 -1
- package/dist/actions/mcp/editorConfigs.js +192 -132
- package/dist/actions/mcp/editorConfigs.js.map +1 -1
- package/dist/actions/mcp/setupMCP.js +4 -1
- package/dist/actions/mcp/setupMCP.js.map +1 -1
- package/dist/actions/mcp/writeMCPConfig.js +2 -2
- package/dist/actions/mcp/writeMCPConfig.js.map +1 -1
- package/dist/actions/schema/extractSchema.js +5 -7
- package/dist/actions/schema/extractSchema.js.map +1 -1
- package/dist/actions/versions/buildPackageArray.js +2 -2
- package/dist/actions/versions/buildPackageArray.js.map +1 -1
- package/dist/actions/versions/findSanityModulesVersions.js +3 -3
- package/dist/actions/versions/findSanityModulesVersions.js.map +1 -1
- package/dist/commands/datasets/copy.js +14 -0
- package/dist/commands/datasets/copy.js.map +1 -1
- package/dist/commands/init.js +11 -1244
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mcp/configure.js +1 -1
- package/dist/commands/mcp/configure.js.map +1 -1
- package/dist/hooks/prerun/injectEnvVariables.js +3 -5
- package/dist/hooks/prerun/injectEnvVariables.js.map +1 -1
- package/dist/server/vite/plugin-sanity-build-entries.js +2 -1
- package/dist/server/vite/plugin-sanity-build-entries.js.map +1 -1
- package/dist/services/datasets.js +2 -1
- package/dist/services/datasets.js.map +1 -1
- package/dist/telemetry/init.telemetry.js.map +1 -1
- package/dist/util/compareDependencyVersions.js +4 -4
- package/dist/util/compareDependencyVersions.js.map +1 -1
- package/dist/util/createExpiringConfig.js +1 -1
- package/dist/util/createExpiringConfig.js.map +1 -1
- package/dist/util/packageManager/installationInfo/analyzeIssues.js +7 -7
- package/dist/util/packageManager/installationInfo/analyzeIssues.js.map +1 -1
- package/dist/util/packageManager/installationInfo/detectPackages.js +13 -7
- package/dist/util/packageManager/installationInfo/detectPackages.js.map +1 -1
- package/dist/util/packageManager/installationInfo/types.js.map +1 -1
- package/dist/util/packageManager/packageManagerChoice.js +2 -2
- package/dist/util/packageManager/packageManagerChoice.js.map +1 -1
- package/dist/util/packageManager/preferredPm.js +106 -0
- package/dist/util/packageManager/preferredPm.js.map +1 -0
- package/dist/util/update/fetchUpdateInfo.js +40 -0
- package/dist/util/update/fetchUpdateInfo.js.map +1 -0
- package/dist/util/update/fetchUpdateInfo.worker.js +19 -0
- package/dist/util/update/fetchUpdateInfo.worker.js.map +1 -0
- package/dist/util/update/getRunnerUpdateCommand.js +33 -0
- package/dist/util/update/getRunnerUpdateCommand.js.map +1 -0
- package/dist/util/update/getUpdateCommand.js +6 -7
- package/dist/util/update/getUpdateCommand.js.map +1 -1
- package/dist/util/update/packageRunner.js +10 -0
- package/dist/util/update/packageRunner.js.map +1 -0
- package/dist/util/update/resolveRunnerPackage.js +45 -0
- package/dist/util/update/resolveRunnerPackage.js.map +1 -0
- package/dist/util/update/resolveUpdateTarget.js +31 -0
- package/dist/util/update/resolveUpdateTarget.js.map +1 -0
- package/dist/util/update/showNotificationUpdate.js +8 -6
- package/dist/util/update/showNotificationUpdate.js.map +1 -1
- package/dist/util/update/updateChecker.js +73 -38
- package/dist/util/update/updateChecker.js.map +1 -1
- package/oclif.manifest.json +17 -2
- package/package.json +23 -22
- package/templates/app-quickstart/src/App.tsx +2 -2
- package/templates/app-sanity-ui/src/App.tsx +2 -2
- package/templates/shopify/schemaTypes/objects/hotspot/imageWithProductHotspotsType.ts +1 -1
- package/dist/util/update/fetchLatestVersion.js +0 -21
- package/dist/util/update/fetchLatestVersion.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/util/createExpiringConfig.ts"],"sourcesContent":["import {type ConfigStore} from '@sanity/cli-core'\n\ninterface ExpiringConfigValue {\n updatedAt: number\n value: unknown\n}\n\ninterface ExpiringConfigOptions<Type> {\n /** Fetch value */\n fetchValue: () => Promise<Type> | Type\n /** Config key */\n key: string\n /** Config store */\n store: ConfigStore\n /** TTL (milliseconds) */\n ttl: number\n\n /** Subscribe to cache hit event */\n onCacheHit?: () => void\n /** Subscribe to fetch event */\n onFetch?: () => void\n /** Subscribe to revalidate event */\n onRevalidate?: () => void\n\n /**\n * Assert the fetched value is valid, or throw if invalid.\n * If none is provided, it will always accept the fetched value.\n */\n validateValue?: (value: unknown) => value is Type\n}\n\ninterface ExpiringConfigApi<Type> {\n /**\n * Delete the cached value.\n */\n delete: () => void\n /**\n * Attempt to get the cached value. If there is no cached value, or the cached value has expired,\n * fetch, cache, and return the value.\n */\n get: () => Promise<Type>\n}\n\n/**\n * Create a config in the provided config store that expires after the provided TTL.\n */\nexport function createExpiringConfig<Type>({\n fetchValue,\n key,\n onCacheHit = () => null,\n onFetch = () => null,\n onRevalidate = () => null,\n store,\n ttl,\n validateValue = (
|
|
1
|
+
{"version":3,"sources":["../../src/util/createExpiringConfig.ts"],"sourcesContent":["import {type ConfigStore} from '@sanity/cli-core'\n\ninterface ExpiringConfigValue {\n updatedAt: number\n value: unknown\n}\n\ninterface ExpiringConfigOptions<Type> {\n /** Fetch value */\n fetchValue: () => Promise<Type> | Type\n /** Config key */\n key: string\n /** Config store */\n store: ConfigStore\n /** TTL (milliseconds) */\n ttl: number\n\n /** Subscribe to cache hit event */\n onCacheHit?: () => void\n /** Subscribe to fetch event */\n onFetch?: () => void\n /** Subscribe to revalidate event */\n onRevalidate?: () => void\n\n /**\n * Assert the fetched value is valid, or throw if invalid.\n * If none is provided, it will always accept the fetched value.\n */\n validateValue?: (value: unknown) => value is Type\n}\n\ninterface ExpiringConfigApi<Type> {\n /**\n * Delete the cached value.\n */\n delete: () => void\n /**\n * Attempt to get the cached value. If there is no cached value, or the cached value has expired,\n * fetch, cache, and return the value.\n */\n get: () => Promise<Type>\n}\n\n/**\n * Create a config in the provided config store that expires after the provided TTL.\n */\nexport function createExpiringConfig<Type>({\n fetchValue,\n key,\n onCacheHit = () => null,\n onFetch = () => null,\n onRevalidate = () => null,\n store,\n ttl,\n validateValue = (_value: unknown): _value is Type => true,\n}: ExpiringConfigOptions<Type>): ExpiringConfigApi<Type> {\n let currentFetch: Promise<Type> | null = null\n return {\n delete() {\n store.delete(key)\n },\n async get(): Promise<Type> {\n const stored = store.get(key)\n\n if (isExpiringValue(stored)) {\n const {updatedAt, value} = stored\n if (!validateValue(value)) {\n throw new Error('Stored value is invalid')\n }\n\n const hasExpired = Date.now() - updatedAt > ttl\n\n if (!hasExpired) {\n onCacheHit()\n return value\n }\n\n onRevalidate()\n }\n\n // Return existing fetch if one is already in progress\n if (currentFetch) {\n return currentFetch\n }\n\n onFetch()\n\n currentFetch = Promise.resolve(fetchValue())\n const nextValue = await currentFetch\n if (!validateValue(nextValue)) {\n throw new Error('Fetched value is invalid')\n }\n\n currentFetch = null\n\n store.set(key, {\n updatedAt: Date.now(),\n value: nextValue,\n })\n\n return nextValue\n },\n }\n}\n\n/**\n * Checks if the given stored value is valid (does not check if expired, only verified shape)\n *\n * @param stored - The stored value to check\n * @returns True if the stored value is valid\n * @internal\n */\nfunction isExpiringValue(stored: unknown): stored is ExpiringConfigValue {\n if (typeof stored !== 'object' || stored === null || Array.isArray(stored)) {\n return false\n }\n\n if (!('updatedAt' in stored) || typeof stored.updatedAt !== 'number') {\n return false\n }\n\n if (!('value' in stored)) {\n return false\n }\n\n return true\n}\n"],"names":["createExpiringConfig","fetchValue","key","onCacheHit","onFetch","onRevalidate","store","ttl","validateValue","_value","currentFetch","delete","get","stored","isExpiringValue","updatedAt","value","Error","hasExpired","Date","now","Promise","resolve","nextValue","set","Array","isArray"],"mappings":"AA2CA;;CAEC,GACD,OAAO,SAASA,qBAA2B,EACzCC,UAAU,EACVC,GAAG,EACHC,aAAa,IAAM,IAAI,EACvBC,UAAU,IAAM,IAAI,EACpBC,eAAe,IAAM,IAAI,EACzBC,KAAK,EACLC,GAAG,EACHC,gBAAgB,CAACC,SAAoC,IAAI,EAC7B;IAC5B,IAAIC,eAAqC;IACzC,OAAO;QACLC;YACEL,MAAMK,MAAM,CAACT;QACf;QACA,MAAMU;YACJ,MAAMC,SAASP,MAAMM,GAAG,CAACV;YAEzB,IAAIY,gBAAgBD,SAAS;gBAC3B,MAAM,EAACE,SAAS,EAAEC,KAAK,EAAC,GAAGH;gBAC3B,IAAI,CAACL,cAAcQ,QAAQ;oBACzB,MAAM,IAAIC,MAAM;gBAClB;gBAEA,MAAMC,aAAaC,KAAKC,GAAG,KAAKL,YAAYR;gBAE5C,IAAI,CAACW,YAAY;oBACff;oBACA,OAAOa;gBACT;gBAEAX;YACF;YAEA,sDAAsD;YACtD,IAAIK,cAAc;gBAChB,OAAOA;YACT;YAEAN;YAEAM,eAAeW,QAAQC,OAAO,CAACrB;YAC/B,MAAMsB,YAAY,MAAMb;YACxB,IAAI,CAACF,cAAce,YAAY;gBAC7B,MAAM,IAAIN,MAAM;YAClB;YAEAP,eAAe;YAEfJ,MAAMkB,GAAG,CAACtB,KAAK;gBACba,WAAWI,KAAKC,GAAG;gBACnBJ,OAAOO;YACT;YAEA,OAAOA;QACT;IACF;AACF;AAEA;;;;;;CAMC,GACD,SAAST,gBAAgBD,MAAe;IACtC,IAAI,OAAOA,WAAW,YAAYA,WAAW,QAAQY,MAAMC,OAAO,CAACb,SAAS;QAC1E,OAAO;IACT;IAEA,IAAI,CAAE,CAAA,eAAeA,MAAK,KAAM,OAAOA,OAAOE,SAAS,KAAK,UAAU;QACpE,OAAO;IACT;IAEA,IAAI,CAAE,CAAA,WAAWF,MAAK,GAAI;QACxB,OAAO;IACT;IAEA,OAAO;AACT"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { satisfies, parse as semverParse, subset, valid } from 'semver';
|
|
2
2
|
import { getGlobalUninstallCommand, getLocalRemoveCommand, getLocalUpdateCommand } from './commands.js';
|
|
3
3
|
/**
|
|
4
4
|
* Analyzes package information and workspace configuration to detect potential issues.
|
|
@@ -52,10 +52,10 @@ import { getGlobalUninstallCommand, getLocalRemoveCommand, getLocalUpdateCommand
|
|
|
52
52
|
// Skip @sanity/cli here — global-cli-incompatible (below) handles it with
|
|
53
53
|
// better context by checking against sanity's actual required range.
|
|
54
54
|
if (info.installed && name !== '@sanity/cli') {
|
|
55
|
-
const localMajor =
|
|
55
|
+
const localMajor = semverParse(info.installed.version)?.major;
|
|
56
56
|
if (localMajor !== undefined) {
|
|
57
57
|
for (const globalMatch of globals.filter((g)=>g.packageName === name)){
|
|
58
|
-
const globalMajor =
|
|
58
|
+
const globalMajor = semverParse(globalMatch.version)?.major;
|
|
59
59
|
if (globalMajor !== undefined && globalMajor !== localMajor) {
|
|
60
60
|
issues.push({
|
|
61
61
|
message: `${name} version mismatch: global ${globalMatch.version} (${globalMatch.packageManager}) vs local ${info.installed.version}.`,
|
|
@@ -86,7 +86,7 @@ import { getGlobalUninstallCommand, getLocalRemoveCommand, getLocalUpdateCommand
|
|
|
86
86
|
if (cliInfo?.declared && !isNonSemverProtocol(cliInfo.declared.versionRange)) {
|
|
87
87
|
const declaredRange = cliInfo.declared.versionRange;
|
|
88
88
|
// Only flag as redundant when every version in the declared range also
|
|
89
|
-
// satisfies the required range.
|
|
89
|
+
// satisfies the required range. subset('^5.0.0', '^5.33.0') → false,
|
|
90
90
|
// so a too-wide range is correctly classified as conflicting.
|
|
91
91
|
if (safeSubset(declaredRange, expectedCliRange)) {
|
|
92
92
|
issues.push({
|
|
@@ -190,7 +190,7 @@ function isNonSemverProtocol(range) {
|
|
|
190
190
|
* correctly matched against caret ranges like ^5.0.0-rc.1.
|
|
191
191
|
*/ function safeSatisfies(version, range) {
|
|
192
192
|
try {
|
|
193
|
-
return
|
|
193
|
+
return satisfies(version, range, {
|
|
194
194
|
includePrerelease: true
|
|
195
195
|
});
|
|
196
196
|
} catch {
|
|
@@ -202,7 +202,7 @@ function isNonSemverProtocol(range) {
|
|
|
202
202
|
* like workspace:*, catalog:, file:, or git URLs instead of throwing.
|
|
203
203
|
*/ function safeSubset(sub, sup) {
|
|
204
204
|
try {
|
|
205
|
-
return
|
|
205
|
+
return subset(sub, sup) ?? false;
|
|
206
206
|
} catch {
|
|
207
207
|
return false;
|
|
208
208
|
}
|
|
@@ -216,7 +216,7 @@ function isNonSemverProtocol(range) {
|
|
|
216
216
|
return range;
|
|
217
217
|
}
|
|
218
218
|
// If it's a plain version like "5.33.0", treat as ^5.33.0
|
|
219
|
-
if (
|
|
219
|
+
if (valid(range)) {
|
|
220
220
|
return `^${range}`;
|
|
221
221
|
}
|
|
222
222
|
return range;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/util/packageManager/installationInfo/analyzeIssues.ts"],"sourcesContent":["import semver from 'semver'\n\nimport {\n getGlobalUninstallCommand,\n getLocalRemoveCommand,\n getLocalUpdateCommand,\n} from './commands.js'\nimport {\n type GlobalInstallation,\n type Issue,\n type LockfileType,\n type PackageInfo,\n type SanityPackage,\n type WorkspaceInfo,\n} from './types.js'\n\n/**\n * Analyzes package information and workspace configuration to detect potential issues.\n */\nexport function analyzeIssues(\n packages: Partial<Record<SanityPackage, PackageInfo>>,\n workspace: WorkspaceInfo,\n globals: GlobalInstallation[],\n): Issue[] {\n const issues: Issue[] = []\n // When multiple lockfiles exist, the \"winning\" lockfile is arbitrary (based on\n // detection priority order), so fall back to workspace-level signals instead.\n const pm: LockfileType =\n workspace.hasMultipleLockfiles || !workspace.lockfile\n ? inferPackageManager(workspace)\n : workspace.lockfile.type\n const updateOptions = {yarnBerry: workspace.yarnBerry}\n\n // Check for multiple lockfiles\n if (workspace.hasMultipleLockfiles) {\n issues.push({\n message: 'Multiple lockfiles found. This can cause inconsistent installations.',\n packageName: null,\n severity: 'warning',\n suggestion: 'Remove all but one lockfile and use a single package manager.',\n type: 'multiple-lockfiles',\n })\n }\n\n // Check each package for issues\n for (const [name, info] of Object.entries(packages) as [\n SanityPackage,\n PackageInfo | undefined,\n ][]) {\n if (!info) continue\n\n // override-in-effect\n if (info.override) {\n issues.push({\n message: `${name} has an override (${info.override.mechanism}) set to ${info.override.versionRange}.`,\n packageName: name,\n severity: 'info',\n suggestion: null,\n type: 'override-in-effect',\n })\n }\n\n // declared-not-installed — skip when an override is in effect, since the\n // override may explain the unusual state and the user already sees override-in-effect.\n // Also skip @sanity/cli when sanity is installed — the CLI-specific section below\n // produces more specific diagnostics (conflicting/redundant/cli-not-installed).\n const cliSectionWillHandle =\n name === '@sanity/cli' && Boolean(packages.sanity?.installed?.cliDependencyRange)\n if (info.declared && !info.installed && !info.override && !cliSectionWillHandle) {\n issues.push({\n message: `${name} is declared in package.json but not installed.`,\n packageName: name,\n severity: 'error',\n suggestion: `Run: ${pm} install`,\n type: 'declared-not-installed',\n })\n }\n\n // global-local-mismatch — only warn on major version differences\n // Check all global installations, not just the first match.\n // Skip @sanity/cli here — global-cli-incompatible (below) handles it with\n // better context by checking against sanity's actual required range.\n if (info.installed && name !== '@sanity/cli') {\n const localMajor = semver.parse(info.installed.version)?.major\n if (localMajor !== undefined) {\n for (const globalMatch of globals.filter((g) => g.packageName === name)) {\n const globalMajor = semver.parse(globalMatch.version)?.major\n if (globalMajor !== undefined && globalMajor !== localMajor) {\n issues.push({\n message: `${name} version mismatch: global ${globalMatch.version} (${globalMatch.packageManager}) vs local ${info.installed.version}.`,\n packageName: name,\n severity: globalMatch.isActive ? 'warning' : 'info',\n suggestion: `Run: ${getGlobalUninstallCommand(globalMatch.packageManager, name)}`,\n type: 'global-local-mismatch',\n })\n }\n }\n }\n }\n }\n\n // Check @sanity/cli compatibility with sanity\n const sanityInfo = packages.sanity\n const cliInfo = packages['@sanity/cli']\n\n if (sanityInfo?.installed?.cliDependencyRange) {\n const expectedCliRange = sanityInfo.installed.cliDependencyRange\n // Normalize pinned versions (e.g. \"5.33.0\" → \"^5.33.0\") for satisfies checks.\n // Already-ranged values like \"^5.33.0\" pass through unchanged.\n // Note: sanity always uses caret ranges in practice. The pinned→caret conversion\n // is a defensive fallback — if sanity ever pinned an exact version, caret semantics\n // (any compatible minor/patch) are the expected behavior for CLI compatibility.\n const compatRange = toCaretRange(expectedCliRange)\n\n // Check if @sanity/cli is explicitly declared with an incompatible version.\n // Skip when the declared range uses a non-semver protocol (workspace:*, file:,\n // portal:, link:) — these are local package references, not version conflicts.\n if (cliInfo?.declared && !isNonSemverProtocol(cliInfo.declared.versionRange)) {\n const declaredRange = cliInfo.declared.versionRange\n // Only flag as redundant when every version in the declared range also\n // satisfies the required range. semver.subset('^5.0.0', '^5.33.0') → false,\n // so a too-wide range is correctly classified as conflicting.\n if (safeSubset(declaredRange, expectedCliRange)) {\n issues.push({\n message: `@sanity/cli is listed as a dependency but is already provided by sanity. Remove it to avoid version conflicts.`,\n packageName: '@sanity/cli',\n severity: 'info',\n suggestion: `Run: ${getLocalRemoveCommand(pm, '@sanity/cli')}`,\n type: 'redundant-cli-dependency',\n })\n } else {\n issues.push({\n message: `@sanity/cli is declared as ${declaredRange} but sanity requires ${expectedCliRange}. Sanity provides @sanity/cli automatically — removing the explicit declaration lets sanity manage the correct version.`,\n packageName: '@sanity/cli',\n severity: 'error',\n suggestion: `Run: ${getLocalRemoveCommand(pm, '@sanity/cli')}`,\n type: 'conflicting-cli-dependency',\n })\n }\n }\n\n // Check if installed @sanity/cli satisfies sanity's requirement.\n // Skip if we already flagged a conflicting declaration — that's the root cause.\n // Skip if an override is in effect — the user already knows about it, and\n // suggesting an update would be misleading since the override controls the version.\n const hasConflictingDeclaration = issues.some((i) => i.type === 'conflicting-cli-dependency')\n if (cliInfo?.installed && !hasConflictingDeclaration && !cliInfo.override) {\n const installedVersion = cliInfo.installed.version\n if (!safeSatisfies(installedVersion, compatRange)) {\n issues.push({\n message: `Installed @sanity/cli@${installedVersion} does not satisfy sanity's requirement of ${compatRange}.`,\n packageName: '@sanity/cli',\n severity: 'error',\n suggestion: `Run: ${getLocalUpdateCommand(pm, '@sanity/cli', updateOptions)}`,\n type: 'cli-version-incompatible',\n })\n }\n }\n\n // Check if @sanity/cli is missing entirely (not declared, not installed)\n // This indicates a broken node_modules state since sanity depends on @sanity/cli.\n if (!cliInfo?.installed && !cliInfo?.declared && !cliInfo?.override) {\n issues.push({\n message: `@sanity/cli is not installed. It is required by sanity@${sanityInfo.installed.version}.`,\n packageName: '@sanity/cli',\n severity: 'error',\n suggestion: `Run: ${pm} install`,\n type: 'cli-not-installed',\n })\n }\n\n // Check global @sanity/cli compatibility with local sanity\n for (const global of globals) {\n if (global.packageName !== '@sanity/cli') continue\n\n if (!safeSatisfies(global.version, compatRange)) {\n issues.push({\n message: `Global @sanity/cli@${global.version} (installed via ${global.packageManager}) is incompatible with local sanity@${sanityInfo.installed.version} (requires @sanity/cli ${compatRange}).`,\n packageName: '@sanity/cli',\n severity: global.isActive ? 'warning' : 'info',\n suggestion: `Run: ${getGlobalUninstallCommand(global.packageManager, '@sanity/cli')}`,\n type: 'global-cli-incompatible',\n })\n }\n }\n }\n\n return issues\n}\n\n/**\n * Protocols and prefixes that are not semver ranges.\n * Includes local package references (workspace:, file:, portal:, link:),\n * catalog: which may appear as an unresolved range when pnpm catalog lookup fails,\n * and git/URL-based dependency specifiers.\n */\nconst NON_SEMVER_PROTOCOLS = [\n 'workspace:',\n 'file:',\n 'portal:',\n 'link:',\n 'catalog:',\n 'git+',\n 'git:',\n 'github:',\n 'https:',\n 'http:',\n]\n\nfunction isNonSemverProtocol(range: string): boolean {\n return NON_SEMVER_PROTOCOLS.some((p) => range.startsWith(p))\n}\n\n/**\n * Infers the package manager from workspace info when no lockfile is present.\n * Uses both the workspace type (which captures workspace config markers like\n * pnpm-workspace.yaml) and the yarnBerry flag (which captures .yarnrc.yml\n * even for standalone projects).\n */\nfunction inferPackageManager(workspace: WorkspaceInfo): LockfileType {\n if (workspace.type.startsWith('pnpm')) return 'pnpm'\n if (workspace.type.startsWith('yarn')) return 'yarn'\n if (workspace.type.startsWith('bun')) return 'bun'\n if (workspace.yarnBerry) return 'yarn'\n if (workspace.bunfig) return 'bun'\n return 'npm'\n}\n\n/**\n * Safe wrapper around semver.satisfies that returns false for non-semver\n * versions or ranges (workspace:*, catalog:, file:, git URLs) instead of throwing.\n * Uses includePrerelease so that pre-release versions (e.g. 5.0.0-rc.1) are\n * correctly matched against caret ranges like ^5.0.0-rc.1.\n */\nfunction safeSatisfies(version: string, range: string): boolean {\n try {\n return semver.satisfies(version, range, {includePrerelease: true})\n } catch {\n return false\n }\n}\n\n/**\n * Safe wrapper around semver.subset that returns false for non-semver ranges\n * like workspace:*, catalog:, file:, or git URLs instead of throwing.\n */\nfunction safeSubset(sub: string, sup: string): boolean {\n try {\n return semver.subset(sub, sup) ?? false\n } catch {\n return false\n }\n}\n\n/**\n * Converts a pinned version like \"5.33.0\" to \"^5.33.0\".\n * If it's already a range (^, ~, \\>=, etc.), returns as-is.\n */\nfunction toCaretRange(range: string): string {\n // If it's already a range operator, return as-is\n if (/^[\\^~><!=]/.test(range) || range.includes(' ')) {\n return range\n }\n // If it's a plain version like \"5.33.0\", treat as ^5.33.0\n if (semver.valid(range)) {\n return `^${range}`\n }\n return range\n}\n"],"names":["semver","getGlobalUninstallCommand","getLocalRemoveCommand","getLocalUpdateCommand","analyzeIssues","packages","workspace","globals","issues","pm","hasMultipleLockfiles","lockfile","inferPackageManager","type","updateOptions","yarnBerry","push","message","packageName","severity","suggestion","name","info","Object","entries","override","mechanism","versionRange","cliSectionWillHandle","Boolean","sanity","installed","cliDependencyRange","declared","localMajor","parse","version","major","undefined","globalMatch","filter","g","globalMajor","packageManager","isActive","sanityInfo","cliInfo","expectedCliRange","compatRange","toCaretRange","isNonSemverProtocol","declaredRange","safeSubset","hasConflictingDeclaration","some","i","installedVersion","safeSatisfies","global","NON_SEMVER_PROTOCOLS","range","p","startsWith","bunfig","satisfies","includePrerelease","sub","sup","subset","test","includes","valid"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../src/util/packageManager/installationInfo/analyzeIssues.ts"],"sourcesContent":["import {satisfies, parse as semverParse, subset, valid} from 'semver'\n\nimport {\n getGlobalUninstallCommand,\n getLocalRemoveCommand,\n getLocalUpdateCommand,\n} from './commands.js'\nimport {\n type GlobalInstallation,\n type Issue,\n type LockfileType,\n type PackageInfo,\n type SanityPackage,\n type WorkspaceInfo,\n} from './types.js'\n\n/**\n * Analyzes package information and workspace configuration to detect potential issues.\n */\nexport function analyzeIssues(\n packages: Partial<Record<SanityPackage, PackageInfo>>,\n workspace: WorkspaceInfo,\n globals: GlobalInstallation[],\n): Issue[] {\n const issues: Issue[] = []\n // When multiple lockfiles exist, the \"winning\" lockfile is arbitrary (based on\n // detection priority order), so fall back to workspace-level signals instead.\n const pm: LockfileType =\n workspace.hasMultipleLockfiles || !workspace.lockfile\n ? inferPackageManager(workspace)\n : workspace.lockfile.type\n const updateOptions = {yarnBerry: workspace.yarnBerry}\n\n // Check for multiple lockfiles\n if (workspace.hasMultipleLockfiles) {\n issues.push({\n message: 'Multiple lockfiles found. This can cause inconsistent installations.',\n packageName: null,\n severity: 'warning',\n suggestion: 'Remove all but one lockfile and use a single package manager.',\n type: 'multiple-lockfiles',\n })\n }\n\n // Check each package for issues\n for (const [name, info] of Object.entries(packages) as [\n SanityPackage,\n PackageInfo | undefined,\n ][]) {\n if (!info) continue\n\n // override-in-effect\n if (info.override) {\n issues.push({\n message: `${name} has an override (${info.override.mechanism}) set to ${info.override.versionRange}.`,\n packageName: name,\n severity: 'info',\n suggestion: null,\n type: 'override-in-effect',\n })\n }\n\n // declared-not-installed — skip when an override is in effect, since the\n // override may explain the unusual state and the user already sees override-in-effect.\n // Also skip @sanity/cli when sanity is installed — the CLI-specific section below\n // produces more specific diagnostics (conflicting/redundant/cli-not-installed).\n const cliSectionWillHandle =\n name === '@sanity/cli' && Boolean(packages.sanity?.installed?.cliDependencyRange)\n if (info.declared && !info.installed && !info.override && !cliSectionWillHandle) {\n issues.push({\n message: `${name} is declared in package.json but not installed.`,\n packageName: name,\n severity: 'error',\n suggestion: `Run: ${pm} install`,\n type: 'declared-not-installed',\n })\n }\n\n // global-local-mismatch — only warn on major version differences\n // Check all global installations, not just the first match.\n // Skip @sanity/cli here — global-cli-incompatible (below) handles it with\n // better context by checking against sanity's actual required range.\n if (info.installed && name !== '@sanity/cli') {\n const localMajor = semverParse(info.installed.version)?.major\n if (localMajor !== undefined) {\n for (const globalMatch of globals.filter((g) => g.packageName === name)) {\n const globalMajor = semverParse(globalMatch.version)?.major\n if (globalMajor !== undefined && globalMajor !== localMajor) {\n issues.push({\n message: `${name} version mismatch: global ${globalMatch.version} (${globalMatch.packageManager}) vs local ${info.installed.version}.`,\n packageName: name,\n severity: globalMatch.isActive ? 'warning' : 'info',\n suggestion: `Run: ${getGlobalUninstallCommand(globalMatch.packageManager, name)}`,\n type: 'global-local-mismatch',\n })\n }\n }\n }\n }\n }\n\n // Check @sanity/cli compatibility with sanity\n const sanityInfo = packages.sanity\n const cliInfo = packages['@sanity/cli']\n\n if (sanityInfo?.installed?.cliDependencyRange) {\n const expectedCliRange = sanityInfo.installed.cliDependencyRange\n // Normalize pinned versions (e.g. \"5.33.0\" → \"^5.33.0\") for satisfies checks.\n // Already-ranged values like \"^5.33.0\" pass through unchanged.\n // Note: sanity always uses caret ranges in practice. The pinned→caret conversion\n // is a defensive fallback — if sanity ever pinned an exact version, caret semantics\n // (any compatible minor/patch) are the expected behavior for CLI compatibility.\n const compatRange = toCaretRange(expectedCliRange)\n\n // Check if @sanity/cli is explicitly declared with an incompatible version.\n // Skip when the declared range uses a non-semver protocol (workspace:*, file:,\n // portal:, link:) — these are local package references, not version conflicts.\n if (cliInfo?.declared && !isNonSemverProtocol(cliInfo.declared.versionRange)) {\n const declaredRange = cliInfo.declared.versionRange\n // Only flag as redundant when every version in the declared range also\n // satisfies the required range. subset('^5.0.0', '^5.33.0') → false,\n // so a too-wide range is correctly classified as conflicting.\n if (safeSubset(declaredRange, expectedCliRange)) {\n issues.push({\n message: `@sanity/cli is listed as a dependency but is already provided by sanity. Remove it to avoid version conflicts.`,\n packageName: '@sanity/cli',\n severity: 'info',\n suggestion: `Run: ${getLocalRemoveCommand(pm, '@sanity/cli')}`,\n type: 'redundant-cli-dependency',\n })\n } else {\n issues.push({\n message: `@sanity/cli is declared as ${declaredRange} but sanity requires ${expectedCliRange}. Sanity provides @sanity/cli automatically — removing the explicit declaration lets sanity manage the correct version.`,\n packageName: '@sanity/cli',\n severity: 'error',\n suggestion: `Run: ${getLocalRemoveCommand(pm, '@sanity/cli')}`,\n type: 'conflicting-cli-dependency',\n })\n }\n }\n\n // Check if installed @sanity/cli satisfies sanity's requirement.\n // Skip if we already flagged a conflicting declaration — that's the root cause.\n // Skip if an override is in effect — the user already knows about it, and\n // suggesting an update would be misleading since the override controls the version.\n const hasConflictingDeclaration = issues.some((i) => i.type === 'conflicting-cli-dependency')\n if (cliInfo?.installed && !hasConflictingDeclaration && !cliInfo.override) {\n const installedVersion = cliInfo.installed.version\n if (!safeSatisfies(installedVersion, compatRange)) {\n issues.push({\n message: `Installed @sanity/cli@${installedVersion} does not satisfy sanity's requirement of ${compatRange}.`,\n packageName: '@sanity/cli',\n severity: 'error',\n suggestion: `Run: ${getLocalUpdateCommand(pm, '@sanity/cli', updateOptions)}`,\n type: 'cli-version-incompatible',\n })\n }\n }\n\n // Check if @sanity/cli is missing entirely (not declared, not installed)\n // This indicates a broken node_modules state since sanity depends on @sanity/cli.\n if (!cliInfo?.installed && !cliInfo?.declared && !cliInfo?.override) {\n issues.push({\n message: `@sanity/cli is not installed. It is required by sanity@${sanityInfo.installed.version}.`,\n packageName: '@sanity/cli',\n severity: 'error',\n suggestion: `Run: ${pm} install`,\n type: 'cli-not-installed',\n })\n }\n\n // Check global @sanity/cli compatibility with local sanity\n for (const global of globals) {\n if (global.packageName !== '@sanity/cli') continue\n\n if (!safeSatisfies(global.version, compatRange)) {\n issues.push({\n message: `Global @sanity/cli@${global.version} (installed via ${global.packageManager}) is incompatible with local sanity@${sanityInfo.installed.version} (requires @sanity/cli ${compatRange}).`,\n packageName: '@sanity/cli',\n severity: global.isActive ? 'warning' : 'info',\n suggestion: `Run: ${getGlobalUninstallCommand(global.packageManager, '@sanity/cli')}`,\n type: 'global-cli-incompatible',\n })\n }\n }\n }\n\n return issues\n}\n\n/**\n * Protocols and prefixes that are not semver ranges.\n * Includes local package references (workspace:, file:, portal:, link:),\n * catalog: which may appear as an unresolved range when pnpm catalog lookup fails,\n * and git/URL-based dependency specifiers.\n */\nconst NON_SEMVER_PROTOCOLS = [\n 'workspace:',\n 'file:',\n 'portal:',\n 'link:',\n 'catalog:',\n 'git+',\n 'git:',\n 'github:',\n 'https:',\n 'http:',\n]\n\nfunction isNonSemverProtocol(range: string): boolean {\n return NON_SEMVER_PROTOCOLS.some((p) => range.startsWith(p))\n}\n\n/**\n * Infers the package manager from workspace info when no lockfile is present.\n * Uses both the workspace type (which captures workspace config markers like\n * pnpm-workspace.yaml) and the yarnBerry flag (which captures .yarnrc.yml\n * even for standalone projects).\n */\nfunction inferPackageManager(workspace: WorkspaceInfo): LockfileType {\n if (workspace.type.startsWith('pnpm')) return 'pnpm'\n if (workspace.type.startsWith('yarn')) return 'yarn'\n if (workspace.type.startsWith('bun')) return 'bun'\n if (workspace.yarnBerry) return 'yarn'\n if (workspace.bunfig) return 'bun'\n return 'npm'\n}\n\n/**\n * Safe wrapper around semver.satisfies that returns false for non-semver\n * versions or ranges (workspace:*, catalog:, file:, git URLs) instead of throwing.\n * Uses includePrerelease so that pre-release versions (e.g. 5.0.0-rc.1) are\n * correctly matched against caret ranges like ^5.0.0-rc.1.\n */\nfunction safeSatisfies(version: string, range: string): boolean {\n try {\n return satisfies(version, range, {includePrerelease: true})\n } catch {\n return false\n }\n}\n\n/**\n * Safe wrapper around semver.subset that returns false for non-semver ranges\n * like workspace:*, catalog:, file:, or git URLs instead of throwing.\n */\nfunction safeSubset(sub: string, sup: string): boolean {\n try {\n return subset(sub, sup) ?? false\n } catch {\n return false\n }\n}\n\n/**\n * Converts a pinned version like \"5.33.0\" to \"^5.33.0\".\n * If it's already a range (^, ~, \\>=, etc.), returns as-is.\n */\nfunction toCaretRange(range: string): string {\n // If it's already a range operator, return as-is\n if (/^[\\^~><!=]/.test(range) || range.includes(' ')) {\n return range\n }\n // If it's a plain version like \"5.33.0\", treat as ^5.33.0\n if (valid(range)) {\n return `^${range}`\n }\n return range\n}\n"],"names":["satisfies","parse","semverParse","subset","valid","getGlobalUninstallCommand","getLocalRemoveCommand","getLocalUpdateCommand","analyzeIssues","packages","workspace","globals","issues","pm","hasMultipleLockfiles","lockfile","inferPackageManager","type","updateOptions","yarnBerry","push","message","packageName","severity","suggestion","name","info","Object","entries","override","mechanism","versionRange","cliSectionWillHandle","Boolean","sanity","installed","cliDependencyRange","declared","localMajor","version","major","undefined","globalMatch","filter","g","globalMajor","packageManager","isActive","sanityInfo","cliInfo","expectedCliRange","compatRange","toCaretRange","isNonSemverProtocol","declaredRange","safeSubset","hasConflictingDeclaration","some","i","installedVersion","safeSatisfies","global","NON_SEMVER_PROTOCOLS","range","p","startsWith","bunfig","includePrerelease","sub","sup","test","includes"],"mappings":"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"}
|
|
@@ -6,11 +6,14 @@ import { readJsonFile } from './readJsonFile.js';
|
|
|
6
6
|
import { resolveVersionRange } from './resolveVersionRange.js';
|
|
7
7
|
/**
|
|
8
8
|
* Finds where a package is declared in the workspace, walking up from startDir.
|
|
9
|
-
* Resolves catalog: protocol if used.
|
|
9
|
+
* Resolves catalog: protocol if used (requires workspaceInfo).
|
|
10
|
+
*
|
|
11
|
+
* When workspaceInfo is omitted, walks to the filesystem root instead of stopping
|
|
12
|
+
* at the workspace root, and returns the raw declared range without catalog resolution.
|
|
10
13
|
*/ export async function findPackageDeclaration(packageName, startDir, workspaceInfo) {
|
|
11
14
|
let currentDir = path.resolve(startDir);
|
|
12
15
|
const fsRoot = path.parse(currentDir).root;
|
|
13
|
-
// Walk up until we pass the workspace root
|
|
16
|
+
// Walk up until we pass the workspace root (or filesystem root if no workspace info)
|
|
14
17
|
while(currentDir !== fsRoot){
|
|
15
18
|
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
16
19
|
const packageJson = await readJsonFile(packageJsonPath);
|
|
@@ -27,7 +30,7 @@ import { resolveVersionRange } from './resolveVersionRange.js';
|
|
|
27
30
|
const deps = packageJson[depType];
|
|
28
31
|
if (deps && packageName in deps) {
|
|
29
32
|
const declaredVersionRange = deps[packageName];
|
|
30
|
-
const versionRange = await resolveVersionRange(declaredVersionRange, packageName, workspaceInfo);
|
|
33
|
+
const versionRange = workspaceInfo ? await resolveVersionRange(declaredVersionRange, packageName, workspaceInfo) : declaredVersionRange;
|
|
31
34
|
return {
|
|
32
35
|
declaredVersionRange,
|
|
33
36
|
dependencyType: depType,
|
|
@@ -37,8 +40,8 @@ import { resolveVersionRange } from './resolveVersionRange.js';
|
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
|
-
// Stop at workspace root
|
|
41
|
-
if (currentDir === workspaceInfo.root) {
|
|
43
|
+
// Stop at workspace root if provided, otherwise continue to filesystem root
|
|
44
|
+
if (workspaceInfo && currentDir === workspaceInfo.root) {
|
|
42
45
|
break;
|
|
43
46
|
}
|
|
44
47
|
currentDir = path.dirname(currentDir);
|
|
@@ -85,6 +88,9 @@ import { resolveVersionRange } from './resolveVersionRange.js';
|
|
|
85
88
|
* Also extracts \@sanity/cli dependency range from sanity package if applicable.
|
|
86
89
|
*
|
|
87
90
|
* Handles both hoisted (npm/yarn) and nested (pnpm) node_modules structures.
|
|
91
|
+
*
|
|
92
|
+
* When workspaceRoot is omitted, walks to the filesystem root instead of stopping
|
|
93
|
+
* at the workspace root.
|
|
88
94
|
*/ export async function findInstalledPackage(packageName, startDir, workspaceRoot) {
|
|
89
95
|
let currentDir = path.resolve(startDir);
|
|
90
96
|
const fsRoot = path.parse(currentDir).root;
|
|
@@ -105,8 +111,8 @@ import { resolveVersionRange } from './resolveVersionRange.js';
|
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
|
-
// Stop at workspace root
|
|
109
|
-
if (currentDir === workspaceRoot) {
|
|
114
|
+
// Stop at workspace root if provided, otherwise continue to filesystem root
|
|
115
|
+
if (workspaceRoot && currentDir === workspaceRoot) {
|
|
110
116
|
break;
|
|
111
117
|
}
|
|
112
118
|
currentDir = path.dirname(currentDir);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/util/packageManager/installationInfo/detectPackages.ts"],"sourcesContent":["import fs from 'node:fs/promises'\nimport path from 'node:path'\nimport {fileURLToPath, pathToFileURL} from 'node:url'\n\nimport {resolve as resolveImport} from 'import-meta-resolve'\n\nimport {readJsonFile} from './readJsonFile.js'\nimport {resolveVersionRange} from './resolveVersionRange.js'\nimport {\n type InstalledPackage,\n type PackageDeclaration,\n type PackageInfo,\n type PackageOverride,\n type SanityPackage,\n type WorkspaceInfo,\n} from './types.js'\n\ninterface PackageJson {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n name?: string\n overrides?: Record<string, string>\n pnpm?: {\n overrides?: Record<string, string>\n }\n resolutions?: Record<string, string>\n version?: string\n}\n\n/**\n * Finds where a package is declared in the workspace, walking up from startDir.\n * Resolves catalog: protocol if used.\n */\nexport async function findPackageDeclaration(\n packageName: SanityPackage,\n startDir: string,\n workspaceInfo: WorkspaceInfo,\n): Promise<PackageDeclaration | null> {\n let currentDir = path.resolve(startDir)\n const fsRoot = path.parse(currentDir).root\n\n // Walk up until we pass the workspace root\n while (currentDir !== fsRoot) {\n const packageJsonPath = path.join(currentDir, 'package.json')\n const packageJson = await readJsonFile<PackageJson>(packageJsonPath)\n\n if (packageJson) {\n // Check dependencies and devDependencies only.\n // peerDependencies are excluded because they are not auto-installed —\n // flagging them as declared-not-installed would be a false positive\n // (common in Sanity plugins that list sanity as a peer dep).\n const depTypes = ['dependencies', 'devDependencies'] as const\n for (const depType of depTypes) {\n const deps = packageJson[depType]\n if (deps && packageName in deps) {\n const declaredVersionRange = deps[packageName]\n const versionRange = await resolveVersionRange(\n declaredVersionRange,\n packageName,\n workspaceInfo,\n )\n\n return {\n declaredVersionRange,\n dependencyType: depType,\n packageJsonPath,\n versionRange,\n }\n }\n }\n }\n\n // Stop at workspace root\n if (currentDir === workspaceInfo.root) {\n break\n }\n\n currentDir = path.dirname(currentDir)\n }\n\n return null\n}\n\n/**\n * Finds if a package has an override/resolution defined in the workspace root.\n * Checks npm overrides, yarn resolutions, and pnpm overrides.\n */\nexport async function findPackageOverride(\n packageName: SanityPackage,\n workspaceInfo: WorkspaceInfo,\n): Promise<PackageOverride | null> {\n const rootPackageJsonPath = path.join(workspaceInfo.root, 'package.json')\n const packageJson = await readJsonFile<PackageJson>(rootPackageJsonPath)\n\n if (!packageJson) {\n return null\n }\n\n // Check npm/pnpm overrides\n if (packageJson.overrides && packageName in packageJson.overrides) {\n return {\n mechanism: 'npm-overrides',\n packageJsonPath: rootPackageJsonPath,\n versionRange: packageJson.overrides[packageName],\n }\n }\n\n // Check pnpm.overrides (alternative location)\n if (packageJson.pnpm?.overrides && packageName in packageJson.pnpm.overrides) {\n return {\n mechanism: 'pnpm-overrides',\n packageJsonPath: rootPackageJsonPath,\n versionRange: packageJson.pnpm.overrides[packageName],\n }\n }\n\n // Check yarn resolutions\n if (packageJson.resolutions && packageName in packageJson.resolutions) {\n return {\n mechanism: 'yarn-resolutions',\n packageJsonPath: rootPackageJsonPath,\n versionRange: packageJson.resolutions[packageName],\n }\n }\n\n return null\n}\n\n/**\n * Finds installed package in node_modules, walking up from startDir.\n * Also extracts \\@sanity/cli dependency range from sanity package if applicable.\n *\n * Handles both hoisted (npm/yarn) and nested (pnpm) node_modules structures.\n */\nexport async function findInstalledPackage(\n packageName: SanityPackage,\n startDir: string,\n workspaceRoot: string,\n): Promise<InstalledPackage | null> {\n let currentDir = path.resolve(startDir)\n const fsRoot = path.parse(currentDir).root\n\n while (currentDir !== fsRoot) {\n // First, check the top-level node_modules (works for npm, yarn, and hoisted pnpm)\n const result = await findPackageInNodeModules(\n packageName,\n path.join(currentDir, 'node_modules'),\n )\n if (result) {\n return result\n }\n\n // For @sanity/cli, use Node's module resolution from sanity's location.\n // This handles all package manager layouts (pnpm nested deps, hoisting, etc.)\n if (packageName === '@sanity/cli') {\n const sanityPath = await resolvePackagePath(path.join(currentDir, 'node_modules', 'sanity'))\n if (sanityPath) {\n const nestedResult = await resolveCliFromSanity(sanityPath)\n if (nestedResult) {\n return nestedResult\n }\n }\n }\n\n // Stop at workspace root\n if (currentDir === workspaceRoot) {\n break\n }\n\n currentDir = path.dirname(currentDir)\n }\n\n return null\n}\n\n/**\n * Looks for a package in a specific node_modules directory.\n */\nasync function findPackageInNodeModules(\n packageName: SanityPackage,\n nodeModulesDir: string,\n): Promise<InstalledPackage | null> {\n const packagePath = path.join(nodeModulesDir, packageName)\n const resolvedPath = await resolvePackagePath(packagePath)\n\n if (!resolvedPath) {\n return null\n }\n\n const packageJsonPath = path.join(resolvedPath, 'package.json')\n const packageJson = await readJsonFile<PackageJson>(packageJsonPath)\n\n if (!packageJson?.version) {\n return null\n }\n\n let cliDependencyRange: string | null = null\n\n // If this is the sanity package, extract @sanity/cli dependency\n if (packageName === 'sanity') {\n cliDependencyRange = packageJson.dependencies?.['@sanity/cli'] ?? null\n }\n\n return {\n cliDependencyRange,\n path: resolvedPath,\n version: packageJson.version,\n }\n}\n\n/**\n * Resolves a package path, following symlinks (pnpm uses symlinks).\n * Returns null if the path doesn't exist.\n */\nasync function resolvePackagePath(packagePath: string): Promise<string | null> {\n try {\n // Use realpath to follow symlinks (important for pnpm)\n return await fs.realpath(packagePath)\n } catch {\n return null\n }\n}\n\n/**\n * Uses Node's module resolution to find \\@sanity/cli from sanity's location.\n * This is more robust than manually traversing directories as it handles\n * all package manager layouts (pnpm nested deps, hoisting variations, etc.)\n */\nasync function resolveCliFromSanity(sanityPath: string): Promise<InstalledPackage | null> {\n try {\n // Resolve @sanity/cli/package.json from sanity's perspective\n const sanityPkgUrl = pathToFileURL(path.join(sanityPath, 'package.json')).href\n const cliPkgUrl = resolveImport('@sanity/cli/package.json', sanityPkgUrl)\n const cliPkgPath = fileURLToPath(cliPkgUrl)\n const cliPath = path.dirname(cliPkgPath)\n\n const packageJson = await readJsonFile<PackageJson>(cliPkgPath)\n if (!packageJson?.version) {\n return null\n }\n\n return {\n cliDependencyRange: null,\n path: cliPath,\n version: packageJson.version,\n }\n } catch {\n return null\n }\n}\n\n/**\n * Collects all package info (declaration, override, installed) for a package.\n */\nexport async function collectPackageInfo(\n packageName: SanityPackage,\n startDir: string,\n workspaceInfo: WorkspaceInfo,\n): Promise<PackageInfo> {\n const [declared, override, installed] = await Promise.all([\n findPackageDeclaration(packageName, startDir, workspaceInfo),\n findPackageOverride(packageName, workspaceInfo),\n findInstalledPackage(packageName, startDir, workspaceInfo.root),\n ])\n\n return {declared, installed, override}\n}\n"],"names":["fs","path","fileURLToPath","pathToFileURL","resolve","resolveImport","readJsonFile","resolveVersionRange","findPackageDeclaration","packageName","startDir","workspaceInfo","currentDir","fsRoot","parse","root","packageJsonPath","join","packageJson","depTypes","depType","deps","declaredVersionRange","versionRange","dependencyType","dirname","findPackageOverride","rootPackageJsonPath","overrides","mechanism","pnpm","resolutions","findInstalledPackage","workspaceRoot","result","findPackageInNodeModules","sanityPath","resolvePackagePath","nestedResult","resolveCliFromSanity","nodeModulesDir","packagePath","resolvedPath","version","cliDependencyRange","dependencies","realpath","sanityPkgUrl","href","cliPkgUrl","cliPkgPath","cliPath","collectPackageInfo","declared","override","installed","Promise","all"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../src/util/packageManager/installationInfo/detectPackages.ts"],"sourcesContent":["import fs from 'node:fs/promises'\nimport path from 'node:path'\nimport {fileURLToPath, pathToFileURL} from 'node:url'\n\nimport {resolve as resolveImport} from 'import-meta-resolve'\n\nimport {readJsonFile} from './readJsonFile.js'\nimport {resolveVersionRange} from './resolveVersionRange.js'\nimport {\n type InstalledPackage,\n type PackageDeclaration,\n type PackageInfo,\n type PackageOverride,\n type SanityPackage,\n type WorkspaceInfo,\n} from './types.js'\n\ninterface PackageJson {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n name?: string\n overrides?: Record<string, string>\n pnpm?: {\n overrides?: Record<string, string>\n }\n resolutions?: Record<string, string>\n version?: string\n}\n\n/**\n * Finds where a package is declared in the workspace, walking up from startDir.\n * Resolves catalog: protocol if used (requires workspaceInfo).\n *\n * When workspaceInfo is omitted, walks to the filesystem root instead of stopping\n * at the workspace root, and returns the raw declared range without catalog resolution.\n */\nexport async function findPackageDeclaration(\n packageName: SanityPackage,\n startDir: string,\n workspaceInfo?: WorkspaceInfo,\n): Promise<PackageDeclaration | null> {\n let currentDir = path.resolve(startDir)\n const fsRoot = path.parse(currentDir).root\n\n // Walk up until we pass the workspace root (or filesystem root if no workspace info)\n while (currentDir !== fsRoot) {\n const packageJsonPath = path.join(currentDir, 'package.json')\n const packageJson = await readJsonFile<PackageJson>(packageJsonPath)\n\n if (packageJson) {\n // Check dependencies and devDependencies only.\n // peerDependencies are excluded because they are not auto-installed —\n // flagging them as declared-not-installed would be a false positive\n // (common in Sanity plugins that list sanity as a peer dep).\n const depTypes = ['dependencies', 'devDependencies'] as const\n for (const depType of depTypes) {\n const deps = packageJson[depType]\n if (deps && packageName in deps) {\n const declaredVersionRange = deps[packageName]\n const versionRange = workspaceInfo\n ? await resolveVersionRange(declaredVersionRange, packageName, workspaceInfo)\n : declaredVersionRange\n\n return {\n declaredVersionRange,\n dependencyType: depType,\n packageJsonPath,\n versionRange,\n }\n }\n }\n }\n\n // Stop at workspace root if provided, otherwise continue to filesystem root\n if (workspaceInfo && currentDir === workspaceInfo.root) {\n break\n }\n\n currentDir = path.dirname(currentDir)\n }\n\n return null\n}\n\n/**\n * Finds if a package has an override/resolution defined in the workspace root.\n * Checks npm overrides, yarn resolutions, and pnpm overrides.\n */\nexport async function findPackageOverride(\n packageName: SanityPackage,\n workspaceInfo: WorkspaceInfo,\n): Promise<PackageOverride | null> {\n const rootPackageJsonPath = path.join(workspaceInfo.root, 'package.json')\n const packageJson = await readJsonFile<PackageJson>(rootPackageJsonPath)\n\n if (!packageJson) {\n return null\n }\n\n // Check npm/pnpm overrides\n if (packageJson.overrides && packageName in packageJson.overrides) {\n return {\n mechanism: 'npm-overrides',\n packageJsonPath: rootPackageJsonPath,\n versionRange: packageJson.overrides[packageName],\n }\n }\n\n // Check pnpm.overrides (alternative location)\n if (packageJson.pnpm?.overrides && packageName in packageJson.pnpm.overrides) {\n return {\n mechanism: 'pnpm-overrides',\n packageJsonPath: rootPackageJsonPath,\n versionRange: packageJson.pnpm.overrides[packageName],\n }\n }\n\n // Check yarn resolutions\n if (packageJson.resolutions && packageName in packageJson.resolutions) {\n return {\n mechanism: 'yarn-resolutions',\n packageJsonPath: rootPackageJsonPath,\n versionRange: packageJson.resolutions[packageName],\n }\n }\n\n return null\n}\n\n/**\n * Finds installed package in node_modules, walking up from startDir.\n * Also extracts \\@sanity/cli dependency range from sanity package if applicable.\n *\n * Handles both hoisted (npm/yarn) and nested (pnpm) node_modules structures.\n *\n * When workspaceRoot is omitted, walks to the filesystem root instead of stopping\n * at the workspace root.\n */\nexport async function findInstalledPackage(\n packageName: SanityPackage,\n startDir: string,\n workspaceRoot?: string,\n): Promise<InstalledPackage | null> {\n let currentDir = path.resolve(startDir)\n const fsRoot = path.parse(currentDir).root\n\n while (currentDir !== fsRoot) {\n // First, check the top-level node_modules (works for npm, yarn, and hoisted pnpm)\n const result = await findPackageInNodeModules(\n packageName,\n path.join(currentDir, 'node_modules'),\n )\n if (result) {\n return result\n }\n\n // For @sanity/cli, use Node's module resolution from sanity's location.\n // This handles all package manager layouts (pnpm nested deps, hoisting, etc.)\n if (packageName === '@sanity/cli') {\n const sanityPath = await resolvePackagePath(path.join(currentDir, 'node_modules', 'sanity'))\n if (sanityPath) {\n const nestedResult = await resolveCliFromSanity(sanityPath)\n if (nestedResult) {\n return nestedResult\n }\n }\n }\n\n // Stop at workspace root if provided, otherwise continue to filesystem root\n if (workspaceRoot && currentDir === workspaceRoot) {\n break\n }\n\n currentDir = path.dirname(currentDir)\n }\n\n return null\n}\n\n/**\n * Looks for a package in a specific node_modules directory.\n */\nasync function findPackageInNodeModules(\n packageName: SanityPackage,\n nodeModulesDir: string,\n): Promise<InstalledPackage | null> {\n const packagePath = path.join(nodeModulesDir, packageName)\n const resolvedPath = await resolvePackagePath(packagePath)\n\n if (!resolvedPath) {\n return null\n }\n\n const packageJsonPath = path.join(resolvedPath, 'package.json')\n const packageJson = await readJsonFile<PackageJson>(packageJsonPath)\n\n if (!packageJson?.version) {\n return null\n }\n\n let cliDependencyRange: string | null = null\n\n // If this is the sanity package, extract @sanity/cli dependency\n if (packageName === 'sanity') {\n cliDependencyRange = packageJson.dependencies?.['@sanity/cli'] ?? null\n }\n\n return {\n cliDependencyRange,\n path: resolvedPath,\n version: packageJson.version,\n }\n}\n\n/**\n * Resolves a package path, following symlinks (pnpm uses symlinks).\n * Returns null if the path doesn't exist.\n */\nasync function resolvePackagePath(packagePath: string): Promise<string | null> {\n try {\n // Use realpath to follow symlinks (important for pnpm)\n return await fs.realpath(packagePath)\n } catch {\n return null\n }\n}\n\n/**\n * Uses Node's module resolution to find \\@sanity/cli from sanity's location.\n * This is more robust than manually traversing directories as it handles\n * all package manager layouts (pnpm nested deps, hoisting variations, etc.)\n */\nasync function resolveCliFromSanity(sanityPath: string): Promise<InstalledPackage | null> {\n try {\n // Resolve @sanity/cli/package.json from sanity's perspective\n const sanityPkgUrl = pathToFileURL(path.join(sanityPath, 'package.json')).href\n const cliPkgUrl = resolveImport('@sanity/cli/package.json', sanityPkgUrl)\n const cliPkgPath = fileURLToPath(cliPkgUrl)\n const cliPath = path.dirname(cliPkgPath)\n\n const packageJson = await readJsonFile<PackageJson>(cliPkgPath)\n if (!packageJson?.version) {\n return null\n }\n\n return {\n cliDependencyRange: null,\n path: cliPath,\n version: packageJson.version,\n }\n } catch {\n return null\n }\n}\n\n/**\n * Collects all package info (declaration, override, installed) for a package.\n */\nexport async function collectPackageInfo(\n packageName: SanityPackage,\n startDir: string,\n workspaceInfo: WorkspaceInfo,\n): Promise<PackageInfo> {\n const [declared, override, installed] = await Promise.all([\n findPackageDeclaration(packageName, startDir, workspaceInfo),\n findPackageOverride(packageName, workspaceInfo),\n findInstalledPackage(packageName, startDir, workspaceInfo.root),\n ])\n\n return {declared, installed, override}\n}\n"],"names":["fs","path","fileURLToPath","pathToFileURL","resolve","resolveImport","readJsonFile","resolveVersionRange","findPackageDeclaration","packageName","startDir","workspaceInfo","currentDir","fsRoot","parse","root","packageJsonPath","join","packageJson","depTypes","depType","deps","declaredVersionRange","versionRange","dependencyType","dirname","findPackageOverride","rootPackageJsonPath","overrides","mechanism","pnpm","resolutions","findInstalledPackage","workspaceRoot","result","findPackageInNodeModules","sanityPath","resolvePackagePath","nestedResult","resolveCliFromSanity","nodeModulesDir","packagePath","resolvedPath","version","cliDependencyRange","dependencies","realpath","sanityPkgUrl","href","cliPkgUrl","cliPkgPath","cliPath","collectPackageInfo","declared","override","installed","Promise","all"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/util/packageManager/installationInfo/types.ts"],"sourcesContent":["import {type PackageManager} from '../packageManagerChoice.js'\n\n/** Lockfile types — excludes 'manual' since lockfiles always map to a real package manager. */\nexport type LockfileType = Exclude<PackageManager, 'manual'>\n\nexport type WorkspaceType =\n | 'bun-workspaces'\n | 'npm-workspaces'\n | 'pnpm-workspaces'\n | 'standalone'\n | 'yarn-workspaces'\n\nexport type SanityPackage = '@sanity/cli' | 'sanity'\n\nexport interface CliInstallationInfo {\n currentExecution: ExecutionContext\n globalInstallations: GlobalInstallation[]\n issues: Issue[]\n packages: Partial<Record<SanityPackage, PackageInfo>>\n workspace: WorkspaceInfo\n}\n\nexport interface ExecutionContext {\n binaryPath: string | null\n packageManager: PackageManager | null\n resolvedFrom: 'global' | 'local' | 'npx' | 'unknown'\n}\n\nexport interface WorkspaceInfo {\n /** True when bunfig.toml exists at workspace root (bun project marker). */\n bunfig: boolean\n hasMultipleLockfiles: boolean\n lockfile: {\n path: string\n type: LockfileType\n } | null\n nearestPackageJson: string | null\n root: string\n type: WorkspaceType\n /** True when .yarnrc.yml exists at workspace root (Yarn Berry / v2+). */\n yarnBerry: boolean\n}\n\nexport interface PackageInfo {\n declared: PackageDeclaration | null\n installed: InstalledPackage | null\n override: PackageOverride | null\n}\n\nexport interface PackageDeclaration {\n declaredVersionRange: string\n dependencyType: 'dependencies' | 'devDependencies'\n packageJsonPath: string\n versionRange: string\n}\n\nexport interface PackageOverride {\n mechanism: 'npm-overrides' | 'pnpm-overrides' | 'yarn-resolutions'\n packageJsonPath: string\n versionRange: string\n}\n\nexport interface InstalledPackage {\n cliDependencyRange: string | null\n path: string\n version: string\n}\n\nexport interface GlobalInstallation {\n isActive: boolean\n packageManager: LockfileType\n packageName: SanityPackage\n /** Filesystem path to the global installation, or null when it can't be determined. */\n path: string | null\n version: string\n}\n\
|
|
1
|
+
{"version":3,"sources":["../../../../src/util/packageManager/installationInfo/types.ts"],"sourcesContent":["import {type PackageManager} from '../packageManagerChoice.js'\n\n/** Lockfile types — excludes 'manual' since lockfiles always map to a real package manager. */\nexport type LockfileType = Exclude<PackageManager, 'manual'>\n\nexport type WorkspaceType =\n | 'bun-workspaces'\n | 'npm-workspaces'\n | 'pnpm-workspaces'\n | 'standalone'\n | 'yarn-workspaces'\n\nexport type SanityPackage = '@sanity/cli' | 'sanity'\n\nexport interface CliInstallationInfo {\n currentExecution: ExecutionContext\n globalInstallations: GlobalInstallation[]\n issues: Issue[]\n packages: Partial<Record<SanityPackage, PackageInfo>>\n workspace: WorkspaceInfo\n}\n\nexport interface ExecutionContext {\n binaryPath: string | null\n packageManager: PackageManager | null\n resolvedFrom: 'global' | 'local' | 'npx' | 'unknown'\n}\n\nexport interface WorkspaceInfo {\n /** True when bunfig.toml exists at workspace root (bun project marker). */\n bunfig: boolean\n hasMultipleLockfiles: boolean\n lockfile: {\n path: string\n type: LockfileType\n } | null\n nearestPackageJson: string | null\n root: string\n type: WorkspaceType\n /** True when .yarnrc.yml exists at workspace root (Yarn Berry / v2+). */\n yarnBerry: boolean\n}\n\nexport interface PackageInfo {\n declared: PackageDeclaration | null\n installed: InstalledPackage | null\n override: PackageOverride | null\n}\n\nexport interface PackageDeclaration {\n declaredVersionRange: string\n dependencyType: 'dependencies' | 'devDependencies'\n packageJsonPath: string\n versionRange: string\n}\n\nexport interface PackageOverride {\n mechanism: 'npm-overrides' | 'pnpm-overrides' | 'yarn-resolutions'\n packageJsonPath: string\n versionRange: string\n}\n\nexport interface InstalledPackage {\n cliDependencyRange: string | null\n path: string\n version: string\n}\n\nexport interface GlobalInstallation {\n isActive: boolean\n packageManager: LockfileType\n packageName: SanityPackage\n /** Filesystem path to the global installation, or null when it can't be determined. */\n path: string | null\n version: string\n}\n\ntype IssueType =\n | 'cli-not-installed'\n | 'cli-version-incompatible'\n | 'conflicting-cli-dependency'\n | 'declared-not-installed'\n | 'global-cli-incompatible'\n | 'global-local-mismatch'\n | 'multiple-lockfiles'\n | 'override-in-effect'\n | 'redundant-cli-dependency'\n\ntype IssueSeverity = 'error' | 'info' | 'warning'\n\nexport interface Issue {\n message: string\n packageName: SanityPackage | null\n severity: IssueSeverity\n suggestion: string | null\n type: IssueType\n}\n"],"names":[],"mappings":"AA0FA,WAMC"}
|
|
@@ -2,8 +2,8 @@ import path from 'node:path';
|
|
|
2
2
|
import { isInteractive } from '@sanity/cli-core';
|
|
3
3
|
import { getRunningPackageManager } from '@sanity/cli-core/package-manager';
|
|
4
4
|
import { select } from '@sanity/cli-core/ux';
|
|
5
|
-
import { preferredPM } from 'preferred-pm';
|
|
6
5
|
import which from 'which';
|
|
6
|
+
import { preferredPm } from './preferredPm.js';
|
|
7
7
|
const EXPERIMENTAL = new Set([
|
|
8
8
|
'bun'
|
|
9
9
|
]);
|
|
@@ -37,7 +37,7 @@ export const allowedPackageManagersString = ALLOWED_PACKAGE_MANAGERS.join(' | ')
|
|
|
37
37
|
* @returns Object of `chosen` and, if a lockfile is found, the `mostOptimal` choice
|
|
38
38
|
*/ export async function getPackageManagerChoice(workDir, options) {
|
|
39
39
|
const rootDir = workDir || process.cwd();
|
|
40
|
-
const preferred = (
|
|
40
|
+
const preferred = preferredPm(rootDir) ?? undefined;
|
|
41
41
|
if (preferred && await hasCommand(preferred, rootDir)) {
|
|
42
42
|
// There is an optimal/preferred package manager, and the user has it installed!
|
|
43
43
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/util/packageManager/packageManagerChoice.ts"],"sourcesContent":["import path from 'node:path'\n\nimport {isInteractive} from '@sanity/cli-core'\nimport {getRunningPackageManager} from '@sanity/cli-core/package-manager'\nimport {select} from '@sanity/cli-core/ux'\nimport {preferredPM} from 'preferred-pm'\nimport which from 'which'\n\nexport type PackageManager = 'bun' | 'manual' | 'npm' | 'pnpm' | 'yarn'\n\nconst EXPERIMENTAL = new Set(['bun'])\n\nexport const ALLOWED_PACKAGE_MANAGERS: readonly PackageManager[] = [\n 'npm',\n 'yarn',\n 'pnpm',\n 'bun',\n 'manual',\n] as const\n\nexport const allowedPackageManagersString = ALLOWED_PACKAGE_MANAGERS.join(' | ')\n\n/**\n * Attempts to resolve the most optimal package manager to use to install/upgrade\n * packages/dependencies at a given path. It does so by looking for package manager\n * specific lockfiles. If it finds a lockfile belonging to a certain package manager,\n * it prioritizes this one. However, if that package manager is not installed, it will\n * prompt the user for which one they want to use and hint at the most optimal one\n * not being installed.\n *\n * Note that this function also takes local npm binary paths into account - for instance,\n * `yarn` can be installed as a dependency of the project instead of globally, and it\n * will use that is available.\n *\n * The user can also select 'manual' to skip the process and run their preferred package\n * manager manually. Commands using this function must take this `manual` choice into\n * account and act accordingly if chosen.\n *\n * @param workDir - The working directory where a lockfile is most likely to be present\n * @param options - Pass `interactive: false` to fall back to npm if most optimal is\n * not available, instead of prompting\n * @returns Object of `chosen` and, if a lockfile is found, the `mostOptimal` choice\n */\nexport async function getPackageManagerChoice(\n workDir: string,\n options: {interactive: boolean},\n): Promise<{chosen: PackageManager; mostOptimal?: PackageManager}> {\n const rootDir = workDir || process.cwd()\n const preferred = (await preferredPM(rootDir))?.name\n\n if (preferred && (await hasCommand(preferred, rootDir))) {\n // There is an optimal/preferred package manager, and the user has it installed!\n return {chosen: preferred, mostOptimal: preferred}\n }\n\n const mostLikelyPM = await getMostLikelyInstalledPackageManager(rootDir)\n const interactive =\n typeof options.interactive === 'boolean' ? options.interactive : isInteractive()\n if (!interactive) {\n // We can't ask the user for their preference, so fall back to either the one that is being run\n // or whatever is installed on the system (npm being the preferred choice).\n // Note that the most optimal choice is already picked above if available.\n return {chosen: mostLikelyPM || (await getFallback(rootDir)), mostOptimal: preferred}\n }\n\n // We can ask the user for their preference, hurray!\n const messageSuffix = preferred ? ` (preferred is ${preferred}, but is not installed)` : ''\n const installed = await getAvailablePackageManagers(rootDir)\n const chosen = await select<PackageManager>({\n choices: installed.map((pm) => ({\n name: EXPERIMENTAL.has(pm) ? `${pm} (experimental)` : pm,\n value: pm,\n })),\n default: preferred || mostLikelyPM,\n message: `Package manager to use for installing dependencies?${messageSuffix}`,\n })\n\n return {chosen, mostOptimal: preferred}\n}\n\nasync function getFallback(cwd: string): Promise<PackageManager> {\n if (await hasNpmInstalled(cwd)) {\n return 'npm'\n }\n\n if (await hasYarnInstalled(cwd)) {\n return 'yarn'\n }\n\n if (await hasPnpmInstalled(cwd)) {\n return 'pnpm'\n }\n\n if (await hasBunInstalled(cwd)) {\n return 'bun'\n }\n\n return 'manual'\n}\n\nasync function getAvailablePackageManagers(cwd: string): Promise<PackageManager[]> {\n const [npm, yarn, pnpm, bun] = await Promise.all([\n hasNpmInstalled(cwd),\n hasYarnInstalled(cwd),\n hasPnpmInstalled(cwd),\n hasBunInstalled(cwd),\n ])\n\n const choices = [npm && 'npm', yarn && 'yarn', pnpm && 'pnpm', bun && 'bun', 'manual']\n return choices.filter((pm): pm is PackageManager => pm !== false)\n}\n\nfunction hasNpmInstalled(cwd?: string): Promise<boolean> {\n return hasCommand('npm', cwd)\n}\n\nfunction hasYarnInstalled(cwd?: string): Promise<boolean> {\n return hasCommand('yarn', cwd)\n}\n\nfunction hasPnpmInstalled(cwd?: string): Promise<boolean> {\n return hasCommand('pnpm', cwd)\n}\n\nfunction hasBunInstalled(cwd?: string): Promise<boolean> {\n return hasCommand('bun', cwd)\n}\n\nfunction getNpmRunPath(cwd: string): string {\n let previous\n let cwdPath = path.resolve(cwd)\n const result: string[] = []\n\n while (previous !== cwdPath) {\n result.push(path.join(cwdPath, 'node_modules', '.bin'))\n previous = cwdPath\n cwdPath = path.resolve(cwdPath, '..')\n }\n\n result.push(path.resolve(cwd, process.execPath, '..'))\n\n const pathEnv = process.env[getPathEnvVarKey()]\n return [...result, pathEnv].join(path.delimiter)\n}\n\nexport function getPartialEnvWithNpmPath(cwd: string): NodeJS.ProcessEnv {\n const key = getPathEnvVarKey()\n return {[key]: getNpmRunPath(cwd)}\n}\n\nfunction getPathEnvVarKey(): string {\n if (process.platform !== 'win32') {\n return 'PATH'\n }\n\n return (\n Object.keys(process.env)\n .toReversed()\n .find((key) => key.toUpperCase() === 'PATH') || 'Path'\n )\n}\n\nfunction getCommandPath(cmd: string, cwd?: string): Promise<string | null> {\n const options = cwd ? {path: getNpmRunPath(cwd)} : undefined\n return which(cmd, options).catch(() => null)\n}\n\nfunction hasCommand(cmd: string, cwd?: string): Promise<boolean> {\n return getCommandPath(cmd, cwd).then((cmdPath) => cmdPath !== null)\n}\n\nasync function getMostLikelyInstalledPackageManager(\n rootDir: string,\n): Promise<PackageManager | undefined> {\n const installed = await getAvailablePackageManagers(rootDir)\n const running = getRunningPackageManager()\n return running && installed.includes(running) ? running : undefined\n}\n"],"names":["path","isInteractive","getRunningPackageManager","select","preferredPM","which","EXPERIMENTAL","Set","ALLOWED_PACKAGE_MANAGERS","allowedPackageManagersString","join","getPackageManagerChoice","workDir","options","rootDir","process","cwd","preferred","name","hasCommand","chosen","mostOptimal","mostLikelyPM","getMostLikelyInstalledPackageManager","interactive","getFallback","messageSuffix","installed","getAvailablePackageManagers","choices","map","pm","has","value","default","message","hasNpmInstalled","hasYarnInstalled","hasPnpmInstalled","hasBunInstalled","npm","yarn","pnpm","bun","Promise","all","filter","getNpmRunPath","previous","cwdPath","resolve","result","push","execPath","pathEnv","env","getPathEnvVarKey","delimiter","getPartialEnvWithNpmPath","key","platform","Object","keys","toReversed","find","toUpperCase","getCommandPath","cmd","undefined","catch","then","cmdPath","running","includes"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/util/packageManager/packageManagerChoice.ts"],"sourcesContent":["import path from 'node:path'\n\nimport {isInteractive} from '@sanity/cli-core'\nimport {getRunningPackageManager} from '@sanity/cli-core/package-manager'\nimport {select} from '@sanity/cli-core/ux'\nimport which from 'which'\n\nimport {preferredPm} from './preferredPm.js'\n\nexport type PackageManager = 'bun' | 'manual' | 'npm' | 'pnpm' | 'yarn'\n\nconst EXPERIMENTAL = new Set(['bun'])\n\nexport const ALLOWED_PACKAGE_MANAGERS: readonly PackageManager[] = [\n 'npm',\n 'yarn',\n 'pnpm',\n 'bun',\n 'manual',\n] as const\n\nexport const allowedPackageManagersString = ALLOWED_PACKAGE_MANAGERS.join(' | ')\n\n/**\n * Attempts to resolve the most optimal package manager to use to install/upgrade\n * packages/dependencies at a given path. It does so by looking for package manager\n * specific lockfiles. If it finds a lockfile belonging to a certain package manager,\n * it prioritizes this one. However, if that package manager is not installed, it will\n * prompt the user for which one they want to use and hint at the most optimal one\n * not being installed.\n *\n * Note that this function also takes local npm binary paths into account - for instance,\n * `yarn` can be installed as a dependency of the project instead of globally, and it\n * will use that is available.\n *\n * The user can also select 'manual' to skip the process and run their preferred package\n * manager manually. Commands using this function must take this `manual` choice into\n * account and act accordingly if chosen.\n *\n * @param workDir - The working directory where a lockfile is most likely to be present\n * @param options - Pass `interactive: false` to fall back to npm if most optimal is\n * not available, instead of prompting\n * @returns Object of `chosen` and, if a lockfile is found, the `mostOptimal` choice\n */\nexport async function getPackageManagerChoice(\n workDir: string,\n options: {interactive: boolean},\n): Promise<{chosen: PackageManager; mostOptimal?: PackageManager}> {\n const rootDir = workDir || process.cwd()\n const preferred = preferredPm(rootDir) ?? undefined\n\n if (preferred && (await hasCommand(preferred, rootDir))) {\n // There is an optimal/preferred package manager, and the user has it installed!\n return {chosen: preferred, mostOptimal: preferred}\n }\n\n const mostLikelyPM = await getMostLikelyInstalledPackageManager(rootDir)\n const interactive =\n typeof options.interactive === 'boolean' ? options.interactive : isInteractive()\n if (!interactive) {\n // We can't ask the user for their preference, so fall back to either the one that is being run\n // or whatever is installed on the system (npm being the preferred choice).\n // Note that the most optimal choice is already picked above if available.\n return {chosen: mostLikelyPM || (await getFallback(rootDir)), mostOptimal: preferred}\n }\n\n // We can ask the user for their preference, hurray!\n const messageSuffix = preferred ? ` (preferred is ${preferred}, but is not installed)` : ''\n const installed = await getAvailablePackageManagers(rootDir)\n const chosen = await select<PackageManager>({\n choices: installed.map((pm) => ({\n name: EXPERIMENTAL.has(pm) ? `${pm} (experimental)` : pm,\n value: pm,\n })),\n default: preferred || mostLikelyPM,\n message: `Package manager to use for installing dependencies?${messageSuffix}`,\n })\n\n return {chosen, mostOptimal: preferred}\n}\n\nasync function getFallback(cwd: string): Promise<PackageManager> {\n if (await hasNpmInstalled(cwd)) {\n return 'npm'\n }\n\n if (await hasYarnInstalled(cwd)) {\n return 'yarn'\n }\n\n if (await hasPnpmInstalled(cwd)) {\n return 'pnpm'\n }\n\n if (await hasBunInstalled(cwd)) {\n return 'bun'\n }\n\n return 'manual'\n}\n\nasync function getAvailablePackageManagers(cwd: string): Promise<PackageManager[]> {\n const [npm, yarn, pnpm, bun] = await Promise.all([\n hasNpmInstalled(cwd),\n hasYarnInstalled(cwd),\n hasPnpmInstalled(cwd),\n hasBunInstalled(cwd),\n ])\n\n const choices = [npm && 'npm', yarn && 'yarn', pnpm && 'pnpm', bun && 'bun', 'manual']\n return choices.filter((pm): pm is PackageManager => pm !== false)\n}\n\nfunction hasNpmInstalled(cwd?: string): Promise<boolean> {\n return hasCommand('npm', cwd)\n}\n\nfunction hasYarnInstalled(cwd?: string): Promise<boolean> {\n return hasCommand('yarn', cwd)\n}\n\nfunction hasPnpmInstalled(cwd?: string): Promise<boolean> {\n return hasCommand('pnpm', cwd)\n}\n\nfunction hasBunInstalled(cwd?: string): Promise<boolean> {\n return hasCommand('bun', cwd)\n}\n\nfunction getNpmRunPath(cwd: string): string {\n let previous\n let cwdPath = path.resolve(cwd)\n const result: string[] = []\n\n while (previous !== cwdPath) {\n result.push(path.join(cwdPath, 'node_modules', '.bin'))\n previous = cwdPath\n cwdPath = path.resolve(cwdPath, '..')\n }\n\n result.push(path.resolve(cwd, process.execPath, '..'))\n\n const pathEnv = process.env[getPathEnvVarKey()]\n return [...result, pathEnv].join(path.delimiter)\n}\n\nexport function getPartialEnvWithNpmPath(cwd: string): NodeJS.ProcessEnv {\n const key = getPathEnvVarKey()\n return {[key]: getNpmRunPath(cwd)}\n}\n\nfunction getPathEnvVarKey(): string {\n if (process.platform !== 'win32') {\n return 'PATH'\n }\n\n return (\n Object.keys(process.env)\n .toReversed()\n .find((key) => key.toUpperCase() === 'PATH') || 'Path'\n )\n}\n\nfunction getCommandPath(cmd: string, cwd?: string): Promise<string | null> {\n const options = cwd ? {path: getNpmRunPath(cwd)} : undefined\n return which(cmd, options).catch(() => null)\n}\n\nfunction hasCommand(cmd: string, cwd?: string): Promise<boolean> {\n return getCommandPath(cmd, cwd).then((cmdPath) => cmdPath !== null)\n}\n\nasync function getMostLikelyInstalledPackageManager(\n rootDir: string,\n): Promise<PackageManager | undefined> {\n const installed = await getAvailablePackageManagers(rootDir)\n const running = getRunningPackageManager()\n return running && installed.includes(running) ? running : undefined\n}\n"],"names":["path","isInteractive","getRunningPackageManager","select","which","preferredPm","EXPERIMENTAL","Set","ALLOWED_PACKAGE_MANAGERS","allowedPackageManagersString","join","getPackageManagerChoice","workDir","options","rootDir","process","cwd","preferred","undefined","hasCommand","chosen","mostOptimal","mostLikelyPM","getMostLikelyInstalledPackageManager","interactive","getFallback","messageSuffix","installed","getAvailablePackageManagers","choices","map","pm","name","has","value","default","message","hasNpmInstalled","hasYarnInstalled","hasPnpmInstalled","hasBunInstalled","npm","yarn","pnpm","bun","Promise","all","filter","getNpmRunPath","previous","cwdPath","resolve","result","push","execPath","pathEnv","env","getPathEnvVarKey","delimiter","getPartialEnvWithNpmPath","key","platform","Object","keys","toReversed","find","toUpperCase","getCommandPath","cmd","catch","then","cmdPath","running","includes"],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Based on preferred-pm (MIT) by Zoltan Kochan — https://github.com/zkochan/packages
|
|
2
|
+
// Based on which-pm (MIT) by pnpm — https://github.com/pnpm/which-pm
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import picomatch from 'picomatch';
|
|
6
|
+
import { toForwardSlashes } from '../toForwardSlashes.js';
|
|
7
|
+
/**
|
|
8
|
+
* Detects the preferred package manager for a project by examining lock files,
|
|
9
|
+
* workspace configurations, and node_modules markers.
|
|
10
|
+
*/ export function preferredPm(pkgPath) {
|
|
11
|
+
const fromLockFile = detectFromLockFile(pkgPath);
|
|
12
|
+
if (fromLockFile) return fromLockFile;
|
|
13
|
+
const fromParentPnpm = findUp('pnpm-lock.yaml', pkgPath) || findUp('pnpm-workspace.yaml', pkgPath);
|
|
14
|
+
if (fromParentPnpm) return 'pnpm';
|
|
15
|
+
const fromWorkspace = detectFromWorkspaceRoot(pkgPath);
|
|
16
|
+
if (fromWorkspace) return fromWorkspace;
|
|
17
|
+
return detectFromNodeModules(pkgPath);
|
|
18
|
+
}
|
|
19
|
+
function detectFromLockFile(dir) {
|
|
20
|
+
if (fs.existsSync(path.join(dir, 'package-lock.json'))) return 'npm';
|
|
21
|
+
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';
|
|
24
|
+
if (fs.existsSync(path.join(dir, 'bun.lockb')) || fs.existsSync(path.join(dir, 'bun.lock'))) return 'bun';
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function findUp(filename, startDir) {
|
|
28
|
+
let dir = path.resolve(startDir);
|
|
29
|
+
const { root } = path.parse(dir);
|
|
30
|
+
while(dir){
|
|
31
|
+
const filePath = path.join(dir, filename);
|
|
32
|
+
if (fs.existsSync(filePath)) return filePath;
|
|
33
|
+
if (dir === root) break;
|
|
34
|
+
dir = path.dirname(dir);
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
function findNearestPackageDir(startDir) {
|
|
39
|
+
let dir = path.resolve(startDir);
|
|
40
|
+
const { root } = path.parse(dir);
|
|
41
|
+
while(dir !== root){
|
|
42
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
|
|
43
|
+
dir = path.dirname(dir);
|
|
44
|
+
}
|
|
45
|
+
return startDir;
|
|
46
|
+
}
|
|
47
|
+
function detectFromWorkspaceRoot(pkgPath) {
|
|
48
|
+
const resolvedPkgPath = path.resolve(pkgPath);
|
|
49
|
+
const packageDir = findNearestPackageDir(resolvedPkgPath);
|
|
50
|
+
let dir = resolvedPkgPath;
|
|
51
|
+
const { root } = path.parse(dir);
|
|
52
|
+
while(true){
|
|
53
|
+
const manifestPath = path.join(dir, 'package.json');
|
|
54
|
+
if (fs.existsSync(manifestPath)) {
|
|
55
|
+
try {
|
|
56
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
57
|
+
const workspaces = extractWorkspaces(manifest);
|
|
58
|
+
if (workspaces) {
|
|
59
|
+
const matchDir = packageDir === dir ? resolvedPkgPath : packageDir;
|
|
60
|
+
const relativePath = toForwardSlashes(path.relative(dir, matchDir));
|
|
61
|
+
if (relativePath === '' || picomatch.isMatch(relativePath, workspaces)) {
|
|
62
|
+
return detectFromLockFile(dir) ?? 'yarn';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// malformed package.json, skip
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (dir === root) break;
|
|
70
|
+
dir = path.dirname(dir);
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
function extractWorkspaces(manifest) {
|
|
75
|
+
const workspaces = manifest?.workspaces;
|
|
76
|
+
if (Array.isArray(workspaces)) return workspaces;
|
|
77
|
+
if (workspaces && typeof workspaces === 'object' && 'packages' in workspaces && Array.isArray(workspaces.packages)) {
|
|
78
|
+
return workspaces.packages;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function detectFromNodeModules(pkgPath) {
|
|
83
|
+
const modulesPath = path.join(pkgPath, 'node_modules');
|
|
84
|
+
if (fs.existsSync(path.join(modulesPath, '.yarn-integrity'))) return 'yarn';
|
|
85
|
+
try {
|
|
86
|
+
const modulesYaml = fs.readFileSync(path.join(modulesPath, '.modules.yaml'), 'utf8');
|
|
87
|
+
const pmLine = modulesYaml.split('\n').find((line)=>/^\s*"?packageManager"?\s*[:=]/.test(line));
|
|
88
|
+
if (pmLine) {
|
|
89
|
+
const valueMatch = pmLine.match(/[:=]\s*['"]?([^'",\s]+)/);
|
|
90
|
+
if (valueMatch) {
|
|
91
|
+
const pmSpec = valueMatch[1];
|
|
92
|
+
const name = pmSpec.startsWith('@') ? `@${pmSpec.slice(1).split('@')[0]}` : pmSpec.split('@')[0];
|
|
93
|
+
if (name === 'pnpm') return 'pnpm';
|
|
94
|
+
if (name === 'yarn') return 'yarn';
|
|
95
|
+
if (name === 'npm') return 'npm';
|
|
96
|
+
if (name === 'bun') return 'bun';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// best-effort detection — swallow all read errors
|
|
101
|
+
}
|
|
102
|
+
if (fs.existsSync(modulesPath)) return 'npm';
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
//# sourceMappingURL=preferredPm.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { getUserConfig, subdebug } from '@sanity/cli-core';
|
|
2
|
+
import { getLatestVersion } from 'get-latest-version';
|
|
3
|
+
import { promiseRaceWithTimeout } from '../promiseRaceWithTimeout.js';
|
|
4
|
+
import { resolveUpdateTarget } from './resolveUpdateTarget.js';
|
|
5
|
+
const debug = subdebug('updateChecker');
|
|
6
|
+
const FETCH_TIMEOUT = 15_000;
|
|
7
|
+
/**
|
|
8
|
+
* Fetch the latest version of the update target package and write it to the config cache.
|
|
9
|
+
* Designed to run in a detached child process so it never blocks the main CLI.
|
|
10
|
+
*
|
|
11
|
+
* When `packageOverride` is given, the cwd-based resolver is skipped — used by
|
|
12
|
+
* the main process to pin the worker to the same package it already resolved
|
|
13
|
+
* (e.g. via a runner's symlinked install) so cache reads and writes align.
|
|
14
|
+
*/ export async function fetchUpdateInfo(cwd, cliVersion, packageOverride) {
|
|
15
|
+
const { packageName } = packageOverride ? {
|
|
16
|
+
packageName: packageOverride
|
|
17
|
+
} : await resolveUpdateTarget(cwd, cliVersion);
|
|
18
|
+
debug('Worker: fetching latest version of %s', packageName);
|
|
19
|
+
let latestVersion;
|
|
20
|
+
try {
|
|
21
|
+
latestVersion = await promiseRaceWithTimeout(getLatestVersion(packageName), FETCH_TIMEOUT);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
debug('Worker: failed to fetch latest version of %s from npm: %s', packageName, err instanceof Error ? err.message : String(err));
|
|
24
|
+
throw err;
|
|
25
|
+
}
|
|
26
|
+
if (latestVersion === null) {
|
|
27
|
+
debug('Worker: fetch timed out after %dms', FETCH_TIMEOUT);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
debug('Worker: latest %s version is %s', packageName, latestVersion);
|
|
31
|
+
const store = getUserConfig();
|
|
32
|
+
const cacheKey = `latestVersion:${packageName}`;
|
|
33
|
+
store.set(cacheKey, {
|
|
34
|
+
updatedAt: Date.now(),
|
|
35
|
+
value: latestVersion
|
|
36
|
+
});
|
|
37
|
+
debug('Worker: cached result to %s', cacheKey);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=fetchUpdateInfo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/util/update/fetchUpdateInfo.ts"],"sourcesContent":["import {getUserConfig, subdebug} from '@sanity/cli-core'\nimport {getLatestVersion} from 'get-latest-version'\n\nimport {type SanityPackage} from '../packageManager/installationInfo/types.js'\nimport {promiseRaceWithTimeout} from '../promiseRaceWithTimeout.js'\nimport {resolveUpdateTarget} from './resolveUpdateTarget.js'\n\nconst debug = subdebug('updateChecker')\n\nconst FETCH_TIMEOUT = 15_000\n\n/**\n * Fetch the latest version of the update target package and write it to the config cache.\n * Designed to run in a detached child process so it never blocks the main CLI.\n *\n * When `packageOverride` is given, the cwd-based resolver is skipped — used by\n * the main process to pin the worker to the same package it already resolved\n * (e.g. via a runner's symlinked install) so cache reads and writes align.\n */\nexport async function fetchUpdateInfo(\n cwd: string,\n cliVersion: string,\n packageOverride?: SanityPackage,\n): Promise<void> {\n const {packageName} = packageOverride\n ? {packageName: packageOverride}\n : await resolveUpdateTarget(cwd, cliVersion)\n debug('Worker: fetching latest version of %s', packageName)\n\n let latestVersion: string | null | undefined\n try {\n latestVersion = await promiseRaceWithTimeout(getLatestVersion(packageName), FETCH_TIMEOUT)\n } catch (err) {\n debug(\n 'Worker: failed to fetch latest version of %s from npm: %s',\n packageName,\n err instanceof Error ? err.message : String(err),\n )\n throw err\n }\n\n if (latestVersion === null) {\n debug('Worker: fetch timed out after %dms', FETCH_TIMEOUT)\n return\n }\n\n debug('Worker: latest %s version is %s', packageName, latestVersion)\n\n const store = getUserConfig()\n const cacheKey = `latestVersion:${packageName}`\n\n store.set(cacheKey, {\n updatedAt: Date.now(),\n value: latestVersion,\n })\n\n debug('Worker: cached result to %s', cacheKey)\n}\n"],"names":["getUserConfig","subdebug","getLatestVersion","promiseRaceWithTimeout","resolveUpdateTarget","debug","FETCH_TIMEOUT","fetchUpdateInfo","cwd","cliVersion","packageOverride","packageName","latestVersion","err","Error","message","String","store","cacheKey","set","updatedAt","Date","now","value"],"mappings":"AAAA,SAAQA,aAAa,EAAEC,QAAQ,QAAO,mBAAkB;AACxD,SAAQC,gBAAgB,QAAO,qBAAoB;AAGnD,SAAQC,sBAAsB,QAAO,+BAA8B;AACnE,SAAQC,mBAAmB,QAAO,2BAA0B;AAE5D,MAAMC,QAAQJ,SAAS;AAEvB,MAAMK,gBAAgB;AAEtB;;;;;;;CAOC,GACD,OAAO,eAAeC,gBACpBC,GAAW,EACXC,UAAkB,EAClBC,eAA+B;IAE/B,MAAM,EAACC,WAAW,EAAC,GAAGD,kBAClB;QAACC,aAAaD;IAAe,IAC7B,MAAMN,oBAAoBI,KAAKC;IACnCJ,MAAM,yCAAyCM;IAE/C,IAAIC;IACJ,IAAI;QACFA,gBAAgB,MAAMT,uBAAuBD,iBAAiBS,cAAcL;IAC9E,EAAE,OAAOO,KAAK;QACZR,MACE,6DACAM,aACAE,eAAeC,QAAQD,IAAIE,OAAO,GAAGC,OAAOH;QAE9C,MAAMA;IACR;IAEA,IAAID,kBAAkB,MAAM;QAC1BP,MAAM,sCAAsCC;QAC5C;IACF;IAEAD,MAAM,mCAAmCM,aAAaC;IAEtD,MAAMK,QAAQjB;IACd,MAAMkB,WAAW,CAAC,cAAc,EAAEP,aAAa;IAE/CM,MAAME,GAAG,CAACD,UAAU;QAClBE,WAAWC,KAAKC,GAAG;QACnBC,OAAOX;IACT;IAEAP,MAAM,+BAA+Ba;AACvC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { fetchUpdateInfo } from './fetchUpdateInfo.js';
|
|
4
|
+
// Only run if executed directly (not imported)
|
|
5
|
+
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
6
|
+
const cwd = process.env.SANITY_UPDATE_CHECK_CWD || process.cwd();
|
|
7
|
+
const cliVersion = process.env.SANITY_UPDATE_CHECK_CLI_VERSION || '0.0.0';
|
|
8
|
+
const rawPackage = process.env.SANITY_UPDATE_CHECK_PACKAGE;
|
|
9
|
+
const packageOverride = rawPackage === 'sanity' || rawPackage === '@sanity/cli' ? rawPackage : undefined;
|
|
10
|
+
try {
|
|
11
|
+
await fetchUpdateInfo(cwd, cliVersion, packageOverride);
|
|
12
|
+
process.exit(0);
|
|
13
|
+
} catch {
|
|
14
|
+
// Silently exit - don't leave zombie processes
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=fetchUpdateInfo.worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/util/update/fetchUpdateInfo.worker.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport {pathToFileURL} from 'node:url'\n\nimport {type SanityPackage} from '../packageManager/installationInfo/types.js'\nimport {fetchUpdateInfo} from './fetchUpdateInfo.js'\n\n// Only run if executed directly (not imported)\nif (import.meta.url === pathToFileURL(process.argv[1]).href) {\n const cwd = process.env.SANITY_UPDATE_CHECK_CWD || process.cwd()\n const cliVersion = process.env.SANITY_UPDATE_CHECK_CLI_VERSION || '0.0.0'\n const rawPackage = process.env.SANITY_UPDATE_CHECK_PACKAGE\n const packageOverride: SanityPackage | undefined =\n rawPackage === 'sanity' || rawPackage === '@sanity/cli' ? rawPackage : undefined\n\n try {\n await fetchUpdateInfo(cwd, cliVersion, packageOverride)\n process.exit(0)\n } catch {\n // Silently exit - don't leave zombie processes\n process.exit(1)\n }\n}\n"],"names":["pathToFileURL","fetchUpdateInfo","url","process","argv","href","cwd","env","SANITY_UPDATE_CHECK_CWD","cliVersion","SANITY_UPDATE_CHECK_CLI_VERSION","rawPackage","SANITY_UPDATE_CHECK_PACKAGE","packageOverride","undefined","exit"],"mappings":";AAEA,SAAQA,aAAa,QAAO,WAAU;AAGtC,SAAQC,eAAe,QAAO,uBAAsB;AAEpD,+CAA+C;AAC/C,IAAI,YAAYC,GAAG,KAAKF,cAAcG,QAAQC,IAAI,CAAC,EAAE,EAAEC,IAAI,EAAE;IAC3D,MAAMC,MAAMH,QAAQI,GAAG,CAACC,uBAAuB,IAAIL,QAAQG,GAAG;IAC9D,MAAMG,aAAaN,QAAQI,GAAG,CAACG,+BAA+B,IAAI;IAClE,MAAMC,aAAaR,QAAQI,GAAG,CAACK,2BAA2B;IAC1D,MAAMC,kBACJF,eAAe,YAAYA,eAAe,gBAAgBA,aAAaG;IAEzE,IAAI;QACF,MAAMb,gBAAgBK,KAAKG,YAAYI;QACvCV,QAAQY,IAAI,CAAC;IACf,EAAE,OAAM;QACN,+CAA+C;QAC/CZ,QAAQY,IAAI,CAAC;IACf;AACF"}
|