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.
- package/README.md +70 -5
- package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
- package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
- package/dist/_chunks/credentials-MYdHdKah.js +188 -0
- package/dist/_chunks/env-NbA61JR3.js +585 -0
- package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
- package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
- package/dist/_chunks/profiles-Ir29rqns.js +217 -0
- package/dist/_chunks/psql-DWH-kc69.js +2169 -0
- package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +573 -690
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +255 -305
- package/dist/commands/functions.js +275 -355
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +615 -846
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +2 -446
- package/dist/dev/functions.js +54 -68
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +2 -51
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +728 -984
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +2 -2193
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +2 -1148
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +260 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +2 -1253
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1303
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +2 -846
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +2 -2030
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +8 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +130 -189
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +39 -45
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +11 -6
- package/dist/_shared/auth_selection.js +0 -86
- package/dist/_shared/credentials.js +0 -209
- package/dist/_shared/env-core/env.js +0 -558
- package/dist/_shared/env-core/reuse-secrets.js +0 -223
- package/dist/_shared/paths.js +0 -148
- package/dist/_shared/profiles.js +0 -276
- package/dist/_shared/secure_file.js +0 -43
package/dist/commands/config.js
CHANGED
|
@@ -1,758 +1,641 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
14
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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 = [
|
|
75
|
+
const INSPECT_FIELDS = [
|
|
76
|
+
"project",
|
|
77
|
+
"branch",
|
|
78
|
+
"config"
|
|
79
|
+
];
|
|
71
80
|
/**
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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 = [
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}), (args) =>
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
348
|
-
|
|
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
|
-
|
|
353
|
-
|
|
302
|
+
const handler = (args) => {
|
|
303
|
+
return args;
|
|
354
304
|
};
|
|
355
305
|
/**
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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 };
|