UNPKG

@typespec/compiler

Version:

TypeSpec compiler and standard library

152 lines 6.25 kB
import semverParse from "semver/functions/parse.js"; import semverSatisfies from "semver/functions/satisfies.js"; import semverValid from "semver/functions/valid.js"; import semverValidRange from "semver/ranges/valid.js"; import { DiagnosticError } from "../core/diagnostic-error.js"; import { getDirectoryPath, joinPaths } from "../core/path-utils.js"; import { isSupportedPackageManager } from "./config.js"; const nodeModulesRegExp = /\/node_modules\//; /** * Resolve the package manager required for the current working directory. * @throws {PackageManagerSpecError} if there is error resolving it(Invalid package.json, invalid packageManager field) */ export async function resolvePackageManagerSpec(host, parentTracer, cwd) { const tracer = parentTracer.sub("pm-spec"); let nextCwd = cwd; let currentCwd = ""; let resolved = null; while (nextCwd !== currentCwd && !resolved) { currentCwd = nextCwd; nextCwd = getDirectoryPath(currentCwd); if (nodeModulesRegExp.test(currentCwd)) continue; const pkgJsonPath = joinPaths(currentCwd, "package.json"); const packageJson = await readPackage(host, pkgJsonPath); if (packageJson) { resolved = packageJson; } } if (resolved === null) { return { kind: "no-package", path: cwd }; } const [packageJson, source] = resolved; const spec = extractSpecFromPackageJson(tracer, packageJson, source); if (spec === undefined) { return { kind: "no-spec", path: source.path }; } tracer.trace("resolved", `${source.path} defines ${spec} as local package manager`); return { kind: "resolved", path: source.path, spec, }; } function serializeSpec(spec) { return `${spec.name}@${spec.range}${spec.hash ? `+${spec.hash.algorithm}.${spec.hash.value}` : ""}`; } export async function updatePackageManagerInPackageJson(host, path, spec) { const [pkg] = (await readPackage(host, path)); pkg.packageManager = serializeSpec(spec); return host.writeFile(path, JSON.stringify(pkg, undefined, 2)); } async function readPackage(host, path) { let file; try { file = await host.readFile(path); } catch (err) { if (err?.code === "ENOENT") return undefined; throw err; } let packageJson; try { packageJson = JSON.parse(file.text); } catch { return undefined; } if (typeof packageJson !== "object" || packageJson === null) { throw new PackageManagerSpecError(`Invalid package.json ${path}}`, file); } return [packageJson, file]; } function extractSpecFromPackageJson(tracer, packageJSONContent, source) { const pm = packageJSONContent.packageManager ? parsePackageManagerField(packageJSONContent.packageManager, source) : undefined; const devEnginePm = packageJSONContent.devEngines?.packageManager; if (devEnginePm != null) { if (typeof devEnginePm !== "object") { return pm; } if (Array.isArray(devEnginePm)) { return pm; } const { name, version } = devEnginePm; validatePackageManager(name, source); if (typeof name !== "string" || name.includes("@")) { throw new PackageManagerSpecError(`devEngines.packageManager.name ${JSON.stringify(name)} must be a package manager name without the version`, source); } if (version == null || typeof version !== "string" || !semverValidRange(version)) { throw new PackageManagerSpecError(`devEngines.packageManager.version ${JSON.stringify(version)} is not a valid semver range`, source); } validateVersionNotUrl(version, source); tracer.trace("devEngines", `devEngines.packageManager specify ${name}@${version}`); if (pm) { if (pm.name !== name) throw new PackageManagerSpecError(`"packageManager" field "${pm}" does not match name of "devEngines.packageManager" set to "${name}"`, source); else if (version != null && !semverSatisfies(pm.range, version)) throw new PackageManagerSpecError(`"packageManager" field is set to "${pm}" does not match the version defined in "devEngines.packageManager" "${name}" @ "${version}"`, source); return pm; } return { name, range: version }; } return pm; } function validatePackageManager(name, source) { if (!isSupportedPackageManager(name)) { throw new PackageManagerSpecError(`Unsupported packageManager specification: Package manager ${name} is not supported.`, source); } } function validateVersionNotUrl(version, source) { const isURL = URL.canParse(version); if (isURL) { throw new PackageManagerSpecError(`Urls are not supported for package manager specifications "${version}"`, source); } } function parsePackageManagerField(raw, source) { if (typeof raw !== "string") { throw new PackageManagerSpecError(`Invalid packageManager field : expected a string`, source); } const atIndex = raw.indexOf("@"); if (atIndex === -1 || atIndex === raw.length - 1) { throw new PackageManagerSpecError(`Invalid packageManager field "${raw}": a version to be specified for package manager.`, source); } const name = raw.slice(0, atIndex); const version = raw.slice(atIndex + 1); validatePackageManager(name, source); validateVersionNotUrl(version, source); if (!semverValid(version)) throw new PackageManagerSpecError(`Invalid packageManager field "${raw}": expected a semver version`, source); const semver = semverParse(version); const hash = semver.build[0] && semver.build[1] ? { algorithm: semver.build[0], value: semver.build[1] } : undefined; return { name, range: semver.version, hash, }; } export class PackageManagerSpecError extends DiagnosticError { constructor(message, source) { super({ code: "invalid-package-manager-spec", message, severity: "error", target: { file: source, pos: 0, end: 0 }, }); } } //# sourceMappingURL=spec.js.map