@neon/config-runtime 0.12.5 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/lib/function-bundle.d.ts +22 -2
- package/dist/lib/function-bundle.d.ts.map +1 -1
- package/dist/lib/function-bundle.js +29 -6
- package/dist/lib/function-bundle.js.map +1 -1
- package/dist/lib/native-detect.d.ts +64 -0
- package/dist/lib/native-detect.d.ts.map +1 -0
- package/dist/lib/native-detect.js +202 -0
- package/dist/lib/native-detect.js.map +1 -0
- package/dist/lib/native-packages.d.ts +108 -0
- package/dist/lib/native-packages.d.ts.map +1 -0
- package/dist/lib/native-packages.js +424 -0
- package/dist/lib/native-packages.js.map +1 -0
- package/dist/lib/push-config.js +8 -4
- package/dist/lib/push-config.js.map +1 -1
- package/dist/lib/resolve-package.d.ts +28 -0
- package/dist/lib/resolve-package.d.ts.map +1 -0
- package/dist/lib/resolve-package.js +52 -0
- package/dist/lib/resolve-package.js.map +1 -0
- package/dist/v1.d.ts +3 -1
- package/dist/v1.js +3 -1
- package/package.json +4 -3
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { installedPackageVersion, readPackageManifest } from "./resolve-package.js";
|
|
2
|
+
import { ErrorCode, PlatformError, externalPackageRoot } from "@neon/config";
|
|
3
|
+
import { join, resolve, sep } from "node:path";
|
|
4
|
+
import { lstatSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
//#region src/lib/native-packages.ts
|
|
8
|
+
/**
|
|
9
|
+
* The architecture, OS and libc a Neon Function runs on. Native packages are installed for
|
|
10
|
+
* this triple rather than for the machine running the deploy, which is usually a macOS or
|
|
11
|
+
* linux-x64 laptop.
|
|
12
|
+
*/
|
|
13
|
+
const RUNTIME_TARGET = {
|
|
14
|
+
cpu: "arm64",
|
|
15
|
+
os: "linux",
|
|
16
|
+
libc: "glibc"
|
|
17
|
+
};
|
|
18
|
+
/** Human-readable form of {@link RUNTIME_TARGET}, for error messages. */
|
|
19
|
+
const RUNTIME_TARGET_LABEL = `${RUNTIME_TARGET.os}-${RUNTIME_TARGET.cpu} (${RUNTIME_TARGET.libc})`;
|
|
20
|
+
/**
|
|
21
|
+
* `e_machine` for AArch64 in an ELF header, and the offsets needed to read it. Used to
|
|
22
|
+
* verify that a binary about to be shipped really is built for the runtime, so a wrong-arch
|
|
23
|
+
* file fails the deploy instead of failing `dlopen` at invoke.
|
|
24
|
+
*/
|
|
25
|
+
const ELF = {
|
|
26
|
+
magic: "ELF",
|
|
27
|
+
machineOffset: 18,
|
|
28
|
+
headerBytes: 20,
|
|
29
|
+
aarch64: 183
|
|
30
|
+
};
|
|
31
|
+
/** Extensions treated as compiled artifacts, whose architecture is worth verifying. */
|
|
32
|
+
const BINARY_PATTERN = /\.(node|so)(\.\d+)*$/;
|
|
33
|
+
/**
|
|
34
|
+
* The WebAssembly fallback cannot be identified from its manifest: `@img/sharp-wasm32`
|
|
35
|
+
* declares no `os`, `cpu`, or `libc` at all — wasm genuinely runs anywhere — so npm installs
|
|
36
|
+
* it whatever target is asked for and only the name distinguishes it.
|
|
37
|
+
*
|
|
38
|
+
* Matched as a whole dash-delimited token rather than a substring, and only when a real
|
|
39
|
+
* native build for the runtime is also being shipped. That condition is the actual semantic:
|
|
40
|
+
* the fallback is dropped *because the real thing is there*, and sharp's loader would
|
|
41
|
+
* otherwise fall through to it silently, turning a broken native path into a working but far
|
|
42
|
+
* slower one. A project where the wasm build is the only implementation keeps it.
|
|
43
|
+
*
|
|
44
|
+
* Two narrower rules were tried and both failed. Scoping to a staged package's
|
|
45
|
+
* `optionalDependencies` looked more principled and is wrong: `sharp` declares
|
|
46
|
+
* `@img/sharp-freebsd-wasm32` and `@img/sharp-webcontainers-wasm32` but not
|
|
47
|
+
* `@img/sharp-wasm32`, which arrives transitively — CI caught the fallback shipping. Matching
|
|
48
|
+
* `@img/sharp-wasm32` by name would work only for sharp.
|
|
49
|
+
*/
|
|
50
|
+
const WASM_VARIANT_TOKEN = "wasm32";
|
|
51
|
+
/**
|
|
52
|
+
* Whether a package cannot load on the runtime at all, read from its own manifest the way npm
|
|
53
|
+
* decides what to install — `@img/sharp-linuxmusl-arm64` declares `libc: ["musl"]`, which a
|
|
54
|
+
* glibc runtime cannot load.
|
|
55
|
+
*
|
|
56
|
+
* A fact about the package rather than a guess from its name, so a package called
|
|
57
|
+
* `musl-helper` is untouched. The WebAssembly fallback is the one case this cannot see, since
|
|
58
|
+
* wasm genuinely runs anywhere; {@link WASM_VARIANT_TOKEN} covers it.
|
|
59
|
+
*/
|
|
60
|
+
function isIncompatibleWithRuntime(packageDir) {
|
|
61
|
+
const manifest = readPackageManifest(packageDir);
|
|
62
|
+
return manifest !== void 0 && !manifestRunsOnRuntime(manifest);
|
|
63
|
+
}
|
|
64
|
+
/** Whether a package name reads as a WebAssembly build. */
|
|
65
|
+
function isWasmVariantName(packageName) {
|
|
66
|
+
return packageName.split(/[-/]/).includes(WASM_VARIANT_TOKEN);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The library a platform-variant package belongs to, taken as the name up to its first
|
|
70
|
+
* platform token: `@img/sharp` from both `@img/sharp-wasm32` and `@img/sharp-linux-arm64`.
|
|
71
|
+
*
|
|
72
|
+
* This is what lets "a real build is present" be asked per library rather than over the whole
|
|
73
|
+
* tree. A package with no platform token is its own family, so it can never be mistaken for
|
|
74
|
+
* a sibling of something else.
|
|
75
|
+
*/
|
|
76
|
+
function platformFamily(packageName) {
|
|
77
|
+
const [scope, rest] = packageName.startsWith("@") ? [`${packageName.split("/")[0]}/`, packageName.split("/")[1] ?? ""] : ["", packageName];
|
|
78
|
+
const parts = rest.split("-");
|
|
79
|
+
const cut = parts.findIndex((part) => PLATFORM_TOKENS.has(part));
|
|
80
|
+
return `${scope}${(cut === -1 ? parts : parts.slice(0, cut)).join("-")}`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The tokens npm's platform-variant packages are named with. Only used to find where a name
|
|
84
|
+
* stops being the library and starts being the target, so an unrecognised token simply leaves
|
|
85
|
+
* the package in a family of its own — which keeps its files.
|
|
86
|
+
*/
|
|
87
|
+
const PLATFORM_TOKENS = /* @__PURE__ */ new Set([
|
|
88
|
+
"wasm32",
|
|
89
|
+
"linux",
|
|
90
|
+
"linuxmusl",
|
|
91
|
+
"darwin",
|
|
92
|
+
"win32",
|
|
93
|
+
"freebsd",
|
|
94
|
+
"android",
|
|
95
|
+
"webcontainers"
|
|
96
|
+
]);
|
|
97
|
+
/** nft reports an unresolvable import as `Failed to resolve dependency "<specifier>"`. */
|
|
98
|
+
const UNRESOLVED_PATTERN = /Failed to resolve dependency "([^"]+)"/;
|
|
99
|
+
/**
|
|
100
|
+
* The unresolved specifiers worth reporting: a file that will be missing from the archive.
|
|
101
|
+
*
|
|
102
|
+
* Only **relative** ones. A relative specifier names a path inside a package that is already
|
|
103
|
+
* staged, so failing to resolve it means the file is absent from the trace and nothing
|
|
104
|
+
* downstream notices.
|
|
105
|
+
*
|
|
106
|
+
* A bare specifier is the opposite — on a platform-gated package it is how the loader probes.
|
|
107
|
+
* `sharp` names every platform's binary in turn, so tracing a correctly staged `sharp`
|
|
108
|
+
* produces about a dozen unresolved bare specifiers, one per platform that was correctly not
|
|
109
|
+
* installed, plus several built by string concatenation (`@img/sharp-libvips-/package`) that
|
|
110
|
+
* no tracer can follow. Measured: twelve of them, none indicating a missing file.
|
|
111
|
+
*
|
|
112
|
+
* **Reported, never fatal.** Relative does not mean required: a package may do
|
|
113
|
+
* `try { require("./optional.node") } catch {}` and fall back to JavaScript, which is correct
|
|
114
|
+
* code that this would otherwise refuse to deploy. Telling the two apart needs control flow
|
|
115
|
+
* the tracer does not expose — the same reason the undeclared-native report is advisory.
|
|
116
|
+
*/
|
|
117
|
+
function concreteUnresolved(unresolved) {
|
|
118
|
+
return [...new Set(unresolved.filter((specifier) => specifier.startsWith("./") || specifier.startsWith("../")))];
|
|
119
|
+
}
|
|
120
|
+
/** Whether a manifest's `os`/`cpu`/`libc` permit {@link RUNTIME_TARGET}. */
|
|
121
|
+
function manifestRunsOnRuntime(manifest) {
|
|
122
|
+
const permits = (raw, value) => {
|
|
123
|
+
const field = typeof raw === "string" ? [raw] : Array.isArray(raw) ? raw.filter((entry) => typeof entry === "string") : void 0;
|
|
124
|
+
if (field === void 0 || field.length === 0) return true;
|
|
125
|
+
if (field.includes(`!${value}`)) return false;
|
|
126
|
+
const positives = field.filter((entry) => !entry.startsWith("!"));
|
|
127
|
+
return positives.length === 0 || positives.includes(value);
|
|
128
|
+
};
|
|
129
|
+
return permits(manifest.os, RUNTIME_TARGET.os) && permits(manifest.cpu, RUNTIME_TARGET.cpu) && permits(manifest.libc, RUNTIME_TARGET.libc);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The package a `node_modules/...` archive path belongs to, and where its manifest sits
|
|
133
|
+
* inside the staging tree.
|
|
134
|
+
*
|
|
135
|
+
* Anchored on the **last** `node_modules` segment. Anchoring on the first attributes
|
|
136
|
+
* `node_modules/parent/node_modules/child-musl/...` to `parent`, so the nested package is
|
|
137
|
+
* classified by its parent's manifest and an incompatible build survives into the archive —
|
|
138
|
+
* where an AArch64 musl binary passes the architecture check and then fails to load on glibc.
|
|
139
|
+
*/
|
|
140
|
+
function packageOfArchivePath(path) {
|
|
141
|
+
const segments = path.split("/");
|
|
142
|
+
const last = segments.lastIndexOf("node_modules");
|
|
143
|
+
if (last === -1) return void 0;
|
|
144
|
+
const first = segments[last + 1];
|
|
145
|
+
if (first === void 0) return void 0;
|
|
146
|
+
const scoped = first.startsWith("@");
|
|
147
|
+
const parts = scoped ? segments.slice(last + 1, last + 3) : [first];
|
|
148
|
+
if (scoped && parts.length < 2) return void 0;
|
|
149
|
+
return {
|
|
150
|
+
name: parts.join("/"),
|
|
151
|
+
dir: segments.slice(0, last + 1 + parts.length).join("/")
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/** How long the staging install may take before the deploy gives up on it. */
|
|
155
|
+
const INSTALL_TIMEOUT_MS = 12e4;
|
|
156
|
+
const DEFAULT_ARCHIVE_LIMITS = {
|
|
157
|
+
maxZipBytes: 10485760,
|
|
158
|
+
maxTotalBytes: 67108864,
|
|
159
|
+
maxEntries: 4096
|
|
160
|
+
};
|
|
161
|
+
const bytes = (n) => `${(n / 1048576).toFixed(1)} MiB`;
|
|
162
|
+
const invalid = (message, cause) => new PlatformError(ErrorCode.InvalidConfig, message, cause === void 0 ? void 0 : { cause });
|
|
163
|
+
/**
|
|
164
|
+
* Collect the files the declared native packages need, ready to be merged into the archive.
|
|
165
|
+
*
|
|
166
|
+
* Three steps, in this order for reasons that are not interchangeable:
|
|
167
|
+
*
|
|
168
|
+
* 1. **Install for the runtime target into a throwaway directory.** The user's own
|
|
169
|
+
* `node_modules` is never read for these files. Its binaries are built for their machine,
|
|
170
|
+
* and a cross-platform install is not sticky — a later plain `npm install` re-resolves
|
|
171
|
+
* optional dependencies for the host and replaces the target's packages with the host's.
|
|
172
|
+
* Only the resolved *version* of the declared package is taken from the user's tree. That
|
|
173
|
+
* is not the same as honouring their lockfile: transitive pins, overrides, patches, and
|
|
174
|
+
* aliases are not carried across, so the staged graph can differ below the root.
|
|
175
|
+
* 2. **Trace the installed tree.** The file set cannot be discovered by asking Node:
|
|
176
|
+
* `@img/sharp-linux-arm64` exports only `./sharp.node` and `./package`, with no `.` and no
|
|
177
|
+
* wildcard, so its directory is unreachable through the resolver. A tracer that follows
|
|
178
|
+
* `createRequire` is required — which is also why esbuild cannot see these files, and why
|
|
179
|
+
* `sharp` bundles cleanly today and then fails at invoke.
|
|
180
|
+
* 3. **Copy the traced files, preserving the `node_modules` layout.** A `.node` addon locates
|
|
181
|
+
* its sibling shared libraries by a path relative to its own directory, so flattening the
|
|
182
|
+
* tree breaks loading even though every file is present.
|
|
183
|
+
*/
|
|
184
|
+
async function traceNativePackages(options) {
|
|
185
|
+
const { slug, packages, projectDir } = options;
|
|
186
|
+
const limits = options.limits ?? DEFAULT_ARCHIVE_LIMITS;
|
|
187
|
+
const deps = {
|
|
188
|
+
...defaultDeps,
|
|
189
|
+
...options.deps
|
|
190
|
+
};
|
|
191
|
+
const roots = [...new Set(packages.map(externalPackageRoot))];
|
|
192
|
+
const warnings = [];
|
|
193
|
+
const specs = roots.map((pkg) => {
|
|
194
|
+
const version = deps.installedVersion(projectDir, pkg);
|
|
195
|
+
if (version === void 0) throw invalid(`Function "${slug}" stages "${pkg}", but no installed copy of it could be found in this project, so the deploy cannot tell which version you tested against. Install "${pkg}" as a dependency, or exclude it if the function never reaches it:\n externalPackages: [{ name: "${pkg}", includeFiles: false }]`);
|
|
196
|
+
return `${pkg}@${version}`;
|
|
197
|
+
});
|
|
198
|
+
const staging = mkdtempSync(join(tmpdir(), "neon-fn-native-"));
|
|
199
|
+
try {
|
|
200
|
+
writeFileSync(join(staging, "package.json"), `${JSON.stringify({
|
|
201
|
+
name: "neon-fn-native-staging",
|
|
202
|
+
private: true
|
|
203
|
+
}, null, 2)}\n`);
|
|
204
|
+
await deps.install(staging, specs);
|
|
205
|
+
verifyInstalled(slug, roots, staging);
|
|
206
|
+
writeFileSync(join(staging, "trace-entry.mjs"), packages.map((pkg) => `import ${JSON.stringify(pkg)};`).join("\n"));
|
|
207
|
+
const traced = await deps.trace(staging, "trace-entry.mjs");
|
|
208
|
+
for (const specifier of concreteUnresolved(traced.unresolved ?? [])) warnings.push(`Function "${slug}" stages a package that imports "${specifier}", which is not in the tree the deploy installed. The import is relative to a package that *was* installed, so it will not appear in the archive and nothing else will notice. If the package reaches it, the function fails at invoke with "Cannot find module". If the import is wrapped in a try/catch — a native accelerator behind a JavaScript fallback — the deploy is already correct.`);
|
|
209
|
+
const entries = readArchiveFiles(slug, staging, selectArchiveFiles(slug, traced.files, staging));
|
|
210
|
+
enforceLimits(slug, entries, limits);
|
|
211
|
+
return {
|
|
212
|
+
entries,
|
|
213
|
+
warnings
|
|
214
|
+
};
|
|
215
|
+
} finally {
|
|
216
|
+
rmSync(staging, {
|
|
217
|
+
recursive: true,
|
|
218
|
+
force: true
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Fail when a declared package produced no build for the runtime target. npm omits a
|
|
224
|
+
* platform-specific optional dependency silently when none matches, so "installed" does not
|
|
225
|
+
* imply "usable here" — without this check the archive would ship a package whose binary is
|
|
226
|
+
* simply absent, and the failure would surface at invoke.
|
|
227
|
+
*/
|
|
228
|
+
function verifyInstalled(slug, packages, staging) {
|
|
229
|
+
for (const pkg of packages) {
|
|
230
|
+
const manifest = join(staging, "node_modules", pkg, "package.json");
|
|
231
|
+
if (lstatSync(manifest, { throwIfNoEntry: false }) === void 0) throw invalid(`Function "${slug}" declares "${pkg}" in externalPackages, but it did not install for the Functions runtime (${RUNTIME_TARGET_LABEL}). Either the package does not publish a build for that platform, or it compiles from source at install time, which the deploy cannot do. Check that it lists a ${RUNTIME_TARGET.os}-${RUNTIME_TARGET.cpu} build among its optional dependencies. If this function never reaches "${pkg}", exclude it instead:\n externalPackages: [{ name: "${pkg}", includeFiles: false }]`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Reduce a trace to the files worth archiving: everything under `node_modules`, minus the
|
|
236
|
+
* sibling builds that cannot run on the runtime (see {@link isIncompatibleWithRuntime}) and
|
|
237
|
+
* the WebAssembly fallback when a real build is shipping (see {@link WASM_VARIANT_TOKEN}).
|
|
238
|
+
* The trace entry itself is dropped — the real entry is the esbuild bundle.
|
|
239
|
+
*/
|
|
240
|
+
function selectArchiveFiles(slug, traced, staging) {
|
|
241
|
+
const inTree = traced.filter((path) => path.startsWith(`node_modules${sep}`) || path.startsWith("node_modules/")).map((path) => path.split(sep).join("/"));
|
|
242
|
+
const incompatible = /* @__PURE__ */ new Map();
|
|
243
|
+
const isIncompatible = (dir) => {
|
|
244
|
+
const known = incompatible.get(dir);
|
|
245
|
+
if (known !== void 0) return known;
|
|
246
|
+
const decision = isIncompatibleWithRuntime(join(staging, ...dir.split("/")));
|
|
247
|
+
incompatible.set(dir, decision);
|
|
248
|
+
return decision;
|
|
249
|
+
};
|
|
250
|
+
const compatible = inTree.filter((path) => {
|
|
251
|
+
const pkg = packageOfArchivePath(path);
|
|
252
|
+
return pkg !== void 0 && !isIncompatible(pkg.dir);
|
|
253
|
+
});
|
|
254
|
+
/**
|
|
255
|
+
* A wasm build is only redundant next to the real build of the *same library at the same
|
|
256
|
+
* version*. Keying on the family alone conflates versions: a tree holding
|
|
257
|
+
* `foo-linux-arm64@1` and a nested `foo-wasm32@2` would delete v2 — whose consumer has no
|
|
258
|
+
* other implementation — because v1 shipped a binary.
|
|
259
|
+
*/
|
|
260
|
+
const variantKey = (name, dir) => {
|
|
261
|
+
const version = readPackageManifest(join(staging, ...dir.split("/")))?.version;
|
|
262
|
+
return `${platformFamily(name)}@${typeof version === "string" ? version : "?"}`;
|
|
263
|
+
};
|
|
264
|
+
const nativeBuilds = new Set(compatible.filter((path) => BINARY_PATTERN.test(path)).flatMap((path) => {
|
|
265
|
+
const pkg = packageOfArchivePath(path);
|
|
266
|
+
return pkg === void 0 ? [] : [variantKey(pkg.name, pkg.dir)];
|
|
267
|
+
}));
|
|
268
|
+
const files = compatible.filter((path) => {
|
|
269
|
+
const pkg = packageOfArchivePath(path);
|
|
270
|
+
if (pkg === void 0) return true;
|
|
271
|
+
return !(isWasmVariantName(pkg.name) && nativeBuilds.has(variantKey(pkg.name, pkg.dir)));
|
|
272
|
+
}).sort();
|
|
273
|
+
if (files.length === 0) throw invalid(`Function "${slug}" stages external packages, but tracing the installed packages found no files to ship. This is a bug in the deploy — please report it at https://github.com/neondatabase/neon-pkgs/issues.`);
|
|
274
|
+
return files;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Read the traced files, checking each one is shippable as it goes.
|
|
278
|
+
*
|
|
279
|
+
* Two rejections here, both cheaper to hit now than after upload. A symlink cannot be
|
|
280
|
+
* represented in the archive at all, and a compiled binary built for the wrong architecture
|
|
281
|
+
* would deploy fine and then fail to load — which is the exact failure this whole option
|
|
282
|
+
* exists to prevent, so it must not be reintroduced by a stale or hand-patched tree.
|
|
283
|
+
*/
|
|
284
|
+
function readArchiveFiles(slug, staging, files) {
|
|
285
|
+
const entries = {};
|
|
286
|
+
for (const relative of files) {
|
|
287
|
+
const absolute = resolve(staging, relative);
|
|
288
|
+
if (!absolute.startsWith(`${resolve(staging)}${sep}`)) throw invalid(`Function "${slug}": traced file "${relative}" resolves outside the staging directory. This is a bug in the deploy — please report it.`);
|
|
289
|
+
const stat = lstatSync(absolute, { throwIfNoEntry: false });
|
|
290
|
+
if (stat === void 0) throw invalid(`Function "${slug}": traced file "${relative}" disappeared before it could be read. This is a bug in the deploy — please report it at https://github.com/neondatabase/neon-pkgs/issues.`);
|
|
291
|
+
if (stat.isSymbolicLink()) throw invalid(`Function "${slug}" would ship a symbolic link, which the build does not support: ${relative}. This is a bug in the deploy — please report it at https://github.com/neondatabase/neon-pkgs/issues.`);
|
|
292
|
+
if (!stat.isFile()) continue;
|
|
293
|
+
const contents = readFileSync(absolute);
|
|
294
|
+
if (BINARY_PATTERN.test(relative)) verifyArchitecture(slug, relative, contents);
|
|
295
|
+
entries[relative] = new Uint8Array(contents);
|
|
296
|
+
}
|
|
297
|
+
return entries;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Reject a compiled artifact that is not built for the runtime. Only ELF AArch64 can load
|
|
301
|
+
* there, so anything else — a Mach-O from the user's laptop, an x64 ELF from a stale
|
|
302
|
+
* install — is a deploy-time error rather than an invoke-time one.
|
|
303
|
+
*/
|
|
304
|
+
function verifyArchitecture(slug, relative, contents) {
|
|
305
|
+
if (contents.length < ELF.headerBytes) throw invalid(`Function "${slug}" would ship "${relative}", which is ${contents.length} bytes — too short to be a valid binary. The package may have been installed incompletely; reinstall it and try again.`);
|
|
306
|
+
if (!(contents.subarray(0, 4).toString("binary") === ELF.magic)) throw invalid(`Function "${slug}" would ship "${relative}", which is not a Linux binary. The Functions runtime is ${RUNTIME_TARGET_LABEL}, so a binary built for another platform cannot load there. A package that compiles from source at install time produces a binary for the machine that installed it and cannot be cross-installed by the deploy.`);
|
|
307
|
+
const machine = contents.readUInt16LE(ELF.machineOffset);
|
|
308
|
+
if (machine !== ELF.aarch64) throw invalid(`Function "${slug}" would ship "${relative}", which is built for a different architecture (ELF machine 0x${machine.toString(16)}). The Functions runtime is ${RUNTIME_TARGET_LABEL}.`);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Check the archive against the build service's limits, naming the largest contributors. The
|
|
312
|
+
* compressed size is not known until the archive is zipped, so only the counts and
|
|
313
|
+
* uncompressed total are checked here; {@link assertZipWithinLimits} covers the rest.
|
|
314
|
+
*
|
|
315
|
+
* Exported so the caller can re-check the **final** archive: these entries are the staged
|
|
316
|
+
* files alone, and the bundle is merged in afterwards.
|
|
317
|
+
*/
|
|
318
|
+
function enforceLimits(slug, entries, limits = DEFAULT_ARCHIVE_LIMITS) {
|
|
319
|
+
const names = Object.keys(entries);
|
|
320
|
+
const count = names.length;
|
|
321
|
+
if (count > limits.maxEntries) throw invalid(`Function "${slug}" archive has ${count} files; the limit is ${limits.maxEntries}. Native packages bring their whole file tree — ship fewer of them, or split the work across functions.`);
|
|
322
|
+
const total = names.reduce((sum, name) => sum + entries[name].length, 0);
|
|
323
|
+
if (total > limits.maxTotalBytes) throw invalid(`Function "${slug}" archive is ${bytes(total)} uncompressed; the limit is ${bytes(limits.maxTotalBytes)}.\n\n${describeLargest(entries)}`);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Check the compressed archive, which is only measurable once it exists. Separate from
|
|
327
|
+
* {@link enforceLimits} for that reason, and exported so the bundler can call it on the
|
|
328
|
+
* finished ZIP.
|
|
329
|
+
*/
|
|
330
|
+
function assertZipWithinLimits(slug, zip, entries, limits = DEFAULT_ARCHIVE_LIMITS) {
|
|
331
|
+
if (zip.length <= limits.maxZipBytes) return;
|
|
332
|
+
throw invalid(`Function "${slug}" archive is ${bytes(zip.length)} compressed; the limit is ${bytes(limits.maxZipBytes)}.\n\n${describeLargest(entries)}`);
|
|
333
|
+
}
|
|
334
|
+
/** The handful of files worth naming when an archive is too big. */
|
|
335
|
+
function describeLargest(entries) {
|
|
336
|
+
return `Largest contributors:\n${Object.entries(entries).sort(([, a], [, b]) => b.length - a.length).slice(0, 4).map(([name, data]) => ` ${bytes(data.length).padStart(9)} ${name}`).join("\n")}\n\nNative binaries are large. Ship fewer of them, or split the work across functions.`;
|
|
337
|
+
}
|
|
338
|
+
const defaultDeps = {
|
|
339
|
+
install: (cwd, specs) => runNpmInstall(cwd, specs),
|
|
340
|
+
trace: async (base, entry) => {
|
|
341
|
+
const { nodeFileTrace } = await loadNft();
|
|
342
|
+
const result = await nodeFileTrace([join(base, entry)], { base });
|
|
343
|
+
return {
|
|
344
|
+
files: [...result.fileList],
|
|
345
|
+
unresolved: [...result.warnings].flatMap((warning) => {
|
|
346
|
+
const specifier = UNRESOLVED_PATTERN.exec(warning.message)?.[1];
|
|
347
|
+
return specifier === void 0 ? [] : [specifier];
|
|
348
|
+
})
|
|
349
|
+
};
|
|
350
|
+
},
|
|
351
|
+
installedVersion: (projectDir, pkg) => installedPackageVersion(projectDir, pkg)
|
|
352
|
+
};
|
|
353
|
+
/**
|
|
354
|
+
* Install into the staging directory by shelling out to whatever `npm` is on `PATH`, rather
|
|
355
|
+
* than importing it. The CLI also ships as a standalone binary with no npm and no
|
|
356
|
+
* `node_modules` inside it, so a `require()` would be unresolvable there; a subprocess works
|
|
357
|
+
* in both forms.
|
|
358
|
+
*
|
|
359
|
+
* `--cpu/--os/--libc` are what select the runtime's builds instead of the host's.
|
|
360
|
+
* `--ignore-scripts` matters for more than speed: an install script would compile for the
|
|
361
|
+
* host architecture and overwrite the prebuilt binary we are here to collect.
|
|
362
|
+
*/
|
|
363
|
+
async function runNpmInstall(cwd, specs) {
|
|
364
|
+
const args = [
|
|
365
|
+
"install",
|
|
366
|
+
...specs,
|
|
367
|
+
`--cpu=${RUNTIME_TARGET.cpu}`,
|
|
368
|
+
`--os=${RUNTIME_TARGET.os}`,
|
|
369
|
+
`--libc=${RUNTIME_TARGET.libc}`,
|
|
370
|
+
"--ignore-scripts",
|
|
371
|
+
"--no-audit",
|
|
372
|
+
"--no-fund",
|
|
373
|
+
"--no-package-lock",
|
|
374
|
+
"--loglevel=error"
|
|
375
|
+
];
|
|
376
|
+
const { code, stderr, spawnError } = await new Promise((resolveRun) => {
|
|
377
|
+
const child = spawn("npm", args, {
|
|
378
|
+
cwd,
|
|
379
|
+
stdio: [
|
|
380
|
+
"ignore",
|
|
381
|
+
"ignore",
|
|
382
|
+
"pipe"
|
|
383
|
+
],
|
|
384
|
+
timeout: INSTALL_TIMEOUT_MS
|
|
385
|
+
});
|
|
386
|
+
let stderr = "";
|
|
387
|
+
child.stderr.on("data", (chunk) => {
|
|
388
|
+
stderr += chunk.toString();
|
|
389
|
+
});
|
|
390
|
+
child.on("error", (spawnError) => resolveRun({
|
|
391
|
+
code: null,
|
|
392
|
+
stderr,
|
|
393
|
+
spawnError
|
|
394
|
+
}));
|
|
395
|
+
child.on("close", (code) => resolveRun({
|
|
396
|
+
code,
|
|
397
|
+
stderr
|
|
398
|
+
}));
|
|
399
|
+
});
|
|
400
|
+
if (spawnError !== void 0) {
|
|
401
|
+
const missing = spawnError.code === "ENOENT";
|
|
402
|
+
throw invalid(missing ? `Shipping a function's externalPackages requires "npm" on PATH, to install them for the Functions runtime (${RUNTIME_TARGET_LABEL}). It could not be found. Install Node.js with npm, or set includeFiles: false on every entry so nothing needs staging.` : `Failed to install externalPackages for the Functions runtime: ${spawnError.message}`, spawnError);
|
|
403
|
+
}
|
|
404
|
+
if (code !== 0) {
|
|
405
|
+
if (stderr.includes("EBADPLATFORM")) throw invalid(`One of ${specs.map((s) => `"${s}"`).join(", ")} has no build for the Functions runtime (${RUNTIME_TARGET_LABEL}), so its files cannot be staged. Use a package that publishes a ${RUNTIME_TARGET.os}-${RUNTIME_TARGET.cpu} build (sharp does). If this function never reaches it, exclude it instead:\n externalPackages: [{ name: "<package>", includeFiles: false }]\n\nnpm reported:\n${stderr.trim()}`);
|
|
406
|
+
throw invalid(`Failed to install externalPackages for the Functions runtime (${RUNTIME_TARGET_LABEL}). npm exited with code ${code}.\n${stderr.trim()}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Loaded with a dynamic `import()` for the same reason as esbuild and fflate: nothing in this
|
|
411
|
+
* package's static graph should name the tracer, so a `neon.ts` that only reads config never
|
|
412
|
+
* pulls it in. A deploy that declares no native packages never reaches this.
|
|
413
|
+
*/
|
|
414
|
+
async function loadNft() {
|
|
415
|
+
try {
|
|
416
|
+
return await import("@vercel/nft");
|
|
417
|
+
} catch (cause) {
|
|
418
|
+
throw invalid("Staging a function's external packages requires `@vercel/nft`, which could not be loaded. Installed from npm it is a dependency of @neon/config-runtime, so reinstalling your dependencies fixes it. If you are running the standalone `neon` binary, this is a packaging bug — please report it at https://github.com/neondatabase/neon-pkgs/issues.", cause);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
//#endregion
|
|
422
|
+
export { DEFAULT_ARCHIVE_LIMITS, RUNTIME_TARGET, RUNTIME_TARGET_LABEL, assertZipWithinLimits, enforceLimits, traceNativePackages };
|
|
423
|
+
|
|
424
|
+
//# sourceMappingURL=native-packages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-packages.js","names":[],"sources":["../../src/lib/native-packages.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport {\n\tlstatSync,\n\tmkdtempSync,\n\treadFileSync,\n\trmSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join, resolve, sep } from \"node:path\";\nimport { ErrorCode, externalPackageRoot, PlatformError } from \"@neon/config\";\nimport {\n\tinstalledPackageVersion,\n\treadPackageManifest,\n} from \"./resolve-package.js\";\n\n/**\n * The architecture, OS and libc a Neon Function runs on. Native packages are installed for\n * this triple rather than for the machine running the deploy, which is usually a macOS or\n * linux-x64 laptop.\n */\nexport const RUNTIME_TARGET = {\n\tcpu: \"arm64\",\n\tos: \"linux\",\n\tlibc: \"glibc\",\n} as const;\n\n/** Human-readable form of {@link RUNTIME_TARGET}, for error messages. */\nexport const RUNTIME_TARGET_LABEL = `${RUNTIME_TARGET.os}-${RUNTIME_TARGET.cpu} (${RUNTIME_TARGET.libc})`;\n\n/**\n * `e_machine` for AArch64 in an ELF header, and the offsets needed to read it. Used to\n * verify that a binary about to be shipped really is built for the runtime, so a wrong-arch\n * file fails the deploy instead of failing `dlopen` at invoke.\n */\nconst ELF = {\n\tmagic: \"\\x7fELF\",\n\tmachineOffset: 18,\n\theaderBytes: 20,\n\taarch64: 0xb7,\n} as const;\n\n/** Extensions treated as compiled artifacts, whose architecture is worth verifying. */\nconst BINARY_PATTERN = /\\.(node|so)(\\.\\d+)*$/;\n\n/**\n * The WebAssembly fallback cannot be identified from its manifest: `@img/sharp-wasm32`\n * declares no `os`, `cpu`, or `libc` at all — wasm genuinely runs anywhere — so npm installs\n * it whatever target is asked for and only the name distinguishes it.\n *\n * Matched as a whole dash-delimited token rather than a substring, and only when a real\n * native build for the runtime is also being shipped. That condition is the actual semantic:\n * the fallback is dropped *because the real thing is there*, and sharp's loader would\n * otherwise fall through to it silently, turning a broken native path into a working but far\n * slower one. A project where the wasm build is the only implementation keeps it.\n *\n * Two narrower rules were tried and both failed. Scoping to a staged package's\n * `optionalDependencies` looked more principled and is wrong: `sharp` declares\n * `@img/sharp-freebsd-wasm32` and `@img/sharp-webcontainers-wasm32` but not\n * `@img/sharp-wasm32`, which arrives transitively — CI caught the fallback shipping. Matching\n * `@img/sharp-wasm32` by name would work only for sharp.\n */\nconst WASM_VARIANT_TOKEN = \"wasm32\";\n\n/**\n * Whether a package cannot load on the runtime at all, read from its own manifest the way npm\n * decides what to install — `@img/sharp-linuxmusl-arm64` declares `libc: [\"musl\"]`, which a\n * glibc runtime cannot load.\n *\n * A fact about the package rather than a guess from its name, so a package called\n * `musl-helper` is untouched. The WebAssembly fallback is the one case this cannot see, since\n * wasm genuinely runs anywhere; {@link WASM_VARIANT_TOKEN} covers it.\n */\nfunction isIncompatibleWithRuntime(packageDir: string): boolean {\n\tconst manifest = readPackageManifest(packageDir);\n\treturn manifest !== undefined && !manifestRunsOnRuntime(manifest);\n}\n\n/** Whether a package name reads as a WebAssembly build. */\nfunction isWasmVariantName(packageName: string): boolean {\n\treturn packageName.split(/[-/]/).includes(WASM_VARIANT_TOKEN);\n}\n\n/**\n * The library a platform-variant package belongs to, taken as the name up to its first\n * platform token: `@img/sharp` from both `@img/sharp-wasm32` and `@img/sharp-linux-arm64`.\n *\n * This is what lets \"a real build is present\" be asked per library rather than over the whole\n * tree. A package with no platform token is its own family, so it can never be mistaken for\n * a sibling of something else.\n */\nfunction platformFamily(packageName: string): string {\n\tconst [scope, rest] = packageName.startsWith(\"@\")\n\t\t? [`${packageName.split(\"/\")[0]}/`, packageName.split(\"/\")[1] ?? \"\"]\n\t\t: [\"\", packageName];\n\tconst parts = rest.split(\"-\");\n\tconst cut = parts.findIndex((part) => PLATFORM_TOKENS.has(part));\n\treturn `${scope}${(cut === -1 ? parts : parts.slice(0, cut)).join(\"-\")}`;\n}\n\n/**\n * The tokens npm's platform-variant packages are named with. Only used to find where a name\n * stops being the library and starts being the target, so an unrecognised token simply leaves\n * the package in a family of its own — which keeps its files.\n */\nconst PLATFORM_TOKENS = new Set([\n\t\"wasm32\",\n\t\"linux\",\n\t\"linuxmusl\",\n\t\"darwin\",\n\t\"win32\",\n\t\"freebsd\",\n\t\"android\",\n\t\"webcontainers\",\n]);\n\n/** nft reports an unresolvable import as `Failed to resolve dependency \"<specifier>\"`. */\nconst UNRESOLVED_PATTERN = /Failed to resolve dependency \"([^\"]+)\"/;\n\n/**\n * The unresolved specifiers worth reporting: a file that will be missing from the archive.\n *\n * Only **relative** ones. A relative specifier names a path inside a package that is already\n * staged, so failing to resolve it means the file is absent from the trace and nothing\n * downstream notices.\n *\n * A bare specifier is the opposite — on a platform-gated package it is how the loader probes.\n * `sharp` names every platform's binary in turn, so tracing a correctly staged `sharp`\n * produces about a dozen unresolved bare specifiers, one per platform that was correctly not\n * installed, plus several built by string concatenation (`@img/sharp-libvips-/package`) that\n * no tracer can follow. Measured: twelve of them, none indicating a missing file.\n *\n * **Reported, never fatal.** Relative does not mean required: a package may do\n * `try { require(\"./optional.node\") } catch {}` and fall back to JavaScript, which is correct\n * code that this would otherwise refuse to deploy. Telling the two apart needs control flow\n * the tracer does not expose — the same reason the undeclared-native report is advisory.\n */\nfunction concreteUnresolved(unresolved: readonly string[]): string[] {\n\treturn [\n\t\t...new Set(\n\t\t\tunresolved.filter(\n\t\t\t\t(specifier) =>\n\t\t\t\t\tspecifier.startsWith(\"./\") || specifier.startsWith(\"../\"),\n\t\t\t),\n\t\t),\n\t];\n}\n\n/** Whether a manifest's `os`/`cpu`/`libc` permit {@link RUNTIME_TARGET}. */\nfunction manifestRunsOnRuntime(manifest: Record<string, unknown>): boolean {\n\tconst permits = (raw: unknown, value: string): boolean => {\n\t\tconst field =\n\t\t\ttypeof raw === \"string\"\n\t\t\t\t? [raw]\n\t\t\t\t: Array.isArray(raw)\n\t\t\t\t\t? raw.filter(\n\t\t\t\t\t\t\t(entry): entry is string =>\n\t\t\t\t\t\t\t\ttypeof entry === \"string\",\n\t\t\t\t\t\t)\n\t\t\t\t\t: undefined;\n\t\tif (field === undefined || field.length === 0) return true;\n\t\tif (field.includes(`!${value}`)) return false;\n\t\tconst positives = field.filter((entry) => !entry.startsWith(\"!\"));\n\t\treturn positives.length === 0 || positives.includes(value);\n\t};\n\treturn (\n\t\tpermits(manifest.os, RUNTIME_TARGET.os) &&\n\t\tpermits(manifest.cpu, RUNTIME_TARGET.cpu) &&\n\t\tpermits(manifest.libc, RUNTIME_TARGET.libc)\n\t);\n}\n\n/**\n * The package a `node_modules/...` archive path belongs to, and where its manifest sits\n * inside the staging tree.\n *\n * Anchored on the **last** `node_modules` segment. Anchoring on the first attributes\n * `node_modules/parent/node_modules/child-musl/...` to `parent`, so the nested package is\n * classified by its parent's manifest and an incompatible build survives into the archive —\n * where an AArch64 musl binary passes the architecture check and then fails to load on glibc.\n */\nfunction packageOfArchivePath(\n\tpath: string,\n): { name: string; dir: string } | undefined {\n\tconst segments = path.split(\"/\");\n\tconst last = segments.lastIndexOf(\"node_modules\");\n\tif (last === -1) return undefined;\n\n\tconst first = segments[last + 1];\n\tif (first === undefined) return undefined;\n\tconst scoped = first.startsWith(\"@\");\n\tconst parts = scoped ? segments.slice(last + 1, last + 3) : [first];\n\tif (scoped && parts.length < 2) return undefined;\n\n\treturn {\n\t\tname: parts.join(\"/\"),\n\t\t// Everything up to and including the package's own segments.\n\t\tdir: segments.slice(0, last + 1 + parts.length).join(\"/\"),\n\t};\n}\n\n/** How long the staging install may take before the deploy gives up on it. */\nconst INSTALL_TIMEOUT_MS = 120_000;\n\n/**\n * Size ceilings the deploy must respect. The build service rejects an archive over these,\n * with an error that surfaces long after the CLI has exited, so they are checked here where\n * the message can name the files responsible.\n *\n * These mirror the service's limits rather than defining them. Passing them in lets a caller\n * track a change on the service side without editing this module.\n */\nexport type ArchiveLimits = {\n\t/** Compressed archive bytes — the ZIP that is uploaded. */\n\tmaxZipBytes: number;\n\t/** Total uncompressed bytes across every archive entry. */\n\tmaxTotalBytes: number;\n\t/** Number of archive entries. */\n\tmaxEntries: number;\n};\n\nexport const DEFAULT_ARCHIVE_LIMITS: ArchiveLimits = {\n\tmaxZipBytes: 10 * 1024 * 1024,\n\tmaxTotalBytes: 64 * 1024 * 1024,\n\tmaxEntries: 4096,\n};\n\n/** Injectable side effects, so the tracing logic is testable without a registry or npm. */\nexport type NativeTraceDeps = {\n\t/** Install `specs` for the runtime target into `cwd`. Resolves on success. */\n\tinstall: (cwd: string, specs: string[]) => Promise<void>;\n\t/** Trace `entry` within `base`, returning archive-relative file paths. */\n\t/**\n\t * Trace `entry` within `base`.\n\t *\n\t * `files` are archive-relative paths. `unresolved` are the specifiers the tracer could\n\t * not follow, which are classified rather than reported wholesale — see\n\t * {@link concreteUnresolved}.\n\t */\n\ttrace: (\n\t\tbase: string,\n\t\tentry: string,\n\t) => Promise<{ files: string[]; unresolved?: string[] }>;\n\t/** Version of `pkg` as resolved in the user's project, or undefined if not installed. */\n\tinstalledVersion: (projectDir: string, pkg: string) => string | undefined;\n};\n\n/** Files to place in the archive under `node_modules/`, keyed by archive-relative path. */\nexport type NativeTraceResult = {\n\tentries: Record<string, Uint8Array>;\n\t/**\n\t * Advisory findings from the staging run — currently a relative import the tracer could\n\t * not resolve, meaning that file will not be in the archive.\n\t *\n\t * The caller must surface these. They are advisory rather than fatal because the tracer\n\t * cannot tell a required import from one wrapped in a try/catch, but an unreported one is\n\t * a function that deploys clean and fails at invoke.\n\t */\n\twarnings: string[];\n};\n\nconst bytes = (n: number): string => `${(n / (1024 * 1024)).toFixed(1)} MiB`;\n\nconst invalid = (message: string, cause?: unknown): PlatformError =>\n\tnew PlatformError(\n\t\tErrorCode.InvalidConfig,\n\t\tmessage,\n\t\tcause === undefined ? undefined : { cause },\n\t);\n\n/**\n * Collect the files the declared native packages need, ready to be merged into the archive.\n *\n * Three steps, in this order for reasons that are not interchangeable:\n *\n * 1. **Install for the runtime target into a throwaway directory.** The user's own\n * `node_modules` is never read for these files. Its binaries are built for their machine,\n * and a cross-platform install is not sticky — a later plain `npm install` re-resolves\n * optional dependencies for the host and replaces the target's packages with the host's.\n * Only the resolved *version* of the declared package is taken from the user's tree. That\n * is not the same as honouring their lockfile: transitive pins, overrides, patches, and\n * aliases are not carried across, so the staged graph can differ below the root.\n * 2. **Trace the installed tree.** The file set cannot be discovered by asking Node:\n * `@img/sharp-linux-arm64` exports only `./sharp.node` and `./package`, with no `.` and no\n * wildcard, so its directory is unreachable through the resolver. A tracer that follows\n * `createRequire` is required — which is also why esbuild cannot see these files, and why\n * `sharp` bundles cleanly today and then fails at invoke.\n * 3. **Copy the traced files, preserving the `node_modules` layout.** A `.node` addon locates\n * its sibling shared libraries by a path relative to its own directory, so flattening the\n * tree breaks loading even though every file is present.\n */\nexport async function traceNativePackages(options: {\n\tslug: string;\n\tpackages: readonly string[];\n\tprojectDir: string;\n\tlimits?: ArchiveLimits;\n\tdeps?: Partial<NativeTraceDeps>;\n}): Promise<NativeTraceResult> {\n\tconst { slug, packages, projectDir } = options;\n\tconst limits = options.limits ?? DEFAULT_ARCHIVE_LIMITS;\n\tconst deps: NativeTraceDeps = { ...defaultDeps, ...options.deps };\n\n\t// `packages` are the specifiers as authored, which is what the trace entry must import.\n\t// Installing and verifying work on whole packages, so those use the roots.\n\tconst roots = [...new Set(packages.map(externalPackageRoot))];\n\n\tconst warnings: string[] = [];\n\tconst specs = roots.map((pkg) => {\n\t\tconst version = deps.installedVersion(projectDir, pkg);\n\t\t// Pinned to the user's own resolution, and refused outright when it cannot be read.\n\t\t// The alternative is installing whatever the registry calls latest, which ships code\n\t\t// the user has never run — a difference that surfaces as an unreproducible production\n\t\t// bug rather than as a deploy error. Guaranteeing the declared root is pinned is what\n\t\t// makes the remaining gap (transitive pins, overrides, patches) a bounded one.\n\t\tif (version === undefined) {\n\t\t\tthrow invalid(\n\t\t\t\t`Function \"${slug}\" stages \"${pkg}\", but no installed copy of it could be found ` +\n\t\t\t\t\t`in this project, so the deploy cannot tell which version you tested ` +\n\t\t\t\t\t`against. Install \"${pkg}\" as a dependency, or exclude it if the function ` +\n\t\t\t\t\t`never reaches it:\\n` +\n\t\t\t\t\t` externalPackages: [{ name: \"${pkg}\", includeFiles: false }]`,\n\t\t\t);\n\t\t}\n\t\treturn `${pkg}@${version}`;\n\t});\n\n\tconst staging = mkdtempSync(join(tmpdir(), \"neon-fn-native-\"));\n\ttry {\n\t\t// A manifest whose name matches no dependency, and no lockfile, so npm treats the\n\t\t// directory as a fresh project and resolves the declared specs on their own.\n\t\twriteFileSync(\n\t\t\tjoin(staging, \"package.json\"),\n\t\t\t`${JSON.stringify({ name: \"neon-fn-native-staging\", private: true }, null, 2)}\\n`,\n\t\t);\n\t\tawait deps.install(staging, specs);\n\t\tverifyInstalled(slug, roots, staging);\n\n\t\t// One entry importing every declared specifier, so a single trace covers them all.\n\t\t// The authored specifier rather than the root: a package whose `exports` map lists\n\t\t// only a subpath cannot be imported by its root at all.\n\t\twriteFileSync(\n\t\t\tjoin(staging, \"trace-entry.mjs\"),\n\t\t\tpackages.map((pkg) => `import ${JSON.stringify(pkg)};`).join(\"\\n\"),\n\t\t);\n\n\t\tconst traced = await deps.trace(staging, \"trace-entry.mjs\");\n\t\tfor (const specifier of concreteUnresolved(traced.unresolved ?? [])) {\n\t\t\twarnings.push(\n\t\t\t\t`Function \"${slug}\" stages a package that imports \"${specifier}\", which is not ` +\n\t\t\t\t\t`in the tree the deploy installed. The import is relative to a package that ` +\n\t\t\t\t\t`*was* installed, so it will not appear in the archive and nothing else will ` +\n\t\t\t\t\t`notice. If the package reaches it, the function fails at invoke with ` +\n\t\t\t\t\t`\"Cannot find module\". If the import is wrapped in a try/catch — a native ` +\n\t\t\t\t\t`accelerator behind a JavaScript fallback — the deploy is already correct.`,\n\t\t\t);\n\t\t}\n\t\tconst files = selectArchiveFiles(slug, traced.files, staging);\n\t\tconst entries = readArchiveFiles(slug, staging, files);\n\t\tenforceLimits(slug, entries, limits);\n\t\treturn { entries, warnings };\n\t} finally {\n\t\trmSync(staging, { recursive: true, force: true });\n\t}\n}\n\n/**\n * Fail when a declared package produced no build for the runtime target. npm omits a\n * platform-specific optional dependency silently when none matches, so \"installed\" does not\n * imply \"usable here\" — without this check the archive would ship a package whose binary is\n * simply absent, and the failure would surface at invoke.\n */\nfunction verifyInstalled(\n\tslug: string,\n\tpackages: readonly string[],\n\tstaging: string,\n): void {\n\tfor (const pkg of packages) {\n\t\tconst manifest = join(staging, \"node_modules\", pkg, \"package.json\");\n\t\tif (lstatSync(manifest, { throwIfNoEntry: false }) === undefined) {\n\t\t\tthrow invalid(\n\t\t\t\t`Function \"${slug}\" declares \"${pkg}\" in externalPackages, but it did not ` +\n\t\t\t\t\t`install for the Functions runtime (${RUNTIME_TARGET_LABEL}). Either the ` +\n\t\t\t\t\t`package does not publish a build for that platform, or it compiles from ` +\n\t\t\t\t\t`source at install time, which the deploy cannot do. Check that it lists a ` +\n\t\t\t\t\t`${RUNTIME_TARGET.os}-${RUNTIME_TARGET.cpu} build among its optional ` +\n\t\t\t\t\t`dependencies. If this function never reaches \"${pkg}\", exclude it instead:` +\n\t\t\t\t\t`\\n externalPackages: [{ name: \"${pkg}\", includeFiles: false }]`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * Reduce a trace to the files worth archiving: everything under `node_modules`, minus the\n * sibling builds that cannot run on the runtime (see {@link isIncompatibleWithRuntime}) and\n * the WebAssembly fallback when a real build is shipping (see {@link WASM_VARIANT_TOKEN}).\n * The trace entry itself is dropped — the real entry is the esbuild bundle.\n */\nfunction selectArchiveFiles(\n\tslug: string,\n\ttraced: readonly string[],\n\tstaging: string,\n): string[] {\n\tconst inTree = traced\n\t\t.filter(\n\t\t\t(path) =>\n\t\t\t\tpath.startsWith(`node_modules${sep}`) ||\n\t\t\t\tpath.startsWith(\"node_modules/\"),\n\t\t)\n\t\t.map((path) => path.split(sep).join(\"/\"));\n\n\t// Resolved once per package directory: the decision is a property of the package, and its\n\t// manifest would otherwise be re-read for every file it contributes.\n\tconst incompatible = new Map<string, boolean>();\n\tconst isIncompatible = (dir: string): boolean => {\n\t\tconst known = incompatible.get(dir);\n\t\tif (known !== undefined) return known;\n\t\tconst decision = isIncompatibleWithRuntime(\n\t\t\tjoin(staging, ...dir.split(\"/\")),\n\t\t);\n\t\tincompatible.set(dir, decision);\n\t\treturn decision;\n\t};\n\n\tconst compatible = inTree.filter((path) => {\n\t\tconst pkg = packageOfArchivePath(path);\n\t\treturn pkg !== undefined && !isIncompatible(pkg.dir);\n\t});\n\n\t/**\n\t * A wasm build is only redundant next to the real build of the *same library at the same\n\t * version*. Keying on the family alone conflates versions: a tree holding\n\t * `foo-linux-arm64@1` and a nested `foo-wasm32@2` would delete v2 — whose consumer has no\n\t * other implementation — because v1 shipped a binary.\n\t */\n\tconst variantKey = (name: string, dir: string): string => {\n\t\tconst version = readPackageManifest(\n\t\t\tjoin(staging, ...dir.split(\"/\")),\n\t\t)?.version;\n\t\treturn `${platformFamily(name)}@${typeof version === \"string\" ? version : \"?\"}`;\n\t};\n\n\t// The library versions that ship a compiled binary able to load on the runtime. Scoped\n\t// rather than a single flag over the whole tree: a function may stage two unrelated\n\t// packages, one with a real native build and one where wasm is the only implementation,\n\t// and a global \"some binary exists\" test would delete the second.\n\tconst nativeBuilds = new Set(\n\t\tcompatible\n\t\t\t.filter((path) => BINARY_PATTERN.test(path))\n\t\t\t.flatMap((path) => {\n\t\t\t\tconst pkg = packageOfArchivePath(path);\n\t\t\t\treturn pkg === undefined ? [] : [variantKey(pkg.name, pkg.dir)];\n\t\t\t}),\n\t);\n\n\tconst files = compatible\n\t\t.filter((path) => {\n\t\t\tconst pkg = packageOfArchivePath(path);\n\t\t\tif (pkg === undefined) return true;\n\t\t\treturn !(\n\t\t\t\tisWasmVariantName(pkg.name) &&\n\t\t\t\tnativeBuilds.has(variantKey(pkg.name, pkg.dir))\n\t\t\t);\n\t\t})\n\t\t.sort();\n\n\tif (files.length === 0) {\n\t\tthrow invalid(\n\t\t\t`Function \"${slug}\" stages external packages, but tracing the installed packages ` +\n\t\t\t\t`found no files to ship. This is a bug in the deploy — please report it at ` +\n\t\t\t\t`https://github.com/neondatabase/neon-pkgs/issues.`,\n\t\t);\n\t}\n\treturn files;\n}\n\n/**\n * Read the traced files, checking each one is shippable as it goes.\n *\n * Two rejections here, both cheaper to hit now than after upload. A symlink cannot be\n * represented in the archive at all, and a compiled binary built for the wrong architecture\n * would deploy fine and then fail to load — which is the exact failure this whole option\n * exists to prevent, so it must not be reintroduced by a stale or hand-patched tree.\n */\nfunction readArchiveFiles(\n\tslug: string,\n\tstaging: string,\n\tfiles: readonly string[],\n): Record<string, Uint8Array> {\n\tconst entries: Record<string, Uint8Array> = {};\n\tfor (const relative of files) {\n\t\tconst absolute = resolve(staging, relative);\n\t\t// Defence in depth: a traced path must stay inside the staging tree.\n\t\tif (!absolute.startsWith(`${resolve(staging)}${sep}`)) {\n\t\t\tthrow invalid(\n\t\t\t\t`Function \"${slug}\": traced file \"${relative}\" resolves outside the staging ` +\n\t\t\t\t\t`directory. This is a bug in the deploy — please report it.`,\n\t\t\t);\n\t\t}\n\t\t// lstat, not stat: stat follows the link and would report the target's type, so a\n\t\t// symlink would pass this check silently.\n\t\tconst stat = lstatSync(absolute, { throwIfNoEntry: false });\n\t\t// The tracer said this file is needed and it is not there. Skipping it would ship an\n\t\t// archive that is quietly missing something and fail at invoke, which is the failure\n\t\t// this whole path exists to prevent.\n\t\tif (stat === undefined) {\n\t\t\tthrow invalid(\n\t\t\t\t`Function \"${slug}\": traced file \"${relative}\" disappeared before it could be ` +\n\t\t\t\t\t`read. This is a bug in the deploy — please report it at ` +\n\t\t\t\t\t`https://github.com/neondatabase/neon-pkgs/issues.`,\n\t\t\t);\n\t\t}\n\t\tif (stat.isSymbolicLink()) {\n\t\t\tthrow invalid(\n\t\t\t\t`Function \"${slug}\" would ship a symbolic link, which the build does not ` +\n\t\t\t\t\t`support: ${relative}. This is a bug in the deploy — please report it at ` +\n\t\t\t\t\t`https://github.com/neondatabase/neon-pkgs/issues.`,\n\t\t\t);\n\t\t}\n\t\tif (!stat.isFile()) continue;\n\n\t\tconst contents = readFileSync(absolute);\n\t\tif (BINARY_PATTERN.test(relative))\n\t\t\tverifyArchitecture(slug, relative, contents);\n\t\tentries[relative] = new Uint8Array(contents);\n\t}\n\treturn entries;\n}\n\n/**\n * Reject a compiled artifact that is not built for the runtime. Only ELF AArch64 can load\n * there, so anything else — a Mach-O from the user's laptop, an x64 ELF from a stale\n * install — is a deploy-time error rather than an invoke-time one.\n */\nfunction verifyArchitecture(\n\tslug: string,\n\trelative: string,\n\tcontents: Buffer,\n): void {\n\t// Too short to carry an ELF header at all. Returning here would let a truncated or\n\t// placeholder binary through the one check that exists to stop a wrong-architecture file\n\t// reaching the runtime.\n\tif (contents.length < ELF.headerBytes) {\n\t\tthrow invalid(\n\t\t\t`Function \"${slug}\" would ship \"${relative}\", which is ${contents.length} bytes — ` +\n\t\t\t\t`too short to be a valid binary. The package may have been installed ` +\n\t\t\t\t`incompletely; reinstall it and try again.`,\n\t\t);\n\t}\n\tconst isElf = contents.subarray(0, 4).toString(\"binary\") === ELF.magic;\n\tif (!isElf) {\n\t\tthrow invalid(\n\t\t\t`Function \"${slug}\" would ship \"${relative}\", which is not a Linux binary. ` +\n\t\t\t\t`The Functions runtime is ${RUNTIME_TARGET_LABEL}, so a binary built for ` +\n\t\t\t\t`another platform cannot load there. A package that compiles from source at ` +\n\t\t\t\t`install time produces a binary for the machine that installed it and cannot ` +\n\t\t\t\t`be cross-installed by the deploy.`,\n\t\t);\n\t}\n\tconst machine = contents.readUInt16LE(ELF.machineOffset);\n\tif (machine !== ELF.aarch64) {\n\t\tthrow invalid(\n\t\t\t`Function \"${slug}\" would ship \"${relative}\", which is built for a different ` +\n\t\t\t\t`architecture (ELF machine 0x${machine.toString(16)}). The Functions runtime is ` +\n\t\t\t\t`${RUNTIME_TARGET_LABEL}.`,\n\t\t);\n\t}\n}\n\n/**\n * Check the archive against the build service's limits, naming the largest contributors. The\n * compressed size is not known until the archive is zipped, so only the counts and\n * uncompressed total are checked here; {@link assertZipWithinLimits} covers the rest.\n *\n * Exported so the caller can re-check the **final** archive: these entries are the staged\n * files alone, and the bundle is merged in afterwards.\n */\nexport function enforceLimits(\n\tslug: string,\n\tentries: Record<string, Uint8Array>,\n\tlimits: ArchiveLimits = DEFAULT_ARCHIVE_LIMITS,\n): void {\n\tconst names = Object.keys(entries);\n\tconst count = names.length;\n\tif (count > limits.maxEntries) {\n\t\tthrow invalid(\n\t\t\t`Function \"${slug}\" archive has ${count} files; the limit is ${limits.maxEntries}. ` +\n\t\t\t\t`Native packages bring their whole file tree — ship fewer of them, or split the ` +\n\t\t\t\t`work across functions.`,\n\t\t);\n\t}\n\tconst total = names.reduce((sum, name) => sum + entries[name].length, 0);\n\tif (total > limits.maxTotalBytes) {\n\t\tthrow invalid(\n\t\t\t`Function \"${slug}\" archive is ${bytes(total)} uncompressed; the limit is ` +\n\t\t\t\t`${bytes(limits.maxTotalBytes)}.\\n\\n${describeLargest(entries)}`,\n\t\t);\n\t}\n}\n\n/**\n * Check the compressed archive, which is only measurable once it exists. Separate from\n * {@link enforceLimits} for that reason, and exported so the bundler can call it on the\n * finished ZIP.\n */\nexport function assertZipWithinLimits(\n\tslug: string,\n\tzip: Uint8Array,\n\tentries: Record<string, Uint8Array>,\n\tlimits: ArchiveLimits = DEFAULT_ARCHIVE_LIMITS,\n): void {\n\tif (zip.length <= limits.maxZipBytes) return;\n\tthrow invalid(\n\t\t`Function \"${slug}\" archive is ${bytes(zip.length)} compressed; the limit is ` +\n\t\t\t`${bytes(limits.maxZipBytes)}.\\n\\n${describeLargest(entries)}`,\n\t);\n}\n\n/** The handful of files worth naming when an archive is too big. */\nfunction describeLargest(entries: Record<string, Uint8Array>): string {\n\tconst largest = Object.entries(entries)\n\t\t.sort(([, a], [, b]) => b.length - a.length)\n\t\t.slice(0, 4)\n\t\t.map(([name, data]) => ` ${bytes(data.length).padStart(9)} ${name}`);\n\treturn `Largest contributors:\\n${largest.join(\"\\n\")}\\n\\nNative binaries are large. Ship fewer of them, or split the work across functions.`;\n}\n\nconst defaultDeps: NativeTraceDeps = {\n\tinstall: (cwd, specs) => runNpmInstall(cwd, specs),\n\ttrace: async (base, entry) => {\n\t\tconst { nodeFileTrace } = await loadNft();\n\t\t// The entry is passed absolute: nft resolves a relative one against the process cwd,\n\t\t// not against `base`. `fileList` still comes back relative to `base`, which is what\n\t\t// the archive keys need.\n\t\tconst result = await nodeFileTrace([join(base, entry)], { base });\n\t\treturn {\n\t\t\tfiles: [...result.fileList],\n\t\t\tunresolved: [...result.warnings].flatMap((warning) => {\n\t\t\t\tconst specifier = UNRESOLVED_PATTERN.exec(warning.message)?.[1];\n\t\t\t\treturn specifier === undefined ? [] : [specifier];\n\t\t\t}),\n\t\t};\n\t},\n\tinstalledVersion: (projectDir, pkg) =>\n\t\tinstalledPackageVersion(projectDir, pkg),\n};\n\n/**\n * Install into the staging directory by shelling out to whatever `npm` is on `PATH`, rather\n * than importing it. The CLI also ships as a standalone binary with no npm and no\n * `node_modules` inside it, so a `require()` would be unresolvable there; a subprocess works\n * in both forms.\n *\n * `--cpu/--os/--libc` are what select the runtime's builds instead of the host's.\n * `--ignore-scripts` matters for more than speed: an install script would compile for the\n * host architecture and overwrite the prebuilt binary we are here to collect.\n */\nasync function runNpmInstall(cwd: string, specs: string[]): Promise<void> {\n\tconst args = [\n\t\t\"install\",\n\t\t...specs,\n\t\t`--cpu=${RUNTIME_TARGET.cpu}`,\n\t\t`--os=${RUNTIME_TARGET.os}`,\n\t\t`--libc=${RUNTIME_TARGET.libc}`,\n\t\t\"--ignore-scripts\",\n\t\t\"--no-audit\",\n\t\t\"--no-fund\",\n\t\t\"--no-package-lock\",\n\t\t\"--loglevel=error\",\n\t];\n\n\tconst { code, stderr, spawnError } = await new Promise<{\n\t\tcode: number | null;\n\t\tstderr: string;\n\t\tspawnError?: Error;\n\t}>((resolveRun) => {\n\t\tconst child = spawn(\"npm\", args, {\n\t\t\tcwd,\n\t\t\tstdio: [\"ignore\", \"ignore\", \"pipe\"],\n\t\t\ttimeout: INSTALL_TIMEOUT_MS,\n\t\t});\n\t\tlet stderr = \"\";\n\t\tchild.stderr.on(\"data\", (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\t\tchild.on(\"error\", (spawnError) =>\n\t\t\tresolveRun({ code: null, stderr, spawnError }),\n\t\t);\n\t\tchild.on(\"close\", (code) => resolveRun({ code, stderr }));\n\t});\n\n\tif (spawnError !== undefined) {\n\t\tconst missing = (spawnError as NodeJS.ErrnoException).code === \"ENOENT\";\n\t\tthrow invalid(\n\t\t\tmissing\n\t\t\t\t? `Shipping a function's externalPackages requires \"npm\" on PATH, to install ` +\n\t\t\t\t\t\t`them for the Functions runtime (${RUNTIME_TARGET_LABEL}). It could not ` +\n\t\t\t\t\t\t`be found. Install Node.js with npm, or set includeFiles: false on every ` +\n\t\t\t\t\t\t`entry so nothing needs staging.`\n\t\t\t\t: `Failed to install externalPackages for the Functions runtime: ${spawnError.message}`,\n\t\t\tspawnError,\n\t\t);\n\t}\n\tif (code !== 0) {\n\t\t// npm reports a package that declares no build for the target as EBADPLATFORM, whose\n\t\t// own message compares against the *host* platform and so reads as though the wrong\n\t\t// thing was asked for. Say what actually happened instead.\n\t\tif (stderr.includes(\"EBADPLATFORM\")) {\n\t\t\tthrow invalid(\n\t\t\t\t`One of ${specs.map((s) => `\"${s}\"`).join(\", \")} has no build for the ` +\n\t\t\t\t\t`Functions runtime (${RUNTIME_TARGET_LABEL}), so its files cannot be ` +\n\t\t\t\t\t`staged. Use a package that publishes a ` +\n\t\t\t\t\t`${RUNTIME_TARGET.os}-${RUNTIME_TARGET.cpu} build (sharp does). If this ` +\n\t\t\t\t\t`function never reaches it, exclude it instead:\\n` +\n\t\t\t\t\t` externalPackages: [{ name: \"<package>\", includeFiles: false }]` +\n\t\t\t\t\t`\\n\\nnpm reported:\\n${stderr.trim()}`,\n\t\t\t);\n\t\t}\n\t\tthrow invalid(\n\t\t\t`Failed to install externalPackages for the Functions runtime ` +\n\t\t\t\t`(${RUNTIME_TARGET_LABEL}). npm exited with code ${code}.\\n${stderr.trim()}`,\n\t\t);\n\t}\n}\n\n/**\n * Loaded with a dynamic `import()` for the same reason as esbuild and fflate: nothing in this\n * package's static graph should name the tracer, so a `neon.ts` that only reads config never\n * pulls it in. A deploy that declares no native packages never reaches this.\n */\nasync function loadNft(): Promise<typeof import(\"@vercel/nft\")> {\n\ttry {\n\t\treturn await import(\"@vercel/nft\");\n\t} catch (cause) {\n\t\tthrow invalid(\n\t\t\t\"Staging a function's external packages requires `@vercel/nft`, which could not \" +\n\t\t\t\t\"be loaded. Installed from npm it is a dependency of @neon/config-runtime, so \" +\n\t\t\t\t\"reinstalling your dependencies fixes it. If you are running the standalone \" +\n\t\t\t\t\"`neon` binary, this is a packaging bug — please report it at \" +\n\t\t\t\t\"https://github.com/neondatabase/neon-pkgs/issues.\",\n\t\t\tcause,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
package/dist/lib/push-config.js
CHANGED
|
@@ -7,9 +7,9 @@ import { ErrorCode, PlatformError, PushAbortedError, PushConflictError, createNe
|
|
|
7
7
|
* injects its own bundler never triggers this import, so esbuild can be dropped
|
|
8
8
|
* from their build entirely.
|
|
9
9
|
*/
|
|
10
|
-
const
|
|
10
|
+
const makeDefaultBundleFunction = (onWarning) => async (fn) => {
|
|
11
11
|
const { buildFunctionBundle } = await import("./function-bundle.js");
|
|
12
|
-
return buildFunctionBundle(fn);
|
|
12
|
+
return buildFunctionBundle(fn, { onWarning });
|
|
13
13
|
};
|
|
14
14
|
/**
|
|
15
15
|
* Push a Neon branch policy to a specific project + branch.
|
|
@@ -23,6 +23,7 @@ const defaultBundleFunction = async (fn) => {
|
|
|
23
23
|
async function pushConfig(config, options) {
|
|
24
24
|
const api = options.api ?? createApiFromOptions(options);
|
|
25
25
|
const projectId = options.projectId;
|
|
26
|
+
const warnings = [];
|
|
26
27
|
const dryRun = options.dryRun === true;
|
|
27
28
|
const updateExisting = options.updateExisting === true;
|
|
28
29
|
const allowProtectedBranch = options.allowProtectedBranch === true;
|
|
@@ -91,7 +92,9 @@ async function pushConfig(config, options) {
|
|
|
91
92
|
remoteProjectId: remoteProject.id,
|
|
92
93
|
branchById,
|
|
93
94
|
branchByName,
|
|
94
|
-
bundleFunction: options.bundleFunction ??
|
|
95
|
+
bundleFunction: options.bundleFunction ?? makeDefaultBundleFunction((message) => {
|
|
96
|
+
warnings.push(message);
|
|
97
|
+
})
|
|
95
98
|
});
|
|
96
99
|
applied.push(change);
|
|
97
100
|
}
|
|
@@ -110,7 +113,8 @@ async function pushConfig(config, options) {
|
|
|
110
113
|
branchName: branch.name,
|
|
111
114
|
dryRun,
|
|
112
115
|
applied,
|
|
113
|
-
conflicts: diff.conflicts
|
|
116
|
+
conflicts: diff.conflicts,
|
|
117
|
+
warnings
|
|
114
118
|
};
|
|
115
119
|
if (remoteProject.orgId) result.orgId = remoteProject.orgId;
|
|
116
120
|
return result;
|