mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-14 03:26:32 +08:00
Alias a value held from process.env to the live environment
`const env = process.env` compiled the hold to a snapshot record copy of the environment, so `env.X = "1"` wrote the copy and was lost while Node's write reached the real environment table (undefined natively, "1" in Node; `repros/d3-env-write.ts`). Node's held value IS the environment object. A hold whose type is the env record now aliases the live table for every member access: dotted and computed reads lower to process.envGet, writes to process.envSet, `delete` to process.envUnset, and the absence probe (`env[k] ?? fallback`, `||`, `if`) reads getenv instead of reconstructing presence over the snapshot. A BARE use of the hold (spread, Object.keys, argument, return) takes a fresh %env.snapshot at that site, so it observes the environment as it is there rather than as it was when the binding was initialized. Two holds keep copy semantics because they cannot alias safely, and a write through either is refused with SC2014 rather than lost: a hold that escapes into another function, and a hold whose declared type is not the env record. Compound and update writes through a hold (`env.X += v`, `env.X++`) are refused the same way. tests/corpus/held-env-value-writes.ts covers writes, computed writes, deletes, child inheritance, direct writes seen through the hold, a copy staying a copy, and bare-use re-snapshotting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
02d7230c66
commit
3fac2e5766
@@ -0,0 +1,58 @@
|
||||
// A value HELD from process.env aliases the live environment, exactly as
|
||||
// Node's one process.env object does: writes, deletes and reads through the
|
||||
// hold reach the real environment table (setenv/unsetenv/getenv), so a
|
||||
// later direct read, `in` test, and a spawned child's inherited environment
|
||||
// all observe them. A COPY made from the hold stays a copy.
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const env: Record<string, string | undefined> = process.env;
|
||||
env.SCRIPTC_HELD_WRITE = "one";
|
||||
console.log("direct:", process.env.SCRIPTC_HELD_WRITE ?? "(unset)");
|
||||
console.log("held:", env.SCRIPTC_HELD_WRITE ?? "(unset)");
|
||||
|
||||
// A computed key writes the same variable.
|
||||
const key = "SCRIPTC_HELD_" + "computed";
|
||||
env[key] = "two";
|
||||
console.log("computed:", process.env[key] ?? "(unset)", env[key] ?? "(unset)");
|
||||
|
||||
// The write reaches children through the inherited environment.
|
||||
const child = spawnSync("sh", ["-c", 'printf %s "$SCRIPTC_HELD_WRITE"'], { encoding: "utf8" });
|
||||
console.log("child sees:", child.stdout);
|
||||
|
||||
// A write made directly is visible through the hold (one object, not a copy).
|
||||
process.env.SCRIPTC_HELD_LATE = "late";
|
||||
console.log("late via hold:", env.SCRIPTC_HELD_LATE ?? "(unset)");
|
||||
|
||||
// Deletes through the hold unset the real variable.
|
||||
delete env.SCRIPTC_HELD_WRITE;
|
||||
console.log(
|
||||
"deleted:",
|
||||
process.env.SCRIPTC_HELD_WRITE === undefined,
|
||||
env.SCRIPTC_HELD_WRITE === undefined,
|
||||
"SCRIPTC_HELD_WRITE" in process.env,
|
||||
);
|
||||
delete env[key];
|
||||
console.log("computed deleted:", process.env[key] === undefined, key in process.env);
|
||||
|
||||
// An absent key deletes as a no-op, like JS.
|
||||
delete env.SCRIPTC_HELD_NEVER;
|
||||
console.log("absent delete:", process.env.SCRIPTC_HELD_NEVER === undefined);
|
||||
|
||||
// A COPY of the hold keeps copy semantics: its writes never touch the
|
||||
// environment, and its own entries stay. The copy sees the environment as
|
||||
// it is at the spread, including writes made after the hold.
|
||||
const copy = { ...env };
|
||||
copy.SCRIPTC_HELD_COPY = "copy-only";
|
||||
console.log(
|
||||
"copy:",
|
||||
copy.SCRIPTC_HELD_COPY,
|
||||
process.env.SCRIPTC_HELD_COPY === undefined,
|
||||
copy.SCRIPTC_HELD_LATE ?? "(unset)",
|
||||
);
|
||||
// Every other bare use of the hold reads the environment at that point too.
|
||||
console.log(
|
||||
"bare:",
|
||||
Object.keys(env).includes("SCRIPTC_HELD_LATE"),
|
||||
JSON.stringify({ ...env }).includes('"SCRIPTC_HELD_LATE":"late"'),
|
||||
);
|
||||
console.log("done");
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 1700, "san": 3300 }
|
||||
Reference in New Issue
Block a user