neon 2.38.5 → 2.39.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Neon CLI
2
2
 
3
- The `neon` package is a command-line interface that lets you manage [Neon Serverless Postgres](https://neon.tech/) directly from the terminal. For the complete documentation, see [Neon CLI](https://neon.tech/docs/reference/neon-cli).
3
+ The `neon` package is a command-line interface that lets you manage [Neon](https://neon.tech/) — Lakebase Postgres, Object Storage, Functions, Managed Better Auth, and the AI Gateway — directly from the terminal. For the complete documentation, see [Neon CLI](https://neon.tech/docs/reference/neon-cli).
4
4
 
5
5
  The legacy `neonctl` package is a lightweight compatibility package that depends
6
6
  on this package and invokes the same CLI entry point. The implementation and
@@ -529,6 +529,8 @@ neon deploy --branch my-feature --update-existing
529
529
 
530
530
  Function deploys declared under `preview.functions` are bundled by neon's own esbuild helper and uploaded as part of `apply`, so the policy stays declarative and the packaged CLI never has to embed esbuild's native binary.
531
531
 
532
+ When a package cannot be bundled — a native addon with no esbuild loader, or an optional peer dependency a library references on an untaken code path — list it in that function's `externalPackages` and the bundler leaves the import alone. `neon dev` honours the same list. It does not make the package resolvable in the deployed archive (there is no `node_modules` next to the bundle), so it only unblocks an import that is never evaluated — a dependency the handler actually calls has to be bundled, and a natively-backed one cannot be. See [`@neon/config`](../config/README.md#unbundleable-dependencies-externalpackages).
533
+
532
534
  ## Scaffold a project (`bootstrap`)
533
535
 
534
536
  `neon bootstrap` copies a Neon starter template into a new (or current) directory — conceptually like `degit`, but it only pulls from a small set of templates we maintain in the public [`neondatabase/examples`](https://github.com/neondatabase/examples) repo. It requires no Neon login: it just downloads files from GitHub.
@@ -24,7 +24,11 @@ import { autoPullEnvAfterPin } from "./env.js";
24
24
  * out of config-runtime's static module graph — and therefore out of the packaged
25
25
  * neonctl snapshot, which resolves esbuild dynamically at deploy time.
26
26
  */
27
- const neonctlBundler = async (fn) => zipBundle(await bundleEntry(fn.source));
27
+ const neonctlBundler = async (fn) => zipBundle(await bundleEntry(fn.source, {
28
+ ...(fn.externalPackages
29
+ ? { externalPackages: fn.externalPackages }
30
+ : {}),
31
+ }));
28
32
  const INSPECT_FIELDS = ["project", "branch", "config"];
29
33
  /**
30
34
  * Shared `--env` flag for `config plan|apply` and `deploy`. Loads a `.env` into
@@ -182,10 +182,14 @@ const plannedToUnit = (fn, branchEnv, searchBase) => {
182
182
  // base) so reconcile can tell a real change from a no-op save. A search-mode function
183
183
  // re-planned with a different base must hash identically, or it would be needlessly
184
184
  // restarted — see reconcile().
185
+ ...(fn.externalPackages
186
+ ? { externalPackages: fn.externalPackages }
187
+ : {}),
185
188
  configKey: JSON.stringify({
186
189
  source: fn.source,
187
190
  port: fn.port ?? null,
188
191
  env: fn.env,
192
+ externalPackages: fn.externalPackages ?? null,
189
193
  }),
190
194
  };
191
195
  };
@@ -229,7 +233,7 @@ const runSupervisor = async (units, options = {}) => {
229
233
  const bundleAndStart = async (r) => {
230
234
  let bundlePath;
231
235
  try {
232
- bundlePath = await writeBundle(r.unit.source, r.unit.bundleDir);
236
+ bundlePath = await writeBundle(r.unit.source, r.unit.bundleDir, r.unit.externalPackages);
233
237
  }
234
238
  catch (err) {
235
239
  r.status = "error";
@@ -477,8 +481,10 @@ const spawnChild = (unit, runtimePath, bundlePath) => {
477
481
  detached: true,
478
482
  });
479
483
  };
480
- const writeBundle = async (source, bundleDir) => {
481
- const files = await bundleEntry(source);
484
+ const writeBundle = async (source, bundleDir, externalPackages) => {
485
+ const files = await bundleEntry(source, {
486
+ ...(externalPackages ? { externalPackages } : {}),
487
+ });
482
488
  mkdirSync(bundleDir, { recursive: true });
483
489
  // bundleEntry emits a single `index.mjs` (no source map). The `.mjs` extension makes Node
484
490
  // load it as ESM directly, so no `package.json` `"type": "module"` marker is needed.
@@ -36,6 +36,9 @@ export const resolveFunctionsFromConfig = async (cwd, branchName) => {
36
36
  ? { port: devPort(fn.dev) }
37
37
  : {}),
38
38
  env: { ...fn.env },
39
+ ...(fn.externalPackages
40
+ ? { externalPackages: [...fn.externalPackages] }
41
+ : {}),
39
42
  };
40
43
  });
41
44
  return { configPath, functions: planned };
@@ -29,7 +29,7 @@ const toFilesByBasename = (files) => {
29
29
  out[basename(f.path)] = f.contents;
30
30
  return out;
31
31
  };
32
- const bundleViaModule = async (source, loadEsbuild) => {
32
+ const bundleViaModule = async (source, loadEsbuild, externalPackages) => {
33
33
  // esbuild is resolved by a COMPUTED specifier, never the literal string
34
34
  // 'esbuild'. Both rollup (bundle step) and @yao-pkg/pkg (binary step)
35
35
  // statically scan for literal import()/require() calls and would otherwise
@@ -66,6 +66,8 @@ const bundleViaModule = async (source, loadEsbuild) => {
66
66
  // Functions runtime has no node_modules). Node built-ins stay external on
67
67
  // platform:'node'. The banner re-creates require/__filename/__dirname for bundled CJS.
68
68
  banner: { js: ESM_CJS_INTEROP_BANNER },
69
+ // Only what the policy declared unbundleable; everything else is still inlined.
70
+ external: [...externalPackages],
69
71
  logLevel: "silent",
70
72
  })
71
73
  .catch((err) => {
@@ -113,7 +115,7 @@ const runEsbuild = (bin, args) => new Promise((resolve, reject) => {
113
115
  resolve({ code, stderr });
114
116
  });
115
117
  });
116
- const bundleViaBinary = async (source) => {
118
+ const bundleViaBinary = async (source, externalPackages) => {
117
119
  const bin = resolveEsbuild();
118
120
  const outDir = mkdtempSync(join(tmpdir(), "neon-fn-bundle-"));
119
121
  const outfile = join(outDir, "index.mjs");
@@ -126,6 +128,8 @@ const bundleViaBinary = async (source) => {
126
128
  "--format=esm",
127
129
  "--platform=node",
128
130
  `--banner:js=${ESM_CJS_INTEROP_BANNER}`,
131
+ // One flag per package, mirroring the module path's `external` array.
132
+ ...externalPackages.map((pkg) => `--external:${pkg}`),
129
133
  "--log-level=error",
130
134
  ]);
131
135
  if (code !== 0) {
@@ -144,15 +148,18 @@ const bundleViaBinary = async (source) => {
144
148
  // Bundle `source` into the files the Functions archive expects, keyed by
145
149
  // basename. npm installs bundle in-process via the esbuild module; the packaged
146
150
  // binary (and platforms esbuild can't run on) shell out to an esbuild binary.
147
- export const bundleEntry = async (source, deps = defaultDeps) => {
148
- if (deps.isPackaged())
149
- return bundleViaBinary(source);
151
+ export const bundleEntry = async (source, options = {}) => {
152
+ const isPackaged = options.isPackaged ?? defaultDeps.isPackaged;
153
+ const loadEsbuild = options.loadEsbuild ?? defaultDeps.loadEsbuild;
154
+ const externalPackages = options.externalPackages ?? [];
155
+ if (isPackaged())
156
+ return bundleViaBinary(source, externalPackages);
150
157
  try {
151
- return await bundleViaModule(source, deps.loadEsbuild);
158
+ return await bundleViaModule(source, loadEsbuild, externalPackages);
152
159
  }
153
160
  catch (err) {
154
161
  if (err instanceof ModuleNotAvailable)
155
- return bundleViaBinary(source);
162
+ return bundleViaBinary(source, externalPackages);
156
163
  throw err;
157
164
  }
158
165
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "2.38.5",
4
- "description": "CLI tool for Neon Serverless Postgres",
3
+ "version": "2.39.0",
4
+ "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",
7
7
  "neonctl",
@@ -47,7 +47,7 @@
47
47
  "cliui": "8.0.1",
48
48
  "diff": "5.2.0",
49
49
  "fflate": "^0.8.3",
50
- "neon-init": "0.20.4",
50
+ "neon-init": "0.20.5",
51
51
  "open": "^10.2.0",
52
52
  "openid-client": "6.8.1",
53
53
  "pg-protocol": "^1.14.0",
@@ -57,9 +57,9 @@
57
57
  "yaml": "^2.9.0",
58
58
  "yargs": "17.7.2",
59
59
  "@neon/sdk": "1.4.0",
60
- "@neon/config": "0.10.1",
61
- "@neon/config-runtime": "0.10.1",
62
- "@neon/env": "0.12.1"
60
+ "@neon/config": "0.11.0",
61
+ "@neon/config-runtime": "0.11.0",
62
+ "@neon/env": "0.12.2"
63
63
  },
64
64
  "optionalDependencies": {
65
65
  "esbuild": "0.28.1"