neon 3.0.0 → 3.1.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.
Files changed (198) hide show
  1. package/README.md +54 -0
  2. package/dist/_shared/auth_selection.js +76 -79
  3. package/dist/_shared/credentials.js +166 -187
  4. package/dist/_shared/env-core/env.js +354 -517
  5. package/dist/_shared/env-core/reuse-secrets.js +159 -203
  6. package/dist/_shared/paths.js +129 -126
  7. package/dist/_shared/profiles.js +192 -242
  8. package/dist/_shared/secure_file.js +36 -38
  9. package/dist/_virtual/_rolldown/runtime.js +13 -0
  10. package/dist/analytics.js +163 -207
  11. package/dist/api.js +815 -758
  12. package/dist/auth.js +121 -141
  13. package/dist/auth_context.js +39 -53
  14. package/dist/cli.js +4 -7
  15. package/dist/commands/api.js +220 -250
  16. package/dist/commands/api_keys.js +251 -314
  17. package/dist/commands/auth.js +283 -328
  18. package/dist/commands/bootstrap.js +372 -437
  19. package/dist/commands/branches.js +304 -455
  20. package/dist/commands/bucket.js +374 -514
  21. package/dist/commands/checkout.js +213 -298
  22. package/dist/commands/config.js +573 -690
  23. package/dist/commands/connection_string.js +137 -165
  24. package/dist/commands/data_api.js +238 -260
  25. package/dist/commands/databases.js +67 -76
  26. package/dist/commands/deploy.js +31 -25
  27. package/dist/commands/dev.js +639 -719
  28. package/dist/commands/diff.js +156 -200
  29. package/dist/commands/env.js +243 -303
  30. package/dist/commands/functions.js +275 -355
  31. package/dist/commands/index.js +70 -65
  32. package/dist/commands/init.js +84 -119
  33. package/dist/commands/inspect.js +55 -55
  34. package/dist/commands/ip_allow.js +88 -120
  35. package/dist/commands/link.js +874 -1019
  36. package/dist/commands/logs.js +291 -0
  37. package/dist/commands/neon_auth.js +725 -933
  38. package/dist/commands/operations.js +34 -25
  39. package/dist/commands/orgs.js +28 -18
  40. package/dist/commands/profile.js +614 -845
  41. package/dist/commands/projects.js +313 -373
  42. package/dist/commands/psql.js +60 -58
  43. package/dist/commands/roles.js +55 -58
  44. package/dist/commands/schema_diff.js +87 -131
  45. package/dist/commands/set_context.js +34 -26
  46. package/dist/commands/snapshots.js +288 -413
  47. package/dist/commands/status.js +41 -37
  48. package/dist/commands/user.js +21 -10
  49. package/dist/commands/vpc_endpoints.js +85 -113
  50. package/dist/config.js +7 -6
  51. package/dist/config_format.js +50 -66
  52. package/dist/config_template.js +128 -157
  53. package/dist/context.js +183 -235
  54. package/dist/current_branch_fast_path.js +40 -49
  55. package/dist/dev/env.js +313 -394
  56. package/dist/dev/functions.js +54 -68
  57. package/dist/dev/inputs.js +46 -58
  58. package/dist/dev/runtime.js +135 -164
  59. package/dist/dev/websocket.js +766 -959
  60. package/dist/env.js +27 -33
  61. package/dist/env_file.js +118 -132
  62. package/dist/env_services.js +36 -38
  63. package/dist/errors.js +57 -68
  64. package/dist/functions_api.js +45 -43
  65. package/dist/help.js +189 -140
  66. package/dist/index.js +182 -257
  67. package/dist/init/agents.js +137 -118
  68. package/dist/init/auth.js +58 -68
  69. package/dist/init/bootstrap.js +325 -396
  70. package/dist/init/build_config.js +4 -2
  71. package/dist/init/detect_agent.js +56 -101
  72. package/dist/init/editors.js +35 -52
  73. package/dist/init/enrich_output.js +51 -66
  74. package/dist/init/extension.js +134 -171
  75. package/dist/init/inspect.js +179 -266
  76. package/dist/init/interactive.js +510 -622
  77. package/dist/init/neonctl.js +117 -168
  78. package/dist/init/orchestrate.js +157 -173
  79. package/dist/init/phases/auth.js +188 -202
  80. package/dist/init/phases/cleanup.js +23 -23
  81. package/dist/init/phases/db.js +251 -277
  82. package/dist/init/phases/getting_started.js +213 -223
  83. package/dist/init/phases/mcp.js +174 -224
  84. package/dist/init/phases/migrations.js +247 -248
  85. package/dist/init/phases/neon_auth.js +114 -133
  86. package/dist/init/phases/setup.js +546 -703
  87. package/dist/init/phases/skills.js +75 -86
  88. package/dist/init/phases/status.js +72 -67
  89. package/dist/init/resolve_context.js +102 -99
  90. package/dist/init/route_command.js +91 -98
  91. package/dist/init/skills.js +174 -218
  92. package/dist/init/vsix.js +77 -99
  93. package/dist/log.js +17 -16
  94. package/dist/neon_services.js +104 -129
  95. package/dist/parameters.gen.js +481 -471
  96. package/dist/pkg.js +17 -19
  97. package/dist/profile_keys.js +44 -47
  98. package/dist/psql/cli.js +44 -47
  99. package/dist/psql/command/cmd_cond.js +231 -406
  100. package/dist/psql/command/cmd_connect.js +557 -764
  101. package/dist/psql/command/cmd_copy.js +727 -983
  102. package/dist/psql/command/cmd_describe.js +1499 -1688
  103. package/dist/psql/command/cmd_format.js +733 -905
  104. package/dist/psql/command/cmd_io.js +1293 -2082
  105. package/dist/psql/command/cmd_lo.js +297 -359
  106. package/dist/psql/command/cmd_meta.js +727 -878
  107. package/dist/psql/command/cmd_misc.js +138 -172
  108. package/dist/psql/command/cmd_pipeline.js +547 -1099
  109. package/dist/psql/command/cmd_restrict.js +119 -155
  110. package/dist/psql/command/cmd_show.js +529 -688
  111. package/dist/psql/command/dispatch.js +261 -325
  112. package/dist/psql/command/inputQueue.js +35 -33
  113. package/dist/psql/command/shared.js +49 -63
  114. package/dist/psql/complete/filenames.js +90 -133
  115. package/dist/psql/complete/index.js +59 -97
  116. package/dist/psql/complete/matcher.js +236 -300
  117. package/dist/psql/complete/psqlVars.js +218 -223
  118. package/dist/psql/complete/queries.js +159 -177
  119. package/dist/psql/complete/rules.js +1493 -2299
  120. package/dist/psql/core/common.js +762 -1180
  121. package/dist/psql/core/help.js +456 -546
  122. package/dist/psql/core/mainloop.js +692 -1302
  123. package/dist/psql/core/prompt.js +391 -408
  124. package/dist/psql/core/settings.js +429 -644
  125. package/dist/psql/core/sqlHelp.js +480 -554
  126. package/dist/psql/core/startup.js +626 -815
  127. package/dist/psql/core/syncVars.js +67 -110
  128. package/dist/psql/core/variables.js +156 -278
  129. package/dist/psql/describe/formatters.js +884 -1285
  130. package/dist/psql/describe/processNamePattern.js +173 -260
  131. package/dist/psql/describe/queries.js +1368 -2403
  132. package/dist/psql/describe/versionGate.js +32 -41
  133. package/dist/psql/index.js +1414 -1927
  134. package/dist/psql/io/history.js +232 -271
  135. package/dist/psql/io/input.js +103 -108
  136. package/dist/psql/io/lineEditor/buffer.js +238 -319
  137. package/dist/psql/io/lineEditor/complete.js +135 -213
  138. package/dist/psql/io/lineEditor/filename.js +139 -148
  139. package/dist/psql/io/lineEditor/index.js +653 -870
  140. package/dist/psql/io/lineEditor/keymap.js +544 -702
  141. package/dist/psql/io/lineEditor/vt100.js +294 -341
  142. package/dist/psql/io/pgpass.js +158 -187
  143. package/dist/psql/io/pgservice.js +146 -183
  144. package/dist/psql/io/psqlrc.js +328 -403
  145. package/dist/psql/print/aligned.js +1020 -1683
  146. package/dist/psql/print/asciidoc.js +180 -214
  147. package/dist/psql/print/crosstab.js +281 -442
  148. package/dist/psql/print/csv.js +48 -70
  149. package/dist/psql/print/html.js +195 -226
  150. package/dist/psql/print/json.js +75 -88
  151. package/dist/psql/print/latex.js +291 -364
  152. package/dist/psql/print/pager.js +171 -242
  153. package/dist/psql/print/troff.js +194 -226
  154. package/dist/psql/print/unaligned.js +69 -95
  155. package/dist/psql/print/units.js +167 -169
  156. package/dist/psql/scanner/slash.js +428 -483
  157. package/dist/psql/scanner/sql.js +445 -889
  158. package/dist/psql/scanner/stringutils.js +309 -379
  159. package/dist/psql/types/index.js +2 -7
  160. package/dist/psql/types/scanner.js +25 -22
  161. package/dist/psql/wire/connection.js +2042 -2803
  162. package/dist/psql/wire/copy.js +84 -100
  163. package/dist/psql/wire/notify.js +39 -59
  164. package/dist/psql/wire/pipeline.js +305 -518
  165. package/dist/psql/wire/protocol.js +349 -417
  166. package/dist/psql/wire/sasl.js +180 -265
  167. package/dist/psql/wire/tls.js +400 -561
  168. package/dist/storage_api.js +115 -129
  169. package/dist/test_utils/fixtures.js +94 -113
  170. package/dist/test_utils/oauth_server.js +10 -7
  171. package/dist/test_utils/project_dir.js +33 -0
  172. package/dist/utils/ai_gateway_notice.js +131 -162
  173. package/dist/utils/api_enums.js +21 -28
  174. package/dist/utils/auth.js +10 -4
  175. package/dist/utils/branch_notice.js +20 -19
  176. package/dist/utils/branch_picker.js +83 -89
  177. package/dist/utils/cli_name.js +15 -12
  178. package/dist/utils/compute_units.js +20 -27
  179. package/dist/utils/config_diff.js +127 -158
  180. package/dist/utils/enrichers.js +95 -148
  181. package/dist/utils/esbuild.js +130 -189
  182. package/dist/utils/flags.js +35 -47
  183. package/dist/utils/formats.js +8 -15
  184. package/dist/utils/git_diff.js +69 -80
  185. package/dist/utils/inspect_db.js +101 -143
  186. package/dist/utils/inspect_queries.js +179 -142
  187. package/dist/utils/middlewares.js +37 -44
  188. package/dist/utils/openapi.js +87 -99
  189. package/dist/utils/package_manager.js +312 -110
  190. package/dist/utils/point_in_time.js +49 -53
  191. package/dist/utils/psql.js +89 -106
  192. package/dist/utils/service_picker.js +55 -58
  193. package/dist/utils/string.js +5 -5
  194. package/dist/utils/ui.js +38 -55
  195. package/dist/utils/write_sync.js +26 -35
  196. package/dist/utils/zip.js +4 -3
  197. package/dist/writer.js +67 -87
  198. package/package.json +7 -5
