chore: import upstream snapshot with attribution
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { statSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { parsePackageJSON, readFileSync } from "./parse";
|
||||
|
||||
/**
|
||||
* Resolves the filesystem path for an installed npm package.
|
||||
*
|
||||
* Tries two strategies:
|
||||
* 1. `require.resolve("<pkg>/package.json")` -- works when the package exports its package.json
|
||||
* 2. `require.resolve("<pkg>")` -- fallback for packages that don't export package.json
|
||||
*
|
||||
* @param packageName - The npm package name to resolve
|
||||
* @param projectPath - The project directory to resolve from
|
||||
* @returns The resolved directory path, or `undefined` if the package is not installed
|
||||
*/
|
||||
export function getPackagePath(
|
||||
packageName: string,
|
||||
projectPath: string
|
||||
): string | undefined {
|
||||
try {
|
||||
// Try to resolve the package.json directly — works when the package exports it
|
||||
return path.dirname(
|
||||
require.resolve(`${packageName}/package.json`, {
|
||||
paths: [projectPath],
|
||||
})
|
||||
);
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
// Fallback: resolve the package entry point and return its directory
|
||||
return path.dirname(
|
||||
require.resolve(packageName, {
|
||||
paths: [projectPath],
|
||||
})
|
||||
);
|
||||
} catch {}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an npm package is installed in a target project.
|
||||
*
|
||||
* @param packageName - The name of the target package
|
||||
* @param projectPath - The path of the project to check
|
||||
* @returns `true` if the package is installed, `false` otherwise
|
||||
*/
|
||||
export function isPackageInstalled(
|
||||
packageName: string,
|
||||
projectPath: string
|
||||
): boolean {
|
||||
return !!getPackagePath(packageName, projectPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the exact version of an npm package installed in a project by resolving
|
||||
* it from node_modules and reading its package.json.
|
||||
*
|
||||
* @param packageName - The name of the target package
|
||||
* @param projectPath - The path of the project to check
|
||||
* @param opts - Options
|
||||
* @param opts.stopAtProjectPath - If `true`, stop walking up at the project's path
|
||||
* @returns The installed version string, or `undefined` if the package is not installed
|
||||
*/
|
||||
export function getInstalledPackageVersion(
|
||||
packageName: string,
|
||||
projectPath: string,
|
||||
opts: {
|
||||
stopAtProjectPath?: boolean;
|
||||
} = {}
|
||||
): string | undefined {
|
||||
try {
|
||||
const packagePath = getPackagePath(packageName, projectPath);
|
||||
if (!packagePath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const lastDir = opts.stopAtProjectPath === true ? projectPath : undefined;
|
||||
const packageJsonPath = findFileUp("package.json", packagePath, lastDir);
|
||||
|
||||
if (!packageJsonPath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const packageJson = parsePackageJSON(
|
||||
readFileSync(packageJsonPath),
|
||||
packageJsonPath
|
||||
);
|
||||
// The requested package may be installed under an alias (e.g. vite+
|
||||
// installs `@voidzero-dev/vite-plus-core` under the `vite` alias). In that
|
||||
// case the resolved package.json belongs to the aliased package, so its
|
||||
// `version` is not the version of the requested package.
|
||||
//
|
||||
// `bundledVersions` is NOT a standard package.json field (it is not the
|
||||
// standard `bundledDependencies`) — it is a vite+ convention that maps the
|
||||
// names of the tools it bundles to the versions it provides. When the
|
||||
// resolved package name doesn't match the requested one, prefer the version
|
||||
// declared there for the requested package.
|
||||
if (packageJson.name !== packageName) {
|
||||
const bundledVersion = packageJson.bundledVersions?.[packageName];
|
||||
if (bundledVersion !== undefined) {
|
||||
return bundledVersion;
|
||||
}
|
||||
}
|
||||
return packageJson.version;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks up from `startDir` looking for a file named `name`.
|
||||
* Stops at `lastDir` (inclusive) if provided, otherwise walks to the filesystem root.
|
||||
*
|
||||
* @param name - The filename to search for
|
||||
* @param startDir - The directory to start searching from
|
||||
* @param lastDir - If provided, stop searching after reaching this directory
|
||||
* @returns The full path to the found file, or `undefined`
|
||||
*/
|
||||
function findFileUp(
|
||||
name: string,
|
||||
startDir: string,
|
||||
lastDir?: string
|
||||
): string | undefined {
|
||||
let dir = startDir;
|
||||
const root = path.parse(dir).root;
|
||||
|
||||
while (true) {
|
||||
const candidate = path.join(dir, name);
|
||||
try {
|
||||
if (statSync(candidate).isFile()) {
|
||||
return candidate;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (lastDir !== undefined && dir === lastDir) {
|
||||
break;
|
||||
}
|
||||
|
||||
const parent = path.dirname(dir);
|
||||
if (parent === dir || dir === root) {
|
||||
break;
|
||||
}
|
||||
dir = parent;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user