diff --git a/apps/server/package.json b/apps/server/package.json index cf243b40c1071..25d57720deec4 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -69,7 +69,8 @@ "stream-json": "3.6.0", "undici": "catalog:", "yaml": "catalog:", - "yauzl": "^3.4.0" + "yauzl": "^3.4.0", + "zod": "^4.6.5" }, "devDependencies": { "@effect/graphql-generator": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dbd9dfa57bb33..61af37debf8e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -940,6 +940,9 @@ importers: yauzl: specifier: ^3.4.0 version: 3.4.0 + zod: + specifier: ^4.6.5 + version: 4.6.5 devDependencies: '@effect/graphql-generator': specifier: 'catalog:' diff --git a/scripts/lib/cli-external-packages.test.ts b/scripts/lib/cli-external-packages.test.ts index b55e3a9c10c18..09803de29e70c 100644 --- a/scripts/lib/cli-external-packages.test.ts +++ b/scripts/lib/cli-external-packages.test.ts @@ -12,6 +12,7 @@ import { findEsmImportsOfExternalPackages } from "./cli-executable-imports.ts"; import { isRuntimeExternalCliDependency, findInlinedExternalPackages, + selectCliRuntimeExternalDependencies, shouldBundleCliDependency, } from "./cli-external-packages.ts"; @@ -20,6 +21,9 @@ const PackageManifest = Schema.Struct({ dependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)), optionalDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)), peerDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)), + peerDependenciesMeta: Schema.optional( + Schema.Record(Schema.String, Schema.Struct({ optional: Schema.optional(Schema.Boolean) })), + ), }); type PackageManifest = typeof PackageManifest.Type; @@ -120,6 +124,47 @@ it.layer(NodeServices.layer)("external package dependency closure", (it) => { ); }), ); + + // The stage installs only externals the server lists, so a required peer must be reachable from them. + it.effect("stages a provider for every required peer of a staged external", () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const installed = yield* readInstalledPackages; + const server = decodeManifest( + yield* fileSystem.readFileString( + path.resolve( + path.dirname(NodeURL.fileURLToPath(import.meta.url)), + "../../apps/server/package.json", + ), + ), + ); + + const staged = new Set(); + const queue = Object.keys(selectCliRuntimeExternalDependencies(server.dependencies ?? {})); + for (const name of queue) { + if (staged.has(name)) continue; + staged.add(name); + const manifest = installed.get(name); + queue.push( + ...Object.keys({ ...manifest?.dependencies, ...manifest?.optionalDependencies }), + ); + } + + const missing = [...staged].flatMap((name) => { + const manifest = installed.get(name); + return Object.keys(manifest?.peerDependencies ?? {}) + .filter((peer) => !manifest?.peerDependenciesMeta?.[peer]?.optional && !staged.has(peer)) + .map((peer) => `${name} -> ${peer}`); + }); + + assert.deepStrictEqual( + missing, + [], + `these peers of staged external packages are missing from the packaged app; add them to apps/server dependencies: ${missing.join(", ")}`, + ); + }), + ); }); describe("findInlinedExternalPackages", () => {