Skip to content

[POC] Compile augmented npm packages from scriptc-pm source overlays (--sources) - #1289

Draft
cramforce wants to merge 2 commits into
mainfrom
poc/spm-compiler
Draft

cramforce wants to merge 2 commits into
mainfrom
poc/spm-compiler

Conversation

@cramforce

Copy link
Copy Markdown
Contributor

Draft / proof of concept. Part of the scriptc-pm exploration (TypeScript-source overlays for npm packages). Not intended for merge as-is; see the usefulness findings below.

Summary

This is the compiler side of the scriptc-pm proof of concept: DESIGN §5.6 Phase 0 items 1–6, plus the cheapest form of item 7. scriptc reads a scriptc-pm manifest describing TypeScript-source overlays for installed npm package instances and compiles those instances from source instead of from their published JS. Any overlay that does not compile falls back to the published JS, one package at a time.

$ scriptc build routes.ts --sources
$ scriptc build flags.ts --sources
scriptc: sources: @openfeature/server-sdk@1.18.0 compiles from published JS (source overlay dropped: SC0001 at src/client/internal/open-feature-client.ts:115: Property 'setLogger' in type 'OpenFeatureClient' is not assignable …)
scriptc: sources: @openfeature/core@1.9.2 compiles from published JS (source overlay dropped: SC1090 at src/errors/open-feature-error-abstract.ts:…: redeclaring Error.cause (pass the cause in the Error constructor options) is not supported yet)

What changes

Manifest input.

  • --sources[=<manifest>] works on build, run, coverage and build --lib. Without a value, scriptc walks up from the entry (or the profile) to the nearest .scriptc-pm/manifest.json.
  • scriptc reads schema 1 as specified in §5.5 and validates it. A malformed manifest is a CLI error. Instances whose directories are missing are skipped with a note.
  • --sources and --provenance-sources are mutually exclusive. The bootstrap fast paths and cache warm step aside for both.

Instance-keyed registry (frontend/source-overlays.ts).

  • Instances are keyed by realpath(installedDir).
  • The --provenance-sources pilot keeps working. It is now one more producer of the same overlay set: setProvenanceSources converts its packages to instances. Its fetch code is untouched. Its set is marked uncacheable.

Resolver redirect.

  • resolveSourceOverlayModule is the new first step of resolveProjectModule.
  • It runs Node's node_modules walk from the importing file. When the package directory's realpath is an active instance, the subpath resolves against the instance's exports inside overlayDir, and the result is a program source.
  • resolveNpmImport7 and library-mode auto-detection skip redirected imports.
  • Bare imports inside an overlay resolve through the overlay's node_modules link to the instance's own pnpm dependencies. When such an import lands in another augmented instance, it is redirected too, for example server-sdk's source importing core.

Agreement with tsgo (MVP).

  • tsgo receives paths entries for every exported subpath, including * patterns, of active instances.
  • Names with more than one augmented instance start dropped (ambiguous-name: …).
  • If the resolver's walk reaches a non-augmented copy of a redirected name, that name is dropped as well, because the global paths entry would disagree with it.

Attribution.

  • Coverage rows gain static from source and published JS (source overlay: <reason>) (new NpmStaticStatus values source and source-fallback). A generalized source overlays section reports per-package statement totals, the source origin, fallbacks and manifest notes.
  • The pilot's third-party-source lowering policies now gate on overlay membership.

Per-package fallback. These triggers drop an instance, and the drop-and-reload loop retries the package from published JS:

  • any preflight diagnostic in an overlay file, including SC1xxx module-form refusals and type errors;
  • a program SC0001 that names the overlay's specifier or files;
  • a top-level require() edge to the overlay (the item 7 MVP);
  • a resolver-noted disagreement: a missing export, or a second installed copy of the name;
  • a lowering diagnostic inside an overlay, which the frontend wrapper turns into a drop, reload and re-lower.

As a last resort, a program that fails preflight or lowering outside every overlay is probed with all used overlays dropped. The probe is kept only when it compiles cleanly. This covers consumers that fail only against a declared source type that the published JS's inferred surface avoids (mitt).

Each reason goes to stderr on build and to coverage. If the published JS also fails, the build fails exactly as it does today.

Build cache.

  • The provenanceSources() === null bypasses are gone.
  • The early executable and library caches, the native frontend caches and the coverage verdict key now include the manifest identity: lockDigest plus a digest of the manifest bytes. The field is omitted without --sources, so existing keys are unchanged.
  • Overlay files are program sources, so edits to them invalidate the cache through the existing input tracking (verified).

Evidence

The fixture is tests/fixtures/source-overlays: a real pnpm 11 isolated install of path-to-regexp@6.3.0, @openfeature/server-sdk@1.18.0, @openfeature/core@1.9.2 and mitt@3.0.1, with the upstream TypeScript at the published commits. tests/harness/source-overlays.test.ts writes .scriptc-pm/ the way scriptc-pm sync would and drives the CLI. Results on Linux:

