Builds on Linux and macOS now connect to a shared `tsc-ts --api --listen`
server by default, which keeps the files it parsed and bound, so a later
build in the same working directory does not parse the TypeScript libs and
scriptc's declaration files again. SCRIPTC_CHECKER_SERVER=0 turns it off.
When CI is set to a value other than 0 or false, builds use their own tsc-ts
process unless SCRIPTC_CHECKER_SERVER=1.
- The server exits after 600 seconds without a build (was 300).
- Its name also digests the compiler executable's identity, so each scriptc
installation and build connects only to servers it started.
- The server process runs in the root directory, so it does not keep the
first build's directory in use; its --cwd argument is unchanged.
- When the connection to the server fails after it answered, through a
failed transfer or an ended stream, the native CLI runs the command again
in a new process with SCRIPTC_CHECKER_SERVER=0 and exits with its status.
- SCRIPTC_CHECKER_SERVER no longer changes the frontend cache key: the
server's answers are the build's own checker's.
The CLI docs describe the background process, its socket directory, when it
exits and how to stop it.
A value that contradicts a type an npm package declares used to throw
"expected number at $, got undefined" and nothing else. Such a check now
names the declaration and the type patch that admits the value:
profilekit@3.1.0 declares Profile.email as string (index.d.ts:7)
if that declaration is wrong, patch it in package.json: ...
Origins are found for values a package function returns under a written
return type, for properties of records typed by a package-declared type
(record projections into such a type are keyed by it), and for
declaration-boundary casts, whose origin the projection records from the
declaration-world checker. Generic declarations get none: their types
come from the program's own instantiation.
Checked conversions carry dynCheck.origin, an index into
IrModule.declOrigins; the site's existing cold exception block calls
scr_type_origin_note (new runtime unit behind the typeOrigins feature),
which appends the note to the pending TypeError and derives the selector
suffix and the admitted kind from the failure. Union extractions get the
note per arm at compile time. The hot path is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The project configuration records the local files a type patch names,
with whether each existed. A configuration read again from an unchanged
package.json is reused only while every answer still holds, so creating
or deleting a local declaration file between builds in one process
validates again.
- A types patch supplies the declaration TypeScript probes for a .jsx
JavaScript entry (x.d.ts for x.jsx), as it does for .js, .mjs and .cjs.
- Remove the two tests/fixtures/effect4/node_modules links the previous
commit added; that directory is ignored and pnpm creates them.
A scriptc.typePatches entry can now replace a declaration file of the
package (or of its @types twin, written @types/<name>/<path>) with a
local .d.ts ("files"), or give the package a declaration entry ("types").
A supplied entry answers the package's types file and the declarations
TypeScript probes beside its JavaScript entries, and exists for the
program host, the resolver and npm-static's untyped-package test, so a
package without declarations becomes a typed package. Declaration patches
still apply on top. A files target the installed copy does not ship is
SC8031.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An opted-in package's JavaScript types its own values. The lowering types
a consumer's view of such a value from that JavaScript, and the
declaration boundary (decl-projection.ts) casts to the declared type only
where the JavaScript world answers `any`. A function that returns an
object literal has a precise JavaScript type, so it never crossed, even
where that literal type lacks members the declarations promise:
- `{ [key: string]: string }` over `{ size: "12px" }`: the consumer read
`settings["font"] ?? "x"` through the literal's fixed key set. The
checker types the miss `any` (a JavaScript literal), but the keyed read
took the declared fields' common type, so the miss trapped with
"record has no key" and aborted the process.
- an expando `out.max = ...` on a non-empty literal, which the JavaScript
checker leaves out of the literal's type: reading `stats(xs).max` was
refused with SC1090.
A package value whose JavaScript type is an object literal that lacks a
declared property or string index signature now crosses at its declared
type, so missing keys read undefined through the index signature and
expando members are read from the object. An index signature whose values
do not project (a package interface) keeps the value untyped (`as any`),
so its reads are the JavaScript reads. Only programs compiled with
--npm-static whose consumers read such values are rewritten.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A project can now patch a package's declarations in package.json:
"scriptc": { "typePatches": { "<package>@<range>": { "declarations":
{ "<selector>": "<type>" } } } }. A leading "|" widens the declared type,
any other type replaces it. Selectors name declarations by path
(Type.member, fn(), fn(param), list()[], Env[string], export=(), default,
file.js:local()) and apply to the package's .d.ts, its JSDoc and its
@types twin alike.
Patches edit file text where every consumer reads it: the program host's
source reads (both checker worlds) and the npm-static passes that read
package files themselves. Only type nodes and JSDoc type tags change, and
no edit adds or removes a line. Installed copies are selected by version;
an @types twin by the runtime package's installed version. Patches that do
not apply as written warn (SC8030-SC8033) and the warnings repeat on
executable cache hits. Without the setting nothing changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A guarded declaration of a name the CommonJS wrapper defines (`var
exports = exports || {}`, and `module`, `require`, `__filename`,
`__dirname`, `arguments`) reads the wrapper's value, not undefined, so
it keeps its text instead of becoming a fresh object.
- The write scan counts destructuring assignments, and destructuring
for-in/for-of targets, as writes, so a hoisted function that sets the
namespace before its guarded declaration keeps the declaration as it
is.
- An exported `var` that a later declaration of the same name sets
again is not settled at the export: importers would read the
redeclared value, where Node copies the first one into
module.exports. It stays refused (SC1090), as before. The npm case
var-export-redeclared pins it.
- A function's results include undefined when a call can run off the end
of its body or reach a bare return, so the overlay's return type keeps
undefined and a caller's branch on it stays reachable. `new` of an ES5
constructor ignores primitive results, and functions called with `new`
get no @returns tag.
- Package JSDoc tags are neutralized only in comments: string, template
and regular expression literals and JSX text keep their text.
- A branch replaced as unreachable keeps the var names it declares that
its scope declares nowhere else, so references outside it read
undefined. A branch that declares a function in a block is kept.
- The shape-overlay-facts npm case covers all three; four files of the
overlay change are formatted.
Two refusals kept pre-module JavaScript packages from compiling:
- `var ns = ns || {}` (and `?? {}`): the read in the variable's own
initializer made the checker type the namespace `any`, and requirers
then failed to reach the binding (SC1030). In a module, the read sees
the hoisted `undefined` whenever nothing else writes the variable, so a
new package-source normalization respells the declaration as its
default (`var ns = ({})`) when no other declaration, assignment, update,
function, class, parameter or local of that name exists in the file and
no earlier top-level statement mentions it.
- `module.exports = { ns }` with a `var` binding was refused as exporting
a mutable binding by reference. A `var` (or `let`) whose initialized
declaration precedes the export statement at top level and that nothing
reassigns exports exactly the value Node copies, so it now aliases like
a const.
The namespace benchmark (results/design-shape-inference/nsbench) now
compiles and matches Node; the new npm case legacy-namespaces pins a
namespace library with guarded declarations, constructor functions with
prototype methods, enums, a revealing module and a handler registry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With SCRIPTC_CHECKER_SERVER=1 on Linux and macOS, the native compiler's
checker connections go to a shared `tsc-ts --api --listen` server, which
keeps the files it parsed for the next build. When the variable is unset,
nothing changes.
checkerServerName names the server by a digest of the tsc-ts executable's
identity, the working directory, the file system callbacks, the timing
flag and the TSC_TS_* environment. scriptc_ts7_attach takes the server's
lock file with a non-blocking flock, connects to its socket in this
user's private server directory ($XDG_RUNTIME_DIR/scriptc or
/tmp/scriptc-<uid>), and starts the server when none answers: in its own
process group, with its standard streams on /dev/null and no other
descriptor of the build, with --idle-exit 300 and TSC_TS_LISTEN_MEMORY=2048
unless the environment sets it. A build that finds the server in use,
cannot start or reach it, or gets no answer to a ping checks with its own
tsc-ts process, as without the variable; so does a tsc-ts without
--listen, which exits as it starts. The syntax-only source parser, which
reads no libraries, always uses its own process and leaves the server to
the program's checker.
Preflight admits a createRequire loader's require.resolve call with a
computed request inside a try block, but lowering refused the call, so
in JavaScript source the whole statement became a runtime fence. The
fence threw before the request expression ran, and before anything else
in the statement: `try { where = load.resolve(candidate(name)); }`
skipped candidate()'s side effects, so the catch clause saw a different
state than in Node.
Such calls (a computed request, another argument shape, or
runtime-computed options, on require.resolve and require.resolve.paths)
now evaluate their arguments in order and then throw the fence, as a
computed require call does. The addon-probe fixture checks that a
computed candidate path runs before each failed lookup.