Island user program modules end to end

A file the island decision put in the set is no longer compiled natively:
it is keyed by its absolute path, its declarations type the program as
foreign, its diagnostics do not fail the program the build does not
compile, and every import or require of it lowers to an island load.
`--node-island=<path>` therefore takes effect for a user module, not only
for a package.

The recovery stays one round: when the lowering refuses a user JavaScript
module, the reload adds every such module at once and each one is reported
once as an SC8001 note naming the module and its reason. The build reports
the islands it ran (packages and modules) with its result, and an
executable cache hit reports them again, so a runner can tell an island
result from a native one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Malte Ubl
2026-10-29 18:15:57 -07:00
co-authored by Claude Opus 5.5
parent 794ede51db
commit 678ddf8a4e
11 changed files with 346 additions and 36 deletions
@@ -147,6 +147,8 @@ interface EarlyExecutableCacheStamp {
native: EarlyExecutableNativeFeatures;
/** The build's performance warnings (SC61xx), which a cache hit repeats. */
performance?: ScrDiagnostic[];
/** The Node islands the build ran, which a cache hit reports again. */
islands?: { packages: string[]; modules: string[] };
/** Programs that call into Node.js: the libnode pack identity the build
* recorded (EarlyExecutableCacheOptions.libnodeIdentity). */
libnode?: string;
@@ -259,6 +261,8 @@ export interface EarlyExecutableCacheHit {
frontend: FrontendInputSnapshot;
/** The performance warnings the build reported (SC61xx), repeated on a hit. */
performance?: ScrDiagnostic[];
/** The Node islands the build ran, repeated on a hit. */
islands?: { packages: string[]; modules: string[] };
/** How a --profile-use build's profile matched, reported again on a hit. */
profileMatch?: NativeProfileMatch;
}
@@ -267,6 +271,16 @@ export interface EarlyExecutableCachePublish extends EarlyExecutableCacheHit {
nativeDependencies?: NativeArtifactDependency[];
}
function isIslands(value: unknown): value is { packages: string[]; modules: string[] } {
if (typeof value !== "object" || value === null) return false;
const islands = value as Record<string, unknown>;
return (
Array.isArray(islands.packages) &&
Array.isArray(islands.modules) &&
[...islands.packages, ...islands.modules].every((item) => typeof item === "string")
);
}
function isProfileMatch(value: unknown): value is NativeProfileMatch {
if (typeof value !== "object" || value === null) return false;
const match = value as Record<string, unknown>;
@@ -745,6 +759,7 @@ async function readExecutableCache(
...(Array.isArray(stamp.performance) && stamp.performance.length > 0
? { performance: stamp.performance }
: {}),
...(isIslands(stamp.islands) ? { islands: stamp.islands } : {}),
...(isProfileMatch(stamp.profileMatch) ? { profileMatch: stamp.profileMatch } : {}),
...(ir === null ? {} : { irPath: paths.irPath }),
...(llvmSourcePath === undefined ? {} : { llvmSourcePath }),
@@ -936,6 +951,7 @@ export async function publishEarlyExecutableCache(
...(result.performance === undefined || result.performance.length === 0
? {}
: { performance: result.performance }),
...(result.islands === undefined ? {} : { islands: result.islands }),
...(result.native.nodeIsland === true ? { libnode: options.libnodeIdentity?.() ?? "" } : {}),
...(result.profileMatch === undefined ? {} : { profileMatch: result.profileMatch }),
};