Archived
fix(install): allow node-pty scripts with npm 12
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { createRequire } from "node:module";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const NPM_VERSION = "12.0.1";
|
||||
const MARKER = "pi-web-package-pty-ok";
|
||||
const execFileAsync = promisify(execFile);
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
|
||||
if (process.platform === "win32") {
|
||||
throw new Error("The installed-package PTY smoke test requires a POSIX shell");
|
||||
}
|
||||
|
||||
const npmExecPath = process.env["npm_execpath"];
|
||||
if (npmExecPath === undefined || npmExecPath === "") {
|
||||
throw new Error("npm_execpath is required; run this check through `npm run smoke:package-install`");
|
||||
}
|
||||
|
||||
const root = await mkdtemp(join(tmpdir(), "pi-web-package-install-"));
|
||||
try {
|
||||
const packDir = join(root, "pack");
|
||||
const npmToolDir = join(root, "npm-tool");
|
||||
const globalPrefix = join(root, "global");
|
||||
await Promise.all([
|
||||
mkdir(packDir, { recursive: true }),
|
||||
mkdir(join(globalPrefix, "lib"), { recursive: true }),
|
||||
mkdir(npmToolDir, { recursive: true }),
|
||||
writeFile(join(npmToolDir, "package.json"), '{"private":true}\n'),
|
||||
]);
|
||||
|
||||
const packOutput = await runNpm(npmExecPath, ["pack", "--ignore-scripts", "--json", "--pack-destination", packDir], repoRoot);
|
||||
const tarballPath = join(packDir, packageTarballFilename(packOutput));
|
||||
|
||||
await runNpm(npmExecPath, [
|
||||
"install",
|
||||
"--ignore-scripts",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
"--no-package-lock",
|
||||
"--no-save",
|
||||
`npm@${NPM_VERSION}`,
|
||||
], npmToolDir);
|
||||
const npm12ExecPath = join(npmToolDir, "node_modules", "npm", "bin", "npm-cli.js");
|
||||
|
||||
await runNpm(npm12ExecPath, [
|
||||
"install",
|
||||
"--global",
|
||||
tarballPath,
|
||||
"--prefix",
|
||||
globalPrefix,
|
||||
"--allow-scripts=node-pty",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
], root);
|
||||
|
||||
const packageRoot = join(globalPrefix, "lib", "node_modules", "@jmfederico", "pi-web");
|
||||
await smokeInstalledTerminalService(packageRoot);
|
||||
console.log(`Installed-package PTY smoke test passed with npm ${NPM_VERSION}.`);
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
async function runNpm(npmCliPath, args, cwd) {
|
||||
const result = await execFileAsync(process.execPath, [npmCliPath, ...args], {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
maxBuffer: 10 * 1024 * 1024,
|
||||
timeout: 180_000,
|
||||
});
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
function packageTarballFilename(output) {
|
||||
const parsed = JSON.parse(output);
|
||||
if (!Array.isArray(parsed) || parsed.length !== 1 || typeof parsed[0]?.filename !== "string") {
|
||||
throw new Error("npm pack returned an unexpected result");
|
||||
}
|
||||
return parsed[0].filename;
|
||||
}
|
||||
|
||||
async function smokeInstalledTerminalService(packageRoot) {
|
||||
const requireFromPackage = createRequire(join(packageRoot, "package.json"));
|
||||
const nodePtyPackageJsonPath = requireFromPackage.resolve("node-pty/package.json");
|
||||
const nodePtyPackage = JSON.parse(await readFile(nodePtyPackageJsonPath, "utf8"));
|
||||
if (typeof nodePtyPackage.version !== "string" || nodePtyPackage.version.includes("-")) {
|
||||
throw new Error(`Installed package resolved a non-stable node-pty version: ${String(nodePtyPackage.version)}`);
|
||||
}
|
||||
|
||||
const terminalModuleUrl = pathToFileURL(join(packageRoot, "dist", "server", "terminals", "terminalService.js")).href;
|
||||
const { TerminalService } = await import(terminalModuleUrl);
|
||||
const previousShell = process.env["SHELL"];
|
||||
process.env["SHELL"] = "/bin/sh";
|
||||
const service = new TerminalService();
|
||||
try {
|
||||
const run = service.runCommand({
|
||||
origin: "package-smoke",
|
||||
projectId: "package-smoke",
|
||||
workspaceId: "package-smoke",
|
||||
cwd: packageRoot,
|
||||
title: "Installed package PTY smoke test",
|
||||
command: `printf '%s' '${MARKER}'`,
|
||||
});
|
||||
let output = "";
|
||||
let detach = () => undefined;
|
||||
const exitCode = await new Promise((resolvePromise, reject) => {
|
||||
const timeout = setTimeout(() => reject(new Error(`Timed out waiting for installed node-pty output: ${JSON.stringify(output)}`)), 10_000);
|
||||
try {
|
||||
detach = service.attach(run.terminalId, {
|
||||
output: (data) => { output += data; },
|
||||
exit: (code) => {
|
||||
clearTimeout(timeout);
|
||||
resolvePromise(code);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
detach();
|
||||
if (exitCode !== 0) throw new Error(`Installed PTY command exited with ${String(exitCode)}`);
|
||||
if (!output.includes(MARKER)) throw new Error(`Installed PTY output did not contain ${MARKER}: ${JSON.stringify(output)}`);
|
||||
} finally {
|
||||
service.dispose();
|
||||
if (previousShell === undefined) delete process.env["SHELL"];
|
||||
else process.env["SHELL"] = previousShell;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user