@@ -1,758 +1,641 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import { packagesToStage, resolveConfig } from "@neon/config";
4
- import { apply, assertZipWithinLimits, createBranch as createBranchFromPolicy, describeNativeFinding, enforceLimits, findUndeclaredNativePackages, inspect, isPartialBranchCreateError, loadConfigFromFile, PushConflictError, plan, traceNativePackages, } from "@neon/config-runtime";
5
- import chalk from "chalk";
6
- import { getApiClient } from "../api.js";
7
- import { toNeonConfigView } from "../config_format.js";
8
- import { CONFIG_INIT_NONE_MEANS, CONFIG_INIT_SERVICES, CONFIG_INIT_UNAVAILABLE, FUNCTION_FILENAME, FUNCTION_SLUG, FUNCTION_TEMPLATE, REQUIRED_PACKAGES, renderNeonConfig, renderNeonConfigFromView, } from "../config_template.js";
9
- import { contextBranch, readContextFile } from "../context.js";
1
+ import { __exportAll } from "../_virtual/_rolldown/runtime.js";
10
2
  import { isCi } from "../env.js";
11
- import { loadEnvFileIntoProcess } from "../env_file.js";
12
3
  import { log } from "../log.js";
13
- import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption, } from "../neon_services.js";
14
- import { assertAiGatewayProvisionable, warnAiGateway, } from "../utils/ai_gateway_notice.js";
15
- import { announceTargetBranch } from "../utils/branch_notice.js";
4
+ import { getApiClient } from "../api.js";
5
+ import { contextBranch, readContextFile } from "../context.js";
16
6
  import { getCliName } from "../utils/cli_name.js";
17
- import { renderAppliedChanges, renderBranchSettingConflicts, } from "../utils/config_diff.js";
7
+ import { toNeonConfigView } from "../config_format.js";
8
+ import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
9
+ import { CONFIG_INIT_NONE_MEANS, CONFIG_INIT_SERVICES, CONFIG_INIT_UNAVAILABLE, FUNCTION_FILENAME, FUNCTION_SLUG, FUNCTION_TEMPLATE, REQUIRED_PACKAGES, renderNeonConfig, renderNeonConfigFromView } from "../config_template.js";
10
+ import { loadEnvFileIntoProcess } from "../env_file.js";
11
+ import { writer } from "../writer.js";
12
+ import { formatInstallCommand, installArgs, resolvePackageManager, runCommand } from "../utils/package_manager.js";
18
13
  import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
14
+ import { assertAiGatewayProvisionable, warnAiGateway } from "../utils/ai_gateway_notice.js";
15
+ import { announceTargetBranch } from "../utils/branch_notice.js";
16
+ import { renderAppliedChanges, renderBranchSettingConflicts } from "../utils/config_diff.js";
19
17
  import { bundleEntry } from "../utils/esbuild.js";
20
- import { addDependenciesArgs, resolvePackageManager, runCommand, } from "../utils/package_manager.js";
21
18
  import { pickServicesInteractively } from "../utils/service_picker.js";
22
19
  import { zipBundle } from "../utils/zip.js";
23
- import { writer } from "../writer.js";
24
20
  import { autoPullEnvAfterPin } from "./env.js";
21
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
22
+ import { dirname, join } from "node:path";
23
+ import chalk from "chalk";
24
+ import { packagesToStage, resolveConfig } from "@neon/config";
25
+ import { PushConflictError, apply, assertZipWithinLimits, createBranch, describeNativeFinding, enforceLimits, findUndeclaredNativePackages, inspect, isPartialBranchCreateError, loadConfigFromFile as loadConfigFromFile$1, plan, traceNativePackages } from "@neon/config-runtime";
26
+ //#region src/commands/config.ts
27
+ var config_exports = /* @__PURE__ */ __exportAll({
28
+ applyCmd: () => applyCmd,
29
+ applyFlags: () => applyFlags,
30
+ applyPolicyOnCreate: () => applyPolicyOnCreate,
31
+ builder: () => builder,
32
+ command: () => command,
33
+ createBranchFromPolicyOnCheckout: () => createBranchFromPolicyOnCheckout,
34
+ describe: () => describe,
35
+ envFlag: () => envFlag,
36
+ envPullFlag: () => envPullFlag,
37
+ handler: () => handler,
38
+ hasNeonConfigFile: () => hasNeonConfigFile,
39
+ initCmd: () => initCmd,
40
+ planCmd: () => planCmd,
41
+ status: () => status
42
+ });
25
43
  /**
26
- * Bundle a function with neonctl's OWN bundler (the shared esbuild helper) so the
27
- * config-runtime never has to import esbuild itself. Injecting this keeps esbuild
28
- * out of config-runtime's static module graph — and therefore out of the packaged
29
- * neonctl snapshot, which resolves esbuild dynamically at deploy time.
30
- */
44
+ * Bundle a function with neonctl's OWN bundler (the shared esbuild helper) so the
45
+ * config-runtime never has to import esbuild itself. Injecting this keeps esbuild
46
+ * out of config-runtime's static module graph — and therefore out of the packaged
47
+ * neonctl snapshot, which resolves esbuild dynamically at deploy time.
48
+ */
31
49
  const neonctlBundler = async (fn) => {
32
- const externalPackages = fn.externalPackages ?? [];
33
- const { files, metafile, warnings } = await bundleEntry(fn.source, {
34
- externalPackages: externalPackages.map((pkg) => pkg.name),
35
- });
36
- for (const warning of warnings)
37
- log.warning(warning);
38
- // Advisory only — the evidence cannot prove the code path is reached, so a package with a
39
- // working JavaScript fallback must not have its deploy blocked. See native-detect.
40
- for (const finding of findUndeclaredNativePackages({
41
- metafile,
42
- declared: externalPackages.map((pkg) => pkg.name),
43
- projectDir: dirname(fn.source),
44
- })) {
45
- log.warning(describeNativeFinding(fn.slug, finding));
46
- }
47
- const staged = packagesToStage(externalPackages);
48
- // Nothing to stage is the pre-existing path: zip the esbuild output and nothing else,
49
- // producing the archive it always did.
50
- if (staged.length === 0)
51
- return zipBundle(files);
52
- // Staged packages are installed for the runtime target, traced, and merged in under their
53
- // `node_modules/...` paths. The tree layout is load-bearing — a `.node` addon finds its
54
- // sibling shared libraries relative to its own directory.
55
- const traced = await traceNativePackages({
56
- slug: fn.slug,
57
- packages: staged,
58
- projectDir: dirname(fn.source),
59
- });
60
- for (const warning of traced.warnings)
61
- log.warning(warning);
62
- const entries = { ...files, ...traced.entries };
63
- // Re-checked against the final archive: the staged files were measured without the
64
- // bundle, so the entry count and uncompressed total are only complete now.
65
- enforceLimits(fn.slug, entries);
66
- const zip = zipBundle(entries);
67
- assertZipWithinLimits(fn.slug, zip, entries);
68
- return zip;
50
+ const externalPackages = fn.externalPackages ?? [];
51
+ const { files, metafile, warnings } = await bundleEntry(fn.source, { externalPackages: externalPackages.map((pkg) => pkg.name) });
52
+ for (const warning of warnings) log.warning(warning);
53
+ for (const finding of findUndeclaredNativePackages({
54
+ metafile,
55
+ declared: externalPackages.map((pkg) => pkg.name),
56
+ projectDir: dirname(fn.source)
57
+ })) log.warning(describeNativeFinding(fn.slug, finding));
58
+ const staged = packagesToStage(externalPackages);
59
+ if (staged.length === 0) return zipBundle(files);
60
+ const traced = await traceNativePackages({
61
+ slug: fn.slug,
62
+ packages: staged,
63
+ projectDir: dirname(fn.source)
64
+ });
65
+ for (const warning of traced.warnings) log.warning(warning);
66
+ const entries = {
67
+ ...files,
68
+ ...traced.entries
69
+ };
70
+ enforceLimits(fn.slug, entries);
71
+ const zip = zipBundle(entries);
72
+ assertZipWithinLimits(fn.slug, zip, entries);
73
+ return zip;
69
74
  };
70
- const INSPECT_FIELDS = ["project", "branch", "config"];
75
+ const INSPECT_FIELDS = [
76
+ "project",
77
+ "branch",
78
+ "config"
79
+ ];
71
80
  /**
72
- * Shared `--env` flag for `config plan|apply` and `deploy`. Loads a `.env` into
73
- * `process.env` before the policy is evaluated.
74
- */
75
- export const envFlag = {
76
- env: {
77
- describe: "Path to a .env file to load into the environment before evaluating neon.ts " +
78
- "(so function env values resolve from it). Existing env vars are not overridden.",
79
- type: "string",
80
- },
81
- };
81
+ * Shared `--env` flag for `config plan|apply` and `deploy`. Loads a `.env` into
82
+ * `process.env` before the policy is evaluated.
83
+ */
84
+ const envFlag = { env: {
85
+ describe: "Path to a .env file to load into the environment before evaluating neon.ts (so function env values resolve from it). Existing env vars are not overridden.",
86
+ type: "string"
87
+ } };
82
88
  /** Apply-only flags, exported so `deploy` can reuse the exact same surface. */
