Skip to content

Commit f5664bd

Browse files
Merge pull request #1292 from jitsucom/fix/env-preload-portless-cwd
fix(dev-scripts): absolutize NODE_OPTIONS --require for portless cwd
2 parents 3e6292b + aa5533b commit f5664bd

1 file changed

Lines changed: 49 additions & 0 deletions

File tree

dev-scripts/src/bin/run-app.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
*/
1818
import { spawn, spawnSync } from "node:child_process";
1919
import { mkdirSync } from "node:fs";
20+
import { createRequire } from "node:module";
2021
import os from "node:os";
2122
import path from "node:path";
2223
import { fileURLToPath } from "node:url";
2324

2425
const __dirname = path.dirname(fileURLToPath(import.meta.url));
26+
const requireFromHere = createRequire(import.meta.url);
2527

2628
/**
2729
* Scratch dir we run portless from. Why:
@@ -78,6 +80,46 @@ function shellQuote(s: string): string {
7880
return `'${s.replace(/'/g, "'\\''")}'`;
7981
}
8082

83+
/**
84+
* Rewrite bare `--require=NAME` / `-r NAME` entries in NODE_OPTIONS to absolute
85+
* paths. Bare names break in spawned children whose cwd is outside any
86+
* `node_modules` chain (here: SHIM_DIR for portless). Unresolvable names are
87+
* left alone — Node will surface the original error in context.
88+
*/
89+
function absolutizeRequires(nodeOptions: string | undefined, resolver: NodeRequire): string | undefined {
90+
if (!nodeOptions) return nodeOptions;
91+
// Tokenize on whitespace; NODE_OPTIONS values don't support shell quoting.
92+
const tokens = nodeOptions.split(/\s+/).filter(Boolean);
93+
const out: string[] = [];
94+
for (let i = 0; i < tokens.length; i++) {
95+
const t = tokens[i];
96+
const eqMatch = t.match(/^(--require|-r)=(.+)$/);
97+
if (eqMatch) {
98+
out.push(`${eqMatch[1]}=${tryResolve(eqMatch[2], resolver)}`);
99+
continue;
100+
}
101+
if (t === "--require" || t === "-r") {
102+
const next = tokens[i + 1];
103+
if (next) {
104+
out.push(t, tryResolve(next, resolver));
105+
i++;
106+
continue;
107+
}
108+
}
109+
out.push(t);
110+
}
111+
return out.join(" ");
112+
}
113+
114+
function tryResolve(spec: string, resolver: NodeRequire): string {
115+
if (path.isAbsolute(spec) || spec.startsWith("./") || spec.startsWith("../")) return spec;
116+
try {
117+
return resolver.resolve(spec);
118+
} catch {
119+
return spec;
120+
}
121+
}
122+
81123
function main(): void {
82124
const argv = process.argv.slice(2);
83125
const noBranch = argv.includes("--no-branch");
@@ -118,9 +160,16 @@ function main(): void {
118160
})`
119161
);
120162

163+
// Resolve `--require=env-preload` (set globally via root .npmrc) to an
164+
// absolute path so it survives the cwd switch into SHIM_DIR. Without this,
165+
// portless starts from SHIM_DIR (no node_modules → no `env-preload`) and
166+
// dies in `loadPreloadModules` before bash ever runs the inner command.
167+
const resolvedNodeOptions = absolutizeRequires(process.env.NODE_OPTIONS, requireFromHere);
168+
121169
const child = spawn("portless", ["--name", slug, "bash", "-c", innerCmd], {
122170
cwd: SHIM_DIR,
123171
stdio: "inherit",
172+
env: { ...process.env, NODE_OPTIONS: resolvedNodeOptions },
124173
});
125174
child.on("error", err => {
126175
if ((err as NodeJS.ErrnoException).code === "ENOENT") {

0 commit comments

Comments
 (0)