neonctl 2.37.1 → 2.38.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 +15 -700
- package/bin/cli.js +3 -0
- package/package.json +8 -75
- package/dist/analytics.js +0 -168
- package/dist/api.js +0 -720
- package/dist/auth.js +0 -125
- package/dist/callback.html +0 -60
- package/dist/cli.js +0 -9
- package/dist/commands/api.js +0 -278
- package/dist/commands/auth.js +0 -214
- package/dist/commands/bootstrap.js +0 -481
- package/dist/commands/branches.js +0 -488
- package/dist/commands/bucket.js +0 -549
- package/dist/commands/checkout.js +0 -321
- package/dist/commands/config.js +0 -626
- package/dist/commands/connection_string.js +0 -172
- package/dist/commands/data_api.js +0 -284
- package/dist/commands/databases.js +0 -82
- package/dist/commands/deploy.js +0 -26
- package/dist/commands/dev.js +0 -698
- package/dist/commands/diff.js +0 -222
- package/dist/commands/env.js +0 -189
- package/dist/commands/functions.js +0 -373
- package/dist/commands/index.js +0 -62
- package/dist/commands/init.js +0 -73
- package/dist/commands/inspect.js +0 -65
- package/dist/commands/ip_allow.js +0 -137
- package/dist/commands/link.js +0 -1121
- package/dist/commands/neon_auth.js +0 -1028
- package/dist/commands/operations.js +0 -28
- package/dist/commands/orgs.js +0 -24
- package/dist/commands/projects.js +0 -413
- package/dist/commands/psql.js +0 -62
- package/dist/commands/roles.js +0 -65
- package/dist/commands/schema_diff.js +0 -151
- package/dist/commands/set_context.js +0 -29
- package/dist/commands/snapshots.js +0 -455
- package/dist/commands/status.js +0 -40
- package/dist/commands/user.js +0 -15
- package/dist/commands/vpc_endpoints.js +0 -134
- package/dist/config.js +0 -11
- package/dist/config_format.js +0 -72
- package/dist/context.js +0 -231
- package/dist/current_branch_fast_path.js +0 -55
- package/dist/dev/env.js +0 -244
- package/dist/dev/functions.js +0 -70
- package/dist/dev/inputs.js +0 -63
- package/dist/dev/runtime.js +0 -146
- package/dist/env.js +0 -36
- package/dist/env_file.js +0 -159
- package/dist/errors.js +0 -80
- package/dist/functions_api.js +0 -48
- package/dist/help.js +0 -146
- package/dist/index.js +0 -240
- package/dist/log.js +0 -18
- package/dist/parameters.gen.js +0 -480
- package/dist/pkg.js +0 -25
- package/dist/psql/cli.js +0 -53
- package/dist/psql/command/cmd_cond.js +0 -437
- package/dist/psql/command/cmd_connect.js +0 -820
- package/dist/psql/command/cmd_copy.js +0 -1035
- package/dist/psql/command/cmd_describe.js +0 -1815
- package/dist/psql/command/cmd_format.js +0 -948
- package/dist/psql/command/cmd_io.js +0 -2193
- package/dist/psql/command/cmd_lo.js +0 -393
- package/dist/psql/command/cmd_meta.js +0 -969
- package/dist/psql/command/cmd_misc.js +0 -187
- package/dist/psql/command/cmd_pipeline.js +0 -1148
- package/dist/psql/command/cmd_restrict.js +0 -171
- package/dist/psql/command/cmd_show.js +0 -766
- package/dist/psql/command/dispatch.js +0 -343
- package/dist/psql/command/inputQueue.js +0 -42
- package/dist/psql/command/shared.js +0 -71
- package/dist/psql/complete/filenames.js +0 -139
- package/dist/psql/complete/index.js +0 -104
- package/dist/psql/complete/matcher.js +0 -315
- package/dist/psql/complete/psqlVars.js +0 -249
- package/dist/psql/complete/queries.js +0 -493
- package/dist/psql/complete/rules.js +0 -2424
- package/dist/psql/core/common.js +0 -1253
- package/dist/psql/core/help.js +0 -576
- package/dist/psql/core/mainloop.js +0 -1360
- package/dist/psql/core/prompt.js +0 -439
- package/dist/psql/core/settings.js +0 -686
- package/dist/psql/core/sqlHelp.js +0 -1066
- package/dist/psql/core/startup.js +0 -846
- package/dist/psql/core/syncVars.js +0 -116
- package/dist/psql/core/variables.js +0 -287
- package/dist/psql/describe/formatters.js +0 -1330
- package/dist/psql/describe/processNamePattern.js +0 -270
- package/dist/psql/describe/queries.js +0 -2452
- package/dist/psql/describe/versionGate.js +0 -44
- package/dist/psql/index.js +0 -2030
- package/dist/psql/io/history.js +0 -299
- package/dist/psql/io/input.js +0 -120
- package/dist/psql/io/lineEditor/buffer.js +0 -325
- package/dist/psql/io/lineEditor/complete.js +0 -227
- package/dist/psql/io/lineEditor/filename.js +0 -159
- package/dist/psql/io/lineEditor/index.js +0 -893
- package/dist/psql/io/lineEditor/keymap.js +0 -745
- package/dist/psql/io/lineEditor/vt100.js +0 -363
- package/dist/psql/io/pgpass.js +0 -202
- package/dist/psql/io/pgservice.js +0 -194
- package/dist/psql/io/psqlrc.js +0 -422
- package/dist/psql/print/aligned.js +0 -1748
- package/dist/psql/print/asciidoc.js +0 -230
- package/dist/psql/print/crosstab.js +0 -463
- package/dist/psql/print/csv.js +0 -76
- package/dist/psql/print/html.js +0 -240
- package/dist/psql/print/json.js +0 -96
- package/dist/psql/print/latex.js +0 -379
- package/dist/psql/print/pager.js +0 -267
- package/dist/psql/print/troff.js +0 -240
- package/dist/psql/print/unaligned.js +0 -99
- package/dist/psql/print/units.js +0 -188
- package/dist/psql/scanner/slash.js +0 -515
- package/dist/psql/scanner/sql.js +0 -914
- package/dist/psql/scanner/stringutils.js +0 -394
- package/dist/psql/types/backslash.js +0 -1
- package/dist/psql/types/connection.js +0 -1
- package/dist/psql/types/index.js +0 -7
- package/dist/psql/types/printer.js +0 -1
- package/dist/psql/types/repl.js +0 -1
- package/dist/psql/types/scanner.js +0 -24
- package/dist/psql/types/settings.js +0 -1
- package/dist/psql/types/variables.js +0 -1
- package/dist/psql/wire/connection.js +0 -2857
- package/dist/psql/wire/copy.js +0 -108
- package/dist/psql/wire/notify.js +0 -59
- package/dist/psql/wire/pipeline.js +0 -521
- package/dist/psql/wire/protocol.js +0 -466
- package/dist/psql/wire/sasl.js +0 -294
- package/dist/psql/wire/tls.js +0 -602
- package/dist/storage_api.js +0 -147
- package/dist/test_utils/fixtures.js +0 -121
- package/dist/test_utils/oauth_server.js +0 -9
- package/dist/types.js +0 -1
- package/dist/utils/ai_gateway_notice.js +0 -180
- package/dist/utils/api_enums.js +0 -33
- package/dist/utils/auth.js +0 -5
- package/dist/utils/branch_notice.js +0 -22
- package/dist/utils/branch_picker.js +0 -103
- package/dist/utils/compute_units.js +0 -28
- package/dist/utils/config_diff.js +0 -185
- package/dist/utils/enrichers.js +0 -161
- package/dist/utils/esbuild.js +0 -158
- package/dist/utils/formats.js +0 -18
- package/dist/utils/git_diff.js +0 -90
- package/dist/utils/inspect_db.js +0 -153
- package/dist/utils/inspect_queries.js +0 -372
- package/dist/utils/middlewares.js +0 -20
- package/dist/utils/openapi.js +0 -114
- package/dist/utils/package_manager.js +0 -68
- package/dist/utils/point_in_time.js +0 -56
- package/dist/utils/psql.js +0 -120
- package/dist/utils/string.js +0 -5
- package/dist/utils/ui.js +0 -59
- package/dist/utils/zip.js +0 -4
- package/dist/writer.js +0 -97
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
const UNSET = "(unset)";
|
|
3
|
-
/**
|
|
4
|
-
* Shared palette for the config diff, matching `neon diff`'s `git diff` styling
|
|
5
|
-
* (bold headers, red "before", green "after", dim connective glyphs). When
|
|
6
|
-
* `color` is false every entry is the identity function, so the identical layout
|
|
7
|
-
* renders without ANSI codes for `--no-color`, non-TTY pipes, and test snapshots.
|
|
8
|
-
*/
|
|
9
|
-
const palette = (color) => {
|
|
10
|
-
const id = (s) => s;
|
|
11
|
-
if (!color) {
|
|
12
|
-
return { title: id, group: id, added: id, removed: id, arrow: id };
|
|
13
|
-
}
|
|
14
|
-
return {
|
|
15
|
-
title: (s) => chalk.bold(s),
|
|
16
|
-
group: (s) => chalk.bold(s),
|
|
17
|
-
added: (s) => chalk.green(s),
|
|
18
|
-
removed: (s) => chalk.red(s),
|
|
19
|
-
arrow: (s) => chalk.dim(s),
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
|
-
const stringifyValue = (value) => {
|
|
24
|
-
if (value === undefined || value === null || value === "")
|
|
25
|
-
return UNSET;
|
|
26
|
-
if (typeof value === "string")
|
|
27
|
-
return value;
|
|
28
|
-
if (typeof value === "boolean" || typeof value === "number") {
|
|
29
|
-
return String(value);
|
|
30
|
-
}
|
|
31
|
-
return JSON.stringify(value);
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* Expand one policy field into flat {@link FieldChange}s. When either side is a
|
|
35
|
-
* plain object (compute settings, Data API settings) it splits into one
|
|
36
|
-
* `field.key` entry per key across both sides, so the diff reads as sorted
|
|
37
|
-
* scalar lines (`computeSettings.autoscalingLimitMaxCu 2 → 4`) rather than an
|
|
38
|
-
* opaque JSON blob. Scalars produce a single entry.
|
|
39
|
-
*/
|
|
40
|
-
const expandField = (field, current, desired) => {
|
|
41
|
-
if (isPlainObject(current) || isPlainObject(desired)) {
|
|
42
|
-
const cur = isPlainObject(current) ? current : {};
|
|
43
|
-
const des = isPlainObject(desired) ? desired : {};
|
|
44
|
-
const keys = Array.from(new Set([...Object.keys(cur), ...Object.keys(des)])).sort();
|
|
45
|
-
return keys.map((key) => ({
|
|
46
|
-
field: `${field}.${key}`,
|
|
47
|
-
...(key in cur ? { current: stringifyValue(cur[key]) } : {}),
|
|
48
|
-
desired: stringifyValue(des[key]),
|
|
49
|
-
}));
|
|
50
|
-
}
|
|
51
|
-
return [
|
|
52
|
-
{
|
|
53
|
-
field,
|
|
54
|
-
...(current !== undefined
|
|
55
|
-
? { current: stringifyValue(current) }
|
|
56
|
-
: {}),
|
|
57
|
-
desired: stringifyValue(desired),
|
|
58
|
-
},
|
|
59
|
-
];
|
|
60
|
-
};
|
|
61
|
-
/** Render the indented `field current → desired` lines for one branch group. */
|
|
62
|
-
const renderFieldLines = (fields, paint) => {
|
|
63
|
-
const width = fields.reduce((max, f) => Math.max(max, f.field.length), 0);
|
|
64
|
-
return fields.map((f) => {
|
|
65
|
-
const name = f.field.padEnd(width);
|
|
66
|
-
const arrow = paint.arrow("→");
|
|
67
|
-
return f.current !== undefined
|
|
68
|
-
? ` ${name} ${paint.removed(f.current)} ${arrow} ${paint.added(f.desired)}`
|
|
69
|
-
: ` ${name} ${arrow} ${paint.added(f.desired)}`;
|
|
70
|
-
});
|
|
71
|
-
};
|
|
72
|
-
/** Group field changes by branch, each block sorted alphabetically by field. */
|
|
73
|
-
const renderBranchGroups = (byBranch, paint) => {
|
|
74
|
-
const lines = [];
|
|
75
|
-
const groups = [...byBranch.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
76
|
-
for (const [branch, fields] of groups) {
|
|
77
|
-
const sorted = [...fields].sort((a, b) => a.field.localeCompare(b.field));
|
|
78
|
-
lines.push(` ${paint.group(`~ ${branch}`)}`);
|
|
79
|
-
lines.push(...renderFieldLines(sorted, paint));
|
|
80
|
-
}
|
|
81
|
-
return lines;
|
|
82
|
-
};
|
|
83
|
-
/** Friendly label for a service change identifier (`bucket:x` → `bucket x`). */
|
|
84
|
-
const serviceLabel = (identifier) => {
|
|
85
|
-
if (identifier === "auth")
|
|
86
|
-
return "Neon Auth";
|
|
87
|
-
if (identifier === "dataApi")
|
|
88
|
-
return "Data API";
|
|
89
|
-
if (identifier.startsWith("bucket:")) {
|
|
90
|
-
return `bucket ${identifier.slice("bucket:".length)}`;
|
|
91
|
-
}
|
|
92
|
-
if (identifier.startsWith("function:")) {
|
|
93
|
-
return `function ${identifier.slice("function:".length)}`;
|
|
94
|
-
}
|
|
95
|
-
return identifier;
|
|
96
|
-
};
|
|
97
|
-
/**
|
|
98
|
-
* The desired-only field changes for an applied/planned **branch** update. The
|
|
99
|
-
* synthesized `AppliedChange.details` carry the new value keyed by `field`
|
|
100
|
-
* (`parent`→`parent`, `ttl`→`expiresAt`, `protected`→`protected`,
|
|
101
|
-
* `computeSettings`→`settings`); the previous value isn't threaded through in
|
|
102
|
-
* Phase 1, so these render as `field → desired` (no red "before"). Object
|
|
103
|
-
* settings expand into sub-fields.
|
|
104
|
-
*
|
|
105
|
-
* `parent` only ever arrives from a branch creation (it cannot be changed
|
|
106
|
-
* afterwards), where it reports the parent the policy named.
|
|
107
|
-
*/
|
|
108
|
-
const appliedBranchFields = (change) => {
|
|
109
|
-
const details = change.details ?? {};
|
|
110
|
-
const field = typeof details.field === "string" ? details.field : "setting";
|
|
111
|
-
switch (field) {
|
|
112
|
-
case "parent":
|
|
113
|
-
return expandField("parent", undefined, details.parent);
|
|
114
|
-
case "ttl":
|
|
115
|
-
return expandField("ttl", undefined, details.expiresAt);
|
|
116
|
-
case "protected":
|
|
117
|
-
return expandField("protected", undefined, details.protected);
|
|
118
|
-
case "computeSettings":
|
|
119
|
-
return expandField("computeSettings", undefined, details.settings);
|
|
120
|
-
default:
|
|
121
|
-
return expandField(field, undefined, details.settings);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
/**
|
|
125
|
-
* Render the applied (or planned) changes as a `git diff`-style list:
|
|
126
|
-
*
|
|
127
|
-
* - **Service changes** (auth / Data API / buckets / functions) are additions
|
|
128
|
-
* with no meaningful "before", so they list as green `+ <label>` lines
|
|
129
|
-
* (`~ <label>` for the lone Data API settings *update*).
|
|
130
|
-
* - **Branch setting changes** group per branch and render as sorted
|
|
131
|
-
* `field → value` lines under a `~ <branch>` header.
|
|
132
|
-
*
|
|
133
|
-
* Returns "" when there is nothing to show. Pure — callers own the heading
|
|
134
|
-
* spacing and any surrounding summary.
|
|
135
|
-
*/
|
|
136
|
-
export const renderAppliedChanges = (changes, title, opts) => {
|
|
137
|
-
if (changes.length === 0)
|
|
138
|
-
return "";
|
|
139
|
-
const paint = palette(opts.color);
|
|
140
|
-
const lines = [paint.title(title)];
|
|
141
|
-
const services = changes
|
|
142
|
-
.filter((c) => c.kind === "service")
|
|
143
|
-
.sort((a, b) => a.identifier.localeCompare(b.identifier));
|
|
144
|
-
for (const service of services) {
|
|
145
|
-
const label = serviceLabel(service.identifier);
|
|
146
|
-
lines.push(service.action === "create"
|
|
147
|
-
? ` ${paint.added(`+ ${label}`)}`
|
|
148
|
-
: ` ${paint.arrow(`~ ${label}`)}`);
|
|
149
|
-
}
|
|
150
|
-
const byBranch = new Map();
|
|
151
|
-
for (const change of changes.filter((c) => c.kind === "branch")) {
|
|
152
|
-
const existing = byBranch.get(change.identifier) ?? [];
|
|
153
|
-
byBranch.set(change.identifier, [
|
|
154
|
-
...existing,
|
|
155
|
-
...appliedBranchFields(change),
|
|
156
|
-
]);
|
|
157
|
-
}
|
|
158
|
-
lines.push(...renderBranchGroups(byBranch, paint));
|
|
159
|
-
return lines.join("\n");
|
|
160
|
-
};
|
|
161
|
-
/**
|
|
162
|
-
* Render branch-setting **conflicts** (drift the policy wants to change but that
|
|
163
|
-
* needs `--update-existing`) as a `git diff`-style before→after report: grouped
|
|
164
|
-
* per branch, sorted by field, `current → desired` with the old value in red and
|
|
165
|
-
* the new in green. Conflicts already carry both sides, so this is the fullest
|
|
166
|
-
* form of the diff. Returns "" when there are no conflicts.
|
|
167
|
-
*/
|
|
168
|
-
export const renderBranchSettingConflicts = (conflicts, opts) => {
|
|
169
|
-
if (conflicts.length === 0)
|
|
170
|
-
return "";
|
|
171
|
-
const paint = palette(opts.color);
|
|
172
|
-
const byBranch = new Map();
|
|
173
|
-
for (const conflict of conflicts) {
|
|
174
|
-
const existing = byBranch.get(conflict.identifier) ?? [];
|
|
175
|
-
byBranch.set(conflict.identifier, [
|
|
176
|
-
...existing,
|
|
177
|
-
...expandField(conflict.field, conflict.current, conflict.desired),
|
|
178
|
-
]);
|
|
179
|
-
}
|
|
180
|
-
const lines = [
|
|
181
|
-
paint.title("Branch settings differ (re-run with --update-existing to apply)"),
|
|
182
|
-
...renderBranchGroups(byBranch, paint),
|
|
183
|
-
];
|
|
184
|
-
return lines.join("\n");
|
|
185
|
-
};
|
package/dist/utils/enrichers.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { isNeonApiError, messageFromBody } from "../api.js";
|
|
2
|
-
import { isConfigInit, isCurrentBranchProbe } from "../context.js";
|
|
3
|
-
import { looksLikeBranchId } from "./formats.js";
|
|
4
|
-
export const branchIdResolve = async ({ branch, apiClient, projectId, }) => {
|
|
5
|
-
branch = branch.toString();
|
|
6
|
-
if (looksLikeBranchId(branch)) {
|
|
7
|
-
return branch;
|
|
8
|
-
}
|
|
9
|
-
const { data } = await apiClient.listProjectBranches({
|
|
10
|
-
projectId,
|
|
11
|
-
});
|
|
12
|
-
const branchData = data.branches.find((b) => b.name === branch);
|
|
13
|
-
if (!branchData) {
|
|
14
|
-
throw new Error(`Branch ${branch} not found.\nAvailable branches: ${data.branches
|
|
15
|
-
.map((b) => b.name)
|
|
16
|
-
.join(", ")}`);
|
|
17
|
-
}
|
|
18
|
-
return branchData.id;
|
|
19
|
-
};
|
|
20
|
-
const getBranchIdFromProps = async (props) => {
|
|
21
|
-
const branch = "branch" in props && typeof props.branch === "string"
|
|
22
|
-
? props.branch
|
|
23
|
-
: props.id;
|
|
24
|
-
if (branch) {
|
|
25
|
-
return await branchIdResolve({
|
|
26
|
-
branch,
|
|
27
|
-
apiClient: props.apiClient,
|
|
28
|
-
projectId: props.projectId,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
const { data } = await props.apiClient.listProjectBranches({
|
|
32
|
-
projectId: props.projectId,
|
|
33
|
-
});
|
|
34
|
-
const defaultBranch = data.branches.find((b) => b.default);
|
|
35
|
-
if (defaultBranch) {
|
|
36
|
-
return defaultBranch.id;
|
|
37
|
-
}
|
|
38
|
-
throw new Error("No default branch found");
|
|
39
|
-
};
|
|
40
|
-
export const branchIdFromProps = async (props) => {
|
|
41
|
-
props.branchId = await getBranchIdFromProps(props);
|
|
42
|
-
return props.branchId;
|
|
43
|
-
};
|
|
44
|
-
export const resolveBranchRef = async (props) => {
|
|
45
|
-
const branch = "branch" in props && typeof props.branch === "string"
|
|
46
|
-
? props.branch
|
|
47
|
-
: props.id;
|
|
48
|
-
const { data } = await props.apiClient.listProjectBranches({
|
|
49
|
-
projectId: props.projectId,
|
|
50
|
-
});
|
|
51
|
-
const branches = data.branches;
|
|
52
|
-
if (branch) {
|
|
53
|
-
const ref = branch.toString();
|
|
54
|
-
const found = looksLikeBranchId(ref)
|
|
55
|
-
? branches.find((b) => b.id === ref)
|
|
56
|
-
: branches.find((b) => b.name === ref);
|
|
57
|
-
if (found) {
|
|
58
|
-
return {
|
|
59
|
-
branchId: found.id,
|
|
60
|
-
branchName: found.name ?? found.id,
|
|
61
|
-
usedDefault: false,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
// A `br-…` id absent from the listing is still usable as an id (trust it like
|
|
65
|
-
// branchIdResolve does); only an unresolved *name* is a genuine error.
|
|
66
|
-
if (looksLikeBranchId(ref)) {
|
|
67
|
-
return { branchId: ref, branchName: ref, usedDefault: false };
|
|
68
|
-
}
|
|
69
|
-
throw new Error(`Branch ${ref} not found.\nAvailable branches: ${branches
|
|
70
|
-
.map((b) => b.name)
|
|
71
|
-
.join(", ")}`);
|
|
72
|
-
}
|
|
73
|
-
const defaultBranch = branches.find((b) => b.default);
|
|
74
|
-
if (!defaultBranch) {
|
|
75
|
-
throw new Error("No default branch found");
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
branchId: defaultBranch.id,
|
|
79
|
-
branchName: defaultBranch.name ?? defaultBranch.id,
|
|
80
|
-
usedDefault: true,
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
export const resolveSingleDatabase = async (props) => {
|
|
84
|
-
const { data } = await props.apiClient.listProjectBranchDatabases(props.projectId, props.branchId);
|
|
85
|
-
const databases = data.databases;
|
|
86
|
-
if (props.database !== undefined) {
|
|
87
|
-
if (!databases.find((d) => d.name === props.database)) {
|
|
88
|
-
throw new Error(`Database not found: ${props.database}. Available databases on branch ${props.branchId}: ${databases.map((d) => d.name).join(", ")}`);
|
|
89
|
-
}
|
|
90
|
-
return props.database;
|
|
91
|
-
}
|
|
92
|
-
if (databases.length === 0) {
|
|
93
|
-
throw new Error(`No databases found for the branch: ${props.branchId}`);
|
|
94
|
-
}
|
|
95
|
-
if (databases.length === 1) {
|
|
96
|
-
return databases[0].name;
|
|
97
|
-
}
|
|
98
|
-
throw new Error(`Multiple databases found for the branch, please provide one with the --database option: ${databases.map((d) => d.name).join(", ")}`);
|
|
99
|
-
};
|
|
100
|
-
export const fillSingleProject = async (props) => {
|
|
101
|
-
// The offline `--current-branch` probe needs no project at all and runs with no
|
|
102
|
-
// API client (auth was skipped), so resolving a single project here would both
|
|
103
|
-
// hit the network and dereference a null client. Skip it entirely.
|
|
104
|
-
if (isCurrentBranchProbe(props)) {
|
|
105
|
-
return props;
|
|
106
|
-
}
|
|
107
|
-
// `config init` is purely local (scaffold + npm install) and runs with no API
|
|
108
|
-
// client, so resolving a single project here would dereference a null client.
|
|
109
|
-
if (isConfigInit(props)) {
|
|
110
|
-
return props;
|
|
111
|
-
}
|
|
112
|
-
if (props.projectId) {
|
|
113
|
-
return { ...props, projectId: props.projectId };
|
|
114
|
-
}
|
|
115
|
-
// If no orgId is provided, try to auto-fill it if there's only one org
|
|
116
|
-
let orgId = props.orgId;
|
|
117
|
-
if (!orgId) {
|
|
118
|
-
const { data: orgsData } = await props.apiClient.getCurrentUserOrganizations();
|
|
119
|
-
if (orgsData.organizations.length === 1) {
|
|
120
|
-
orgId = orgsData.organizations[0].id;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
try {
|
|
124
|
-
const { data } = await props.apiClient.listProjects({
|
|
125
|
-
limit: 2,
|
|
126
|
-
org_id: orgId,
|
|
127
|
-
});
|
|
128
|
-
if (data.projects.length === 0) {
|
|
129
|
-
throw new Error("No projects found");
|
|
130
|
-
}
|
|
131
|
-
if (data.projects.length > 1) {
|
|
132
|
-
throw new Error(`Multiple projects found, please provide one with the --project-id option`);
|
|
133
|
-
}
|
|
134
|
-
return {
|
|
135
|
-
...props,
|
|
136
|
-
projectId: data.projects[0].id,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
catch (error) {
|
|
140
|
-
// If the API error is about missing org_id, provide a user-friendly message
|
|
141
|
-
if (isNeonApiError(error) &&
|
|
142
|
-
error.status === 400 &&
|
|
143
|
-
messageFromBody(error.data)?.includes("org_id is required")) {
|
|
144
|
-
throw new Error("Multiple projects found, please provide one with the --project-id option");
|
|
145
|
-
}
|
|
146
|
-
throw error;
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
export const fillSingleOrg = async (props) => {
|
|
150
|
-
if (props.orgId) {
|
|
151
|
-
return props;
|
|
152
|
-
}
|
|
153
|
-
const { data } = await props.apiClient.getCurrentUserOrganizations();
|
|
154
|
-
if (data.organizations.length === 0) {
|
|
155
|
-
throw new Error("No organizations found");
|
|
156
|
-
}
|
|
157
|
-
if (data.organizations.length > 1) {
|
|
158
|
-
throw new Error(`Multiple organizations found, please provide one with the --org-id option`);
|
|
159
|
-
}
|
|
160
|
-
return { ...props, orgId: data.organizations[0].id };
|
|
161
|
-
};
|
package/dist/utils/esbuild.js
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { basename, join } from "node:path";
|
|
5
|
-
import which from "which";
|
|
6
|
-
const NOT_FOUND = "esbuild not found. neonctl ships esbuild for most platforms; if you see " +
|
|
7
|
-
"this, install esbuild and ensure it is on your PATH (e.g. `npm i -g " +
|
|
8
|
-
"esbuild`), or set NEON_ESBUILD_PATH to an esbuild binary.";
|
|
9
|
-
// Prepended to the ESM bundle. Bundled dependencies are frequently CommonJS, but an ESM
|
|
10
|
-
// output (`--format=esm`) has no `require` / `__filename` / `__dirname` in scope — so any
|
|
11
|
-
// bundled CJS code that calls `require(...)` would fail at load with
|
|
12
|
-
// `Dynamic require of "x" is not supported`. Re-create those globals via `createRequire`
|
|
13
|
-
// so CJS and ESM dependencies coexist in the single `index.mjs`.
|
|
14
|
-
const ESM_CJS_INTEROP_BANNER = "import{createRequire as ___cr}from'module';import{fileURLToPath as ___f}from'url';import{dirname as ___d}from'path';const require=___cr(import.meta.url);const __filename=___f(import.meta.url);const __dirname=___d(__filename);";
|
|
15
|
-
const defaultDeps = {
|
|
16
|
-
// @yao-pkg/pkg defines process.pkg inside the packaged binary.
|
|
17
|
-
isPackaged: () => process.pkg !== undefined,
|
|
18
|
-
loadEsbuild: (name) => import(name),
|
|
19
|
-
};
|
|
20
|
-
// Internal signal: the esbuild JS module could not be imported (exotic platform
|
|
21
|
-
// or not installed). Tells bundleEntry to fall back to the binary; never shown
|
|
22
|
-
// to the user.
|
|
23
|
-
class ModuleNotAvailable extends Error {
|
|
24
|
-
}
|
|
25
|
-
const message = (err) => err instanceof Error ? err.message : String(err);
|
|
26
|
-
const toFilesByBasename = (files) => {
|
|
27
|
-
const out = {};
|
|
28
|
-
for (const f of files)
|
|
29
|
-
out[basename(f.path)] = f.contents;
|
|
30
|
-
return out;
|
|
31
|
-
};
|
|
32
|
-
const bundleViaModule = async (source, loadEsbuild) => {
|
|
33
|
-
// esbuild is resolved by a COMPUTED specifier, never the literal string
|
|
34
|
-
// 'esbuild'. Both rollup (bundle step) and @yao-pkg/pkg (binary step)
|
|
35
|
-
// statically scan for literal import()/require() calls and would otherwise
|
|
36
|
-
// pull esbuild and its native Go binary into the bundle/snapshot — bloating
|
|
37
|
-
// the packaged CLI and emitting "cannot bundle native binary" warnings. The
|
|
38
|
-
// packaged binary never imports esbuild at all (the isPackaged guard sends it
|
|
39
|
-
// to the binary path), so keeping this specifier invisible to the scanners is
|
|
40
|
-
// what keeps esbuild out of the snapshot.
|
|
41
|
-
// Do NOT "simplify" this back to import('esbuild').
|
|
42
|
-
const name = ["es", "build"].join("");
|
|
43
|
-
let esbuild;
|
|
44
|
-
try {
|
|
45
|
-
esbuild = await loadEsbuild(name);
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
throw new ModuleNotAvailable();
|
|
49
|
-
}
|
|
50
|
-
// Mirrors the binary-path bundling flags; write:false keeps output in memory.
|
|
51
|
-
// logLevel:'silent' suppresses esbuild's own stderr — the rejected error
|
|
52
|
-
// still carries the diagnostic, matching the binary path's captured-stderr.
|
|
53
|
-
const result = await esbuild
|
|
54
|
-
.build({
|
|
55
|
-
entryPoints: [source],
|
|
56
|
-
bundle: true,
|
|
57
|
-
// Emit `index.mjs` (not `out.js`): the Functions runtime imports the archive's entry
|
|
58
|
-
// by the conventional `index.{js,mjs}` name, and `.mjs` makes Node treat the ESM
|
|
59
|
-
// output as a module without needing a `package.json` type marker alongside it.
|
|
60
|
-
outfile: "index.mjs",
|
|
61
|
-
write: false,
|
|
62
|
-
minify: true,
|
|
63
|
-
format: "esm",
|
|
64
|
-
platform: "node",
|
|
65
|
-
// Bundle dependencies into the entry so the deployed archive is self-contained (the
|
|
66
|
-
// Functions runtime has no node_modules). Node built-ins stay external on
|
|
67
|
-
// platform:'node'. The banner re-creates require/__filename/__dirname for bundled CJS.
|
|
68
|
-
banner: { js: ESM_CJS_INTEROP_BANNER },
|
|
69
|
-
logLevel: "silent",
|
|
70
|
-
})
|
|
71
|
-
.catch((err) => {
|
|
72
|
-
throw new Error(`Failed to bundle function from ${source}. ${message(err)}`.trim());
|
|
73
|
-
});
|
|
74
|
-
const files = result.outputFiles ?? [];
|
|
75
|
-
// write:false with one entry always yields index.mjs (no source map — we don't emit one);
|
|
76
|
-
// an empty set means the API contract changed under us — fail loud rather than ship an
|
|
77
|
-
// empty archive.
|
|
78
|
-
if (files.length === 0) {
|
|
79
|
-
throw new Error(`Failed to bundle function from ${source}. esbuild produced no output.`);
|
|
80
|
-
}
|
|
81
|
-
return toFilesByBasename(files);
|
|
82
|
-
};
|
|
83
|
-
// Find the esbuild binary at deploy time. An explicit override is authoritative
|
|
84
|
-
// (so it fails loudly if wrong); otherwise prefer the host PATH, then a locally
|
|
85
|
-
// installed copy.
|
|
86
|
-
const resolveEsbuild = () => {
|
|
87
|
-
const override = process.env.NEON_ESBUILD_PATH;
|
|
88
|
-
if (override) {
|
|
89
|
-
if (existsSync(override))
|
|
90
|
-
return override;
|
|
91
|
-
throw new Error(NOT_FOUND);
|
|
92
|
-
}
|
|
93
|
-
const onPath = which.sync("esbuild", { nothrow: true });
|
|
94
|
-
if (onPath)
|
|
95
|
-
return onPath;
|
|
96
|
-
// CWD-relative (not install-relative): helps the dev checkout where esbuild is
|
|
97
|
-
// a devDependency. In `npm i -g` and pkg installs the PATH branch above wins.
|
|
98
|
-
const local = join(process.cwd(), "node_modules", ".bin", "esbuild");
|
|
99
|
-
if (existsSync(local))
|
|
100
|
-
return local;
|
|
101
|
-
throw new Error(NOT_FOUND);
|
|
102
|
-
};
|
|
103
|
-
const runEsbuild = (bin, args) => new Promise((resolve, reject) => {
|
|
104
|
-
// stderr is captured (NOT inherited): with --log-level=error a success emits
|
|
105
|
-
// nothing, and a failure's diagnostic is read out below. Never use 'inherit'.
|
|
106
|
-
const child = spawn(bin, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
107
|
-
let stderr = "";
|
|
108
|
-
child.stderr.on("data", (chunk) => {
|
|
109
|
-
stderr += chunk.toString();
|
|
110
|
-
});
|
|
111
|
-
child.on("error", reject);
|
|
112
|
-
child.on("close", (code) => {
|
|
113
|
-
resolve({ code, stderr });
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
const bundleViaBinary = async (source) => {
|
|
117
|
-
const bin = resolveEsbuild();
|
|
118
|
-
const outDir = mkdtempSync(join(tmpdir(), "neon-fn-bundle-"));
|
|
119
|
-
const outfile = join(outDir, "index.mjs");
|
|
120
|
-
try {
|
|
121
|
-
const { code, stderr } = await runEsbuild(bin, [
|
|
122
|
-
source,
|
|
123
|
-
"--bundle",
|
|
124
|
-
`--outfile=${outfile}`,
|
|
125
|
-
"--minify",
|
|
126
|
-
"--format=esm",
|
|
127
|
-
"--platform=node",
|
|
128
|
-
`--banner:js=${ESM_CJS_INTEROP_BANNER}`,
|
|
129
|
-
"--log-level=error",
|
|
130
|
-
]);
|
|
131
|
-
if (code !== 0) {
|
|
132
|
-
throw new Error(`Failed to bundle function from ${source}. ${stderr.trim()}`.trim());
|
|
133
|
-
}
|
|
134
|
-
// No `--sourcemap`: the Functions runtime has no source-map support, so an uploaded
|
|
135
|
-
// `index.mjs.map` is never consumed — emitting it only inflated the archive.
|
|
136
|
-
return {
|
|
137
|
-
"index.mjs": new Uint8Array(readFileSync(outfile)),
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
finally {
|
|
141
|
-
rmSync(outDir, { recursive: true, force: true });
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
// Bundle `source` into the files the Functions archive expects, keyed by
|
|
145
|
-
// basename. npm installs bundle in-process via the esbuild module; the packaged
|
|
146
|
-
// binary (and platforms esbuild can't run on) shell out to an esbuild binary.
|
|
147
|
-
export const bundleEntry = async (source, deps = defaultDeps) => {
|
|
148
|
-
if (deps.isPackaged())
|
|
149
|
-
return bundleViaBinary(source);
|
|
150
|
-
try {
|
|
151
|
-
return await bundleViaModule(source, deps.loadEsbuild);
|
|
152
|
-
}
|
|
153
|
-
catch (err) {
|
|
154
|
-
if (err instanceof ModuleNotAvailable)
|
|
155
|
-
return bundleViaBinary(source);
|
|
156
|
-
throw err;
|
|
157
|
-
}
|
|
158
|
-
};
|
package/dist/utils/formats.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
const HAIKU_REGEX = /^[a-z0-9]+-[a-z0-9]+-[a-z0-9]+$/;
|
|
2
|
-
export const looksLikeBranchId = (branch) => branch.startsWith("br-") && HAIKU_REGEX.test(branch.substring(3));
|
|
3
|
-
const LSN_REGEX = /^[a-fA-F0-9]{1,8}\/[a-fA-F0-9]{1,8}$/;
|
|
4
|
-
export const looksLikeLSN = (lsn) => LSN_REGEX.test(lsn);
|
|
5
|
-
export const looksLikeTimestamp = (timestamp) => {
|
|
6
|
-
if (isNaN(Date.parse(timestamp)))
|
|
7
|
-
return false;
|
|
8
|
-
/**
|
|
9
|
-
* @info
|
|
10
|
-
* Check for ISO 8601/RFC 3339 format patterns
|
|
11
|
-
* Must contain 'T' separator and end with 'Z' or timezone offset
|
|
12
|
-
*
|
|
13
|
-
* `Date.parse` aggressive parsing will attempt a date out of any string.
|
|
14
|
-
* so if a branch name has a number, `Date.parse` will return a valid timestamp.
|
|
15
|
-
*/
|
|
16
|
-
const iso8601Regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?([+-]\d{2}:\d{2}|Z)$/;
|
|
17
|
-
return iso8601Regex.test(timestamp);
|
|
18
|
-
};
|
package/dist/utils/git_diff.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { structuredPatch } from "diff";
|
|
3
|
-
/**
|
|
4
|
-
* Color palette for the rendered diff. When `color` is false every entry is the
|
|
5
|
-
* identity function, so the exact same layout is emitted without ANSI codes
|
|
6
|
-
* (for `--no-color`, non-TTY pipes, and stable test snapshots).
|
|
7
|
-
*/
|
|
8
|
-
const palette = (color) => {
|
|
9
|
-
const id = (s) => s;
|
|
10
|
-
if (!color) {
|
|
11
|
-
return {
|
|
12
|
-
header: id,
|
|
13
|
-
removedFile: id,
|
|
14
|
-
addedFile: id,
|
|
15
|
-
hunk: id,
|
|
16
|
-
added: id,
|
|
17
|
-
removed: id,
|
|
18
|
-
noNewline: id,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
header: (s) => chalk.bold(s),
|
|
23
|
-
removedFile: (s) => chalk.bold.red(s),
|
|
24
|
-
addedFile: (s) => chalk.bold.green(s),
|
|
25
|
-
hunk: (s) => chalk.cyan(s),
|
|
26
|
-
added: (s) => chalk.green(s),
|
|
27
|
-
removed: (s) => chalk.red(s),
|
|
28
|
-
noNewline: (s) => chalk.dim(s),
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
|
-
const branchLabel = (schema) => `${schema.branchName} (${schema.branchId})`;
|
|
32
|
-
/**
|
|
33
|
-
* Render a single database's schema comparison as a git-style unified diff.
|
|
34
|
-
*
|
|
35
|
-
* Pure: given the two schemas it computes hunks with `diff`'s `structuredPatch`
|
|
36
|
-
* and lays them out like `git diff` — a bold header, red `---` / green `+++`
|
|
37
|
-
* branch lines, cyan `@@` hunk headers, and green/red line bodies. Returns
|
|
38
|
-
* `hasChanges: false` with empty text when the two schemas are identical.
|
|
39
|
-
*/
|
|
40
|
-
export const renderDatabaseSchemaDiff = (diff, opts) => {
|
|
41
|
-
const patch = structuredPatch(diff.database, diff.database, diff.before.sql, diff.after.sql, "", "", { context: 3 });
|
|
42
|
-
if (patch.hunks.length === 0) {
|
|
43
|
-
return { database: diff.database, hasChanges: false, text: "" };
|
|
44
|
-
}
|
|
45
|
-
const paint = palette(opts.color);
|
|
46
|
-
const lines = [
|
|
47
|
-
paint.header(`diff --neon database ${diff.database}`),
|
|
48
|
-
paint.removedFile(`--- ${branchLabel(diff.before)}`),
|
|
49
|
-
paint.addedFile(`+++ ${branchLabel(diff.after)}`),
|
|
50
|
-
];
|
|
51
|
-
for (const hunk of patch.hunks) {
|
|
52
|
-
lines.push(paint.hunk(`@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`));
|
|
53
|
-
for (const line of hunk.lines) {
|
|
54
|
-
// `structuredPatch` prefixes every line: '+' added, '-' removed,
|
|
55
|
-
// ' ' unchanged context, and '\' for the "No newline at end of file"
|
|
56
|
-
// marker. Color the first three; dim the marker; leave context plain.
|
|
57
|
-
if (line.startsWith("+")) {
|
|
58
|
-
lines.push(paint.added(line));
|
|
59
|
-
}
|
|
60
|
-
else if (line.startsWith("-")) {
|
|
61
|
-
lines.push(paint.removed(line));
|
|
62
|
-
}
|
|
63
|
-
else if (line.startsWith("\\")) {
|
|
64
|
-
lines.push(paint.noNewline(line));
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
lines.push(line);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
database: diff.database,
|
|
73
|
-
hasChanges: true,
|
|
74
|
-
text: lines.join("\n"),
|
|
75
|
-
};
|
|
76
|
-
};
|
|
77
|
-
/**
|
|
78
|
-
* Render every database's diff into one report, keeping only databases whose
|
|
79
|
-
* schema actually changed. `hasChanges` is false when nothing changed across
|
|
80
|
-
* all databases, letting the caller print a single "no differences" note.
|
|
81
|
-
*/
|
|
82
|
-
export const renderSchemaDiffReport = (diffs, opts) => {
|
|
83
|
-
const rendered = diffs.map((diff) => renderDatabaseSchemaDiff(diff, opts));
|
|
84
|
-
const changed = rendered.filter((r) => r.hasChanges);
|
|
85
|
-
return {
|
|
86
|
-
hasChanges: changed.length > 0,
|
|
87
|
-
text: changed.map((r) => r.text).join("\n\n"),
|
|
88
|
-
changedDatabases: changed.map((r) => r.database),
|
|
89
|
-
};
|
|
90
|
-
};
|