Program Published JS --sources
routes.ts: path-to-regexp parse builds; binary throws SC2020 (String#concat fence in the ES5 dist) compiles from source (100% static); stdout/stderr/exit identical to Node
flags.ts: OpenFeature server SDK builds; binary throws SC1090 (captureRejections) both overlays fall back with reasons; identical to the published build
emit.ts: mitt builds; matches Node overlay falls back via the surface probe; matches Node
require.cts fails overlay dropped for the require() edge; same failure as published
Rebuild Result
Second identical --sources build executable-cache-hit
Changed lockDigest miss
Edited overlay file miss

Validation:

  • tests/harness/source-overlays.test.ts, tests/harness/provenance.test.ts, and the unit tests in source-overlays.test.ts, resolve.test.ts, native/compiler.test.ts, executable-cache.test.ts and verdict-cache.test.ts pass.
  • tests/harness/npm-static.test.ts passes (95/95).
  • tests/harness/npm.test.ts has one failure, node24-url-authority, which also fails on the untouched base in the same sandbox.
  • pnpm lint and pnpm format:check are clean.
  • The self-hosting --emit=ir build of packages/compiler/src/native/cli.ts succeeds with no diagnostics.
  • pnpm test:ts7 --baselines-only is complete.
  • Pending: the full corpus, tests/harness/self-hosting-native-driver.test.ts, and the full sandbox gate. coverage.test.ts's whole-corpus case hit its 600 s timeout while running concurrently with the npm suites.

Findings

  • Fallbacks are the norm for now. For the sample packages, source overlays mostly reach the next layer of compiler gaps. Each is reproducible outside overlays:

    • narrowed string indexing in a ternary (path-to-regexp tokensToRegexp);
    • RegExpExecArray destructuring, SC1031;
    • Record<string, any> unions, SC2009;
    • the Error#cause redeclaration;
    • EventEmitter captureRejections;
    • unknown-typed class fields (async-sema);
    • the missing Temporal global (smol-toml).

    Only path-to-regexp's parser compiles end to end today.

  • Version skew is real. server-sdk@1.18.0's source does not typecheck against the installed core 1.9.2, because it was built against core 1.8.0 sources in its monorepo. Published .d.ts errors are never reported, but source errors are.

  • Declared types can be worse than inferred ones. The declared surface of a source overlay can lower worse than the published JS's inferred surface (mitt), so the published-surface probe is necessary to keep --sources from ever making a build worse.

  • The overlay package.json type field is optional. Syntax detection handles typeless ESM-syntax overlays.

Follow-ups

  • Phase 2 fsShadow per-instance redirect, so multiple instances of one name can be redirected.
  • Synthesize the require() interop shape from publishedFormat, instead of dropping the overlay.
  • Per-package semantic options (useDefineForClassFields; OpenFeature targets ES2015).
  • A policy for type errors inside overlays caused by dependency skew: fall back (current), or tolerate them as node_modules declarations are tolerated.
  • Per-package attribution for the last-resort probe, which is currently all used overlays at once. Coverage does not probe.
  • Print fallback notes on cache hits. A cached build currently prints only install-time drops.
  • The type-only edge fix in detectAutoPackages (§5.6.9).
  • Port the provenance tests to schema 1, then retire the pilot's fetch code.
  • A docs page section in dependencies.mdx.
  • Corpus programs for the compiler gaps listed above.

🤖 Generated with Claude Code

cramforce and others added 2 commits October 17, 2026 19:40
…rces)

`scriptc build|run|coverage --sources[=<manifest>]` reads a scriptc-pm
schema-1 manifest (default: the nearest .scriptc-pm/manifest.json above
the entry) and compiles each augmented package instance from its
TypeScript overlay instead of its published JS.

- Instances are keyed by realpath(installedDir). The resolver redirects
  an import that lands in an instance's installed directory to the
  instance's exports inside its overlay (resolveSourceOverlayModule);
  overlay files compile as program modules.
- tsgo gets the pilot's "paths" injection, but only for names with a
  single augmented instance; other instances start dropped, and a second
  installed copy of a redirected name drops it too.
- Fallback per instance through the drop-and-reload loop: a preflight
  diagnostic in an overlay, a program type error naming it, a top-level
  require() edge to it, a lowering refusal inside it, or a program that
  only fails against the source surface (probe) drops the overlay and
  the package compiles from published JS. Reasons are reported on stderr
  and as "source"/"source-fallback" rows in coverage.
- Build caches key by the manifest's lockDigest and bytes instead of
  being bypassed; the network-fetching --provenance-sources pilot stays
  uncacheable and becomes one more producer of the same overlay set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
scriptc Ready Ready Preview, v0 Oct 18, 2026 1:29pm UTC

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​mitt@​3.0.11001009980100
Addednpm/​@​openfeature/​core@​1.9.21001008090100
Addednpm/​@​openfeature/​server-sdk@​1.18.010010010091100

View full report

This branch was successfully deployed

1 active deployment
Preview — 0c8946c2 Deployed Oct 18, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant