neonctl 2.28.0 → 2.29.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.
- package/README.md +2 -2
- package/dist/analytics.js +35 -33
- package/dist/api.js +34 -34
- package/dist/auth.js +50 -44
- package/dist/cli.js +2 -2
- package/dist/commands/auth.js +58 -52
- package/dist/commands/bootstrap.js +115 -157
- package/dist/commands/branches.js +154 -147
- package/dist/commands/bucket.js +124 -118
- package/dist/commands/checkout.js +49 -49
- package/dist/commands/config.js +212 -88
- package/dist/commands/connection_string.js +62 -62
- package/dist/commands/data_api.js +96 -96
- package/dist/commands/databases.js +23 -23
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/dev.js +114 -114
- package/dist/commands/env.js +43 -43
- package/dist/commands/functions.js +97 -98
- package/dist/commands/index.js +26 -26
- package/dist/commands/init.js +23 -22
- package/dist/commands/ip_allow.js +29 -29
- package/dist/commands/link.js +223 -166
- package/dist/commands/neon_auth.js +381 -363
- package/dist/commands/operations.js +11 -11
- package/dist/commands/orgs.js +8 -8
- package/dist/commands/projects.js +101 -99
- package/dist/commands/psql.js +31 -31
- package/dist/commands/roles.js +21 -21
- package/dist/commands/schema_diff.js +23 -23
- package/dist/commands/set_context.js +17 -17
- package/dist/commands/status.js +17 -17
- package/dist/commands/user.js +5 -5
- package/dist/commands/vpc_endpoints.js +50 -50
- package/dist/config.js +7 -7
- package/dist/config_format.js +5 -5
- package/dist/context.js +23 -16
- package/dist/current_branch_fast_path.js +6 -6
- package/dist/dev/env.js +33 -33
- package/dist/dev/functions.js +4 -4
- package/dist/dev/inputs.js +6 -6
- package/dist/dev/runtime.js +25 -25
- package/dist/env.js +14 -14
- package/dist/env_file.js +13 -13
- package/dist/errors.js +19 -19
- package/dist/functions_api.js +10 -10
- package/dist/help.js +15 -15
- package/dist/index.js +94 -92
- package/dist/log.js +2 -2
- package/dist/pkg.js +5 -5
- package/dist/psql/cli.js +4 -2
- package/dist/psql/command/cmd_cond.js +61 -61
- package/dist/psql/command/cmd_connect.js +159 -154
- package/dist/psql/command/cmd_copy.js +107 -97
- package/dist/psql/command/cmd_describe.js +368 -363
- package/dist/psql/command/cmd_format.js +276 -263
- package/dist/psql/command/cmd_io.js +269 -263
- package/dist/psql/command/cmd_lo.js +74 -66
- package/dist/psql/command/cmd_meta.js +148 -148
- package/dist/psql/command/cmd_misc.js +17 -17
- package/dist/psql/command/cmd_pipeline.js +142 -135
- package/dist/psql/command/cmd_restrict.js +25 -25
- package/dist/psql/command/cmd_show.js +183 -168
- package/dist/psql/command/dispatch.js +26 -26
- package/dist/psql/command/shared.js +14 -14
- package/dist/psql/complete/filenames.js +16 -16
- package/dist/psql/complete/index.js +4 -4
- package/dist/psql/complete/matcher.js +33 -32
- package/dist/psql/complete/psqlVars.js +173 -173
- package/dist/psql/complete/queries.js +5 -3
- package/dist/psql/complete/rules.js +900 -863
- package/dist/psql/core/common.js +136 -133
- package/dist/psql/core/help.js +343 -343
- package/dist/psql/core/mainloop.js +160 -153
- package/dist/psql/core/prompt.js +126 -123
- package/dist/psql/core/settings.js +111 -111
- package/dist/psql/core/sqlHelp.js +150 -150
- package/dist/psql/core/startup.js +211 -205
- package/dist/psql/core/syncVars.js +14 -14
- package/dist/psql/core/variables.js +24 -24
- package/dist/psql/describe/formatters.js +302 -289
- package/dist/psql/describe/processNamePattern.js +28 -28
- package/dist/psql/describe/queries.js +656 -651
- package/dist/psql/index.js +436 -411
- package/dist/psql/io/history.js +36 -36
- package/dist/psql/io/input.js +15 -15
- package/dist/psql/io/lineEditor/buffer.js +27 -25
- package/dist/psql/io/lineEditor/complete.js +15 -15
- package/dist/psql/io/lineEditor/filename.js +22 -22
- package/dist/psql/io/lineEditor/index.js +65 -62
- package/dist/psql/io/lineEditor/keymap.js +325 -318
- package/dist/psql/io/lineEditor/vt100.js +60 -60
- package/dist/psql/io/pgpass.js +18 -18
- package/dist/psql/io/pgservice.js +14 -14
- package/dist/psql/io/psqlrc.js +46 -46
- package/dist/psql/print/aligned.js +175 -166
- package/dist/psql/print/asciidoc.js +51 -51
- package/dist/psql/print/crosstab.js +34 -31
- package/dist/psql/print/csv.js +25 -22
- package/dist/psql/print/html.js +54 -54
- package/dist/psql/print/json.js +12 -12
- package/dist/psql/print/latex.js +118 -118
- package/dist/psql/print/pager.js +28 -26
- package/dist/psql/print/troff.js +48 -48
- package/dist/psql/print/unaligned.js +15 -14
- package/dist/psql/print/units.js +17 -17
- package/dist/psql/scanner/slash.js +48 -46
- package/dist/psql/scanner/sql.js +88 -84
- package/dist/psql/scanner/stringutils.js +21 -17
- package/dist/psql/types/index.js +7 -7
- package/dist/psql/types/scanner.js +8 -8
- package/dist/psql/wire/connection.js +341 -327
- package/dist/psql/wire/copy.js +7 -7
- package/dist/psql/wire/pipeline.js +26 -24
- package/dist/psql/wire/protocol.js +102 -102
- package/dist/psql/wire/sasl.js +62 -62
- package/dist/psql/wire/tls.js +79 -73
- package/dist/storage_api.js +15 -15
- package/dist/test_utils/fixtures.js +34 -31
- package/dist/test_utils/oauth_server.js +5 -5
- package/dist/utils/api_enums.js +13 -13
- package/dist/utils/branch_notice.js +5 -5
- package/dist/utils/branch_picker.js +26 -26
- package/dist/utils/compute_units.js +4 -4
- package/dist/utils/enrichers.js +20 -15
- package/dist/utils/esbuild.js +28 -28
- package/dist/utils/formats.js +1 -1
- package/dist/utils/middlewares.js +3 -3
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +12 -12
- package/dist/utils/psql.js +30 -30
- package/dist/utils/string.js +2 -2
- package/dist/utils/ui.js +9 -9
- package/dist/utils/zip.js +1 -1
- package/dist/writer.js +17 -17
- package/package.json +6 -7
package/dist/dev/env.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { loadConfigFromFile
|
|
2
|
-
import { plan, pullConfig
|
|
3
|
-
import { fetchEnv, toEntries } from
|
|
4
|
-
import { log } from
|
|
1
|
+
import { loadConfigFromFile } from "@neon/config";
|
|
2
|
+
import { plan, pullConfig } from "@neon/config-runtime";
|
|
3
|
+
import { fetchEnv, toEntries } from "@neon/env";
|
|
4
|
+
import { log } from "../log.js";
|
|
5
5
|
/** The API-targeting options every runtime call forwards from the context. */
|
|
6
6
|
const apiOptions = (ctx) => ({
|
|
7
7
|
...(ctx.apiKey ? { apiKey: ctx.apiKey } : {}),
|
|
@@ -19,7 +19,7 @@ const apiOptions = (ctx) => ({
|
|
|
19
19
|
export class DevEnvMismatchError extends Error {
|
|
20
20
|
constructor() {
|
|
21
21
|
super(...arguments);
|
|
22
|
-
this.name =
|
|
22
|
+
this.name = "DevEnvMismatchError";
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
@@ -30,7 +30,7 @@ export class DevEnvMismatchError extends Error {
|
|
|
30
30
|
export class MissingBranchContextError extends Error {
|
|
31
31
|
constructor() {
|
|
32
32
|
super(...arguments);
|
|
33
|
-
this.name =
|
|
33
|
+
this.name = "MissingBranchContextError";
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
@@ -56,9 +56,9 @@ export const resolveNeonEnvVars = async (ctx) => {
|
|
|
56
56
|
const config = await loadNeonConfig(ctx.cwd);
|
|
57
57
|
if (config) {
|
|
58
58
|
if (!ctx.projectId || !ctx.branchId) {
|
|
59
|
-
throw new MissingBranchContextError(
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
throw new MissingBranchContextError("Found a neon.ts but could not resolve the project/branch. " +
|
|
60
|
+
"Run `neonctl link` and `neonctl checkout <branch>`, or pass " +
|
|
61
|
+
"--project-id / --branch.");
|
|
62
62
|
}
|
|
63
63
|
// Resolve env from the policy with its `preview.functions` removed. Functions carry no
|
|
64
64
|
// branch-level secrets — their env comes from the local `neon.ts` `functions.<slug>.env`,
|
|
@@ -82,8 +82,8 @@ export const resolveNeonEnvVars = async (ctx) => {
|
|
|
82
82
|
// tuning closure), so it feeds straight into fetchEnv — no wrapping needed.
|
|
83
83
|
return await fetchAndProject(pulled.config, ctx);
|
|
84
84
|
}
|
|
85
|
-
throw new MissingBranchContextError(
|
|
86
|
-
|
|
85
|
+
throw new MissingBranchContextError("No project/branch context found. Link a branch (`neonctl link` / " +
|
|
86
|
+
"`neonctl checkout`) or pass --project-id and --branch.");
|
|
87
87
|
};
|
|
88
88
|
/**
|
|
89
89
|
* `neon dev`'s env resolver: {@link resolveNeonEnvVars} with graceful degradation.
|
|
@@ -104,17 +104,17 @@ export const resolveDevEnv = async (ctx) => {
|
|
|
104
104
|
if (err instanceof DevEnvMismatchError)
|
|
105
105
|
throw err;
|
|
106
106
|
if (err instanceof MissingBranchContextError) {
|
|
107
|
-
log.debug(
|
|
107
|
+
log.debug("dev: %s; skipping env injection", err.message);
|
|
108
108
|
return {
|
|
109
109
|
vars: {},
|
|
110
110
|
skipped: {
|
|
111
|
-
reason:
|
|
112
|
-
|
|
111
|
+
reason: "no linked Neon branch — run `neonctl link`, then " +
|
|
112
|
+
"`neonctl checkout <branch>`, to inject DATABASE_URL and friends",
|
|
113
113
|
},
|
|
114
114
|
};
|
|
115
115
|
}
|
|
116
116
|
const detail = err instanceof Error ? err.message : String(err);
|
|
117
|
-
log.debug(
|
|
117
|
+
log.debug("dev: env resolution failed: %s", detail);
|
|
118
118
|
return {
|
|
119
119
|
vars: {},
|
|
120
120
|
skipped: {
|
|
@@ -159,11 +159,11 @@ const assertPolicyMatchesBranch = async (config, ctx) => {
|
|
|
159
159
|
const missing = result.applied.filter(isMissingResource);
|
|
160
160
|
if (missing.length === 0)
|
|
161
161
|
return;
|
|
162
|
-
const names = missing.map((change) => change.identifier).join(
|
|
162
|
+
const names = missing.map((change) => change.identifier).join(", ");
|
|
163
163
|
throw new DevEnvMismatchError(`Your neon.ts declares ${names} for branch ${ctx.branchId}, but the branch ` +
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
"does not have it yet, so the matching env vars cannot be injected. " +
|
|
165
|
+
"Provision it first with `neonctl deploy` (or `neonctl config apply`), " +
|
|
166
|
+
"then re-run `neonctl dev`.");
|
|
167
167
|
};
|
|
168
168
|
/**
|
|
169
169
|
* A planned change that provisions a branch-level resource the branch lacks: a
|
|
@@ -171,9 +171,9 @@ const assertPolicyMatchesBranch = async (config, ctx) => {
|
|
|
171
171
|
* setting drift (`update`) and `noop`s are ignored — they don't block local dev
|
|
172
172
|
* — and functions are excluded (see {@link assertPolicyMatchesBranch}).
|
|
173
173
|
*/
|
|
174
|
-
const isMissingResource = (change) => change.kind ===
|
|
175
|
-
change.action ===
|
|
176
|
-
!change.identifier.startsWith(
|
|
174
|
+
const isMissingResource = (change) => change.kind === "service" &&
|
|
175
|
+
change.action === "create" &&
|
|
176
|
+
!change.identifier.startsWith("function:");
|
|
177
177
|
const fetchAndProject = async (config, ctx) => {
|
|
178
178
|
const env = await fetchEnv(config, {
|
|
179
179
|
projectId: ctx.projectId,
|
|
@@ -191,17 +191,17 @@ const fetchAndProject = async (config, ctx) => {
|
|
|
191
191
|
/**
|
|
192
192
|
* Substrings that mark a module-resolution failure while loading `neon.ts` —
|
|
193
193
|
* almost always because the project's dependencies aren't installed yet (the
|
|
194
|
-
* config imports `@
|
|
194
|
+
* config imports `@neon/config` & friends). Deliberately specific:
|
|
195
195
|
* the generic "…or a missing dependency…" hint the loader always appends is
|
|
196
196
|
* NOT in here, so a real syntax/runtime error doesn't get mislabeled.
|
|
197
197
|
*/
|
|
198
198
|
const MISSING_DEPENDENCY_HINTS = [
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
199
|
+
"cannot find module",
|
|
200
|
+
"cannot find package",
|
|
201
|
+
"err_module_not_found",
|
|
202
|
+
"failed to resolve",
|
|
203
|
+
"could not resolve",
|
|
204
|
+
"module not found",
|
|
205
205
|
];
|
|
206
206
|
/** Flatten an error and its `cause` chain to one lowercased string for matching. */
|
|
207
207
|
const errorChainText = (err) => {
|
|
@@ -211,7 +211,7 @@ const errorChainText = (err) => {
|
|
|
211
211
|
parts.push(current.message);
|
|
212
212
|
current = current.cause;
|
|
213
213
|
}
|
|
214
|
-
return parts.join(
|
|
214
|
+
return parts.join("\n").toLowerCase();
|
|
215
215
|
};
|
|
216
216
|
const looksLikeMissingDependency = (err) => {
|
|
217
217
|
const text = errorChainText(err);
|
|
@@ -230,9 +230,9 @@ const loadNeonConfig = async (cwd) => {
|
|
|
230
230
|
// A neon.ts that imports a package which isn't installed fails here with a
|
|
231
231
|
// cryptic "Cannot find module …". Turn that into the actionable thing to do.
|
|
232
232
|
if (looksLikeMissingDependency(err)) {
|
|
233
|
-
throw new Error(
|
|
234
|
-
|
|
235
|
-
|
|
233
|
+
throw new Error("Could not load neon.ts: a package it imports is not installed. " +
|
|
234
|
+
"Did you run `npm install`? Install your dependencies " +
|
|
235
|
+
"(npm / pnpm / yarn / bun), then try again.\n" +
|
|
236
236
|
`Original error: ${message}`);
|
|
237
237
|
}
|
|
238
238
|
throw err;
|
package/dist/dev/functions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { loadConfigFromFile, resolveConfig, } from
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { loadConfigFromFile, resolveConfig, } from "@neon/config";
|
|
4
4
|
/**
|
|
5
5
|
* Load `neon.ts` (if any) and resolve the list of functions it declares into
|
|
6
6
|
* {@link PlannedFunction}s for `neon dev` to serve. Returns `null` when there is no
|
|
@@ -16,7 +16,7 @@ export const resolveFunctionsFromConfig = async (cwd, branchName) => {
|
|
|
16
16
|
return null;
|
|
17
17
|
const { config, configDir, configPath } = loaded;
|
|
18
18
|
const resolved = resolveConfig(config, {
|
|
19
|
-
name: branchName ??
|
|
19
|
+
name: branchName ?? "local",
|
|
20
20
|
exists: branchName !== undefined,
|
|
21
21
|
});
|
|
22
22
|
const functions = resolved.preview?.functions ?? [];
|
package/dist/dev/inputs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolve } from
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
2
|
const defaultDeps = {
|
|
3
3
|
isPackaged: () => process.pkg !== undefined,
|
|
4
4
|
loadEsbuild: (name) => import(name),
|
|
@@ -25,7 +25,7 @@ export const resolveWatchInputs = async (source, deps = defaultDeps) => {
|
|
|
25
25
|
// 'esbuild', for the same reason as src/utils/esbuild.ts: rollup and
|
|
26
26
|
// @yao-pkg/pkg statically scan for literal import()/require() and would pull
|
|
27
27
|
// esbuild's native Go binary into the bundle/snapshot. Keep it invisible.
|
|
28
|
-
const name = [
|
|
28
|
+
const name = ["es", "build"].join("");
|
|
29
29
|
let esbuild;
|
|
30
30
|
try {
|
|
31
31
|
esbuild = await deps.loadEsbuild(name);
|
|
@@ -42,10 +42,10 @@ export const resolveWatchInputs = async (source, deps = defaultDeps) => {
|
|
|
42
42
|
bundle: true,
|
|
43
43
|
write: false,
|
|
44
44
|
metafile: true,
|
|
45
|
-
format:
|
|
46
|
-
platform:
|
|
47
|
-
packages:
|
|
48
|
-
logLevel:
|
|
45
|
+
format: "esm",
|
|
46
|
+
platform: "node",
|
|
47
|
+
packages: "external",
|
|
48
|
+
logLevel: "silent",
|
|
49
49
|
});
|
|
50
50
|
metafile = result.metafile;
|
|
51
51
|
}
|
package/dist/dev/runtime.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { createServer } from
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { getRequestListener } from
|
|
5
|
-
const isFunction = (value) => typeof value ===
|
|
6
|
-
const hasFetchMethod = (value) => typeof value ===
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { getRequestListener } from "@hono/node-server";
|
|
5
|
+
const isFunction = (value) => typeof value === "function";
|
|
6
|
+
const hasFetchMethod = (value) => typeof value === "object" &&
|
|
7
7
|
value !== null &&
|
|
8
|
-
|
|
9
|
-
typeof value.fetch ===
|
|
8
|
+
"fetch" in value &&
|
|
9
|
+
typeof value.fetch === "function";
|
|
10
10
|
/**
|
|
11
11
|
* Resolve the user's exported handler to a single fetch callback.
|
|
12
12
|
*
|
|
@@ -23,9 +23,9 @@ export const resolveFetchHandler = (mod) => {
|
|
|
23
23
|
if (isFunction(defaultExport)) {
|
|
24
24
|
return defaultExport;
|
|
25
25
|
}
|
|
26
|
-
throw new Error(
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
throw new Error("No request handler found in the source module. Export one of:\n" +
|
|
27
|
+
" export default { fetch(req) { /* ... */ } }\n" +
|
|
28
|
+
" export default function (req) { /* ... */ }");
|
|
29
29
|
};
|
|
30
30
|
/**
|
|
31
31
|
* Wrap a fetch handler so user errors become a 500 response (with the message
|
|
@@ -41,18 +41,18 @@ export const withErrorBoundary = (handler) => {
|
|
|
41
41
|
process.stderr.write(`Request handler threw an error:\n${message}\n`);
|
|
42
42
|
return new Response(`Internal Server Error\n\n${message}`, {
|
|
43
43
|
status: 500,
|
|
44
|
-
headers: {
|
|
44
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
48
|
};
|
|
49
|
-
const isAddressInUse = (err) => typeof err ===
|
|
49
|
+
const isAddressInUse = (err) => typeof err === "object" &&
|
|
50
50
|
err !== null &&
|
|
51
|
-
err.code ===
|
|
51
|
+
err.code === "EADDRINUSE";
|
|
52
52
|
const DEFAULT_SEARCH_BASE = 8787;
|
|
53
53
|
const MAX_SEARCH_STEPS = 100;
|
|
54
54
|
const bindPort = async (server, selection, hostname) => {
|
|
55
|
-
if (selection.mode ===
|
|
55
|
+
if (selection.mode === "explicit") {
|
|
56
56
|
return listen(server, selection.port, hostname);
|
|
57
57
|
}
|
|
58
58
|
for (let step = 0; step < MAX_SEARCH_STEPS; step++) {
|
|
@@ -68,15 +68,15 @@ const bindPort = async (server, selection, hostname) => {
|
|
|
68
68
|
};
|
|
69
69
|
const listen = (server, port, hostname) => new Promise((resolveListen, rejectListen) => {
|
|
70
70
|
const onError = (err) => {
|
|
71
|
-
server.off(
|
|
71
|
+
server.off("listening", onListening);
|
|
72
72
|
rejectListen(err);
|
|
73
73
|
};
|
|
74
74
|
const onListening = () => {
|
|
75
|
-
server.off(
|
|
75
|
+
server.off("error", onError);
|
|
76
76
|
resolveListen(server.address().port);
|
|
77
77
|
};
|
|
78
|
-
server.once(
|
|
79
|
-
server.once(
|
|
78
|
+
server.once("error", onError);
|
|
79
|
+
server.once("listening", onListening);
|
|
80
80
|
server.listen(port, hostname);
|
|
81
81
|
});
|
|
82
82
|
/**
|
|
@@ -106,16 +106,16 @@ export const startRuntime = async ({ source, port, hostname, }) => {
|
|
|
106
106
|
*/
|
|
107
107
|
export const portSelectionFromEnv = (env) => {
|
|
108
108
|
const explicit = env.NEON_DEV_PORT;
|
|
109
|
-
if (explicit !== undefined && explicit !==
|
|
110
|
-
return { mode:
|
|
109
|
+
if (explicit !== undefined && explicit !== "") {
|
|
110
|
+
return { mode: "explicit", port: parsePort(explicit, "NEON_DEV_PORT") };
|
|
111
111
|
}
|
|
112
112
|
const injected = env.PORT;
|
|
113
|
-
if (injected !== undefined && injected !==
|
|
114
|
-
return { mode:
|
|
113
|
+
if (injected !== undefined && injected !== "") {
|
|
114
|
+
return { mode: "explicit", port: parsePort(injected, "PORT") };
|
|
115
115
|
}
|
|
116
116
|
const base = Number(env.NEON_DEV_PORT_BASE ?? DEFAULT_SEARCH_BASE);
|
|
117
117
|
return {
|
|
118
|
-
mode:
|
|
118
|
+
mode: "search",
|
|
119
119
|
from: Number.isInteger(base) ? base : DEFAULT_SEARCH_BASE,
|
|
120
120
|
};
|
|
121
121
|
};
|
|
@@ -135,7 +135,7 @@ const isDirectExecution = () => {
|
|
|
135
135
|
if (isDirectExecution()) {
|
|
136
136
|
const source = process.env.NEON_DEV_SOURCE ?? process.argv[2];
|
|
137
137
|
if (!source) {
|
|
138
|
-
process.stderr.write(
|
|
138
|
+
process.stderr.write("neon-dev runtime: missing source path\n");
|
|
139
139
|
process.exit(1);
|
|
140
140
|
}
|
|
141
141
|
startRuntime({ source, port: portSelectionFromEnv(process.env) }).catch((err) => {
|
package/dist/env.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const isCi = () => {
|
|
2
|
-
return process.env.CI !==
|
|
2
|
+
return process.env.CI !== "false" && Boolean(process.env.CI);
|
|
3
3
|
};
|
|
4
4
|
export const isDebug = () => {
|
|
5
5
|
return Boolean(process.env.DEBUG);
|
|
@@ -7,27 +7,27 @@ export const isDebug = () => {
|
|
|
7
7
|
export const getGithubEnvVars = (env) => {
|
|
8
8
|
const vars = [
|
|
9
9
|
// github action info
|
|
10
|
-
|
|
10
|
+
"GITHUB_ACTION_PATH",
|
|
11
11
|
// source github repository
|
|
12
|
-
|
|
12
|
+
"GITHUB_REPOSITORY",
|
|
13
13
|
// environment info
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
"GITHUB_RUN_ID",
|
|
15
|
+
"GITHUB_RUN_NUMBER",
|
|
16
|
+
"GITHUB_SERVER_URL",
|
|
17
|
+
"GITHUB_WORKFLOW_REF",
|
|
18
|
+
"RUNNER_ARCH",
|
|
19
|
+
"RUNNER_ENVIRONMENT",
|
|
20
|
+
"RUNNER_OS",
|
|
21
21
|
];
|
|
22
22
|
const map = new Map();
|
|
23
23
|
vars.forEach((v) => {
|
|
24
24
|
let value = env[v];
|
|
25
|
-
if (value === undefined || value ===
|
|
25
|
+
if (value === undefined || value === "") {
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
-
if (v ===
|
|
29
|
-
value = value.includes(
|
|
30
|
-
? value.replace(/^.*actions\/(.+)$/,
|
|
28
|
+
if (v === "GITHUB_ACTION_PATH") {
|
|
29
|
+
value = value.includes("actions/")
|
|
30
|
+
? value.replace(/^.*actions\/(.+)$/, "$1")
|
|
31
31
|
: value;
|
|
32
32
|
}
|
|
33
33
|
map.set(v, value);
|
package/dist/env_file.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from
|
|
2
|
-
import { join } from
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
3
|
/**
|
|
4
4
|
* Default dotenv file `env pull` writes to: `.env` when one already exists in the working
|
|
5
5
|
* directory (update where secrets already live), otherwise `.env.local` — matching the
|
|
@@ -8,9 +8,9 @@ import { join } from 'node:path';
|
|
|
8
8
|
export const resolveEnvFilePath = (cwd, file) => {
|
|
9
9
|
if (file)
|
|
10
10
|
return join(cwd, file);
|
|
11
|
-
if (existsSync(join(cwd,
|
|
12
|
-
return join(cwd,
|
|
13
|
-
return join(cwd,
|
|
11
|
+
if (existsSync(join(cwd, ".env")))
|
|
12
|
+
return join(cwd, ".env");
|
|
13
|
+
return join(cwd, ".env.local");
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
16
16
|
* Merge `updates` into the dotenv content at `path`, preserving every other line
|
|
@@ -20,7 +20,7 @@ export const resolveEnvFilePath = (cwd, file) => {
|
|
|
20
20
|
* `updates` is removed. Returns the keys written and the (managed) keys removed.
|
|
21
21
|
*/
|
|
22
22
|
export const mergeEnvFile = (path, updates, options = {}) => {
|
|
23
|
-
const original = existsSync(path) ? readFileSync(path,
|
|
23
|
+
const original = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
24
24
|
const { content, written, removed } = mergeEnvContent(original, updates, options);
|
|
25
25
|
writeFileSync(path, content);
|
|
26
26
|
return { written, removed };
|
|
@@ -40,7 +40,7 @@ export const mergeEnvContent = (original, updates, options = {}) => {
|
|
|
40
40
|
}
|
|
41
41
|
const remaining = new Set(keys);
|
|
42
42
|
const removed = [];
|
|
43
|
-
const lines = original ===
|
|
43
|
+
const lines = original === "" ? [] : original.split("\n");
|
|
44
44
|
// Walk the file: drop stale owned lines, update existing keys in place (so their position
|
|
45
45
|
// and any surrounding comments are preserved), and pass everything else through untouched.
|
|
46
46
|
const updatedLines = [];
|
|
@@ -62,10 +62,10 @@ export const mergeEnvContent = (original, updates, options = {}) => {
|
|
|
62
62
|
.filter((key) => remaining.has(key))
|
|
63
63
|
.map((key) => formatLine(key, updates[key]));
|
|
64
64
|
const body = trimTrailingBlank(updatedLines);
|
|
65
|
-
const content = [...body, ...appended].join(
|
|
65
|
+
const content = [...body, ...appended].join("\n");
|
|
66
66
|
return {
|
|
67
67
|
// A dotenv file ends with a trailing newline.
|
|
68
|
-
content: content ===
|
|
68
|
+
content: content === "" ? "" : `${content}\n`,
|
|
69
69
|
written: keys,
|
|
70
70
|
removed,
|
|
71
71
|
};
|
|
@@ -82,7 +82,7 @@ export const readEnvFile = (path) => {
|
|
|
82
82
|
throw new Error(`Env file not found: ${path}`);
|
|
83
83
|
}
|
|
84
84
|
const out = {};
|
|
85
|
-
for (const line of readFileSync(path,
|
|
85
|
+
for (const line of readFileSync(path, "utf8").split("\n")) {
|
|
86
86
|
const parsed = parseAssignment(line);
|
|
87
87
|
if (parsed)
|
|
88
88
|
out[parsed.key] = parsed.value;
|
|
@@ -130,7 +130,7 @@ const parseAssignment = (line) => {
|
|
|
130
130
|
/** Strip matching surrounding quotes and unescape `\"` / `\\` inside double quotes. */
|
|
131
131
|
const unquote = (value) => {
|
|
132
132
|
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
133
|
-
return value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g,
|
|
133
|
+
return value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
134
134
|
}
|
|
135
135
|
if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) {
|
|
136
136
|
return value.slice(1, -1);
|
|
@@ -147,13 +147,13 @@ const formatLine = (key, value) => {
|
|
|
147
147
|
const needsQuotes = /[\s#"'=]/.test(value);
|
|
148
148
|
if (!needsQuotes)
|
|
149
149
|
return `${key}=${value}`;
|
|
150
|
-
const escaped = value.replace(/\\/g,
|
|
150
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
151
151
|
return `${key}="${escaped}"`;
|
|
152
152
|
};
|
|
153
153
|
/** Drop trailing blank lines so we don't accumulate them across repeated merges. */
|
|
154
154
|
const trimTrailingBlank = (lines) => {
|
|
155
155
|
const out = [...lines];
|
|
156
|
-
while (out.length > 0 && out[out.length - 1]?.trim() ===
|
|
156
|
+
while (out.length > 0 && out[out.length - 1]?.trim() === "")
|
|
157
157
|
out.pop();
|
|
158
158
|
return out;
|
|
159
159
|
};
|
package/dist/errors.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const ERROR_MATCHERS = [
|
|
2
|
-
[/^Unknown command: (.*)$/,
|
|
3
|
-
[/^Missing required argument: (.*)$/,
|
|
4
|
-
[/^Failed to open web browser. (.*)$/,
|
|
2
|
+
[/^Unknown command: (.*)$/, "UNKNOWN_COMMAND"],
|
|
3
|
+
[/^Missing required argument: (.*)$/, "MISSING_ARGUMENT"],
|
|
4
|
+
[/^Failed to open web browser. (.*)$/, "AUTH_BROWSER_FAILED"],
|
|
5
5
|
];
|
|
6
6
|
export const matchErrorCode = (message) => {
|
|
7
7
|
if (!message) {
|
|
8
|
-
return
|
|
8
|
+
return "UNKNOWN_ERROR";
|
|
9
9
|
}
|
|
10
10
|
for (const [matcher, code] of ERROR_MATCHERS) {
|
|
11
11
|
const match = message.match(matcher);
|
|
@@ -13,7 +13,7 @@ export const matchErrorCode = (message) => {
|
|
|
13
13
|
return code;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
return
|
|
16
|
+
return "UNKNOWN_ERROR";
|
|
17
17
|
};
|
|
18
18
|
/**
|
|
19
19
|
* The single, human-readable line shown when the CLI couldn't reach the Neon API because
|
|
@@ -21,24 +21,24 @@ export const matchErrorCode = (message) => {
|
|
|
21
21
|
* cryptic `fetch failed` / empty axios message a network blip otherwise surfaces (see
|
|
22
22
|
* {@link isNetworkError}), pointing at the two things the user can actually check.
|
|
23
23
|
*/
|
|
24
|
-
export const NETWORK_ERROR_MESSAGE =
|
|
25
|
-
|
|
24
|
+
export const NETWORK_ERROR_MESSAGE = "Could not reach the Neon API. Please check your internet connection and try again. " +
|
|
25
|
+
"If your connection is fine and this keeps happening, check https://neonstatus.com for ongoing incidents.";
|
|
26
26
|
/**
|
|
27
27
|
* Node-level socket/DNS error codes that mean the request never reached the server — a
|
|
28
28
|
* genuine connectivity problem rather than an API response we should surface. Deliberately
|
|
29
29
|
* excludes `ECONNABORTED` (axios' timeout), which the CLI already reports as a timeout.
|
|
30
30
|
*/
|
|
31
31
|
const NETWORK_ERROR_CODES = new Set([
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
"ECONNREFUSED",
|
|
33
|
+
"ECONNRESET",
|
|
34
|
+
"ETIMEDOUT",
|
|
35
|
+
"ENOTFOUND",
|
|
36
|
+
"EAI_AGAIN",
|
|
37
|
+
"EPIPE",
|
|
38
|
+
"EHOSTUNREACH",
|
|
39
|
+
"ENETUNREACH",
|
|
40
|
+
"EHOSTDOWN",
|
|
41
|
+
"ENETDOWN",
|
|
42
42
|
]);
|
|
43
43
|
/**
|
|
44
44
|
* Message fragments that mark a connection-level failure across our two transports: the
|
|
@@ -48,10 +48,10 @@ const NETWORK_ERROR_CODES = new Set([
|
|
|
48
48
|
*/
|
|
49
49
|
const NETWORK_ERROR_MESSAGE_PATTERN = /fetch failed|failed to fetch|network error/i;
|
|
50
50
|
const readErrorCode = (value) => {
|
|
51
|
-
if (value === null || typeof value !==
|
|
51
|
+
if (value === null || typeof value !== "object")
|
|
52
52
|
return undefined;
|
|
53
53
|
const code = value.code;
|
|
54
|
-
return typeof code ===
|
|
54
|
+
return typeof code === "string" ? code : undefined;
|
|
55
55
|
};
|
|
56
56
|
/**
|
|
57
57
|
* Whether `err` is a connection-level failure (the network blip the user sees as a cryptic
|
package/dist/functions_api.js
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import { ContentType } from
|
|
1
|
+
import { ContentType } from "./api.js";
|
|
2
2
|
const functionsPath = (projectId, branchId) => `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/functions`;
|
|
3
3
|
export const listFunctions = async (apiClient, projectId, branchId, { cursor, limit } = {}) => {
|
|
4
4
|
const { data } = await apiClient.request({
|
|
5
5
|
path: functionsPath(projectId, branchId),
|
|
6
|
-
method:
|
|
6
|
+
method: "GET",
|
|
7
7
|
query: { cursor, limit },
|
|
8
8
|
secure: true,
|
|
9
|
-
format:
|
|
9
|
+
format: "json",
|
|
10
10
|
});
|
|
11
11
|
return { functions: data.functions ?? [], next: data.pagination?.next };
|
|
12
12
|
};
|
|
13
13
|
export const getFunction = async (apiClient, projectId, branchId, slug) => {
|
|
14
14
|
const { data } = await apiClient.request({
|
|
15
15
|
path: `${functionsPath(projectId, branchId)}/${encodeURIComponent(slug)}`,
|
|
16
|
-
method:
|
|
16
|
+
method: "GET",
|
|
17
17
|
secure: true,
|
|
18
|
-
format:
|
|
18
|
+
format: "json",
|
|
19
19
|
});
|
|
20
20
|
return data.function;
|
|
21
21
|
};
|
|
22
22
|
export const deleteFunction = async (apiClient, projectId, branchId, slug) => {
|
|
23
23
|
await apiClient.request({
|
|
24
24
|
path: `${functionsPath(projectId, branchId)}/${encodeURIComponent(slug)}`,
|
|
25
|
-
method:
|
|
25
|
+
method: "DELETE",
|
|
26
26
|
secure: true,
|
|
27
27
|
});
|
|
28
28
|
};
|
|
29
29
|
export const createDeployment = async (apiClient, projectId, branchId, slug, params) => {
|
|
30
30
|
const form = new FormData();
|
|
31
|
-
form.append(
|
|
32
|
-
form.append(
|
|
31
|
+
form.append("zip", new Blob([params.zip]), "bundle.zip");
|
|
32
|
+
form.append("runtime", params.runtime);
|
|
33
33
|
if (params.environment)
|
|
34
|
-
form.append(
|
|
34
|
+
form.append("environment", params.environment);
|
|
35
35
|
// The deploy POST returns an operation the CLI cannot poll; the body is
|
|
36
36
|
// ignored. We only need the request to succeed.
|
|
37
37
|
await apiClient.request({
|
|
38
38
|
path: `${functionsPath(projectId, branchId)}/${encodeURIComponent(slug)}/deployments`,
|
|
39
|
-
method:
|
|
39
|
+
method: "POST",
|
|
40
40
|
type: ContentType.FormData,
|
|
41
41
|
body: form,
|
|
42
42
|
secure: true,
|