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.
Files changed (159) hide show
  1. package/README.md +15 -700
  2. package/bin/cli.js +3 -0
  3. package/package.json +8 -75
  4. package/dist/analytics.js +0 -168
  5. package/dist/api.js +0 -720
  6. package/dist/auth.js +0 -125
  7. package/dist/callback.html +0 -60
  8. package/dist/cli.js +0 -9
  9. package/dist/commands/api.js +0 -278
  10. package/dist/commands/auth.js +0 -214
  11. package/dist/commands/bootstrap.js +0 -481
  12. package/dist/commands/branches.js +0 -488
  13. package/dist/commands/bucket.js +0 -549
  14. package/dist/commands/checkout.js +0 -321
  15. package/dist/commands/config.js +0 -626
  16. package/dist/commands/connection_string.js +0 -172
  17. package/dist/commands/data_api.js +0 -284
  18. package/dist/commands/databases.js +0 -82
  19. package/dist/commands/deploy.js +0 -26
  20. package/dist/commands/dev.js +0 -698
  21. package/dist/commands/diff.js +0 -222
  22. package/dist/commands/env.js +0 -189
  23. package/dist/commands/functions.js +0 -373
  24. package/dist/commands/index.js +0 -62
  25. package/dist/commands/init.js +0 -73
  26. package/dist/commands/inspect.js +0 -65
  27. package/dist/commands/ip_allow.js +0 -137
  28. package/dist/commands/link.js +0 -1121
  29. package/dist/commands/neon_auth.js +0 -1028
  30. package/dist/commands/operations.js +0 -28
  31. package/dist/commands/orgs.js +0 -24
  32. package/dist/commands/projects.js +0 -413
  33. package/dist/commands/psql.js +0 -62
  34. package/dist/commands/roles.js +0 -65
  35. package/dist/commands/schema_diff.js +0 -151
  36. package/dist/commands/set_context.js +0 -29
  37. package/dist/commands/snapshots.js +0 -455
  38. package/dist/commands/status.js +0 -40
  39. package/dist/commands/user.js +0 -15
  40. package/dist/commands/vpc_endpoints.js +0 -134
  41. package/dist/config.js +0 -11
  42. package/dist/config_format.js +0 -72
  43. package/dist/context.js +0 -231
  44. package/dist/current_branch_fast_path.js +0 -55
  45. package/dist/dev/env.js +0 -244
  46. package/dist/dev/functions.js +0 -70
  47. package/dist/dev/inputs.js +0 -63
  48. package/dist/dev/runtime.js +0 -146
  49. package/dist/env.js +0 -36
  50. package/dist/env_file.js +0 -159
  51. package/dist/errors.js +0 -80
  52. package/dist/functions_api.js +0 -48
  53. package/dist/help.js +0 -146
  54. package/dist/index.js +0 -240
  55. package/dist/log.js +0 -18
  56. package/dist/parameters.gen.js +0 -480
  57. package/dist/pkg.js +0 -25
  58. package/dist/psql/cli.js +0 -53
  59. package/dist/psql/command/cmd_cond.js +0 -437
  60. package/dist/psql/command/cmd_connect.js +0 -820
  61. package/dist/psql/command/cmd_copy.js +0 -1035
  62. package/dist/psql/command/cmd_describe.js +0 -1815
  63. package/dist/psql/command/cmd_format.js +0 -948
  64. package/dist/psql/command/cmd_io.js +0 -2193
  65. package/dist/psql/command/cmd_lo.js +0 -393
  66. package/dist/psql/command/cmd_meta.js +0 -969
  67. package/dist/psql/command/cmd_misc.js +0 -187
  68. package/dist/psql/command/cmd_pipeline.js +0 -1148
  69. package/dist/psql/command/cmd_restrict.js +0 -171
  70. package/dist/psql/command/cmd_show.js +0 -766
  71. package/dist/psql/command/dispatch.js +0 -343
  72. package/dist/psql/command/inputQueue.js +0 -42
  73. package/dist/psql/command/shared.js +0 -71
  74. package/dist/psql/complete/filenames.js +0 -139
  75. package/dist/psql/complete/index.js +0 -104
  76. package/dist/psql/complete/matcher.js +0 -315
  77. package/dist/psql/complete/psqlVars.js +0 -249
  78. package/dist/psql/complete/queries.js +0 -493
  79. package/dist/psql/complete/rules.js +0 -2424
  80. package/dist/psql/core/common.js +0 -1253
  81. package/dist/psql/core/help.js +0 -576
  82. package/dist/psql/core/mainloop.js +0 -1360
  83. package/dist/psql/core/prompt.js +0 -439
  84. package/dist/psql/core/settings.js +0 -686
  85. package/dist/psql/core/sqlHelp.js +0 -1066
  86. package/dist/psql/core/startup.js +0 -846
  87. package/dist/psql/core/syncVars.js +0 -116
  88. package/dist/psql/core/variables.js +0 -287
  89. package/dist/psql/describe/formatters.js +0 -1330
  90. package/dist/psql/describe/processNamePattern.js +0 -270
  91. package/dist/psql/describe/queries.js +0 -2452
  92. package/dist/psql/describe/versionGate.js +0 -44
  93. package/dist/psql/index.js +0 -2030
  94. package/dist/psql/io/history.js +0 -299
  95. package/dist/psql/io/input.js +0 -120
  96. package/dist/psql/io/lineEditor/buffer.js +0 -325
  97. package/dist/psql/io/lineEditor/complete.js +0 -227
  98. package/dist/psql/io/lineEditor/filename.js +0 -159
  99. package/dist/psql/io/lineEditor/index.js +0 -893
  100. package/dist/psql/io/lineEditor/keymap.js +0 -745
  101. package/dist/psql/io/lineEditor/vt100.js +0 -363
  102. package/dist/psql/io/pgpass.js +0 -202
  103. package/dist/psql/io/pgservice.js +0 -194
  104. package/dist/psql/io/psqlrc.js +0 -422
  105. package/dist/psql/print/aligned.js +0 -1748
  106. package/dist/psql/print/asciidoc.js +0 -230
  107. package/dist/psql/print/crosstab.js +0 -463
  108. package/dist/psql/print/csv.js +0 -76
  109. package/dist/psql/print/html.js +0 -240
  110. package/dist/psql/print/json.js +0 -96
  111. package/dist/psql/print/latex.js +0 -379
  112. package/dist/psql/print/pager.js +0 -267
  113. package/dist/psql/print/troff.js +0 -240
  114. package/dist/psql/print/unaligned.js +0 -99
  115. package/dist/psql/print/units.js +0 -188
  116. package/dist/psql/scanner/slash.js +0 -515
  117. package/dist/psql/scanner/sql.js +0 -914
  118. package/dist/psql/scanner/stringutils.js +0 -394
  119. package/dist/psql/types/backslash.js +0 -1
  120. package/dist/psql/types/connection.js +0 -1
  121. package/dist/psql/types/index.js +0 -7
  122. package/dist/psql/types/printer.js +0 -1
  123. package/dist/psql/types/repl.js +0 -1
  124. package/dist/psql/types/scanner.js +0 -24
  125. package/dist/psql/types/settings.js +0 -1
  126. package/dist/psql/types/variables.js +0 -1
  127. package/dist/psql/wire/connection.js +0 -2857
  128. package/dist/psql/wire/copy.js +0 -108
  129. package/dist/psql/wire/notify.js +0 -59
  130. package/dist/psql/wire/pipeline.js +0 -521
  131. package/dist/psql/wire/protocol.js +0 -466
  132. package/dist/psql/wire/sasl.js +0 -294
  133. package/dist/psql/wire/tls.js +0 -602
  134. package/dist/storage_api.js +0 -147
  135. package/dist/test_utils/fixtures.js +0 -121
  136. package/dist/test_utils/oauth_server.js +0 -9
  137. package/dist/types.js +0 -1
  138. package/dist/utils/ai_gateway_notice.js +0 -180
  139. package/dist/utils/api_enums.js +0 -33
  140. package/dist/utils/auth.js +0 -5
  141. package/dist/utils/branch_notice.js +0 -22
  142. package/dist/utils/branch_picker.js +0 -103
  143. package/dist/utils/compute_units.js +0 -28
  144. package/dist/utils/config_diff.js +0 -185
  145. package/dist/utils/enrichers.js +0 -161
  146. package/dist/utils/esbuild.js +0 -158
  147. package/dist/utils/formats.js +0 -18
  148. package/dist/utils/git_diff.js +0 -90
  149. package/dist/utils/inspect_db.js +0 -153
  150. package/dist/utils/inspect_queries.js +0 -372
  151. package/dist/utils/middlewares.js +0 -20
  152. package/dist/utils/openapi.js +0 -114
  153. package/dist/utils/package_manager.js +0 -68
  154. package/dist/utils/point_in_time.js +0 -56
  155. package/dist/utils/psql.js +0 -120
  156. package/dist/utils/string.js +0 -5
  157. package/dist/utils/ui.js +0 -59
  158. package/dist/utils/zip.js +0 -4
  159. 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
- };
@@ -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
- };
@@ -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
- };
@@ -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
- };
@@ -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
- };