From adc87d8b640793753d2b7bd9483091eaa12b0afa Mon Sep 17 00:00:00 2001 From: David Blass Date: Wed, 17 Dec 2025 18:00:36 -0500 Subject: [PATCH] check packageManager --- entry | 64 +++++++++++++++-------- fixtures/basic.txt | 2 +- prep/installNodeDependencies.ts | 92 ++++++++++++++++++++++----------- utils/setup.ts | 17 ++++-- 4 files changed, 120 insertions(+), 55 deletions(-) diff --git a/entry b/entry index 98b2277..05c5aa9 100755 --- a/entry +++ b/entry @@ -67668,6 +67668,7 @@ var flatMorph = (o, flatMapEntry) => { }; // ../node_modules/.pnpm/@ark+util@0.53.0/node_modules/@ark/util/out/records.js +var isKeyOf = (k, o) => k in o; var unset = noSuggest(`unset${ZeroWidthSpace}`); // ../node_modules/.pnpm/@ark+util@0.53.0/node_modules/@ark/util/out/objectKinds.js @@ -125070,7 +125071,7 @@ async function startMcpHttpServer(ctx) { } // prep/installNodeDependencies.ts -import { existsSync as existsSync3 } from "node:fs"; +import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs"; import { join as join9 } from "node:path"; // ../node_modules/.pnpm/package-manager-detector@1.6.0/node_modules/package-manager-detector/dist/commands.mjs @@ -125367,10 +125368,11 @@ function isMetadataYarnClassic(metadataPath) { } // prep/installNodeDependencies.ts -var PM_INSTALL_COMMANDS = { - pnpm: ["npm", "install", "-g", "pnpm"], - yarn: ["npm", "install", "-g", "yarn"], - bun: ["npm", "install", "-g", "bun"], +var nodePackageManagers = { + npm: ["echo", "npm is already installed"], + pnpm: ["npm", "install", "-g", "{version}"], + yarn: ["npm", "install", "-g", "{version}"], + bun: ["npm", "install", "-g", "{version}"], deno: ["sh", "-c", "curl -fsSL https://deno.land/install.sh | sh"] }; async function isCommandAvailable(command) { @@ -125381,9 +125383,28 @@ async function isCommandAvailable(command) { }); return result.exitCode === 0; } -async function installPackageManager(name) { - log.info(`\u{1F4E6} installing ${name}...`); - const [cmd, ...args3] = PM_INSTALL_COMMANDS[name]; +function getPackageManagerFromPackageJson() { + const packageJsonPath = join9(process.cwd(), "package.json"); + try { + const content = readFileSync2(packageJsonPath, "utf-8"); + const pkg = JSON.parse(content); + if (!pkg.packageManager) return null; + const withoutHash = pkg.packageManager.split("+")[0]; + const name = withoutHash.split("@")[0]; + if (isKeyOf(name, nodePackageManagers)) { + return { name, installSpec: withoutHash }; + } + log.warning(`unknown packageManager in package.json: ${pkg.packageManager}`); + return null; + } catch { + return null; + } +} +async function installPackageManager(name, installSpec) { + if (name === "npm") return null; + log.info(`\u{1F4E6} installing ${installSpec}...`); + const [cmd, ...templateArgs] = nodePackageManagers[name]; + const args3 = templateArgs.map((arg) => arg === "{version}" ? installSpec : arg); const result = await spawn4({ cmd, args: args3, @@ -125407,20 +125428,21 @@ var installNodeDependencies = { return existsSync3(packageJsonPath); }, run: async () => { + const fromPackageJson = getPackageManagerFromPackageJson(); const detected = await detect({ cwd: process.cwd() }); - if (!detected) { - return { - language: "node", - packageManager: "npm", - dependenciesInstalled: false, - issues: ["no package manager detected from lockfile"] - }; + const packageManager = fromPackageJson?.name || detected?.name || "npm"; + const installSpec = fromPackageJson?.installSpec || packageManager; + const agent2 = detected?.agent || packageManager; + if (fromPackageJson) { + log.info(`\u{1F4E6} using packageManager from package.json: ${fromPackageJson.installSpec}`); + } else if (detected) { + log.info(`\u{1F4E6} detected package manager: ${packageManager} (${agent2})`); + } else { + log.info(`\u{1F4E6} no package manager detected, defaulting to npm`); } - const packageManager = detected.name; - log.info(`\u{1F4E6} detected package manager: ${packageManager} (${detected.agent})`); - if (packageManager !== "npm" && !await isCommandAvailable(packageManager)) { + if (!await isCommandAvailable(packageManager)) { log.info(`${packageManager} not found, attempting to install...`); - const installError = await installPackageManager(packageManager); + const installError = await installPackageManager(packageManager, installSpec); if (installError) { return { language: "node", @@ -125430,13 +125452,13 @@ var installNodeDependencies = { }; } } - const resolved = resolveCommand(detected.agent, "frozen", []) || resolveCommand(detected.agent, "install", []); + const resolved = resolveCommand(agent2, "frozen", []) || resolveCommand(agent2, "install", []); if (!resolved) { return { language: "node", packageManager, dependenciesInstalled: false, - issues: [`no install command found for ${detected.agent}`] + issues: [`no install command found for ${agent2}`] }; } log.info(`running: ${resolved.command} ${resolved.args.join(" ")}`); diff --git a/fixtures/basic.txt b/fixtures/basic.txt index cc9c341..d0a2ec2 100644 --- a/fixtures/basic.txt +++ b/fixtures/basic.txt @@ -1 +1 @@ -echo the value of the MY_SECRET env var \ No newline at end of file +run pnpm --version and print the output diff --git a/prep/installNodeDependencies.ts b/prep/installNodeDependencies.ts index 6682c01..98069a3 100644 --- a/prep/installNodeDependencies.ts +++ b/prep/installNodeDependencies.ts @@ -1,19 +1,18 @@ -import { existsSync } from "node:fs"; +import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; +import { isKeyOf } from "@ark/util"; import { detect } from "package-manager-detector"; import { resolveCommand } from "package-manager-detector/commands"; import { log } from "../utils/cli.ts"; import { spawn } from "../utils/subprocess.ts"; import type { NodePackageManager, NodePrepResult, PrepDefinition } from "./types.ts"; -// package managers that need installation (npm is always available) -type InstallablePackageManager = Exclude; - -// install commands for each package manager -const PM_INSTALL_COMMANDS: Record = { - pnpm: ["npm", "install", "-g", "pnpm"], - yarn: ["npm", "install", "-g", "yarn"], - bun: ["npm", "install", "-g", "bun"], +// install command templates for each package manager (version placeholder: {version}) +const nodePackageManagers: Record = { + npm: ["echo", "npm is already installed"], + pnpm: ["npm", "install", "-g", "{version}"], + yarn: ["npm", "install", "-g", "{version}"], + bun: ["npm", "install", "-g", "{version}"], deno: ["sh", "-c", "curl -fsSL https://deno.land/install.sh | sh"], }; @@ -26,9 +25,40 @@ async function isCommandAvailable(command: string): Promise { return result.exitCode === 0; } -async function installPackageManager(name: InstallablePackageManager): Promise { - log.info(`📦 installing ${name}...`); - const [cmd, ...args] = PM_INSTALL_COMMANDS[name]; +interface PackageManagerSpec { + name: NodePackageManager; + installSpec: string; // e.g., "pnpm@8.15.0" (without hash suffix) +} + +function getPackageManagerFromPackageJson(): PackageManagerSpec | null { + const packageJsonPath = join(process.cwd(), "package.json"); + try { + const content = readFileSync(packageJsonPath, "utf-8"); + const pkg = JSON.parse(content) as { packageManager?: string }; + if (!pkg.packageManager) return null; + + // format: "pnpm@8.15.0" or "pnpm@8.15.0+sha512.abc123..." + // strip the hash suffix (+sha256.xxx) as npm install doesn't understand it + const withoutHash = pkg.packageManager.split("+")[0]; + const name = withoutHash.split("@")[0]; + if (isKeyOf(name, nodePackageManagers)) { + return { name, installSpec: withoutHash }; + } + log.warning(`unknown packageManager in package.json: ${pkg.packageManager}`); + return null; + } catch { + return null; + } +} + +async function installPackageManager( + name: NodePackageManager, + installSpec: string +): Promise { + if (name === "npm") return null; // npm is always available + log.info(`📦 installing ${installSpec}...`); + const [cmd, ...templateArgs] = nodePackageManagers[name]; + const args = templateArgs.map((arg) => (arg === "{version}" ? installSpec : arg)); const result = await spawn({ cmd, args, @@ -59,24 +89,29 @@ export const installNodeDependencies: PrepDefinition = { }, run: async (): Promise => { - // detect package manager + // check packageManager field in package.json first (takes priority) + const fromPackageJson = getPackageManagerFromPackageJson(); + + // detect from lockfile as fallback const detected = await detect({ cwd: process.cwd() }); - if (!detected) { - return { - language: "node", - packageManager: "npm", - dependenciesInstalled: false, - issues: ["no package manager detected from lockfile"], - }; + + // prefer package.json field, fall back to lockfile detection, default to npm + const packageManager = fromPackageJson?.name || (detected?.name as NodePackageManager) || "npm"; + const installSpec = fromPackageJson?.installSpec || packageManager; + const agent = detected?.agent || packageManager; + + if (fromPackageJson) { + log.info(`📦 using packageManager from package.json: ${fromPackageJson.installSpec}`); + } else if (detected) { + log.info(`📦 detected package manager: ${packageManager} (${agent})`); + } else { + log.info(`📦 no package manager detected, defaulting to npm`); } - const packageManager = detected.name as NodePackageManager; - log.info(`📦 detected package manager: ${packageManager} (${detected.agent})`); - - // check if package manager is available, install if needed (npm is always available) - if (packageManager !== "npm" && !(await isCommandAvailable(packageManager))) { + // check if package manager is available, install if needed + if (!(await isCommandAvailable(packageManager))) { log.info(`${packageManager} not found, attempting to install...`); - const installError = await installPackageManager(packageManager); + const installError = await installPackageManager(packageManager, installSpec); if (installError) { return { language: "node", @@ -88,14 +123,13 @@ export const installNodeDependencies: PrepDefinition = { } // get the frozen install command (or fallback to regular install) - const resolved = - resolveCommand(detected.agent, "frozen", []) || resolveCommand(detected.agent, "install", []); + const resolved = resolveCommand(agent, "frozen", []) || resolveCommand(agent, "install", []); if (!resolved) { return { language: "node", packageManager, dependenciesInstalled: false, - issues: [`no install command found for ${detected.agent}`], + issues: [`no install command found for ${agent}`], }; } diff --git a/utils/setup.ts b/utils/setup.ts index 321c4af..f5b81a8 100644 --- a/utils/setup.ts +++ b/utils/setup.ts @@ -18,13 +18,22 @@ export interface SetupOptions { export function setupTestRepo(options: SetupOptions): void { const { tempDir, forceClean = false } = options; + const repo = process.env.GITHUB_REPOSITORY; + if (!repo) { + throw new Error( + "GITHUB_REPOSITORY environment variable must be specified (e.g. pullfrog/scratch)" + ); + } + + const cloneUrl = `git@github.com:${repo}.git`; + if (existsSync(tempDir)) { if (forceClean) { log.info("» removing existing .temp directory..."); rmSync(tempDir, { recursive: true, force: true }); - log.info("» cloning pullfrog/scratch into .temp..."); - $("git", ["clone", "git@github.com:pullfrog/scratch.git", tempDir]); + log.info(`» cloning ${repo} into .temp...`); + $("git", ["clone", cloneUrl, tempDir]); } else { log.info("» resetting existing .temp repository..."); execSync("git reset --hard HEAD && git clean -fd", { @@ -33,8 +42,8 @@ export function setupTestRepo(options: SetupOptions): void { }); } } else { - log.info("» cloning pullfrog/scratch into .temp..."); - $("git", ["clone", "git@github.com:pullfrog/scratch.git", tempDir]); + log.info(`» cloning ${repo} into .temp...`); + $("git", ["clone", cloneUrl, tempDir]); } }