Surfaced trying tier2 npm package express under the #805 sweep.
Repro
```bash
mkdir /tmp/perry-express && cd /tmp/perry-express
cat > package.json <<'JSON'
{"name":"d","private":true,"type":"module","dependencies":{"express":"*"},"perry":{"compilePackages":["express"]}}
JSON
npm install
cat > main.ts <<'TS'
import express from "express";
const app = (express as any)();
console.log(typeof app.get);
TS
perry main.ts -o out
```
Failure
```
Undefined symbols for architecture arm64:
"_js_node_http_create_server", referenced from:
_perry_closure_node_modules_express_lib_application_js__31 in node_modules_express_lib_application_js.o
Error: Linking failed
```
Diagnosis
`js_node_http_create_server` is exported by `crates/perry-ext-http-server/src/server.rs` — not `perry-runtime` or `perry-stdlib`, but a `perry-ext-*` crate.
The codegen declares the function (`crates/perry-codegen/src/runtime_decls.rs:1626`) and emits a call site for it (`lower_call.rs:9559`) when compiled-package code uses Node's `http.createServer` — but the link step has no idea `libperry_ext_http_server.a` is needed, so it's not linked.
This is the same class of bug as #835 (stream FFIs), one layer deeper: there are 35+ `perry-ext-` crates, each with its own `js_` FFI exports. The codegen emits calls to them based on user code shape, but the manifest doesn't know that emitting a call to symbol X implies linking library Y.
Fix scope
Aligns with the proposal in #835: track each `js_*` FFI's source library at the codegen call site (`ctx.needs_ext_http_server = true` when emitting a call to `js_node_http_create_server`, analogous to `needs_thread` / `needs_plugins`). The set of side-effect flags can be auto-derived from the manifest by labeling each entry with its providing crate.
Closing this individually rather than rolling into #835 because the fix has to cover the whole `perry-ext-*` set, not just stdlib.
Part of #793 + #805. Surfaced by the #805 npm sweep on tier2.
Surfaced trying tier2 npm package express under the #805 sweep.
Repro
```bash
mkdir /tmp/perry-express && cd /tmp/perry-express
cat > package.json <<'JSON'
{"name":"d","private":true,"type":"module","dependencies":{"express":"*"},"perry":{"compilePackages":["express"]}}
JSON
npm install
cat > main.ts <<'TS'
import express from "express";
const app = (express as any)();
console.log(typeof app.get);
TS
perry main.ts -o out
```
Failure
```
Undefined symbols for architecture arm64:
"_js_node_http_create_server", referenced from:
_perry_closure_node_modules_express_lib_application_js__31 in node_modules_express_lib_application_js.o
Error: Linking failed
```
Diagnosis
`js_node_http_create_server` is exported by `crates/perry-ext-http-server/src/server.rs` — not `perry-runtime` or `perry-stdlib`, but a `perry-ext-*` crate.
The codegen declares the function (`crates/perry-codegen/src/runtime_decls.rs:1626`) and emits a call site for it (`lower_call.rs:9559`) when compiled-package code uses Node's `http.createServer` — but the link step has no idea `libperry_ext_http_server.a` is needed, so it's not linked.
This is the same class of bug as #835 (stream FFIs), one layer deeper: there are 35+ `perry-ext-` crates, each with its own `js_` FFI exports. The codegen emits calls to them based on user code shape, but the manifest doesn't know that emitting a call to symbol X implies linking library Y.
Fix scope
Aligns with the proposal in #835: track each `js_*` FFI's source library at the codegen call site (`ctx.needs_ext_http_server = true` when emitting a call to `js_node_http_create_server`, analogous to `needs_thread` / `needs_plugins`). The set of side-effect flags can be auto-derived from the manifest by labeling each entry with its providing crate.
Closing this individually rather than rolling into #835 because the fix has to cover the whole `perry-ext-*` set, not just stdlib.
Part of #793 + #805. Surfaced by the #805 npm sweep on tier2.