83
- export const applyFlags = {
84
- "update-existing": {
85
- describe: "Auto-confirm overriding existing remote settings on the branch",
86
- type: "boolean",
87
- default: false,
88
- },
89
- "allow-protected": {
90
- describe: "Auto-confirm applying to a branch marked protected on Neon",
91
- type: "boolean",
92
- default: false,
93
- },
89
+ const applyFlags = {
90
+ "update-existing": {
91
+ describe: "Auto-confirm overriding existing remote settings on the branch",
92
+ type: "boolean",
93
+ default: false
94
+ },
95
+ "allow-protected": {
96
+ describe: "Auto-confirm applying to a branch marked protected on Neon",
97
+ type: "boolean",
98
+ default: false
99
+ }
94
100
  };
95
101
  /**
96
- * `--env-pull` for `config apply` / `deploy` (shared so both expose the identical surface).
97
- * After a successful apply, the branch's Neon env vars are written to a local `.env` — the
98
- * same bundled convenience as `link` / `checkout`. On by default; `--no-env-pull` opts out.
99
- */
100
- export const envPullFlag = {
101
- "env-pull": {
102
- describe: "Pull the branch's Neon env vars (DATABASE_URL, …) into a local .env after a " +
103
- "successful apply. On by default; use --no-env-pull to skip (e.g. when injecting " +
104
- "env at runtime with `neon-env run` / `neon dev`).",
105
- type: "boolean",
106
- default: true,
107
- },
108
- };
109
- // ── `config init` ─────────────────────────────────────────────────────────────
102
+ * `--env-pull` for `config apply` / `deploy` (shared so both expose the identical surface).
103
+ * After a successful apply, the branch's Neon env vars are written to a local `.env` — the
104
+ * same bundled convenience as `link` / `checkout`. On by default; `--no-env-pull` opts out.
105
+ */
106
+ const envPullFlag = { "env-pull": {
107
+ describe: "Pull the branch's Neon env vars (DATABASE_URL, …) into a local .env after a successful apply. On by default; use --no-env-pull to skip (e.g. when injecting env at runtime with `neon-env run` / `neon dev`).",
108
+ type: "boolean",
109
+ default: true
110
+ } };
110
111
  /** package.json fields a dependency can be declared in. */
111
112
  const DEPENDENCY_FIELDS = [
112
- "dependencies",
113
- "devDependencies",
114
- "peerDependencies",
115
- "optionalDependencies",
113
+ "dependencies",
114
+ "devDependencies",
115
+ "peerDependencies",
116
+ "optionalDependencies"
116
117
  ];
117
118
  /** Config filenames the runtime loads (mirrors @neon/config's loader). */
118
- const NEON_CONFIG_FILENAMES = ["neon.ts", "neon.mts", "neon.js", "neon.mjs"];
119
+ const NEON_CONFIG_FILENAMES = [
120
+ "neon.ts",
121
+ "neon.mts",
122
+ "neon.js",
123
+ "neon.mjs"
124
+ ];
119
125
  /** Whether `dir` already has a Neon config file the runtime would load. */
120
- export const hasNeonConfigFile = (dir) => NEON_CONFIG_FILENAMES.some((name) => existsSync(join(dir, name)));
126
+ const hasNeonConfigFile = (dir) => NEON_CONFIG_FILENAMES.some((name) => existsSync(join(dir, name)));
121
127
  const isRecord = (value) => typeof value === "object" && value !== null;
122
128
  /**
123
- * The {@link REQUIRED_PACKAGES} not already declared in the project's package.json
124
- * (any dependency field). A missing or malformed package.json means none are
125
- * declared, so all are reported missing.
126
- */
129
+ * The {@link REQUIRED_PACKAGES} not already declared in the project's package.json
130
+ * (any dependency field). A missing or malformed package.json means none are
131
+ * declared, so all are reported missing.
132
+ */
127
133
  const missingDependencies = (cwd) => {
128
- const declared = new Set();
129
- const pkgPath = join(cwd, "package.json");
130
- if (existsSync(pkgPath)) {
131
- let parsed;
132
- try {
133
- parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
134
- }
135
- catch {
136
- parsed = undefined;
137
- }
138
- if (isRecord(parsed)) {
139
- for (const field of DEPENDENCY_FIELDS) {
140
- const deps = parsed[field];
141
- if (isRecord(deps)) {
142
- for (const name of Object.keys(deps))
143
- declared.add(name);
144
- }
145
- }
146
- }
147
- }
148
- return REQUIRED_PACKAGES.filter((pkg) => !declared.has(pkg));
134
+ const declared = /* @__PURE__ */ new Set();
135
+ const pkgPath = join(cwd, "package.json");
136
+ if (existsSync(pkgPath)) {
137
+ let parsed;
138
+ try {
139
+ parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
140
+ } catch {
141
+ parsed = void 0;
142
+ }
143
+ if (isRecord(parsed)) for (const field of DEPENDENCY_FIELDS) {
144
+ const deps = parsed[field];
145
+ if (isRecord(deps)) for (const name of Object.keys(deps)) declared.add(name);
146
+ }
147
+ }
148
+ return REQUIRED_PACKAGES.filter((pkg) => !declared.has(pkg));
149
149
  };
150
150
  /**
151
- * Which services the scaffolded policy declares. `--services` always wins so a script or an
152
- * agent gets the same result without a TTY; an injected picker is next (tests); the real
153
- * picker only runs on an interactive terminal outside CI. Everything else scaffolds the
154
- * starter policy, which is what `config init` has always written.
155
- */
151
+ * Which services the scaffolded policy declares. `--services` always wins so a script or an
152
+ * agent gets the same result without a TTY; an injected picker is next (tests); the real
153
+ * picker only runs on an interactive terminal outside CI. Everything else scaffolds the
154
+ * starter policy, which is what `config init` has always written.
155
+ */
156
156
  const resolveServices = async (props) => {
157
- if (props.services !== undefined) {
158
- return parseServices(props.services, {
159
- allowed: CONFIG_INIT_SERVICES,
160
- whyUnavailable: CONFIG_INIT_UNAVAILABLE,
161
- flag: "--services",
162
- noneMeans: CONFIG_INIT_NONE_MEANS,
163
- onDeprecated: (used, canonical) => log.warning(deprecatedServiceMessage(used, canonical)),
164
- });
165
- }
166
- if (props.pickServices) {
167
- return props.pickServices();
168
- }
169
- if (isCi() || !process.stdout.isTTY) {
170
- return [];
171
- }
172
- return pickServicesInteractively();
157
+ if (props.services !== void 0) return parseServices(props.services, {
158
+ allowed: CONFIG_INIT_SERVICES,
159
+ whyUnavailable: CONFIG_INIT_UNAVAILABLE,
160
+ flag: "--services",
161
+ noneMeans: CONFIG_INIT_NONE_MEANS,
162
+ onDeprecated: (used, canonical) => log.warning(deprecatedServiceMessage(used, canonical))
163
+ });
164
+ if (props.pickServices) return props.pickServices();
165
+ if (isCi() || !process.stdout.isTTY) return [];
166
+ return pickServicesInteractively();
173
167
  };
174
168
  /**
175
- * Write the hello-world handler the scaffolded `preview.functions` entry points at. An
176
- * existing `hello.ts` is left alone: the declared function keeps pointing at it, which is the
177
- * better outcome than overwriting a file the user wrote.
178
- */
169
+ * Write the hello-world handler the scaffolded `preview.functions` entry points at. An
170
+ * existing `hello.ts` is left alone: the declared function keeps pointing at it, which is the
171
+ * better outcome than overwriting a file the user wrote.
172
+ */
179
173
  const scaffoldFunction = (cwd) => {
180
- const path = join(cwd, FUNCTION_FILENAME);
181
- if (existsSync(path)) {
182
- log.info("Found an existing %s — leaving it untouched; the %s function points at it.", FUNCTION_FILENAME, FUNCTION_SLUG);
183
- return;
184
- }
185
- writeFileSync(path, FUNCTION_TEMPLATE);
186
- log.info("Created %s — the source of the %s function.", FUNCTION_FILENAME, FUNCTION_SLUG);
174
+ const path = join(cwd, FUNCTION_FILENAME);
175
+ if (existsSync(path)) {
176
+ log.info("Found an existing %s — leaving it untouched; the %s function points at it.", FUNCTION_FILENAME, FUNCTION_SLUG);
177
+ return;
178
+ }
179
+ writeFileSync(path, FUNCTION_TEMPLATE);
180
+ log.info("Created %s — the source of the %s function.", FUNCTION_FILENAME, FUNCTION_SLUG);
187
181
  };
188
182
  /**
189
- * Read a branch's live state and render it as a `neon.ts`. The read goes through the same
190
- * {@link liveConfigView} as `config status`, so a seeded policy declares exactly what
191
- * `config status --config-json` reports — including what it cannot report (see
192
- * {@link renderNeonConfigFromView}).
193
- */
183
+ * Read a branch's live state and render it as a `neon.ts`. The read goes through the same
184
+ * {@link liveConfigView} as `config status`, so a seeded policy declares exactly what
185
+ * `config status --config-json` reports — including what it cannot report (see
186
+ * {@link renderNeonConfigFromView}).
187
+ */
194
188
  const seedFromBranch = async (props) => {
195
- const { apiClient, projectId } = props;
196
- if (!apiClient || !projectId) {
197
- throw new Error("--from-branch needs a project. Pass --project-id, or run `neon link` to pin one in .neon.");
198
- }
199
- const ref = await resolveBranchRef({
200
- apiClient,
201
- projectId,
202
- ...(props.branch !== undefined ? { branch: props.branch } : {}),
203
- });
204
- if (ref.usedDefault) {
205
- log.info("No branch pinned or passed — seeding from the project's default branch %s.", ref.branchName);
206
- }
207
- const { live, view } = await liveConfigView({
208
- projectId,
209
- branchId: ref.branchId,
210
- ...(props.apiKey ? { apiKey: props.apiKey } : {}),
211
- ...(props.apiHost ? { apiHost: props.apiHost } : {}),
212
- ...(props.runtimeApi ? { runtimeApi: props.runtimeApi } : {}),
213
- });
214
- const rendered = renderNeonConfigFromView(view, live.branch.name);
215
- return { ...rendered, branchName: live.branch.name };
189
+ const { apiClient, projectId } = props;
190
+ if (!apiClient || !projectId) throw new Error("--from-branch needs a project. Pass --project-id, or run `neon link` to pin one in .neon.");
191
+ const ref = await resolveBranchRef({
192
+ apiClient,
193
+ projectId,
194
+ ...props.branch !== void 0 ? { branch: props.branch } : {}
195
+ });
196
+ if (ref.usedDefault) log.info("No branch pinned or passed — seeding from the project's default branch %s.", ref.branchName);
197
+ const { live, view } = await liveConfigView({
198
+ projectId,
199
+ branchId: ref.branchId,
200
+ ...props.apiKey ? { apiKey: props.apiKey } : {},
201
+ ...props.apiHost ? { apiHost: props.apiHost } : {},
202
+ ...props.runtimeApi ? { runtimeApi: props.runtimeApi } : {}
203
+ });
204
+ return {
205
+ ...renderNeonConfigFromView(view, live.branch.name),
206
+ branchName: live.branch.name
207
+ };
216
208
  };
217
209
  /**
218
- * Scaffold a `neon.ts` policy and make sure the Neon config packages are
219
- * installed, so a project can go straight to `neon config plan` / `apply`.
220
- * Local-only unless `--from-branch` is set (see {@link isConfigInit}).
221
- */
222
- export const initCmd = async (props) => {
223
- const cwd = props.cwd ?? process.cwd();
224
- const run = props.run ?? runCommand;
225
- // 1. Scaffold neon.ts unless the project already has a Neon config file. Resolving the
226
- // services (which may prompt) happens only when there is something to write — asking
227
- // which services to declare and then declaring nothing would be a lie.
228
- const existing = NEON_CONFIG_FILENAMES.find((name) => existsSync(join(cwd, name)));
229
- if (existing) {
230
- log.info("Found an existing %s — leaving it untouched.", existing);
231
- }
232
- else if (props.fromBranch) {
233
- const { source, seeded, branchName } = await seedFromBranch(props);
234
- writeFileSync(join(cwd, "neon.ts"), source);
235
- if (seeded) {
236
- log.info("Created neon.ts from the live state of %s.", branchName);
237
- }
238
- else {
239
- log.info("%s declares no services and no branch settings — created neon.ts with the starter policy instead.", branchName);
240
- }
241
- }
242
- else {
243
- const services = await resolveServices(props);
244
- writeFileSync(join(cwd, "neon.ts"), renderNeonConfig(services));
245
- if (services.length === 0) {
246
- log.info("Created neon.ts with a starter policy.");
247
- }
248
- else {
249
- log.info("Created neon.ts declaring %s.", services.join(", "));
250
- }
251
- if (services.includes("functions")) {
252
- scaffoldFunction(cwd);
253
- }
254
- }
255
- // 2. Make sure the config packages are installed.
256
- const missing = missingDependencies(cwd);
257
- if (missing.length === 0) {
258
- log.info("%s are already installed.", REQUIRED_PACKAGES.join(" and "));
259
- }
260
- else {
261
- const pm = resolvePackageManager(cwd);
262
- const args = addDependenciesArgs(pm, missing);
263
- if (props.install === false) {
264
- log.info("Install the Neon config packages to use neon.ts: %s %s", pm, args.join(" "));
265
- }
266
- else {
267
- log.info("Installing %s with %s…", missing.join(", "), pm);
268
- const ok = await run(pm, args, cwd);
269
- if (!ok) {
270
- log.warning("Could not install the config packages automatically. Run by hand: %s %s", pm, args.join(" "));
271
- }
272
- }
273
- }
274
- log.info("Next: edit neon.ts, then run `neon config plan` to preview and `neon config apply`.");
210
+ * Scaffold a `neon.ts` policy and make sure the Neon config packages are
211
+ * installed, so a project can go straight to `neon config plan` / `apply`.
212
+ * Local-only unless `--from-branch` is set (see {@link isConfigInit}).
213
+ */
214
+ const initCmd = async (props) => {
215
+ const cwd = props.cwd ?? process.cwd();
216
+ const run = props.run ?? runCommand;
217
+ const existing = NEON_CONFIG_FILENAMES.find((name) => existsSync(join(cwd, name)));
218
+ if (existing) log.info("Found an existing %s — leaving it untouched.", existing);
219
+ else if (props.fromBranch) {
220
+ const { source, seeded, branchName } = await seedFromBranch(props);
221
+ writeFileSync(join(cwd, "neon.ts"), source);
222
+ if (seeded) log.info("Created neon.ts from the live state of %s.", branchName);
223
+ else log.info("%s declares no services and no branch settings — created neon.ts with the starter policy instead.", branchName);
224
+ } else {
225
+ const services = await resolveServices(props);
226
+ writeFileSync(join(cwd, "neon.ts"), renderNeonConfig(services));
227
+ if (services.length === 0) log.info("Created neon.ts with a starter policy.");
228
+ else log.info("Created neon.ts declaring %s.", services.join(", "));
229
+ if (services.includes("functions")) scaffoldFunction(cwd);
230
+ }
231
+ const missing = missingDependencies(cwd);
232
+ if (missing.length === 0) log.info("%s are already installed.", REQUIRED_PACKAGES.join(" and "));
233
+ else {
234
+ const pm = resolvePackageManager(cwd);
235
+ const args = installArgs(pm, missing);
236
+ if (props.install === false) log.info("Install the Neon config packages to use neon.ts: %s", formatInstallCommand(pm, missing));
237
+ else {
238
+ log.info("Installing %s with %s…", missing.join(", "), pm);
239
+ if (!await run(pm, args, cwd)) log.warning("Could not install the config packages automatically. Run by hand: %s", formatInstallCommand(pm, missing));
240
+ }
241
+ }
242
+ log.info("Next: edit neon.ts, then run `neon config plan` to preview and `neon config apply`.");
275
243
  };
276
- export const command = "config";
277
- export const describe = "Manage a branch with a neon.ts policy";
278
- export const builder = (argv) => argv
279
- .usage("$0 config <sub-command> [options]")
280
- .options({
281
- "project-id": {
282
- describe: "Project ID",
283
- type: "string",
284
- },
285
- branch: {
286
- describe: "Branch ID or name",
287
- type: "string",
288
- },
289
- })
290
- .middleware(fillSingleProject)
291
- .command("status", "Show the branch's live Neon state", (yargs) => yargs.options({
292
- "config-json": {
293
- describe: "Print only the branch's live config as neon.ts-shaped JSON " +
294
- "(services + branch tuning + preview), to stdout. Useful for " +
295
- "scripting or copying into a neon.ts.",
296
- type: "boolean",
297
- default: false,
298
- },
299
- "current-branch": {
300
- describe: "Print only the linked branch name from the local .neon file " +
301
- "(no network). Exits non-zero when no branch is pinned.",
302
- type: "boolean",
303
- default: false,
304
- },
305
- }), (args) => status(args))
306
- .command("plan", "Show what `config apply` would change (dry run)", (yargs) => yargs.options({
307
- config: {
308
- describe: "Path to a neon.ts policy (defaults to walking up from cwd)",
309
- type: "string",
310
- },
311
- ...envFlag,
312
- }), (args) => planCmd(args))
313
- .command("apply", "Apply a neon.ts policy to the branch", (yargs) => yargs.options({
314
- config: {
315
- describe: "Path to a neon.ts policy (defaults to walking up from cwd)",
316
- type: "string",
317
- },
318
- ...envFlag,
319
- ...applyFlags,
320
- ...envPullFlag,
321
- }), (args) => applyCmd(args))
322
- .command("init", "Scaffold a neon.ts policy and install the Neon config packages", (yargs) => yargs.options({
323
- install: {
324
- describe: "Install @neon/config and @neon/env if they're missing. " +
325
- "On by default; use --no-install to just print the command.",
326
- type: "boolean",
327
- default: true,
328
- },
329
- services: servicesOption({
330
- key: "services",
331
- allowed: CONFIG_INIT_SERVICES,
332
- noneMeans: CONFIG_INIT_NONE_MEANS,
333
- describe: "Services the scaffolded neon.ts declares",
334
- also: "Omitted: pick interactively on a terminal, starter policy in " +
335
- "CI or without a TTY.",
336
- }),
337
- "from-branch": {
338
- describe: "Seed neon.ts from a branch's live Neon state instead of asking. Uses the " +
339
- "branch pinned in .neon, or --branch <name|id>, or the project's default " +
340
- "branch. The only mode of `config init` that calls the Neon API.",
341
- type: "boolean",
342
- // No `default`: yargs counts a defaulted key as provided, so
343
- // `default: false` makes `conflicts` reject every `--services` run.
344
- conflicts: "services",
345
- },
244
+ const command = "config";
245
+ const describe = "Manage a branch with a neon.ts policy";
246
+ const builder = (argv) => argv.usage("$0 config <sub-command> [options]").options({
247
+ "project-id": {
248
+ describe: "Project ID",
249
+ type: "string"
250
+ },
251
+ branch: {
252
+ describe: "Branch ID or name",
253
+ type: "string"
254
+ }
255
+ }).middleware(fillSingleProject).command("status", "Show the branch's live Neon state", (yargs) => yargs.options({
256
+ "config-json": {
257
+ describe: "Print only the branch's live config as neon.ts-shaped JSON (services + branch tuning + preview), to stdout. Useful for scripting or copying into a neon.ts.",
258
+ type: "boolean",
259
+ default: false
260
+ },
261
+ "current-branch": {
262
+ describe: "Print only the linked branch name from the local .neon file (no network). Exits non-zero when no branch is pinned.",
263
+ type: "boolean",
264
+ default: false
265
+ }
266
+ }), (args) => status(args)).command("plan", "Show what `config apply` would change (dry run)", (yargs) => yargs.options({
267
+ config: {
268
+ describe: "Path to a neon.ts policy (defaults to walking up from cwd)",
269
+ type: "string"
270
+ },
271
+ ...envFlag
272
+ }), (args) => planCmd(args)).command("apply", "Apply a neon.ts policy to the branch", (yargs) => yargs.options({
273
+ config: {
274
+ describe: "Path to a neon.ts policy (defaults to walking up from cwd)",
275
+ type: "string"
276
+ },
277
+ ...envFlag,
278
+ ...applyFlags,
279
+ ...envPullFlag
280
+ }), (args) => applyCmd(args)).command("init", "Scaffold a neon.ts policy and install the Neon config packages", (yargs) => yargs.options({
281
+ install: {
282
+ describe: "Install @neon/config and @neon/env if they're missing. On by default; use --no-install to just print the command.",
283
+ type: "boolean",
284
+ default: true
285
+ },
286
+ services: servicesOption({
287
+ key: "services",
288
+ allowed: CONFIG_INIT_SERVICES,
289
+ noneMeans: CONFIG_INIT_NONE_MEANS,
290
+ describe: "Services the scaffolded neon.ts declares",
291
+ also: "Omitted: pick interactively on a terminal, starter policy in CI or without a TTY."
292
+ }),
293
+ "from-branch": {
294
+ describe: "Seed neon.ts from a branch's live Neon state instead of asking. Uses the branch pinned in .neon, or --branch <name|id>, or the project's default branch. The only mode of `config init` that calls the Neon API.",
295
+ type: "boolean",
296
+ conflicts: "services"
297
+ }
346
298
  }), (args) => initCmd({
347
- ...args,
348
- // `args` is untyped here, and "flag omitted" has to stay distinct from
349
- // "flag given" — it decides whether the picker runs at all.
350
- services: servicesFlagValue(args.services),
299
+ ...args,
300
+ services: servicesFlagValue(args.services)
351
301
  }));
352
- export const handler = (args) => {
353
- return args;
302
+ const handler = (args) => {
303
+ return args;
354
304
  };
355
305
  /**
356
- * A branch's live state, plus that state projected into the `neon.ts`-shaped
357
- * {@link NeonConfigView}. Shared by `config status` and `config init --from-branch` so both
358
- * read the branch through one path: what `status --config-json` prints is exactly what
359
- * `init --from-branch` writes.
360
- *
361
- * The pulled `config` carries the branch's tuning inside a closure that JSON can't render, so
362
- * it is resolved against the live branch target first.
363
- */
306
+ * A branch's live state, plus that state projected into the `neon.ts`-shaped
307
+ * {@link NeonConfigView}. Shared by `config status` and `config init --from-branch` so both
308
+ * read the branch through one path: what `status --config-json` prints is exactly what
309
+ * `init --from-branch` writes.
310
+ *
311
+ * The pulled `config` carries the branch's tuning inside a closure that JSON can't render, so
312
+ * it is resolved against the live branch target first.
313
+ */
364
314
  const liveConfigView = async (opts) => {
365
- const live = await inspect({
366
- projectId: opts.projectId,
367
- branchId: opts.branchId,
368
- ...(opts.apiKey ? { apiKey: opts.apiKey } : {}),
369
- ...(opts.apiHost ? { apiHost: opts.apiHost } : {}),
370
- ...(opts.runtimeApi ? { api: opts.runtimeApi } : {}),
371
- });
372
- const resolved = resolveConfig(live.config, {
373
- name: live.branch.name,
374
- id: live.branch.id,
375
- exists: true,
376
- isDefault: live.branch.isDefault,
377
- isProtected: live.branch.protected,
378
- ...(live.branch.parent ? { parentId: live.branch.parent } : {}),
379
- ...(live.branch.expiresAt ? { expiresAt: live.branch.expiresAt } : {}),
380
- });
381
- return { live, view: toNeonConfigView(resolved, live.preview) };
315
+ const live = await inspect({
316
+ projectId: opts.projectId,
317
+ branchId: opts.branchId,
318
+ ...opts.apiKey ? { apiKey: opts.apiKey } : {},
319
+ ...opts.apiHost ? { apiHost: opts.apiHost } : {},
320
+ ...opts.runtimeApi ? { api: opts.runtimeApi } : {}
321
+ });
322
+ const resolved = resolveConfig(live.config, {
323
+ name: live.branch.name,
324
+ id: live.branch.id,
325
+ exists: true,
326
+ isDefault: live.branch.isDefault,
327
+ isProtected: live.branch.protected,
328
+ ...live.branch.parent ? { parentId: live.branch.parent } : {},
329
+ ...live.branch.expiresAt ? { expiresAt: live.branch.expiresAt } : {}
330
+ });
331
+ return {
332
+ live,
333
+ view: toNeonConfigView(resolved, live.preview)
334
+ };
382
335
  };
383
336
  const loadConfig = async (props) => {
384
- // Load the optional --env file FIRST so a `neon.ts` whose function `env` values read
385
- // `process.env.X` sees them. Must happen before the policy module is imported/evaluated.
386
- if (props.env) {
387
- const applied = loadEnvFileIntoProcess(props.env);
388
- log.debug("Loaded %d var(s) from %s into the environment: %s", applied.length, props.env, applied.join(", "));
389
- }
390
- const { config } = await loadConfigFromFile({
391
- ...(props.config ? { path: props.config } : {}),
392
- });
393
- return config;
337
+ if (props.env) {
338
+ const applied = loadEnvFileIntoProcess(props.env);
339
+ log.debug("Loaded %d var(s) from %s into the environment: %s", applied.length, props.env, applied.join(", "));
340
+ }
341
+ const { config } = await loadConfigFromFile$1({ ...props.config ? { path: props.config } : {} });
342
+ return config;
394
343
  };
395
- export const status = async (props) => {
396
- // `--current-branch` short-circuits here (before resolveBranchRef), so it wins
397
- // over --config-json and ignores --output. See ConfigProps.currentBranch / isCurrentBranchProbe.
398
- if (props.currentBranch) {
399
- const branch = contextBranch(readContextFile(props.contextFile));
400
- if (branch) {
401
- process.stdout.write(`${branch}\n`);
402
- }
403
- else {
404
- // No branch pinned: hint on stderr and exit non-zero (grep-style) so a prompt's
405
- // `when` hides the segment cleanly instead of rendering a bare icon.
406
- log.info(`No branch pinned. Run \`${getCliName()} checkout <branch>\` to pin a branch and pull its env vars.`);
407
- process.exitCode = 1;
408
- }
409
- return;
410
- }
411
- const branch = await resolveBranchRef(props);
412
- // `--config-json` is a script-friendly mode that emits only JSON to stdout, so keep it
413
- // pristine; the regular human view gets the "which branch am I inspecting" guardrail.
414
- if (!props.configJson) {
415
- announceTargetBranch(props, branch, "Inspecting branch");
416
- }
417
- const { live, view: configView } = await liveConfigView({
418
- projectId: props.projectId,
419
- branchId: branch.branchId,
420
- ...(props.apiKey ? { apiKey: props.apiKey } : {}),
421
- ...(props.apiHost ? { apiHost: props.apiHost } : {}),
422
- ...(props.runtimeApi ? { runtimeApi: props.runtimeApi } : {}),
423
- });
424
- // `--config-json`: emit just the neon.ts-shaped config to stdout (script-friendly,
425
- // copy-paste-able), regardless of the global --output.
426
- if (props.configJson) {
427
- process.stdout.write(`${JSON.stringify(configView, null, 2)}\n`);
428
- return;
429
- }
430
- // Default: the live project/branch tables, but with the unhelpful raw `config` replaced
431
- // by the resolved neon.ts-shaped view so the user sees enabled infra + branch tuning.
432
- writer(props).end({ project: live.project, branch: live.branch, config: configView }, { fields: INSPECT_FIELDS });
344
+ const status = async (props) => {
345
+ if (props.currentBranch) {
346
+ const branch = contextBranch(readContextFile(props.contextFile));
347
+ if (branch) process.stdout.write(`${branch}\n`);
348
+ else {
349
+ log.info(`No branch pinned. Run \`${getCliName()} checkout <branch>\` to pin a branch and pull its env vars.`);
350
+ process.exitCode = 1;
351
+ }
352
+ return;
353
+ }
354
+ const branch = await resolveBranchRef(props);
355
+ if (!props.configJson) announceTargetBranch(props, branch, "Inspecting branch");
356
+ const { live, view: configView } = await liveConfigView({
357
+ projectId: props.projectId,
358
+ branchId: branch.branchId,
359
+ ...props.apiKey ? { apiKey: props.apiKey } : {},
360
+ ...props.apiHost ? { apiHost: props.apiHost } : {},
361
+ ...props.runtimeApi ? { runtimeApi: props.runtimeApi } : {}
362
+ });
363
+ if (props.configJson) {
364
+ process.stdout.write(`${JSON.stringify(configView, null, 2)}\n`);
365
+ return;
366
+ }
367
+ writer(props).end({
368
+ project: live.project,
369
+ branch: live.branch,
370
+ config: configView
371
+ }, { fields: INSPECT_FIELDS });
433
372
  };
434
- export const planCmd = async (props) => {
435
- const config = await loadConfig(props);
436
- const branch = await resolveBranchRef(props);
437
- announceTargetBranch(props, branch, "Planning against branch");
438
- const branchId = branch.branchId;
439
- // `plan` is a dry run that never bundles, so its options don't accept (or need)
440
- // an injected bundler — only `apply` does (it uses neonctlBundler).
441
- const result = await plan(config, {
442
- projectId: props.projectId,
443
- branchId,
444
- ...(props.apiKey ? { apiKey: props.apiKey } : {}),
445
- ...(props.apiHost ? { apiHost: props.apiHost } : {}),
446
- ...(props.runtimeApi ? { api: props.runtimeApi } : {}),
447
- });
448
- const services = utilizedServices(config);
449
- reportPushResult(props, result, "plan", services);
450
- // `plan` is a dry run and never pulls credentials, so it can only offer the plan-based
451
- // (Free) AI Gateway notice — the reduced-model-set check needs a live gateway token,
452
- // which `apply`/`checkout`/`env pull` get via the bundled env pull. Best-effort.
453
- if (services.includes("AI Gateway")) {
454
- await warnAiGateway({
455
- apiClient: props.apiClient,
456
- projectId: props.projectId,
457
- branchId,
458
- });
459
- }
373
+ const planCmd = async (props) => {
374
+ const config = await loadConfig(props);
375
+ const branch = await resolveBranchRef(props);
376
+ announceTargetBranch(props, branch, "Planning against branch");
377
+ const branchId = branch.branchId;
378
+ const result = await plan(config, {
379
+ projectId: props.projectId,
380
+ branchId,
381
+ ...props.apiKey ? { apiKey: props.apiKey } : {},
382
+ ...props.apiHost ? { apiHost: props.apiHost } : {},
383
+ ...props.runtimeApi ? { api: props.runtimeApi } : {}
384
+ });
385
+ const services = utilizedServices(config);
386
+ reportPushResult(props, result, "plan", services);
387
+ if (services.includes("AI Gateway")) await warnAiGateway({
388
+ apiClient: props.apiClient,
389
+ projectId: props.projectId,
390
+ branchId
391
+ });
460
392
  };
461
- export const applyCmd = async (props) => {
462
- const config = await loadConfig(props);
463
- const branch = await resolveBranchRef(props);
464
- announceTargetBranch(props, branch, "Applying to branch");
465
- const branchId = branch.branchId;
466
- // The AI Gateway can't serve on the Free plan, so refuse to provision it up front rather
467
- // than write a credential that won't work. Only when the policy actually enables the
468
- // gateway; best-effort on the plan lookup (a transient failure never blocks a paid user).
469
- if (utilizedServices(config).includes("AI Gateway")) {
470
- await assertAiGatewayProvisionable({
471
- apiClient: props.apiClient,
472
- projectId: props.projectId,
473
- });
474
- }
475
- let result;
476
- try {
477
- result = await apply(config, {
478
- projectId: props.projectId,
479
- branchId,
480
- ...(props.apiKey ? { apiKey: props.apiKey } : {}),
481
- ...(props.apiHost ? { apiHost: props.apiHost } : {}),
482
- ...(props.runtimeApi ? { api: props.runtimeApi } : {}),
483
- ...(props.updateExisting ? { updateExisting: true } : {}),
484
- ...(props.allowProtected ? { allowProtectedBranch: true } : {}),
485
- bundleFunction: neonctlBundler,
486
- });
487
- }
488
- catch (err) {
489
- // Drift without `--update-existing` throws with the conflicting fields attached.
490
- // Render them as the same git-style before→after diff, then fail with a concise
491
- // message (the detailed diff above replaces the library's long multi-line text).
492
- if (err instanceof PushConflictError) {
493
- reportConflicts(props, err.conflicts);
494
- throw new Error("Branch settings conflict with the policy. Re-run with --update-existing to apply the changes shown above.");
495
- }
496
- throw err;
497
- }
498
- reportPushResult(props, result, "apply", utilizedServices(config));
499
- // After a successful apply/deploy, write the branch's Neon env vars to a local .env —
500
- // the same bundled convenience as `link` / `checkout`, so the branch is immediately
501
- // usable for local dev. `--no-env-pull` opts out; a pull failure degrades to a warning
502
- // (the apply already succeeded). See autoPullEnvAfterPin.
503
- await autoPullEnvAfterPin({ ...props, envPull: props.envPull !== false });
393
+ const applyCmd = async (props) => {
394
+ const config = await loadConfig(props);
395
+ const branch = await resolveBranchRef(props);
396
+ announceTargetBranch(props, branch, "Applying to branch");
397
+ const branchId = branch.branchId;
398
+ if (utilizedServices(config).includes("AI Gateway")) await assertAiGatewayProvisionable({
399
+ apiClient: props.apiClient,
400
+ projectId: props.projectId
401
+ });
402
+ let result;
403
+ try {
404
+ result = await apply(config, {
405
+ projectId: props.projectId,
406
+ branchId,
407
+ ...props.apiKey ? { apiKey: props.apiKey } : {},
408
+ ...props.apiHost ? { apiHost: props.apiHost } : {},
409
+ ...props.runtimeApi ? { api: props.runtimeApi } : {},
410
+ ...props.updateExisting ? { updateExisting: true } : {},
411
+ ...props.allowProtected ? { allowProtectedBranch: true } : {},
412
+ bundleFunction: neonctlBundler
413
+ });
414
+ } catch (err) {
415
+ if (err instanceof PushConflictError) {
416
+ reportConflicts(props, err.conflicts);
417
+ throw new Error("Branch settings conflict with the policy. Re-run with --update-existing to apply the changes shown above.");
418
+ }
419
+ throw err;
420
+ }
421
+ reportPushResult(props, result, "apply", utilizedServices(config));
422
+ await autoPullEnvAfterPin({
423
+ ...props,
424
+ envPull: props.envPull !== false
425
+ });
504
426
  };
505
427
  /**
506
- * A static service toggle (`auth` / `dataApi` / `preview.aiGateway`) is "on" unless
507
- * explicitly disabled: `true` / `{}` / `{ enabled: true }` enable it; `false` /
508
- * `{ enabled: false }` / absent leave it off. Mirrors the runtime's `isServiceEnabled`
509
- * (which isn't exported), kept tiny and pure so it can be read straight off the policy.
510
- */
428
+ * A static service toggle (`auth` / `dataApi` / `preview.aiGateway`) is "on" unless
429
+ * explicitly disabled: `true` / `{}` / `{ enabled: true }` enable it; `false` /
430
+ * `{ enabled: false }` / absent leave it off. Mirrors the runtime's `isServiceEnabled`
431
+ * (which isn't exported), kept tiny and pure so it can be read straight off the policy.
432
+ */
511
433
  const isToggleEnabled = (toggle) => {
512
- if (toggle === undefined)
513
- return false;
514
- if (typeof toggle === "boolean")
515
- return toggle;
516
- return toggle.enabled !== false;
434
+ if (toggle === void 0) return false;
435
+ if (typeof toggle === "boolean") return toggle;
436
+ return toggle.enabled !== false;
517
437
  };
518
438
  /**
519
- * Human-readable list of the services a `neon.ts` policy utilizes on the branch, shown under
520
- * the plan/apply table. Postgres is always present (every branch has it); the rest are listed
521
- * only when the policy declares them. This deliberately surfaces services that produce **no**
522
- * plan step — notably the AI Gateway, which is always available and only needs a scoped branch
523
- * credential (not a provisioning step) — so adding `preview.aiGateway` to a neon.ts isn't
524
- * mistaken for being silently dropped. Service enablement is static top-level config (it never
525
- * lives in the per-branch closure), so reading it straight off `config` is accurate.
526
- */
439
+ * Human-readable list of the services a `neon.ts` policy utilizes on the branch, shown under
440
+ * the plan/apply table. Postgres is always present (every branch has it); the rest are listed
441
+ * only when the policy declares them. This deliberately surfaces services that produce **no**
442
+ * plan step — notably the AI Gateway, which is always available and only needs a scoped branch
443
+ * credential (not a provisioning step) — so adding `preview.aiGateway` to a neon.ts isn't
444
+ * mistaken for being silently dropped. Service enablement is static top-level config (it never
445
+ * lives in the per-branch closure), so reading it straight off `config` is accurate.
446
+ */
527
447
  const utilizedServices = (config) => {
528
- const services = ["Postgres"];
529
- if (isToggleEnabled(config.auth))
530
- services.push("Neon Auth");
531
- if (isToggleEnabled(config.dataApi))
532
- services.push("Data API");
533
- if (Object.keys(config.preview?.buckets ?? {}).length > 0) {
534
- services.push("Object Storage");
535
- }
536
- if (Object.keys(config.preview?.functions ?? {}).length > 0) {
537
- services.push("Functions");
538
- }
539
- if (isToggleEnabled(config.preview?.aiGateway))
540
- services.push("AI Gateway");
541
- return services;
448
+ const services = ["Postgres"];
449
+ if (isToggleEnabled(config.auth)) services.push("Neon Auth");
450
+ if (isToggleEnabled(config.dataApi)) services.push("Data API");
451
+ if (Object.keys(config.preview?.buckets ?? {}).length > 0) services.push("Object Storage");
452
+ if (Object.keys(config.preview?.functions ?? {}).length > 0) services.push("Functions");
453
+ if (isToggleEnabled(config.preview?.aiGateway)) services.push("AI Gateway");
454
+ return services;
542
455
  };
543
456
  /**
544
- * Render a {@link PushResult}. JSON/YAML output emits the raw result (plus a `services`
545
- * summary) verbatim so it can be piped; the human-readable path renders the actual changes
546
- * (dropping noops) and any blocking conflicts as a `git diff`-style report, or a "nothing to
547
- * do" line when both are empty — and always closes with the list of services the policy
548
- * utilizes so a service that produces no plan step (Postgres, or the credential-gated AI
549
- * Gateway) isn't mistaken for being missing from the plan above.
550
- *
551
- * The diff is asymmetric on purpose (see the CLI's `neon diff`): **service** changes are
552
- * additions with no "before", so they list as `+`/`~` lines; **branch setting** changes have
553
- * a natural before→after, so conflicts render as a sorted `current → desired` diff. Planned
554
- * branch updates (under `--update-existing`) carry only the new value, so they render
555
- * desired-only for now (the previous value isn't threaded through the runtime yet).
556
- */
457
+ * Render a {@link PushResult}. JSON/YAML output emits the raw result (plus a `services`
458
+ * summary) verbatim so it can be piped; the human-readable path renders the actual changes
459
+ * (dropping noops) and any blocking conflicts as a `git diff`-style report, or a "nothing to
460
+ * do" line when both are empty — and always closes with the list of services the policy
461
+ * utilizes so a service that produces no plan step (Postgres, or the credential-gated AI
462
+ * Gateway) isn't mistaken for being missing from the plan above.
463
+ *
464
+ * The diff is asymmetric on purpose (see the CLI's `neon diff`): **service** changes are
465
+ * additions with no "before", so they list as `+`/`~` lines; **branch setting** changes have
466
+ * a natural before→after, so conflicts render as a sorted `current → desired` diff. Planned
467
+ * branch updates (under `--update-existing`) carry only the new value, so they render
468
+ * desired-only for now (the previous value isn't threaded through the runtime yet).
469
+ */
557
470
  const reportPushResult = (props, result, mode, services) => {
558
- if (props.output === "json" || props.output === "yaml") {
559
- writer(props).end({ ...result, services }, { fields: [] });
560
- return;
561
- }
562
- const appliedChanges = result.applied.filter((change) => change.action !== "noop");
563
- // Deployed functions carry their invocation URL in the change details — collect them so
564
- // we can list where to call each function without digging through the raw details blob.
565
- // Keyed by slug so a function never shows twice.
566
- const functionUrlBySlug = new Map();
567
- for (const change of appliedChanges) {
568
- const slug = change.details?.slug;
569
- const invocationUrl = change.details?.invocationUrl;
570
- if (typeof slug === "string" && typeof invocationUrl === "string") {
571
- functionUrlBySlug.set(slug, invocationUrl);
572
- }
573
- }
574
- // chalk self-detects TTY/NO_COLOR; `--no-color` (props.color === false) forces plain.
575
- const color = props.color !== false;
576
- const out = writer(props);
577
- // Conflicts never reach here in the CLI: `plan` runs with updateExisting on, and a bare
578
- // `apply` throws PushConflictError (rendered by reportConflicts). So an empty applied set
579
- // is the whole story here.
580
- const noChanges = appliedChanges.length === 0;
581
- const appliedText = renderAppliedChanges(appliedChanges, mode === "plan" ? "Planned changes" : "Applied changes", { color });
582
- if (appliedText)
583
- out.text(`${appliedText}\n`);
584
- // Function URLs are a plain list rather than a table: an invocation URL can be 70+ chars,
585
- // which makes any bordered table overflow and wrap awkwardly in a normal terminal. A list
586
- // lets each URL reflow on its own line, and stays copy-pasteable.
587
- if (functionUrlBySlug.size > 0) {
588
- const heading = mode === "plan" ? "Function URLs (after apply)" : "Function URLs";
589
- out.text(`\n${isCi() ? heading : chalk.bold(heading)}\n`);
590
- for (const [slug, invocationUrl] of functionUrlBySlug) {
591
- out.text(` • ${slug}: ${invocationUrl}\n`);
592
- }
593
- }
594
- if (noChanges) {
595
- log.info(`No changes — branch ${result.branchName} already matches the policy.`);
596
- }
597
- out.text(`\nUtilized services: ${services.join(", ")}\n`);
471
+ if (props.output === "json" || props.output === "yaml") {
472
+ writer(props).end({
473
+ ...result,
474
+ services
475
+ }, { fields: [] });
476
+ return;
477
+ }
478
+ const appliedChanges = result.applied.filter((change) => change.action !== "noop");
479
+ const functionUrlBySlug = /* @__PURE__ */ new Map();
480
+ for (const change of appliedChanges) {
481
+ const slug = change.details?.slug;
482
+ const invocationUrl = change.details?.invocationUrl;
483
+ if (typeof slug === "string" && typeof invocationUrl === "string") functionUrlBySlug.set(slug, invocationUrl);
484
+ }
485
+ const color = props.color !== false;
486
+ const out = writer(props);
487
+ const noChanges = appliedChanges.length === 0;
488
+ const appliedText = renderAppliedChanges(appliedChanges, mode === "plan" ? "Planned changes" : "Applied changes", { color });
489
+ if (appliedText) out.text(`${appliedText}\n`);
490
+ if (functionUrlBySlug.size > 0) {
491
+ const heading = mode === "plan" ? "Function URLs (after apply)" : "Function URLs";
492
+ out.text(`\n${isCi() ? heading : chalk.bold(heading)}\n`);
493
+ for (const [slug, invocationUrl] of functionUrlBySlug) out.text(` • ${slug}: ${invocationUrl}\n`);
494
+ }
495
+ if (noChanges) log.info(`No changes — branch ${result.branchName} already matches the policy.`);
496
+ out.text(`\nUtilized services: ${services.join(", ")}\n`);
598
497
  };
599
498
  /**
600
- * Render the branch-setting {@link ConflictReport}s a bare `apply` refused to override (drift
601
- * without `--update-existing`) as the git-style before→after diff. JSON/YAML output emits the
602
- * structured conflicts so it can be piped; the human path prints the sorted diff followed by
603
- * any conflict whose fix is *not* `--update-existing` (e.g. an immutable "no endpoint" case),
604
- * so nothing the library's error message carried is lost.
605
- */
499
+ * Render the branch-setting {@link ConflictReport}s a bare `apply` refused to override (drift
500
+ * without `--update-existing`) as the git-style before→after diff. JSON/YAML output emits the
501
+ * structured conflicts so it can be piped; the human path prints the sorted diff followed by
502
+ * any conflict whose fix is *not* `--update-existing` (e.g. an immutable "no endpoint" case),
503
+ * so nothing the library's error message carried is lost.
504
+ */
606
505
  const reportConflicts = (props, conflicts) => {
607
- if (props.output === "json" || props.output === "yaml") {
608
- writer(props).end({ conflicts }, { fields: [] });
609
- return;
610
- }
611
- const out = writer(props);
612
- const text = renderBranchSettingConflicts([...conflicts], {
613
- color: props.color !== false,
614
- });
615
- if (text)
616
- out.text(`${text}\n`);
617
- for (const conflict of conflicts) {
618
- if (!/updateExisting/i.test(conflict.reason)) {
619
- out.text(` ! ${conflict.field}: ${conflict.reason}\n`);
620
- }
621
- }
506
+ if (props.output === "json" || props.output === "yaml") {
507
+ writer(props).end({ conflicts }, { fields: [] });
508
+ return;
509
+ }
510
+ const out = writer(props);
511
+ const text = renderBranchSettingConflicts([...conflicts], { color: props.color !== false });
512
+ if (text) out.text(`${text}\n`);
513
+ for (const conflict of conflicts) if (!/updateExisting/i.test(conflict.reason)) out.text(` ! ${conflict.field}: ${conflict.reason}\n`);
622
514
  };
623
515
  /**
624
- * Block provisioning the AI Gateway on a Free plan from the `checkout` policy paths, which
625
- * carry raw credentials (`apiKey`/`apiHost`) rather than the CLI's api client. Builds a client
626
- * from them and defers to {@link assertAiGatewayProvisionable}. Skipped when a `runtimeApi` is
627
- * injected (tests) or no `apiKey` is available; the interactive commands always have a key.
628
- */
516
+ * Block provisioning the AI Gateway on a Free plan from the `checkout` policy paths, which
517
+ * carry raw credentials (`apiKey`/`apiHost`) rather than the CLI's api client. Builds a client
518
+ * from them and defers to {@link assertAiGatewayProvisionable}. Skipped when a `runtimeApi` is
519
+ * injected (tests) or no `apiKey` is available; the interactive commands always have a key.
520
+ */
629
521
  const assertAiGatewayProvisionableFromCreds = async (props) => {
630
- if (props.runtimeApi || !props.apiKey)
631
- return;
632
- if (!utilizedServices(props.config).includes("AI Gateway"))
633
- return;
634
- const apiClient = getApiClient({
635
- apiKey: props.apiKey,
636
- ...(props.apiHost ? { apiHost: props.apiHost } : {}),
637
- });
638
- await assertAiGatewayProvisionable({
639
- apiClient,
640
- projectId: props.projectId,
641
- });
522
+ if (props.runtimeApi || !props.apiKey) return;
523
+ if (!utilizedServices(props.config).includes("AI Gateway")) return;
524
+ const apiClient = getApiClient({
525
+ apiKey: props.apiKey,
526
+ ...props.apiHost ? { apiHost: props.apiHost } : {}
527
+ });
528
+ await assertAiGatewayProvisionable({
529
+ apiClient,
530
+ projectId: props.projectId
531
+ });
642
532
  };
643
533
  /**
644
- * Apply a `neon.ts` policy to a **freshly created** branch (used by `neonctl checkout`
645
- * when it creates a branch). No-op when there is no `neon.ts` on the path from cwd up to
646
- * the repo root — checkout still succeeds, it just has no policy to apply.
647
- *
648
- * The branch was just created by us, so we apply non-interactively (`updateExisting` /
649
- * `allowProtectedBranch`) — there is no pre-existing state a user would be surprised to
650
- * see overridden. Functions are bundled with neonctl's own esbuild helper.
651
- */
652
- export const applyPolicyOnCreate = async (props) => {
653
- let config;
654
- try {
655
- ({ config } = await loadConfigFromFile({
656
- ...(props.cwd ? { cwd: props.cwd } : {}),
657
- }));
658
- }
659
- catch (err) {
660
- const message = err instanceof Error ? err.message : String(err);
661
- if (/Could not find a Neon config file/i.test(message))
662
- return;
663
- throw err;
664
- }
665
- await assertAiGatewayProvisionableFromCreds({
666
- projectId: props.projectId,
667
- ...(props.apiKey ? { apiKey: props.apiKey } : {}),
668
- ...(props.apiHost ? { apiHost: props.apiHost } : {}),
669
- ...(props.runtimeApi ? { runtimeApi: props.runtimeApi } : {}),
670
- config,
671
- });
672
- log.info("Applying neon.ts policy to the new branch…");
673
- const result = await apply(config, {
674
- projectId: props.projectId,
675
- branchId: props.branchId,
676
- ...(props.apiKey ? { apiKey: props.apiKey } : {}),
677
- ...(props.apiHost ? { apiHost: props.apiHost } : {}),
678
- ...(props.runtimeApi ? { api: props.runtimeApi } : {}),
679
- updateExisting: true,
680
- allowProtectedBranch: true,
681
- bundleFunction: neonctlBundler,
682
- });
683
- logPolicyResult(result, { color: props.color !== false });
534
+ * Apply a `neon.ts` policy to a **freshly created** branch (used by `neonctl checkout`
535
+ * when it creates a branch). No-op when there is no `neon.ts` on the path from cwd up to
536
+ * the repo root — checkout still succeeds, it just has no policy to apply.
537
+ *
538
+ * The branch was just created by us, so we apply non-interactively (`updateExisting` /
539
+ * `allowProtectedBranch`) — there is no pre-existing state a user would be surprised to
540
+ * see overridden. Functions are bundled with neonctl's own esbuild helper.
541
+ */
542
+ const applyPolicyOnCreate = async (props) => {
543
+ let config;
544
+ try {
545
+ ({config} = await loadConfigFromFile$1({ ...props.cwd ? { cwd: props.cwd } : {} }));
546
+ } catch (err) {
547
+ const message = err instanceof Error ? err.message : String(err);
548
+ if (/Could not find a Neon config file/i.test(message)) return;
549
+ throw err;
550
+ }
551
+ await assertAiGatewayProvisionableFromCreds({
552
+ projectId: props.projectId,
553
+ ...props.apiKey ? { apiKey: props.apiKey } : {},
554
+ ...props.apiHost ? { apiHost: props.apiHost } : {},
555
+ ...props.runtimeApi ? { runtimeApi: props.runtimeApi } : {},
556
+ config
557
+ });
558
+ log.info("Applying neon.ts policy to the new branch…");
559
+ const result = await apply(config, {
560
+ projectId: props.projectId,
561
+ branchId: props.branchId,
562
+ ...props.apiKey ? { apiKey: props.apiKey } : {},
563
+ ...props.apiHost ? { apiHost: props.apiHost } : {},
564
+ ...props.runtimeApi ? { api: props.runtimeApi } : {},
565
+ updateExisting: true,
566
+ allowProtectedBranch: true,
567
+ bundleFunction: neonctlBundler
568
+ });
569
+ logPolicyResult(result, { color: props.color !== false });
684
570
  };
685
571
  /**
686
- * Report what applying a `neon.ts` policy changed, using the same `field → value` diff
687
- * `deploy` prints (see {@link renderAppliedChanges}) rather than a bare list of change
688
- * identifiers — the identifier alone repeats the branch name once per change and never says
689
- * *what* was applied, which is the only interesting part on a freshly created branch.
690
- */
572
+ * Report what applying a `neon.ts` policy changed, using the same `field → value` diff
573
+ * `deploy` prints (see {@link renderAppliedChanges}) rather than a bare list of change
574
+ * identifiers — the identifier alone repeats the branch name once per change and never says
575
+ * *what* was applied, which is the only interesting part on a freshly created branch.
576
+ */
691
577
  const logPolicyResult = (result, opts) => {
692
- const changes = result.applied.filter((c) => c.action !== "noop");
693
- if (changes.length === 0) {
694
- log.info("neon.ts applied — no changes were needed.");
695
- return;
696
- }
697
- log.info("%s", renderAppliedChanges(changes, `neon.ts applied — ${changes.length} change${changes.length === 1 ? "" : "s"}:`, opts));
578
+ const changes = result.applied.filter((c) => c.action !== "noop");
579
+ if (changes.length === 0) {
580
+ log.info("neon.ts applied — no changes were needed.");
581
+ return;
582
+ }
583
+ log.info("%s", renderAppliedChanges(changes, `neon.ts applied — ${changes.length} change${changes.length === 1 ? "" : "s"}:`, opts));
698
584
  };
699
585
  /**
700
- * Create a branch **from** the local `neon.ts` policy. Returns `null` when there is no
701
- * `neon.ts` on the path from cwd up to the repo root, so `neonctl checkout` can fall back to a
702
- * bare branch create.
703
- *
704
- * Unlike a bare create followed by {@link applyPolicyOnCreate}, this evaluates the policy for
705
- * the **new** branch (`exists: false`): the runtime branches from the policy's `parent` and
706
- * brings the branch up with its declared TTL / compute settings / services. That's what makes
707
- * a policy keyed on `!branch.exists` (the common "only configure new branches" shape) take
708
- * effect on the very first `checkout` — a bare create + `apply` always saw `exists: true` and
709
- * skipped that block.
710
- *
711
- * A branch that was created but whose policy failed to apply is reported through
712
- * `policyFailure` rather than thrown: the branch is real, so `checkout` still needs to pin it
713
- * (see the handler) instead of leaving it stranded behind an unchanged `.neon`.
714
- */
715
- export const createBranchFromPolicyOnCheckout = async (props) => {
716
- let config;
717
- try {
718
- ({ config } = await loadConfigFromFile({
719
- ...(props.cwd ? { cwd: props.cwd } : {}),
720
- }));
721
- }
722
- catch (err) {
723
- const message = err instanceof Error ? err.message : String(err);
724
- if (/Could not find a Neon config file/i.test(message))
725
- return null;
726
- throw err;
727
- }
728
- await assertAiGatewayProvisionableFromCreds({
729
- projectId: props.projectId,
730
- ...(props.apiKey ? { apiKey: props.apiKey } : {}),
731
- ...(props.apiHost ? { apiHost: props.apiHost } : {}),
732
- ...(props.runtimeApi ? { runtimeApi: props.runtimeApi } : {}),
733
- config,
734
- });
735
- try {
736
- const { branchId, branchName, result } = await createBranchFromPolicy(config, {
737
- projectId: props.projectId,
738
- branchName: props.branchName,
739
- ...(props.apiKey ? { apiKey: props.apiKey } : {}),
740
- ...(props.apiHost ? { apiHost: props.apiHost } : {}),
741
- ...(props.runtimeApi ? { api: props.runtimeApi } : {}),
742
- bundleFunction: neonctlBundler,
743
- });
744
- log.info("Created branch %s (%s) from neon.ts policy.", branchName, branchId);
745
- logPolicyResult(result, { color: props.color !== false });
746
- return { branchId };
747
- }
748
- catch (err) {
749
- // The branch exists but its policy didn't fully apply. Hand the id back so checkout
750
- // pins it and reports the failure with the remediation, rather than aborting with an
751
- // unpinned context and a branch the next `checkout` would silently accept as-is.
752
- if (isPartialBranchCreateError(err)) {
753
- log.info("Created branch %s (%s) from neon.ts policy.", err.branchName, err.branchId);
754
- return { branchId: err.branchId, policyFailure: err.reason };
755
- }
756
- throw err;
757
- }
586
+ * Create a branch **from** the local `neon.ts` policy. Returns `null` when there is no
587
+ * `neon.ts` on the path from cwd up to the repo root, so `neonctl checkout` can fall back to a
588
+ * bare branch create.
589
+ *
590
+ * Unlike a bare create followed by {@link applyPolicyOnCreate}, this evaluates the policy for
591
+ * the **new** branch (`exists: false`): the runtime branches from the policy's `parent` and
592
+ * brings the branch up with its declared TTL / compute settings / services. That's what makes
593
+ * a policy keyed on `!branch.exists` (the common "only configure new branches" shape) take
594
+ * effect on the very first `checkout` — a bare create + `apply` always saw `exists: true` and
595
+ * skipped that block.
596
+ *
597
+ * A branch that was created but whose policy failed to apply is reported through
598
+ * `policyFailure` rather than thrown: the branch is real, so `checkout` still needs to pin it
599
+ * (see the handler) instead of leaving it stranded behind an unchanged `.neon`.
600
+ */
601
+ const createBranchFromPolicyOnCheckout = async (props) => {
602
+ let config;
603
+ try {
604
+ ({config} = await loadConfigFromFile$1({ ...props.cwd ? { cwd: props.cwd } : {} }));
605
+ } catch (err) {
606
+ const message = err instanceof Error ? err.message : String(err);
607
+ if (/Could not find a Neon config file/i.test(message)) return null;
608
+ throw err;
609
+ }
610
+ await assertAiGatewayProvisionableFromCreds({
611
+ projectId: props.projectId,
612
+ ...props.apiKey ? { apiKey: props.apiKey } : {},
613
+ ...props.apiHost ? { apiHost: props.apiHost } : {},
614
+ ...props.runtimeApi ? { runtimeApi: props.runtimeApi } : {},
615
+ config
616
+ });
617
+ try {
618
+ const { branchId, branchName, result } = await createBranch(config, {
619
+ projectId: props.projectId,
620
+ branchName: props.branchName,
621
+ ...props.apiKey ? { apiKey: props.apiKey } : {},
622
+ ...props.apiHost ? { apiHost: props.apiHost } : {},
623
+ ...props.runtimeApi ? { api: props.runtimeApi } : {},
624
+ bundleFunction: neonctlBundler
625
+ });
626
+ log.info("Created branch %s (%s) from neon.ts policy.", branchName, branchId);
627
+ logPolicyResult(result, { color: props.color !== false });
628
+ return { branchId };
629
+ } catch (err) {
630
+ if (isPartialBranchCreateError(err)) {
631
+ log.info("Created branch %s (%s) from neon.ts policy.", err.branchName, err.branchId);
632
+ return {
633
+ branchId: err.branchId,
634
+ policyFailure: err.reason
635
+ };
636
+ }
637
+ throw err;
638
+ }
758
639
  };
640
+ //#endregion
641
+ export { applyCmd, applyFlags, applyPolicyOnCreate, builder, command, config_exports, createBranchFromPolicyOnCheckout, describe, envFlag, envPullFlag, handler, hasNeonConfigFile, initCmd, planCmd, status };