neonctl 2.37.0 → 2.38.0

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