neon 3.0.0 → 3.1.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.
Files changed (207) hide show
  1. package/README.md +70 -5
  2. package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
  3. package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
  4. package/dist/_chunks/credentials-MYdHdKah.js +188 -0
  5. package/dist/_chunks/env-NbA61JR3.js +585 -0
  6. package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
  7. package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
  8. package/dist/_chunks/profiles-Ir29rqns.js +217 -0
  9. package/dist/_chunks/psql-DWH-kc69.js +2169 -0
  10. package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
  11. package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
  12. package/dist/analytics.js +163 -207
  13. package/dist/api.js +815 -758
  14. package/dist/auth.js +121 -141
  15. package/dist/auth_context.js +39 -53
  16. package/dist/cli.js +4 -7
  17. package/dist/commands/api.js +220 -250
  18. package/dist/commands/api_keys.js +251 -314
  19. package/dist/commands/auth.js +283 -328
  20. package/dist/commands/bootstrap.js +372 -437
  21. package/dist/commands/branches.js +304 -455
  22. package/dist/commands/bucket.js +374 -514
  23. package/dist/commands/checkout.js +213 -298
  24. package/dist/commands/config.js +573 -690
  25. package/dist/commands/connection_string.js +137 -165
  26. package/dist/commands/data_api.js +238 -260
  27. package/dist/commands/databases.js +67 -76
  28. package/dist/commands/deploy.js +31 -25
  29. package/dist/commands/dev.js +639 -719
  30. package/dist/commands/diff.js +156 -200
  31. package/dist/commands/env.js +255 -305
  32. package/dist/commands/functions.js +275 -355
  33. package/dist/commands/index.js +70 -65
  34. package/dist/commands/init.js +84 -119
  35. package/dist/commands/inspect.js +55 -55
  36. package/dist/commands/ip_allow.js +88 -120
  37. package/dist/commands/link.js +874 -1019
  38. package/dist/commands/logs.js +291 -0
  39. package/dist/commands/neon_auth.js +725 -933
  40. package/dist/commands/operations.js +34 -25
  41. package/dist/commands/orgs.js +28 -18
  42. package/dist/commands/profile.js +615 -846
  43. package/dist/commands/projects.js +313 -373
  44. package/dist/commands/psql.js +60 -58
  45. package/dist/commands/roles.js +55 -58
  46. package/dist/commands/schema_diff.js +87 -131
  47. package/dist/commands/set_context.js +34 -26
  48. package/dist/commands/snapshots.js +288 -413
  49. package/dist/commands/status.js +41 -37
  50. package/dist/commands/user.js +21 -10
  51. package/dist/commands/vpc_endpoints.js +85 -113
  52. package/dist/config.js +7 -6
  53. package/dist/config_format.js +50 -66
  54. package/dist/config_template.js +128 -157
  55. package/dist/context.js +183 -235
  56. package/dist/current_branch_fast_path.js +40 -49
  57. package/dist/dev/env.js +2 -446
  58. package/dist/dev/functions.js +54 -68
  59. package/dist/dev/inputs.js +46 -58
  60. package/dist/dev/runtime.js +135 -164
  61. package/dist/dev/websocket.js +766 -959
  62. package/dist/env.js +27 -33
  63. package/dist/env_file.js +118 -132
  64. package/dist/env_services.js +2 -51
  65. package/dist/errors.js +57 -68
  66. package/dist/functions_api.js +45 -43
  67. package/dist/help.js +189 -140
  68. package/dist/index.js +182 -257
  69. package/dist/init/agents.js +137 -118
  70. package/dist/init/auth.js +58 -68
  71. package/dist/init/bootstrap.js +325 -396
  72. package/dist/init/build_config.js +4 -2
  73. package/dist/init/detect_agent.js +56 -101
  74. package/dist/init/editors.js +35 -52
  75. package/dist/init/enrich_output.js +51 -66
  76. package/dist/init/extension.js +134 -171
  77. package/dist/init/inspect.js +179 -266
  78. package/dist/init/interactive.js +510 -622
  79. package/dist/init/neonctl.js +117 -168
  80. package/dist/init/orchestrate.js +157 -173
  81. package/dist/init/phases/auth.js +188 -202
  82. package/dist/init/phases/cleanup.js +23 -23
  83. package/dist/init/phases/db.js +251 -277
  84. package/dist/init/phases/getting_started.js +213 -223
  85. package/dist/init/phases/mcp.js +174 -224
  86. package/dist/init/phases/migrations.js +247 -248
  87. package/dist/init/phases/neon_auth.js +114 -133
  88. package/dist/init/phases/setup.js +546 -703
  89. package/dist/init/phases/skills.js +75 -86
  90. package/dist/init/phases/status.js +72 -67
  91. package/dist/init/resolve_context.js +102 -99
  92. package/dist/init/route_command.js +91 -98
  93. package/dist/init/skills.js +174 -218
  94. package/dist/init/vsix.js +77 -99
  95. package/dist/log.js +17 -16
  96. package/dist/neon_services.js +104 -129
  97. package/dist/parameters.gen.js +481 -471
  98. package/dist/pkg.js +17 -19
  99. package/dist/profile_keys.js +44 -47
  100. package/dist/psql/cli.js +44 -47
  101. package/dist/psql/command/cmd_cond.js +231 -406
  102. package/dist/psql/command/cmd_connect.js +557 -764
  103. package/dist/psql/command/cmd_copy.js +728 -984
  104. package/dist/psql/command/cmd_describe.js +1499 -1688
  105. package/dist/psql/command/cmd_format.js +733 -905
  106. package/dist/psql/command/cmd_io.js +2 -2193
  107. package/dist/psql/command/cmd_lo.js +297 -359
  108. package/dist/psql/command/cmd_meta.js +727 -878
  109. package/dist/psql/command/cmd_misc.js +138 -172
  110. package/dist/psql/command/cmd_pipeline.js +2 -1148
  111. package/dist/psql/command/cmd_restrict.js +119 -155
  112. package/dist/psql/command/cmd_show.js +529 -688
  113. package/dist/psql/command/dispatch.js +260 -325
  114. package/dist/psql/command/inputQueue.js +35 -33
  115. package/dist/psql/command/shared.js +49 -63
  116. package/dist/psql/complete/filenames.js +90 -133
  117. package/dist/psql/complete/index.js +59 -97
  118. package/dist/psql/complete/matcher.js +236 -300
  119. package/dist/psql/complete/psqlVars.js +218 -223
  120. package/dist/psql/complete/queries.js +159 -177
  121. package/dist/psql/complete/rules.js +1493 -2299
  122. package/dist/psql/core/common.js +2 -1253
  123. package/dist/psql/core/help.js +456 -546
  124. package/dist/psql/core/mainloop.js +692 -1303
  125. package/dist/psql/core/prompt.js +391 -408
  126. package/dist/psql/core/settings.js +429 -644
  127. package/dist/psql/core/sqlHelp.js +480 -554
  128. package/dist/psql/core/startup.js +2 -846
  129. package/dist/psql/core/syncVars.js +67 -110
  130. package/dist/psql/core/variables.js +156 -278
  131. package/dist/psql/describe/formatters.js +884 -1285
  132. package/dist/psql/describe/processNamePattern.js +173 -260
  133. package/dist/psql/describe/queries.js +1368 -2403
  134. package/dist/psql/describe/versionGate.js +32 -41
  135. package/dist/psql/index.js +2 -2030
  136. package/dist/psql/io/history.js +232 -271
  137. package/dist/psql/io/input.js +103 -108
  138. package/dist/psql/io/lineEditor/buffer.js +238 -319
  139. package/dist/psql/io/lineEditor/complete.js +135 -213
  140. package/dist/psql/io/lineEditor/filename.js +139 -148
  141. package/dist/psql/io/lineEditor/index.js +653 -870
  142. package/dist/psql/io/lineEditor/keymap.js +544 -702
  143. package/dist/psql/io/lineEditor/vt100.js +294 -341
  144. package/dist/psql/io/pgpass.js +158 -187
  145. package/dist/psql/io/pgservice.js +146 -183
  146. package/dist/psql/io/psqlrc.js +328 -403
  147. package/dist/psql/print/aligned.js +1020 -1683
  148. package/dist/psql/print/asciidoc.js +180 -214
  149. package/dist/psql/print/crosstab.js +281 -442
  150. package/dist/psql/print/csv.js +48 -70
  151. package/dist/psql/print/html.js +195 -226
  152. package/dist/psql/print/json.js +75 -88
  153. package/dist/psql/print/latex.js +291 -364
  154. package/dist/psql/print/pager.js +171 -242
  155. package/dist/psql/print/troff.js +194 -226
  156. package/dist/psql/print/unaligned.js +69 -95
  157. package/dist/psql/print/units.js +167 -169
  158. package/dist/psql/scanner/slash.js +428 -483
  159. package/dist/psql/scanner/sql.js +445 -889
  160. package/dist/psql/scanner/stringutils.js +309 -379
  161. package/dist/psql/types/index.js +8 -7
  162. package/dist/psql/types/scanner.js +25 -22
  163. package/dist/psql/wire/connection.js +2042 -2803
  164. package/dist/psql/wire/copy.js +84 -100
  165. package/dist/psql/wire/notify.js +39 -59
  166. package/dist/psql/wire/pipeline.js +305 -518
  167. package/dist/psql/wire/protocol.js +349 -417
  168. package/dist/psql/wire/sasl.js +180 -265
  169. package/dist/psql/wire/tls.js +400 -561
  170. package/dist/storage_api.js +115 -129
  171. package/dist/test_utils/fixtures.js +94 -113
  172. package/dist/test_utils/oauth_server.js +10 -7
  173. package/dist/test_utils/project_dir.js +33 -0
  174. package/dist/utils/ai_gateway_notice.js +131 -162
  175. package/dist/utils/api_enums.js +21 -28
  176. package/dist/utils/auth.js +10 -4
  177. package/dist/utils/branch_notice.js +20 -19
  178. package/dist/utils/branch_picker.js +83 -89
  179. package/dist/utils/cli_name.js +15 -12
  180. package/dist/utils/compute_units.js +20 -27
  181. package/dist/utils/config_diff.js +127 -158
  182. package/dist/utils/enrichers.js +95 -148
  183. package/dist/utils/esbuild.js +130 -189
  184. package/dist/utils/flags.js +35 -47
  185. package/dist/utils/formats.js +8 -15
  186. package/dist/utils/git_diff.js +69 -80
  187. package/dist/utils/inspect_db.js +101 -143
  188. package/dist/utils/inspect_queries.js +179 -142
  189. package/dist/utils/middlewares.js +39 -45
  190. package/dist/utils/openapi.js +87 -99
  191. package/dist/utils/package_manager.js +312 -110
  192. package/dist/utils/point_in_time.js +49 -53
  193. package/dist/utils/psql.js +89 -106
  194. package/dist/utils/service_picker.js +55 -58
  195. package/dist/utils/string.js +5 -5
  196. package/dist/utils/ui.js +38 -55
  197. package/dist/utils/write_sync.js +26 -35
  198. package/dist/utils/zip.js +4 -3
  199. package/dist/writer.js +67 -87
  200. package/package.json +11 -6
  201. package/dist/_shared/auth_selection.js +0 -86
  202. package/dist/_shared/credentials.js +0 -209
  203. package/dist/_shared/env-core/env.js +0 -558
  204. package/dist/_shared/env-core/reuse-secrets.js +0 -223
  205. package/dist/_shared/paths.js +0 -148
  206. package/dist/_shared/profiles.js +0 -276
  207. package/dist/_shared/secure_file.js +0 -43
@@ -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 { t as __exportAll } from "../_chunks/rolldown-runtime-D7D4PA-g.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, createBranchFromPolicyOnCheckout, describe, envFlag, envPullFlag, handler, hasNeonConfigFile, initCmd, planCmd, status, config_exports as t };