neon 2.1.2 → 2.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/LICENSE.md +178 -0
  2. package/README.md +601 -0
  3. package/dist/analytics.js +156 -0
  4. package/dist/api.js +665 -0
  5. package/dist/auth.js +127 -0
  6. package/dist/callback.html +51 -0
  7. package/dist/cli.js +9 -0
  8. package/dist/commands/auth.js +214 -0
  9. package/dist/commands/bootstrap.js +481 -0
  10. package/dist/commands/branches.js +481 -0
  11. package/dist/commands/bucket.js +543 -0
  12. package/dist/commands/checkout.js +289 -0
  13. package/dist/commands/config.js +544 -0
  14. package/dist/commands/connection_string.js +172 -0
  15. package/dist/commands/data_api.js +285 -0
  16. package/dist/commands/databases.js +82 -0
  17. package/dist/commands/deploy.js +26 -0
  18. package/dist/commands/dev.js +698 -0
  19. package/dist/commands/env.js +166 -0
  20. package/dist/commands/functions.js +373 -0
  21. package/dist/commands/index.js +54 -0
  22. package/dist/commands/init.js +73 -0
  23. package/dist/commands/ip_allow.js +137 -0
  24. package/dist/commands/link.js +1121 -0
  25. package/dist/commands/neon_auth.js +1028 -0
  26. package/dist/commands/operations.js +28 -0
  27. package/dist/commands/orgs.js +24 -0
  28. package/dist/commands/projects.js +372 -0
  29. package/dist/commands/psql.js +62 -0
  30. package/dist/commands/roles.js +65 -0
  31. package/dist/commands/schema_diff.js +151 -0
  32. package/dist/commands/set_context.js +29 -0
  33. package/dist/commands/status.js +40 -0
  34. package/dist/commands/user.js +15 -0
  35. package/dist/commands/vpc_endpoints.js +134 -0
  36. package/dist/config.js +11 -0
  37. package/dist/config_format.js +72 -0
  38. package/dist/context.js +177 -0
  39. package/dist/current_branch_fast_path.js +55 -0
  40. package/dist/dev/env.js +240 -0
  41. package/dist/dev/functions.js +70 -0
  42. package/dist/dev/inputs.js +63 -0
  43. package/dist/dev/runtime.js +146 -0
  44. package/dist/env.js +36 -0
  45. package/dist/env_file.js +159 -0
  46. package/dist/errors.js +80 -0
  47. package/dist/functions_api.js +44 -0
  48. package/dist/help.js +146 -0
  49. package/dist/index.js +234 -0
  50. package/dist/log.js +18 -0
  51. package/dist/parameters.gen.js +480 -0
  52. package/dist/pkg.js +25 -0
  53. package/dist/psql/cli.js +53 -0
  54. package/dist/psql/command/cmd_cond.js +437 -0
  55. package/dist/psql/command/cmd_connect.js +820 -0
  56. package/dist/psql/command/cmd_copy.js +1035 -0
  57. package/dist/psql/command/cmd_describe.js +1815 -0
  58. package/dist/psql/command/cmd_format.js +922 -0
  59. package/dist/psql/command/cmd_io.js +2193 -0
  60. package/dist/psql/command/cmd_lo.js +393 -0
  61. package/dist/psql/command/cmd_meta.js +970 -0
  62. package/dist/psql/command/cmd_misc.js +187 -0
  63. package/dist/psql/command/cmd_pipeline.js +1148 -0
  64. package/dist/psql/command/cmd_restrict.js +171 -0
  65. package/dist/psql/command/cmd_show.js +766 -0
  66. package/dist/psql/command/dispatch.js +343 -0
  67. package/dist/psql/command/inputQueue.js +42 -0
  68. package/dist/psql/command/shared.js +71 -0
  69. package/dist/psql/complete/filenames.js +139 -0
  70. package/dist/psql/complete/index.js +104 -0
  71. package/dist/psql/complete/matcher.js +315 -0
  72. package/dist/psql/complete/psqlVars.js +247 -0
  73. package/dist/psql/complete/queries.js +493 -0
  74. package/dist/psql/complete/rules.js +2424 -0
  75. package/dist/psql/core/common.js +1253 -0
  76. package/dist/psql/core/help.js +576 -0
  77. package/dist/psql/core/mainloop.js +1360 -0
  78. package/dist/psql/core/prompt.js +440 -0
  79. package/dist/psql/core/settings.js +684 -0
  80. package/dist/psql/core/sqlHelp.js +1066 -0
  81. package/dist/psql/core/startup.js +846 -0
  82. package/dist/psql/core/syncVars.js +116 -0
  83. package/dist/psql/core/variables.js +287 -0
  84. package/dist/psql/describe/formatters.js +1290 -0
  85. package/dist/psql/describe/processNamePattern.js +270 -0
  86. package/dist/psql/describe/queries.js +2378 -0
  87. package/dist/psql/describe/versionGate.js +43 -0
  88. package/dist/psql/index.js +2030 -0
  89. package/dist/psql/io/history.js +299 -0
  90. package/dist/psql/io/input.js +120 -0
  91. package/dist/psql/io/lineEditor/buffer.js +325 -0
  92. package/dist/psql/io/lineEditor/complete.js +227 -0
  93. package/dist/psql/io/lineEditor/filename.js +159 -0
  94. package/dist/psql/io/lineEditor/index.js +894 -0
  95. package/dist/psql/io/lineEditor/keymap.js +745 -0
  96. package/dist/psql/io/lineEditor/vt100.js +363 -0
  97. package/dist/psql/io/pgpass.js +202 -0
  98. package/dist/psql/io/pgservice.js +194 -0
  99. package/dist/psql/io/psqlrc.js +422 -0
  100. package/dist/psql/print/aligned.js +1765 -0
  101. package/dist/psql/print/asciidoc.js +248 -0
  102. package/dist/psql/print/crosstab.js +463 -0
  103. package/dist/psql/print/csv.js +95 -0
  104. package/dist/psql/print/html.js +258 -0
  105. package/dist/psql/print/json.js +96 -0
  106. package/dist/psql/print/latex.js +396 -0
  107. package/dist/psql/print/pager.js +267 -0
  108. package/dist/psql/print/troff.js +258 -0
  109. package/dist/psql/print/unaligned.js +119 -0
  110. package/dist/psql/print/units.js +135 -0
  111. package/dist/psql/scanner/slash.js +515 -0
  112. package/dist/psql/scanner/sql.js +914 -0
  113. package/dist/psql/scanner/stringutils.js +394 -0
  114. package/dist/psql/types/backslash.js +1 -0
  115. package/dist/psql/types/connection.js +1 -0
  116. package/dist/psql/types/index.js +7 -0
  117. package/dist/psql/types/printer.js +1 -0
  118. package/dist/psql/types/repl.js +1 -0
  119. package/dist/psql/types/scanner.js +24 -0
  120. package/dist/psql/types/settings.js +1 -0
  121. package/dist/psql/types/variables.js +1 -0
  122. package/dist/psql/wire/connection.js +2858 -0
  123. package/dist/psql/wire/copy.js +108 -0
  124. package/dist/psql/wire/notify.js +59 -0
  125. package/dist/psql/wire/pipeline.js +521 -0
  126. package/dist/psql/wire/protocol.js +466 -0
  127. package/dist/psql/wire/sasl.js +296 -0
  128. package/dist/psql/wire/tls.js +602 -0
  129. package/dist/storage_api.js +147 -0
  130. package/dist/test_utils/fixtures.js +122 -0
  131. package/dist/test_utils/oauth_server.js +9 -0
  132. package/dist/types.js +1 -0
  133. package/dist/utils/api_enums.js +33 -0
  134. package/dist/utils/auth.js +5 -0
  135. package/dist/utils/branch_notice.js +22 -0
  136. package/dist/utils/branch_picker.js +103 -0
  137. package/dist/utils/compute_units.js +28 -0
  138. package/dist/utils/enrichers.js +161 -0
  139. package/dist/utils/esbuild.js +158 -0
  140. package/dist/utils/formats.js +18 -0
  141. package/dist/utils/middlewares.js +20 -0
  142. package/dist/utils/package_manager.js +68 -0
  143. package/dist/utils/point_in_time.js +56 -0
  144. package/dist/utils/psql.js +120 -0
  145. package/dist/utils/string.js +5 -0
  146. package/dist/utils/ui.js +59 -0
  147. package/dist/utils/zip.js +4 -0
  148. package/dist/writer.js +97 -0
  149. package/package.json +117 -14
  150. package/.jshintrc +0 -3
  151. package/CHANGELOG.markdown +0 -22
  152. package/LICENSE +0 -21
  153. package/README.markdown +0 -76
  154. package/bower.json +0 -23
  155. package/license.txt +0 -9
  156. package/neon.js +0 -234
  157. package/stdlib/bubbling_support.js +0 -32
  158. package/stdlib/custom_event.js +0 -54
  159. package/stdlib/custom_event_support.js +0 -195
  160. package/stdlib/index.js +0 -7
  161. package/stdlib/node_support.js +0 -123
  162. package/stdlib/widget.js +0 -340
  163. package/test/neon_browser.html +0 -11
  164. package/test/neon_stdlib_browser.html +0 -15
  165. package/test/neon_stdlib_test.js +0 -84
  166. package/test/neon_test.js +0 -64
