neon 2.1.2 → 2.29.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/LICENSE.md +178 -0
- package/README.md +601 -0
- package/dist/analytics.js +156 -0
- package/dist/api.js +665 -0
- package/dist/auth.js +127 -0
- package/dist/callback.html +51 -0
- package/dist/cli.js +9 -0
- package/dist/commands/auth.js +214 -0
- package/dist/commands/bootstrap.js +481 -0
- package/dist/commands/branches.js +481 -0
- package/dist/commands/bucket.js +543 -0
- package/dist/commands/checkout.js +289 -0
- package/dist/commands/config.js +544 -0
- package/dist/commands/connection_string.js +172 -0
- package/dist/commands/data_api.js +285 -0
- package/dist/commands/databases.js +82 -0
- package/dist/commands/deploy.js +26 -0
- package/dist/commands/dev.js +698 -0
- package/dist/commands/env.js +166 -0
- package/dist/commands/functions.js +373 -0
- package/dist/commands/index.js +54 -0
- package/dist/commands/init.js +73 -0
- package/dist/commands/ip_allow.js +137 -0
- package/dist/commands/link.js +1121 -0
- package/dist/commands/neon_auth.js +1028 -0
- package/dist/commands/operations.js +28 -0
- package/dist/commands/orgs.js +24 -0
- package/dist/commands/projects.js +372 -0
- package/dist/commands/psql.js +62 -0
- package/dist/commands/roles.js +65 -0
- package/dist/commands/schema_diff.js +151 -0
- package/dist/commands/set_context.js +29 -0
- package/dist/commands/status.js +40 -0
- package/dist/commands/user.js +15 -0
- package/dist/commands/vpc_endpoints.js +134 -0
- package/dist/config.js +11 -0
- package/dist/config_format.js +72 -0
- package/dist/context.js +177 -0
- package/dist/current_branch_fast_path.js +55 -0
- package/dist/dev/env.js +240 -0
- package/dist/dev/functions.js +70 -0
- package/dist/dev/inputs.js +63 -0
- package/dist/dev/runtime.js +146 -0
- package/dist/env.js +36 -0
- package/dist/env_file.js +159 -0
- package/dist/errors.js +80 -0
- package/dist/functions_api.js +44 -0
- package/dist/help.js +146 -0
- package/dist/index.js +234 -0
- package/dist/log.js +18 -0
- package/dist/parameters.gen.js +480 -0
- package/dist/pkg.js +25 -0
- package/dist/psql/cli.js +53 -0
- package/dist/psql/command/cmd_cond.js +437 -0
- package/dist/psql/command/cmd_connect.js +820 -0
- package/dist/psql/command/cmd_copy.js +1035 -0
- package/dist/psql/command/cmd_describe.js +1815 -0
- package/dist/psql/command/cmd_format.js +922 -0
- package/dist/psql/command/cmd_io.js +2193 -0
- package/dist/psql/command/cmd_lo.js +393 -0
- package/dist/psql/command/cmd_meta.js +970 -0
- package/dist/psql/command/cmd_misc.js +187 -0
- package/dist/psql/command/cmd_pipeline.js +1148 -0
- package/dist/psql/command/cmd_restrict.js +171 -0
- package/dist/psql/command/cmd_show.js +766 -0
- package/dist/psql/command/dispatch.js +343 -0
- package/dist/psql/command/inputQueue.js +42 -0
- package/dist/psql/command/shared.js +71 -0
- package/dist/psql/complete/filenames.js +139 -0
- package/dist/psql/complete/index.js +104 -0
- package/dist/psql/complete/matcher.js +315 -0
- package/dist/psql/complete/psqlVars.js +247 -0
- package/dist/psql/complete/queries.js +493 -0
- package/dist/psql/complete/rules.js +2424 -0
- package/dist/psql/core/common.js +1253 -0
- package/dist/psql/core/help.js +576 -0
- package/dist/psql/core/mainloop.js +1360 -0
- package/dist/psql/core/prompt.js +440 -0
- package/dist/psql/core/settings.js +684 -0
- package/dist/psql/core/sqlHelp.js +1066 -0
- package/dist/psql/core/startup.js +846 -0
- package/dist/psql/core/syncVars.js +116 -0
- package/dist/psql/core/variables.js +287 -0
- package/dist/psql/describe/formatters.js +1290 -0
- package/dist/psql/describe/processNamePattern.js +270 -0
- package/dist/psql/describe/queries.js +2378 -0
- package/dist/psql/describe/versionGate.js +43 -0
- package/dist/psql/index.js +2030 -0
- package/dist/psql/io/history.js +299 -0
- package/dist/psql/io/input.js +120 -0
- package/dist/psql/io/lineEditor/buffer.js +325 -0
- package/dist/psql/io/lineEditor/complete.js +227 -0
- package/dist/psql/io/lineEditor/filename.js +159 -0
- package/dist/psql/io/lineEditor/index.js +894 -0
- package/dist/psql/io/lineEditor/keymap.js +745 -0
- package/dist/psql/io/lineEditor/vt100.js +363 -0
- package/dist/psql/io/pgpass.js +202 -0
- package/dist/psql/io/pgservice.js +194 -0
- package/dist/psql/io/psqlrc.js +422 -0
- package/dist/psql/print/aligned.js +1765 -0
- package/dist/psql/print/asciidoc.js +248 -0
- package/dist/psql/print/crosstab.js +463 -0
- package/dist/psql/print/csv.js +95 -0
- package/dist/psql/print/html.js +258 -0
- package/dist/psql/print/json.js +96 -0
- package/dist/psql/print/latex.js +396 -0
- package/dist/psql/print/pager.js +267 -0
- package/dist/psql/print/troff.js +258 -0
- package/dist/psql/print/unaligned.js +119 -0
- package/dist/psql/print/units.js +135 -0
- package/dist/psql/scanner/slash.js +515 -0
- package/dist/psql/scanner/sql.js +914 -0
- package/dist/psql/scanner/stringutils.js +394 -0
- package/dist/psql/types/backslash.js +1 -0
- package/dist/psql/types/connection.js +1 -0
- package/dist/psql/types/index.js +7 -0
- package/dist/psql/types/printer.js +1 -0
- package/dist/psql/types/repl.js +1 -0
- package/dist/psql/types/scanner.js +24 -0
- package/dist/psql/types/settings.js +1 -0
- package/dist/psql/types/variables.js +1 -0
- package/dist/psql/wire/connection.js +2858 -0
- package/dist/psql/wire/copy.js +108 -0
- package/dist/psql/wire/notify.js +59 -0
- package/dist/psql/wire/pipeline.js +521 -0
- package/dist/psql/wire/protocol.js +466 -0
- package/dist/psql/wire/sasl.js +296 -0
- package/dist/psql/wire/tls.js +602 -0
- package/dist/storage_api.js +147 -0
- package/dist/test_utils/fixtures.js +122 -0
- package/dist/test_utils/oauth_server.js +9 -0
- package/dist/types.js +1 -0
- package/dist/utils/api_enums.js +33 -0
- package/dist/utils/auth.js +5 -0
- package/dist/utils/branch_notice.js +22 -0
- package/dist/utils/branch_picker.js +103 -0
- package/dist/utils/compute_units.js +28 -0
- package/dist/utils/enrichers.js +161 -0
- package/dist/utils/esbuild.js +158 -0
- package/dist/utils/formats.js +18 -0
- package/dist/utils/middlewares.js +20 -0
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +56 -0
- package/dist/utils/psql.js +120 -0
- package/dist/utils/string.js +5 -0
- package/dist/utils/ui.js +59 -0
- package/dist/utils/zip.js +4 -0
- package/dist/writer.js +97 -0
- package/package.json +117 -14
- package/.jshintrc +0 -3
- package/CHANGELOG.markdown +0 -22
- package/LICENSE +0 -21
- package/README.markdown +0 -76
- package/bower.json +0 -23
- package/license.txt +0 -9
- package/neon.js +0 -234
- package/stdlib/bubbling_support.js +0 -32
- package/stdlib/custom_event.js +0 -54
- package/stdlib/custom_event_support.js +0 -195
- package/stdlib/index.js +0 -7
- package/stdlib/node_support.js +0 -123
- package/stdlib/widget.js +0 -340
- package/test/neon_browser.html +0 -11
- package/test/neon_stdlib_browser.html +0 -15
- package/test/neon_stdlib_test.js +0 -84
- package/test/neon_test.js +0 -64
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { resolveConfig } from "@neon/config";
|
|
4
|
+
import { apply, createBranch as createBranchFromPolicy, inspect, loadConfigFromFile, plan, } from "@neon/config-runtime";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { toNeonConfigView } from "../config_format.js";
|
|
7
|
+
import { contextBranch, readContextFile } from "../context.js";
|
|
8
|
+
import { isCi } from "../env.js";
|
|
9
|
+
import { loadEnvFileIntoProcess } from "../env_file.js";
|
|
10
|
+
import { log } from "../log.js";
|
|
11
|
+
import { announceTargetBranch } from "../utils/branch_notice.js";
|
|
12
|
+
import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
|
|
13
|
+
import { bundleEntry } from "../utils/esbuild.js";
|
|
14
|
+
import { addDependenciesArgs, resolvePackageManager, runCommand, } from "../utils/package_manager.js";
|
|
15
|
+
import { zipBundle } from "../utils/zip.js";
|
|
16
|
+
import { writer } from "../writer.js";
|
|
17
|
+
import { autoPullEnvAfterPin } from "./env.js";
|
|
18
|
+
/**
|
|
19
|
+
* Bundle a function with neonctl's OWN bundler (the shared esbuild helper) so the
|
|
20
|
+
* config-runtime never has to import esbuild itself. Injecting this keeps esbuild
|
|
21
|
+
* out of config-runtime's static module graph — and therefore out of the packaged
|
|
22
|
+
* neonctl snapshot, which resolves esbuild dynamically at deploy time.
|
|
23
|
+
*/
|
|
24
|
+
const neonctlBundler = async (fn) => zipBundle(await bundleEntry(fn.source));
|
|
25
|
+
const INSPECT_FIELDS = ["project", "branch", "config"];
|
|
26
|
+
// Deliberately minimal: action/kind/identifier are short and fixed-ish, so the table can
|
|
27
|
+
// never overflow. Per-change `details` (a function's long invocationUrl in particular) are
|
|
28
|
+
// intentionally NOT a column — they used to be JSON-stringified into a cell and blew the
|
|
29
|
+
// table past 190 cols. Function URLs are printed below as a plain list (see reportPushResult),
|
|
30
|
+
// and the full details are still available via `--output json`.
|
|
31
|
+
const APPLIED_FIELDS = ["action", "kind", "identifier"];
|
|
32
|
+
const CONFLICT_FIELDS = [
|
|
33
|
+
"identifier",
|
|
34
|
+
"field",
|
|
35
|
+
"current",
|
|
36
|
+
"desired",
|
|
37
|
+
"reason",
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Shared `--env` flag for `config plan|apply` and `deploy`. Loads a `.env` into
|
|
41
|
+
* `process.env` before the policy is evaluated.
|
|
42
|
+
*/
|
|
43
|
+
export const envFlag = {
|
|
44
|
+
env: {
|
|
45
|
+
describe: "Path to a .env file to load into the environment before evaluating neon.ts " +
|
|
46
|
+
"(so function env values resolve from it). Existing env vars are not overridden.",
|
|
47
|
+
type: "string",
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
/** Apply-only flags, exported so `deploy` can reuse the exact same surface. */
|
|
51
|
+
export const applyFlags = {
|
|
52
|
+
"update-existing": {
|
|
53
|
+
describe: "Auto-confirm overriding existing remote settings on the branch",
|
|
54
|
+
type: "boolean",
|
|
55
|
+
default: false,
|
|
56
|
+
},
|
|
57
|
+
"allow-protected": {
|
|
58
|
+
describe: "Auto-confirm applying to a branch marked protected on Neon",
|
|
59
|
+
type: "boolean",
|
|
60
|
+
default: false,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* `--env-pull` for `config apply` / `deploy` (shared so both expose the identical surface).
|
|
65
|
+
* After a successful apply, the branch's Neon env vars are written to a local `.env` — the
|
|
66
|
+
* same bundled convenience as `link` / `checkout`. On by default; `--no-env-pull` opts out.
|
|
67
|
+
*/
|
|
68
|
+
export const envPullFlag = {
|
|
69
|
+
"env-pull": {
|
|
70
|
+
describe: "Pull the branch's Neon env vars (DATABASE_URL, …) into a local .env after a " +
|
|
71
|
+
"successful apply. On by default; use --no-env-pull to skip (e.g. when injecting " +
|
|
72
|
+
"env at runtime with `neon-env run` / `neon dev`).",
|
|
73
|
+
type: "boolean",
|
|
74
|
+
default: true,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
// ── `config init` ─────────────────────────────────────────────────────────────
|
|
78
|
+
/**
|
|
79
|
+
* The published npm packages a `neon.ts` project needs — the `@neon/*` org names.
|
|
80
|
+
*
|
|
81
|
+
* ⚠️ These ship to users the next time `neonctl` is released, so do NOT release
|
|
82
|
+
* neonctl until `@neon/config` and `@neon/env` are published to npm — otherwise
|
|
83
|
+
* `config init` would install packages that don't exist yet. (The libraries are
|
|
84
|
+
* mid-migration from `@neondatabase/*`; track their publish before cutting a CLI
|
|
85
|
+
* release.)
|
|
86
|
+
*/
|
|
87
|
+
const CONFIG_PACKAGE = "@neon/config";
|
|
88
|
+
const ENV_PACKAGE = "@neon/env";
|
|
89
|
+
const REQUIRED_PACKAGES = [CONFIG_PACKAGE, ENV_PACKAGE];
|
|
90
|
+
/** package.json fields a dependency can be declared in. */
|
|
91
|
+
const DEPENDENCY_FIELDS = [
|
|
92
|
+
"dependencies",
|
|
93
|
+
"devDependencies",
|
|
94
|
+
"peerDependencies",
|
|
95
|
+
"optionalDependencies",
|
|
96
|
+
];
|
|
97
|
+
/** Config filenames the runtime loads (mirrors @neon/config's loader). */
|
|
98
|
+
const NEON_CONFIG_FILENAMES = ["neon.ts", "neon.mts", "neon.js", "neon.mjs"];
|
|
99
|
+
/** Whether `dir` already has a Neon config file the runtime would load. */
|
|
100
|
+
export const hasNeonConfigFile = (dir) => NEON_CONFIG_FILENAMES.some((name) => existsSync(join(dir, name)));
|
|
101
|
+
/** Starter `neon.ts` written by `config init` when a project has none. */
|
|
102
|
+
const NEON_CONFIG_TEMPLATE = `import { defineConfig } from "${CONFIG_PACKAGE}/v1";
|
|
103
|
+
|
|
104
|
+
export default defineConfig({
|
|
105
|
+
// Declare your Neon services here
|
|
106
|
+
auth: false,
|
|
107
|
+
// Branch policy: per-branch tuning
|
|
108
|
+
branch: (branch) => {
|
|
109
|
+
if (branch.isDefault) {
|
|
110
|
+
// Default branch: no overrides, uses project defaults
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
if (!branch.exists) {
|
|
114
|
+
// New non-default branches: auto-expire
|
|
115
|
+
// Run \`neon checkout <name>\` to create a new branch with these settings
|
|
116
|
+
return { ttl: "7d" };
|
|
117
|
+
}
|
|
118
|
+
// Existing branch: no changes
|
|
119
|
+
return {};
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
`;
|
|
123
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
124
|
+
/**
|
|
125
|
+
* The {@link REQUIRED_PACKAGES} not already declared in the project's package.json
|
|
126
|
+
* (any dependency field). A missing or malformed package.json means none are
|
|
127
|
+
* declared, so all are reported missing.
|
|
128
|
+
*/
|
|
129
|
+
const missingDependencies = (cwd) => {
|
|
130
|
+
const declared = new Set();
|
|
131
|
+
const pkgPath = join(cwd, "package.json");
|
|
132
|
+
if (existsSync(pkgPath)) {
|
|
133
|
+
let parsed;
|
|
134
|
+
try {
|
|
135
|
+
parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
parsed = undefined;
|
|
139
|
+
}
|
|
140
|
+
if (isRecord(parsed)) {
|
|
141
|
+
for (const field of DEPENDENCY_FIELDS) {
|
|
142
|
+
const deps = parsed[field];
|
|
143
|
+
if (isRecord(deps)) {
|
|
144
|
+
for (const name of Object.keys(deps))
|
|
145
|
+
declared.add(name);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return REQUIRED_PACKAGES.filter((pkg) => !declared.has(pkg));
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Scaffold a `neon.ts` policy and make sure the Neon config packages are
|
|
154
|
+
* installed, so a project can go straight to `neon config plan` / `apply`.
|
|
155
|
+
* Purely local — it never touches the Neon API (see {@link isConfigInit}).
|
|
156
|
+
*/
|
|
157
|
+
export const initCmd = async (props) => {
|
|
158
|
+
const cwd = props.cwd ?? process.cwd();
|
|
159
|
+
const run = props.run ?? runCommand;
|
|
160
|
+
// 1. Scaffold neon.ts unless the project already has a Neon config file.
|
|
161
|
+
const existing = NEON_CONFIG_FILENAMES.find((name) => existsSync(join(cwd, name)));
|
|
162
|
+
if (existing) {
|
|
163
|
+
log.info("Found an existing %s — leaving it untouched.", existing);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
writeFileSync(join(cwd, "neon.ts"), NEON_CONFIG_TEMPLATE);
|
|
167
|
+
log.info("Created neon.ts with a starter policy.");
|
|
168
|
+
}
|
|
169
|
+
// 2. Make sure the config packages are installed.
|
|
170
|
+
const missing = missingDependencies(cwd);
|
|
171
|
+
if (missing.length === 0) {
|
|
172
|
+
log.info("%s are already installed.", REQUIRED_PACKAGES.join(" and "));
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
const pm = resolvePackageManager();
|
|
176
|
+
const args = addDependenciesArgs(pm, missing);
|
|
177
|
+
if (props.install === false) {
|
|
178
|
+
log.info("Install the Neon config packages to use neon.ts: %s %s", pm, args.join(" "));
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
log.info("Installing %s with %s…", missing.join(", "), pm);
|
|
182
|
+
const ok = await run(pm, args, cwd);
|
|
183
|
+
if (!ok) {
|
|
184
|
+
log.warning("Could not install the config packages automatically. Run by hand: %s %s", pm, args.join(" "));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
log.info("Next: edit neon.ts, then run `neon config plan` to preview and `neon config apply`.");
|
|
189
|
+
};
|
|
190
|
+
export const command = "config";
|
|
191
|
+
export const describe = "Manage a branch with a neon.ts policy";
|
|
192
|
+
export const builder = (argv) => argv
|
|
193
|
+
.usage("$0 config <sub-command> [options]")
|
|
194
|
+
.options({
|
|
195
|
+
"project-id": {
|
|
196
|
+
describe: "Project ID",
|
|
197
|
+
type: "string",
|
|
198
|
+
},
|
|
199
|
+
branch: {
|
|
200
|
+
describe: "Branch ID or name",
|
|
201
|
+
type: "string",
|
|
202
|
+
},
|
|
203
|
+
})
|
|
204
|
+
.middleware(fillSingleProject)
|
|
205
|
+
.command("status", "Show the branch's live Neon state", (yargs) => yargs.options({
|
|
206
|
+
"config-json": {
|
|
207
|
+
describe: "Print only the branch's live config as neon.ts-shaped JSON " +
|
|
208
|
+
"(services + branch tuning + preview), to stdout. Useful for " +
|
|
209
|
+
"scripting or copying into a neon.ts.",
|
|
210
|
+
type: "boolean",
|
|
211
|
+
default: false,
|
|
212
|
+
},
|
|
213
|
+
"current-branch": {
|
|
214
|
+
describe: "Print only the linked branch name from the local .neon file " +
|
|
215
|
+
"(no network). Exits non-zero when no branch is pinned.",
|
|
216
|
+
type: "boolean",
|
|
217
|
+
default: false,
|
|
218
|
+
},
|
|
219
|
+
}), (args) => status(args))
|
|
220
|
+
.command("plan", "Show what `config apply` would change (dry run)", (yargs) => yargs.options({
|
|
221
|
+
config: {
|
|
222
|
+
describe: "Path to a neon.ts policy (defaults to walking up from cwd)",
|
|
223
|
+
type: "string",
|
|
224
|
+
},
|
|
225
|
+
...envFlag,
|
|
226
|
+
}), (args) => planCmd(args))
|
|
227
|
+
.command("apply", "Apply a neon.ts policy to the branch", (yargs) => yargs.options({
|
|
228
|
+
config: {
|
|
229
|
+
describe: "Path to a neon.ts policy (defaults to walking up from cwd)",
|
|
230
|
+
type: "string",
|
|
231
|
+
},
|
|
232
|
+
...envFlag,
|
|
233
|
+
...applyFlags,
|
|
234
|
+
...envPullFlag,
|
|
235
|
+
}), (args) => applyCmd(args))
|
|
236
|
+
.command("init", "Scaffold a neon.ts policy and install the Neon config packages", (yargs) => yargs.options({
|
|
237
|
+
install: {
|
|
238
|
+
describe: "Install @neon/config and @neon/env if they're missing. " +
|
|
239
|
+
"On by default; use --no-install to just print the command.",
|
|
240
|
+
type: "boolean",
|
|
241
|
+
default: true,
|
|
242
|
+
},
|
|
243
|
+
}), (args) => initCmd(args));
|
|
244
|
+
export const handler = (args) => {
|
|
245
|
+
return args;
|
|
246
|
+
};
|
|
247
|
+
const loadConfig = async (props) => {
|
|
248
|
+
// Load the optional --env file FIRST so a `neon.ts` whose function `env` values read
|
|
249
|
+
// `process.env.X` sees them. Must happen before the policy module is imported/evaluated.
|
|
250
|
+
if (props.env) {
|
|
251
|
+
const applied = loadEnvFileIntoProcess(props.env);
|
|
252
|
+
log.debug("Loaded %d var(s) from %s into the environment: %s", applied.length, props.env, applied.join(", "));
|
|
253
|
+
}
|
|
254
|
+
const { config } = await loadConfigFromFile({
|
|
255
|
+
...(props.config ? { path: props.config } : {}),
|
|
256
|
+
});
|
|
257
|
+
return config;
|
|
258
|
+
};
|
|
259
|
+
export const status = async (props) => {
|
|
260
|
+
// `--current-branch` short-circuits here (before resolveBranchRef), so it wins
|
|
261
|
+
// over --config-json and ignores --output. See ConfigProps.currentBranch / isCurrentBranchProbe.
|
|
262
|
+
if (props.currentBranch) {
|
|
263
|
+
const branch = contextBranch(readContextFile(props.contextFile));
|
|
264
|
+
if (branch) {
|
|
265
|
+
process.stdout.write(`${branch}\n`);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
// No branch pinned: hint on stderr and exit non-zero (grep-style) so a prompt's
|
|
269
|
+
// `when` hides the segment cleanly instead of rendering a bare icon.
|
|
270
|
+
log.info("No branch pinned. Run `neonctl checkout <branch>` to pin a branch and pull its env vars.");
|
|
271
|
+
process.exitCode = 1;
|
|
272
|
+
}
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const branch = await resolveBranchRef(props);
|
|
276
|
+
// `--config-json` is a script-friendly mode that emits only JSON to stdout, so keep it
|
|
277
|
+
// pristine; the regular human view gets the "which branch am I inspecting" guardrail.
|
|
278
|
+
if (!props.configJson) {
|
|
279
|
+
announceTargetBranch(props, branch, "Inspecting branch");
|
|
280
|
+
}
|
|
281
|
+
const branchId = branch.branchId;
|
|
282
|
+
const live = await inspect({
|
|
283
|
+
projectId: props.projectId,
|
|
284
|
+
branchId,
|
|
285
|
+
...(props.apiKey ? { apiKey: props.apiKey } : {}),
|
|
286
|
+
...(props.apiHost ? { apiHost: props.apiHost } : {}),
|
|
287
|
+
...(props.runtimeApi ? { api: props.runtimeApi } : {}),
|
|
288
|
+
});
|
|
289
|
+
// The pulled `config` carries the branch's tuning inside a closure that JSON can't
|
|
290
|
+
// render. Resolve it against the live branch target to get the concrete settings, then
|
|
291
|
+
// project both that and the separately-pulled preview state into a neon.ts-shaped view.
|
|
292
|
+
const resolved = resolveConfig(live.config, {
|
|
293
|
+
name: live.branch.name,
|
|
294
|
+
id: live.branch.id,
|
|
295
|
+
exists: true,
|
|
296
|
+
isDefault: live.branch.isDefault,
|
|
297
|
+
isProtected: live.branch.protected,
|
|
298
|
+
...(live.branch.parent ? { parentId: live.branch.parent } : {}),
|
|
299
|
+
...(live.branch.expiresAt ? { expiresAt: live.branch.expiresAt } : {}),
|
|
300
|
+
});
|
|
301
|
+
const configView = toNeonConfigView(resolved, live.preview);
|
|
302
|
+
// `--config-json`: emit just the neon.ts-shaped config to stdout (script-friendly,
|
|
303
|
+
// copy-paste-able), regardless of the global --output.
|
|
304
|
+
if (props.configJson) {
|
|
305
|
+
process.stdout.write(`${JSON.stringify(configView, null, 2)}\n`);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
// Default: the live project/branch tables, but with the unhelpful raw `config` replaced
|
|
309
|
+
// by the resolved neon.ts-shaped view so the user sees enabled infra + branch tuning.
|
|
310
|
+
writer(props).end({ project: live.project, branch: live.branch, config: configView }, { fields: INSPECT_FIELDS });
|
|
311
|
+
};
|
|
312
|
+
export const planCmd = async (props) => {
|
|
313
|
+
const config = await loadConfig(props);
|
|
314
|
+
const branch = await resolveBranchRef(props);
|
|
315
|
+
announceTargetBranch(props, branch, "Planning against branch");
|
|
316
|
+
const branchId = branch.branchId;
|
|
317
|
+
// `plan` is a dry run that never bundles, so its options don't accept (or need)
|
|
318
|
+
// an injected bundler — only `apply` does (it uses neonctlBundler).
|
|
319
|
+
const result = await plan(config, {
|
|
320
|
+
projectId: props.projectId,
|
|
321
|
+
branchId,
|
|
322
|
+
...(props.apiKey ? { apiKey: props.apiKey } : {}),
|
|
323
|
+
...(props.apiHost ? { apiHost: props.apiHost } : {}),
|
|
324
|
+
...(props.runtimeApi ? { api: props.runtimeApi } : {}),
|
|
325
|
+
});
|
|
326
|
+
reportPushResult(props, result, "plan", utilizedServices(config));
|
|
327
|
+
};
|
|
328
|
+
export const applyCmd = async (props) => {
|
|
329
|
+
const config = await loadConfig(props);
|
|
330
|
+
const branch = await resolveBranchRef(props);
|
|
331
|
+
announceTargetBranch(props, branch, "Applying to branch");
|
|
332
|
+
const branchId = branch.branchId;
|
|
333
|
+
const result = await apply(config, {
|
|
334
|
+
projectId: props.projectId,
|
|
335
|
+
branchId,
|
|
336
|
+
...(props.apiKey ? { apiKey: props.apiKey } : {}),
|
|
337
|
+
...(props.apiHost ? { apiHost: props.apiHost } : {}),
|
|
338
|
+
...(props.runtimeApi ? { api: props.runtimeApi } : {}),
|
|
339
|
+
...(props.updateExisting ? { updateExisting: true } : {}),
|
|
340
|
+
...(props.allowProtected ? { allowProtectedBranch: true } : {}),
|
|
341
|
+
bundleFunction: neonctlBundler,
|
|
342
|
+
});
|
|
343
|
+
reportPushResult(props, result, "apply", utilizedServices(config));
|
|
344
|
+
// After a successful apply/deploy, write the branch's Neon env vars to a local .env —
|
|
345
|
+
// the same bundled convenience as `link` / `checkout`, so the branch is immediately
|
|
346
|
+
// usable for local dev. `--no-env-pull` opts out; a pull failure degrades to a warning
|
|
347
|
+
// (the apply already succeeded). See autoPullEnvAfterPin.
|
|
348
|
+
await autoPullEnvAfterPin({ ...props, envPull: props.envPull !== false });
|
|
349
|
+
};
|
|
350
|
+
/**
|
|
351
|
+
* A static service toggle (`auth` / `dataApi` / `preview.aiGateway`) is "on" unless
|
|
352
|
+
* explicitly disabled: `true` / `{}` / `{ enabled: true }` enable it; `false` /
|
|
353
|
+
* `{ enabled: false }` / absent leave it off. Mirrors the runtime's `isServiceEnabled`
|
|
354
|
+
* (which isn't exported), kept tiny and pure so it can be read straight off the policy.
|
|
355
|
+
*/
|
|
356
|
+
const isToggleEnabled = (toggle) => {
|
|
357
|
+
if (toggle === undefined)
|
|
358
|
+
return false;
|
|
359
|
+
if (typeof toggle === "boolean")
|
|
360
|
+
return toggle;
|
|
361
|
+
return toggle.enabled !== false;
|
|
362
|
+
};
|
|
363
|
+
/**
|
|
364
|
+
* Human-readable list of the services a `neon.ts` policy utilizes on the branch, shown under
|
|
365
|
+
* the plan/apply table. Postgres is always present (every branch has it); the rest are listed
|
|
366
|
+
* only when the policy declares them. This deliberately surfaces services that produce **no**
|
|
367
|
+
* plan step — notably the AI Gateway, which is always available and only needs a scoped branch
|
|
368
|
+
* credential (not a provisioning step) — so adding `preview.aiGateway` to a neon.ts isn't
|
|
369
|
+
* mistaken for being silently dropped. Service enablement is static top-level config (it never
|
|
370
|
+
* lives in the per-branch closure), so reading it straight off `config` is accurate.
|
|
371
|
+
*/
|
|
372
|
+
const utilizedServices = (config) => {
|
|
373
|
+
const services = ["Postgres"];
|
|
374
|
+
if (isToggleEnabled(config.auth))
|
|
375
|
+
services.push("Neon Auth");
|
|
376
|
+
if (isToggleEnabled(config.dataApi))
|
|
377
|
+
services.push("Data API");
|
|
378
|
+
if (Object.keys(config.preview?.buckets ?? {}).length > 0) {
|
|
379
|
+
services.push("Object Storage");
|
|
380
|
+
}
|
|
381
|
+
if (Object.keys(config.preview?.functions ?? {}).length > 0) {
|
|
382
|
+
services.push("Functions");
|
|
383
|
+
}
|
|
384
|
+
if (isToggleEnabled(config.preview?.aiGateway))
|
|
385
|
+
services.push("AI Gateway");
|
|
386
|
+
return services;
|
|
387
|
+
};
|
|
388
|
+
/**
|
|
389
|
+
* Render a {@link PushResult}. JSON/YAML output emits the raw result (plus a `services`
|
|
390
|
+
* summary) verbatim so it can be piped; the human-readable path renders the actual changes
|
|
391
|
+
* (dropping noops) and any blocking conflicts as tables, or a "nothing to do" line when both
|
|
392
|
+
* are empty — and always closes with the list of services the policy utilizes so a service
|
|
393
|
+
* that produces no plan step (Postgres, or the credential-gated AI Gateway) isn't mistaken
|
|
394
|
+
* for being missing from the plan above.
|
|
395
|
+
*/
|
|
396
|
+
const reportPushResult = (props, result, mode, services) => {
|
|
397
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
398
|
+
writer(props).end({ ...result, services }, { fields: [] });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const changes = result.applied
|
|
402
|
+
.filter((change) => change.action !== "noop")
|
|
403
|
+
.map((change) => ({
|
|
404
|
+
action: change.action,
|
|
405
|
+
kind: change.kind,
|
|
406
|
+
identifier: change.identifier,
|
|
407
|
+
}));
|
|
408
|
+
const conflicts = result.conflicts.map((conflict) => ({
|
|
409
|
+
identifier: conflict.identifier,
|
|
410
|
+
field: conflict.field,
|
|
411
|
+
current: stringify(conflict.current),
|
|
412
|
+
desired: stringify(conflict.desired),
|
|
413
|
+
reason: conflict.reason,
|
|
414
|
+
}));
|
|
415
|
+
// Deployed functions carry their invocation URL in the change details — collect them so
|
|
416
|
+
// we can list where to call each function without digging through the raw details blob.
|
|
417
|
+
// Keyed by slug so a function never shows twice.
|
|
418
|
+
const functionUrlBySlug = new Map();
|
|
419
|
+
for (const change of result.applied) {
|
|
420
|
+
if (change.action === "noop")
|
|
421
|
+
continue;
|
|
422
|
+
const slug = change.details?.slug;
|
|
423
|
+
const invocationUrl = change.details?.invocationUrl;
|
|
424
|
+
if (typeof slug === "string" && typeof invocationUrl === "string") {
|
|
425
|
+
functionUrlBySlug.set(slug, invocationUrl);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const out = writer(props);
|
|
429
|
+
const noChanges = changes.length === 0 && conflicts.length === 0;
|
|
430
|
+
if (changes.length > 0) {
|
|
431
|
+
out.write(changes, {
|
|
432
|
+
fields: APPLIED_FIELDS,
|
|
433
|
+
title: mode === "plan" ? "Planned changes" : "Applied changes",
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
if (conflicts.length > 0) {
|
|
437
|
+
out.write(conflicts, { fields: CONFLICT_FIELDS, title: "Conflicts" });
|
|
438
|
+
}
|
|
439
|
+
// Flush any tables, then append the lists/summary so they read directly below them.
|
|
440
|
+
out.end();
|
|
441
|
+
// Function URLs are a plain list rather than a table: an invocation URL can be 70+ chars,
|
|
442
|
+
// which makes any bordered table overflow and wrap awkwardly in a normal terminal. A list
|
|
443
|
+
// lets each URL reflow on its own line, and stays copy-pasteable.
|
|
444
|
+
if (functionUrlBySlug.size > 0) {
|
|
445
|
+
const heading = mode === "plan" ? "Function URLs (after apply)" : "Function URLs";
|
|
446
|
+
out.text(`\n${isCi() ? heading : chalk.bold(heading)}\n`);
|
|
447
|
+
for (const [slug, invocationUrl] of functionUrlBySlug) {
|
|
448
|
+
out.text(` • ${slug}: ${invocationUrl}\n`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (noChanges) {
|
|
452
|
+
log.info(`No changes — branch ${result.branchName} already matches the policy.`);
|
|
453
|
+
}
|
|
454
|
+
out.text(`\nUtilized services: ${services.join(", ")}\n`);
|
|
455
|
+
if (conflicts.length > 0) {
|
|
456
|
+
log.info("Resolve the conflicts above, or re-run with --update-existing to override the current remote settings.");
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
const stringify = (value) => value === undefined
|
|
460
|
+
? ""
|
|
461
|
+
: typeof value === "string"
|
|
462
|
+
? value
|
|
463
|
+
: JSON.stringify(value);
|
|
464
|
+
/**
|
|
465
|
+
* Apply a `neon.ts` policy to a **freshly created** branch (used by `neonctl checkout`
|
|
466
|
+
* when it creates a branch). No-op when there is no `neon.ts` on the path from cwd up to
|
|
467
|
+
* the repo root — checkout still succeeds, it just has no policy to apply.
|
|
468
|
+
*
|
|
469
|
+
* The branch was just created by us, so we apply non-interactively (`updateExisting` /
|
|
470
|
+
* `allowProtectedBranch`) — there is no pre-existing state a user would be surprised to
|
|
471
|
+
* see overridden. Functions are bundled with neonctl's own esbuild helper.
|
|
472
|
+
*/
|
|
473
|
+
export const applyPolicyOnCreate = async (props) => {
|
|
474
|
+
let config;
|
|
475
|
+
try {
|
|
476
|
+
({ config } = await loadConfigFromFile({
|
|
477
|
+
...(props.cwd ? { cwd: props.cwd } : {}),
|
|
478
|
+
}));
|
|
479
|
+
}
|
|
480
|
+
catch (err) {
|
|
481
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
482
|
+
if (/Could not find a Neon config file/i.test(message))
|
|
483
|
+
return;
|
|
484
|
+
throw err;
|
|
485
|
+
}
|
|
486
|
+
log.info("Applying neon.ts policy to the new branch…");
|
|
487
|
+
const result = await apply(config, {
|
|
488
|
+
projectId: props.projectId,
|
|
489
|
+
branchId: props.branchId,
|
|
490
|
+
...(props.apiKey ? { apiKey: props.apiKey } : {}),
|
|
491
|
+
...(props.apiHost ? { apiHost: props.apiHost } : {}),
|
|
492
|
+
...(props.runtimeApi ? { api: props.runtimeApi } : {}),
|
|
493
|
+
updateExisting: true,
|
|
494
|
+
allowProtectedBranch: true,
|
|
495
|
+
bundleFunction: neonctlBundler,
|
|
496
|
+
});
|
|
497
|
+
logPolicyResult(result);
|
|
498
|
+
};
|
|
499
|
+
/** Log a one-line summary of what applying a `neon.ts` policy changed (or that nothing did). */
|
|
500
|
+
const logPolicyResult = (result) => {
|
|
501
|
+
const changes = result.applied.filter((c) => c.action !== "noop");
|
|
502
|
+
if (changes.length === 0) {
|
|
503
|
+
log.info("neon.ts applied — no changes were needed.");
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
log.info("neon.ts applied — %d change%s: %s", changes.length, changes.length === 1 ? "" : "s", changes.map((c) => `${c.action} ${c.identifier}`).join(", "));
|
|
507
|
+
};
|
|
508
|
+
/**
|
|
509
|
+
* Create a branch **from** the local `neon.ts` policy. Returns `null` when there is no
|
|
510
|
+
* `neon.ts` on the path from cwd up to the repo root, so `neonctl checkout` can fall back to a
|
|
511
|
+
* bare branch create.
|
|
512
|
+
*
|
|
513
|
+
* Unlike a bare create followed by {@link applyPolicyOnCreate}, this evaluates the policy for
|
|
514
|
+
* the **new** branch (`exists: false`): the runtime branches from the policy's `parent` and
|
|
515
|
+
* brings the branch up with its declared TTL / compute settings / services. That's what makes
|
|
516
|
+
* a policy keyed on `!branch.exists` (the common "only configure new branches" shape) take
|
|
517
|
+
* effect on the very first `checkout` — a bare create + `apply` always saw `exists: true` and
|
|
518
|
+
* skipped that block.
|
|
519
|
+
*/
|
|
520
|
+
export const createBranchFromPolicyOnCheckout = async (props) => {
|
|
521
|
+
let config;
|
|
522
|
+
try {
|
|
523
|
+
({ config } = await loadConfigFromFile({
|
|
524
|
+
...(props.cwd ? { cwd: props.cwd } : {}),
|
|
525
|
+
}));
|
|
526
|
+
}
|
|
527
|
+
catch (err) {
|
|
528
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
529
|
+
if (/Could not find a Neon config file/i.test(message))
|
|
530
|
+
return null;
|
|
531
|
+
throw err;
|
|
532
|
+
}
|
|
533
|
+
const { branchId, branchName, result } = await createBranchFromPolicy(config, {
|
|
534
|
+
projectId: props.projectId,
|
|
535
|
+
branchName: props.branchName,
|
|
536
|
+
...(props.apiKey ? { apiKey: props.apiKey } : {}),
|
|
537
|
+
...(props.apiHost ? { apiHost: props.apiHost } : {}),
|
|
538
|
+
...(props.runtimeApi ? { api: props.runtimeApi } : {}),
|
|
539
|
+
bundleFunction: neonctlBundler,
|
|
540
|
+
});
|
|
541
|
+
log.info("Created branch %s (%s) from neon.ts policy.", branchName, branchId);
|
|
542
|
+
logPolicyResult(result);
|
|
543
|
+
return { branchId };
|
|
544
|
+
};
|