@@ -0,0 +1,158 @@
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
+ };
@@ -0,0 +1,18 @@
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
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * This middleware is needed to fill in the args for nested objects,
3
+ * so that required arguments would work
4
+ * otherwise yargs just throws an error
5
+ */
6
+ export const fillInArgs = (args, currentArgs = args, acc = []) => {
7
+ Object.entries(currentArgs).forEach(([k, v]) => {
8
+ if (k === "_" || k === "--") {
9
+ return;
10
+ }
11
+ // check if the value is an Object
12
+ if (typeof v === "object" && v !== null) {
13
+ fillInArgs(args, v, [...acc, k]);
14
+ }
15
+ else if (acc.length > 0) {
16
+ // if it's not an object, and we have a path, fill it in
17
+ args[acc.join(".") + "." + k] = v;
18
+ }
19
+ });
20
+ };
@@ -0,0 +1,68 @@
1
+ import { spawn } from "node:child_process";
2
+ import which from "which";
3
+ import { log } from "../log.js";
4
+ // npm first so it's the default/preselected choice; the rest follow in rough
5
+ // popularity order.
6
+ export const PACKAGE_MANAGERS = [
7
+ "npm",
8
+ "pnpm",
9
+ "yarn",
10
+ "bun",
11
+ ];
12
+ /**
13
+ * The package manager the CLI was invoked through, read from the
14
+ * `npm_config_user_agent` npm sets for `npm exec`/`npx`, `pnpm dlx`, `yarn
15
+ * dlx`, and `bunx` (so `pnpm dlx neonctl …` installs with pnpm). Returns
16
+ * undefined when there's nothing to infer from — e.g. a globally-installed
17
+ * `neon`/`neonctl` — so the caller can ask (or fall back) instead of silently
18
+ * assuming npm.
19
+ */
20
+ export const detectPackageManager = () => {
21
+ const ua = process.env.npm_config_user_agent ?? "";
22
+ if (ua.startsWith("pnpm"))
23
+ return "pnpm";
24
+ if (ua.startsWith("yarn"))
25
+ return "yarn";
26
+ if (ua.startsWith("bun"))
27
+ return "bun";
28
+ if (ua.startsWith("npm"))
29
+ return "npm";
30
+ return undefined;
31
+ };
32
+ /** The package managers actually on PATH, in {@link PACKAGE_MANAGERS} order. */
33
+ export const installedPackageManagers = () => PACKAGE_MANAGERS.filter((pm) => which.sync(pm, { nothrow: true }) !== null);
34
+ /**
35
+ * Pick a package manager without prompting: the one the CLI was invoked through,
36
+ * else the first one installed, else npm. Used by non-interactive flows (e.g.
37
+ * `config init`) where there's no scaffold prompt to hang a picker off.
38
+ */
39
+ export const resolvePackageManager = () => detectPackageManager() ?? installedPackageManagers()[0] ?? "npm";
40
+ /**
41
+ * The argv that adds `packages` as runtime dependencies with `pm`. npm spells it
42
+ * `install`; pnpm/yarn/bun use `add`.
43
+ */
44
+ export const addDependenciesArgs = (pm, packages) => (pm === "npm" ? ["install", ...packages] : ["add", ...packages]);
45
+ /**
46
+ * Run a command inheriting our stdio so the user sees install / link output
47
+ * live and can answer any prompts the child raises. Resolves to whether it
48
+ * exited cleanly; a non-zero exit is reported but never throws — the caller
49
+ * decides whether to treat it as fatal.
50
+ */
51
+ export const runCommand = (cmd, args, cwd) => new Promise((resolvePromise) => {
52
+ // npm/pnpm/yarn ship as .cmd shims on Windows, which need a shell to run.
53
+ const child = spawn(cmd, args, {
54
+ cwd,
55
+ stdio: "inherit",
56
+ shell: process.platform === "win32",
57
+ });
58
+ child.on("error", (err) => {
59
+ log.warning("Could not run `%s %s`: %s", cmd, args.join(" "), err instanceof Error ? err.message : String(err));
60
+ resolvePromise(false);
61
+ });
62
+ child.on("close", (code) => {
63
+ if (code !== 0) {
64
+ log.warning("`%s %s` exited with code %d.", cmd, args.join(" "), code);
65
+ }
66
+ resolvePromise(code === 0);
67
+ });
68
+ });
@@ -0,0 +1,56 @@
1
+ import { branchIdResolve } from "./enrichers.js";
2
+ import { looksLikeLSN, looksLikeTimestamp } from "./formats.js";
3
+ export class PointInTimeParseError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "PointInTimeParseError";
7
+ }
8
+ }
9
+ export const parsePITBranch = (input) => {
10
+ const splitIndex = input.lastIndexOf("@");
11
+ const sourceBranch = splitIndex === -1 ? input : input.slice(0, splitIndex);
12
+ const exactPIT = splitIndex === -1 ? null : input.slice(splitIndex + 1);
13
+ const result = {
14
+ branch: sourceBranch,
15
+ ...(exactPIT === null
16
+ ? { tag: "head" }
17
+ : looksLikeLSN(exactPIT)
18
+ ? { tag: "lsn", lsn: exactPIT }
19
+ : { tag: "timestamp", timestamp: exactPIT }),
20
+ };
21
+ if (result.tag === "timestamp") {
22
+ const timestamp = result.timestamp;
23
+ if (!looksLikeTimestamp(timestamp)) {
24
+ throw new PointInTimeParseError(`Invalid source branch format - ${input}`);
25
+ }
26
+ if (Date.parse(timestamp) > Date.now()) {
27
+ throw new PointInTimeParseError(`Timestamp can not be in future - ${input}`);
28
+ }
29
+ }
30
+ return result;
31
+ };
32
+ export const parsePointInTime = async ({ pointInTime, targetBranchId, projectId, api, }) => {
33
+ const parsedPIT = parsePITBranch(pointInTime);
34
+ let branchId = "";
35
+ if (parsedPIT.branch === "^self") {
36
+ branchId = targetBranchId;
37
+ }
38
+ else if (parsedPIT.branch === "^parent") {
39
+ const { data } = await api.getProjectBranch(projectId, targetBranchId);
40
+ const { parent_id: parentId } = data.branch;
41
+ if (parentId == null) {
42
+ throw new PointInTimeParseError("Branch has no parent");
43
+ }
44
+ branchId = parentId;
45
+ }
46
+ else {
47
+ branchId = await branchIdResolve({
48
+ branch: parsedPIT.branch,
49
+ projectId,
50
+ apiClient: api,
51
+ });
52
+ }
53
+ // @ts-expect-error extracting pit from parsedPIT
54
+ delete parsedPIT.branch;
55
+ return { ...parsedPIT, branchId };
56
+ };
@@ -0,0 +1,120 @@
1
+ import { spawn } from "child_process";
2
+ import which from "which";
3
+ import { closeAnalytics, trackEvent } from "../analytics.js";
4
+ import { log } from "../log.js";
5
+ const FALLBACK_ENV = "NEONCTL_PSQL_FALLBACK";
6
+ /** Max time we wait for the analytics flush before handing off to psql. */
7
+ const ANALYTICS_FLUSH_TIMEOUT_MS = 3000;
8
+ /**
9
+ * Decide which psql implementation will run, and why. The PATH probe is
10
+ * skipped when TS is forced (flag or env) — we don't need it and it'd be a
11
+ * wasted lookup — so `nativeAvailable` is `null` ("not checked") in those
12
+ * cases rather than a misleading `false`.
13
+ */
14
+ const planPsql = async (opts) => {
15
+ if (opts.mode === "ts") {
16
+ return {
17
+ implementation: "ts",
18
+ reason: "forced_flag",
19
+ nativeAvailable: null,
20
+ nativePath: null,
21
+ };
22
+ }
23
+ if (process.env[FALLBACK_ENV] === "1") {
24
+ return {
25
+ implementation: "ts",
26
+ reason: "forced_env",
27
+ nativeAvailable: null,
28
+ nativePath: null,
29
+ };
30
+ }
31
+ const nativePath = await which("psql", { nothrow: true });
32
+ const nativeAvailable = nativePath !== null;
33
+ if (opts.mode === "native") {
34
+ return {
35
+ implementation: "native",
36
+ reason: "forced_native",
37
+ nativeAvailable,
38
+ nativePath,
39
+ };
40
+ }
41
+ // 'auto' (or unset): strict fallback — prefer native, TS only if missing.
42
+ return nativeAvailable
43
+ ? {
44
+ implementation: "native",
45
+ reason: "native_available",
46
+ nativeAvailable,
47
+ nativePath,
48
+ }
49
+ : {
50
+ implementation: "ts",
51
+ reason: "fallback_no_native",
52
+ nativeAvailable,
53
+ nativePath,
54
+ };
55
+ };
56
+ /**
57
+ * Record which psql implementation is about to run, then flush analytics.
58
+ *
59
+ * Both exec paths below call `process.exit()`, which short-circuits the
60
+ * main loop's `closeAnalytics()` — so without flushing here the event (and
61
+ * any earlier queued events, e.g. `CLI Started`) would be dropped. The
62
+ * flush is bounded by {@link ANALYTICS_FLUSH_TIMEOUT_MS} so a slow or
63
+ * unreachable analytics endpoint can't stall the psql launch. No-ops when
64
+ * analytics is disabled (`--analytics false`), since the client is absent.
65
+ */
66
+ const reportPsqlInvocation = async (plan) => {
67
+ trackEvent("psql_invoked", {
68
+ implementation: plan.implementation,
69
+ reason: plan.reason,
70
+ nativeAvailable: plan.nativeAvailable,
71
+ });
72
+ await closeAnalytics({ timeout: ANALYTICS_FLUSH_TIMEOUT_MS });
73
+ };
74
+ const execNative = async (binary, connection_uri, args) => {
75
+ log.info("Connecting to the database using psql...");
76
+ const child = spawn(binary, [connection_uri, ...args], {
77
+ stdio: "inherit",
78
+ });
79
+ for (const signame of ["SIGINT", "SIGTERM"]) {
80
+ process.on(signame, (code) => {
81
+ if (!child.killed && code !== null) {
82
+ child.kill(code);
83
+ }
84
+ });
85
+ }
86
+ return new Promise((_, reject) => {
87
+ child.on("exit", (code) => {
88
+ process.exit(code === null ? 1 : code);
89
+ });
90
+ child.on("error", reject);
91
+ });
92
+ };
93
+ const execTs = async (connection_uri, args) => {
94
+ log.info("Connecting to the database using embedded psql (TypeScript)...");
95
+ const { runPsql } = await import("../psql/index.js");
96
+ const code = await runPsql([connection_uri, ...args], {
97
+ stdin: process.stdin,
98
+ stdout: process.stdout,
99
+ stderr: process.stderr,
100
+ });
101
+ process.exit(code);
102
+ };
103
+ export const psql = async (connection_uri, args = [], opts = {}) => {
104
+ const plan = await planPsql(opts);
105
+ await reportPsqlInvocation(plan);
106
+ if (plan.implementation === "ts") {
107
+ if (plan.reason === "fallback_no_native") {
108
+ log.info("psql binary not found on PATH; falling back to embedded TypeScript psql");
109
+ }
110
+ return execTs(connection_uri, args);
111
+ }
112
+ // implementation === 'native'
113
+ if (plan.nativePath === null) {
114
+ // Only reachable when native was explicitly requested (mode: 'native')
115
+ // but no binary is on PATH.
116
+ log.error(`psql is not available in the PATH`);
117
+ process.exit(1);
118
+ }
119
+ return execNative(plan.nativePath, connection_uri, args);
120
+ };
@@ -0,0 +1,5 @@
1
+ export const toSnakeCase = (str) => str
2
+ .split(" ")
3
+ .map((word) => word.toLowerCase())
4
+ .join("_");
5
+ export const isObject = (value) => value != null && value === Object(value);
@@ -0,0 +1,59 @@
1
+ // returns the next string if matches the given matcher,
2
+ // otherwise returns null
3
+ // consumes the line from the lines array
4
+ export const consumeNextMatching = (lines, matcher) => {
5
+ while (lines.length > 0) {
6
+ const line = lines.shift().trim();
7
+ if (line === "") {
8
+ continue;
9
+ }
10
+ if (matcher.test(line)) {
11
+ return line;
12
+ }
13
+ return null;
14
+ }
15
+ return null;
16
+ };
17
+ // returns strings if next non-empty line matches the given matcher,
18
+ // otherwise returns empty array
19
+ // consumes the lines from the lines array
20
+ export const consumeBlockIfMatches = (lines, matcher) => {
21
+ const result = [];
22
+ if (lines.length === 0) {
23
+ return result;
24
+ }
25
+ let line = lines.shift();
26
+ while (line.trim() === "") {
27
+ line = lines.shift();
28
+ }
29
+ if (!matcher.test(line)) {
30
+ lines.unshift(line);
31
+ return result;
32
+ }
33
+ result.push(line);
34
+ while (lines.length > 0) {
35
+ line = lines.shift();
36
+ if (line.trim() === "") {
37
+ break;
38
+ }
39
+ result.push(line);
40
+ }
41
+ return result;
42
+ };
43
+ export const splitColumns = (line) => {
44
+ const result = line.trim().split(/\s{2,}/);
45
+ result[1] = result[1] ?? "";
46
+ if (result.length > 2) {
47
+ result[1] = result.slice(1).join(" ");
48
+ }
49
+ return result;
50
+ };
51
+ export const drawPointer = (width) => {
52
+ const result = [];
53
+ result.push("└");
54
+ for (let i = 0; i < width - 4; i++) {
55
+ result.push("─");
56
+ }
57
+ result.push(">");
58
+ return result.join("");
59
+ };
@@ -0,0 +1,4 @@
1
+ import { zipSync } from "fflate";
2
+ // Zip the esbuild output (index.mjs) into the archive the Functions deploy endpoint
3
+ // expects. Compression level 6 matches the previous bundler.
4
+ export const zipBundle = (entries) => zipSync(entries, { level: 6 });
package/dist/writer.js ADDED
@@ -0,0 +1,97 @@
1
+ import chalk from "chalk";
2
+ import Table from "cli-table";
3
+ import YAML from "yaml";
4
+ import { isCi } from "./env.js";
5
+ import { isObject, toSnakeCase } from "./utils/string.js";
6
+ const writeYaml = (chunks) => {
7
+ return YAML.stringify(chunks.length === 1
8
+ ? chunks[0].data
9
+ : Object.fromEntries(chunks.map(({ config, data }, idx) => [
10
+ config.title ? toSnakeCase(config.title) : idx,
11
+ data,
12
+ ])), null, 2);
13
+ };
14
+ const writeJson = (chunks) => {
15
+ return JSON.stringify(chunks.length === 1
16
+ ? chunks[0].data
17
+ : Object.fromEntries(chunks.map(({ config, data }, idx) => [
18
+ config.title ? toSnakeCase(config.title) : idx,
19
+ data,
20
+ ])), null, 2);
21
+ };
22
+ const writeTable = (chunks, out) => {
23
+ chunks.forEach(({ data, config: { emptyMessage, fields, title, renderColumns = {} }, }) => {
24
+ const arrayData = Array.isArray(data) ? data : [data];
25
+ if (!arrayData.length && emptyMessage) {
26
+ out.write("\n" + emptyMessage + "\n");
27
+ return;
28
+ }
29
+ const fieldsFiltered = fields.filter((field) => arrayData.some((item) => item[field] !== undefined && item[field] !== ""));
30
+ const table = new Table({
31
+ style: {
32
+ head: ["green"],
33
+ },
34
+ head: fieldsFiltered.map((field) => field
35
+ .split("_")
36
+ .map((word) => word[0].toUpperCase() + word.slice(1))
37
+ .join(" ")),
38
+ });
39
+ arrayData.forEach((item) => {
40
+ table.push(fieldsFiltered.map((field) => {
41
+ const value = item[field];
42
+ if (renderColumns[field]) {
43
+ return renderColumns[field]?.(item);
44
+ }
45
+ return Array.isArray(value)
46
+ ? value.join("\n")
47
+ : isObject(value)
48
+ ? JSON.stringify(value, null, 2)
49
+ : (value ?? "");
50
+ }));
51
+ });
52
+ if (title) {
53
+ out.write((isCi() ? title : chalk.bold(title)) + "\n");
54
+ }
55
+ out.write(table.toString());
56
+ out.write("\n");
57
+ });
58
+ };
59
+ /**
60
+ *
61
+ * Parses the output format, takes data and writes the output to stdout.
62
+ *
63
+ * @example
64
+ * const { data } = await props.apiClient.listProjectBranches(props.project.id);
65
+ * // to output single data
66
+ * writer(props).end(data, { fields: ['id', 'name', 'created_at'] })
67
+ * // to output multiple data
68
+ * writer(props)
69
+ * .write(data, { fields: ['id', 'name', 'created_at'], title: 'branches' })
70
+ * .write(data, { fields: ['id', 'created_at'], title: 'endpoints' })
71
+ * .end()
72
+ */
73
+ export const writer = (props) => {
74
+ const out = props.out ?? process.stdout;
75
+ const chunks = [];
76
+ return {
77
+ write(data, config) {
78
+ chunks.push({ data, config });
79
+ return this;
80
+ },
81
+ text(data) {
82
+ return out.write(data);
83
+ },
84
+ end: (...args) => {
85
+ if (args.length === 2) {
86
+ chunks.push({ data: args[0], config: args[1] });
87
+ }
88
+ if (props.output == "yaml") {
89
+ return out.write(writeYaml(chunks));
90
+ }
91
+ if (props.output == "json") {
92
+ return out.write(writeJson(chunks));
93
+ }
94
+ writeTable(chunks, out);
95
+ },
96
+ };
97
+ };