neonctl 2.27.1 → 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 +35 -3
- package/dist/analytics.js +52 -34
- package/dist/api.js +643 -13
- package/dist/auth.js +50 -44
- package/dist/cli.js +8 -1
- package/dist/commands/auth.js +64 -51
- package/dist/commands/bootstrap.js +115 -157
- package/dist/commands/branches.js +160 -150
- package/dist/commands/bucket.js +183 -146
- package/dist/commands/checkout.js +51 -51
- package/dist/commands/config.js +228 -82
- package/dist/commands/connection_string.js +62 -62
- package/dist/commands/data_api.js +100 -101
- package/dist/commands/databases.js +29 -26
- 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 +101 -104
- package/dist/commands/index.js +27 -25
- package/dist/commands/init.js +23 -22
- package/dist/commands/ip_allow.js +29 -29
- package/dist/commands/link.js +232 -182
- package/dist/commands/neon_auth.js +385 -370
- package/dist/commands/operations.js +11 -11
- package/dist/commands/orgs.js +8 -8
- package/dist/commands/projects.js +103 -101
- package/dist/commands/psql.js +31 -31
- package/dist/commands/roles.js +27 -24
- package/dist/commands/schema_diff.js +25 -26
- package/dist/commands/set_context.js +17 -17
- package/dist/commands/status.js +40 -0
- 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 +37 -14
- package/dist/current_branch_fast_path.js +55 -0
- 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 +68 -5
- package/dist/functions_api.js +10 -10
- package/dist/help.js +15 -15
- package/dist/index.js +110 -107
- package/dist/log.js +2 -2
- package/dist/parameters.gen.js +14 -14
- 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 +22 -23
- package/dist/test_utils/fixtures.js +74 -41
- package/dist/test_utils/oauth_server.js +5 -5
- package/dist/utils/api_enums.js +33 -0
- 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 +28 -16
- 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 +10 -12
package/dist/auth.js
CHANGED
|
@@ -1,58 +1,62 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { createServer } from
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import open from
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { extendTokenSet } from
|
|
1
|
+
import { createReadStream } from "node:fs";
|
|
2
|
+
import { createServer, } from "node:http";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import open from "open";
|
|
6
|
+
import * as client from "openid-client";
|
|
7
|
+
import { sendError } from "./analytics.js";
|
|
8
|
+
import { matchErrorCode } from "./errors.js";
|
|
9
|
+
import { log } from "./log.js";
|
|
10
|
+
import { extendTokenSet } from "./utils/auth.js";
|
|
11
11
|
// oauth server timeouts
|
|
12
12
|
const SERVER_TIMEOUT = 10000;
|
|
13
13
|
// where to wait for incoming redirect request from oauth server to arrive
|
|
14
14
|
const REDIRECT_URI = (port) => `http://127.0.0.1:${port}/callback`;
|
|
15
15
|
// These scopes cannot be cancelled, they are always needed.
|
|
16
|
-
const ALWAYS_PRESENT_SCOPES = [
|
|
16
|
+
const ALWAYS_PRESENT_SCOPES = ["openid", "offline", "offline_access"];
|
|
17
17
|
const NEONCTL_SCOPES = [
|
|
18
18
|
...ALWAYS_PRESENT_SCOPES,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
"urn:neoncloud:projects:create",
|
|
20
|
+
"urn:neoncloud:projects:read",
|
|
21
|
+
"urn:neoncloud:projects:update",
|
|
22
|
+
"urn:neoncloud:projects:delete",
|
|
23
|
+
"urn:neoncloud:orgs:create",
|
|
24
|
+
"urn:neoncloud:orgs:read",
|
|
25
|
+
"urn:neoncloud:orgs:update",
|
|
26
|
+
"urn:neoncloud:orgs:delete",
|
|
27
|
+
"urn:neoncloud:orgs:permission",
|
|
28
28
|
];
|
|
29
29
|
const AUTH_TIMEOUT_SECONDS = 60;
|
|
30
|
-
export const defaultClientID =
|
|
30
|
+
export const defaultClientID = "neonctl";
|
|
31
31
|
export const refreshToken = async ({ oauthHost, clientId, allowUnsafeTls }, tokenSet) => {
|
|
32
|
-
log.debug(
|
|
33
|
-
const configuration = await client.discovery(new URL(oauthHost), clientId, { token_endpoint_auth_method:
|
|
32
|
+
log.debug("Discovering oauth server");
|
|
33
|
+
const configuration = await client.discovery(new URL(oauthHost), clientId, { token_endpoint_auth_method: "none" }, client.None(), {
|
|
34
34
|
timeout: SERVER_TIMEOUT,
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
execute: allowUnsafeTls
|
|
36
|
+
? // eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
37
|
+
[client.allowInsecureRequests]
|
|
38
|
+
: undefined,
|
|
37
39
|
});
|
|
38
40
|
return await client.refreshTokenGrant(configuration, tokenSet.refresh_token);
|
|
39
41
|
};
|
|
40
42
|
export const auth = async ({ oauthHost, clientId, allowUnsafeTls, }) => {
|
|
41
|
-
log.debug(
|
|
42
|
-
const configuration = await client.discovery(new URL(oauthHost), clientId, { token_endpoint_auth_method:
|
|
43
|
+
log.debug("Discovering oauth server");
|
|
44
|
+
const configuration = await client.discovery(new URL(oauthHost), clientId, { token_endpoint_auth_method: "none" }, client.None(), {
|
|
43
45
|
timeout: SERVER_TIMEOUT,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
execute: allowUnsafeTls
|
|
47
|
+
? // eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
48
|
+
[client.allowInsecureRequests]
|
|
49
|
+
: undefined,
|
|
46
50
|
});
|
|
47
51
|
//
|
|
48
52
|
// Start HTTP server and wait till /callback is hit
|
|
49
53
|
//
|
|
50
|
-
log.debug(
|
|
54
|
+
log.debug("Starting HTTP Server for callback");
|
|
51
55
|
const server = createServer();
|
|
52
|
-
server.listen(0,
|
|
56
|
+
server.listen(0, "127.0.0.1", function () {
|
|
53
57
|
log.debug(`Listening on port ${this.address().port}`);
|
|
54
58
|
});
|
|
55
|
-
await new Promise((resolve) => server.once(
|
|
59
|
+
await new Promise((resolve) => server.once("listening", resolve));
|
|
56
60
|
const listen_port = server.address().port;
|
|
57
61
|
// https://datatracker.ietf.org/doc/html/rfc6819#section-4.4.1.8
|
|
58
62
|
const state = client.randomState();
|
|
@@ -67,17 +71,17 @@ export const auth = async ({ oauthHost, clientId, allowUnsafeTls, }) => {
|
|
|
67
71
|
//
|
|
68
72
|
// Wait for callback and follow oauth flow.
|
|
69
73
|
//
|
|
70
|
-
if (!request.url?.startsWith(
|
|
74
|
+
if (!request.url?.startsWith("/callback")) {
|
|
71
75
|
response.writeHead(404);
|
|
72
76
|
response.end();
|
|
73
77
|
return;
|
|
74
78
|
}
|
|
75
79
|
// process the CORS preflight OPTIONS request
|
|
76
|
-
if (request.method ===
|
|
80
|
+
if (request.method === "OPTIONS") {
|
|
77
81
|
response.writeHead(200, {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
"Access-Control-Allow-Origin": "*",
|
|
83
|
+
"Access-Control-Allow-Methods": "GET, POST",
|
|
84
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
81
85
|
});
|
|
82
86
|
response.end();
|
|
83
87
|
return;
|
|
@@ -87,29 +91,31 @@ export const auth = async ({ oauthHost, clientId, allowUnsafeTls, }) => {
|
|
|
87
91
|
pkceCodeVerifier: codeVerifier,
|
|
88
92
|
expectedState: state,
|
|
89
93
|
});
|
|
90
|
-
response.writeHead(200, {
|
|
91
|
-
createReadStream(join(fileURLToPath(new URL(
|
|
94
|
+
response.writeHead(200, { "Content-Type": "text/html" });
|
|
95
|
+
createReadStream(join(fileURLToPath(new URL(".", import.meta.url)), "./callback.html")).pipe(response);
|
|
92
96
|
clearTimeout(timer);
|
|
93
97
|
const exp = new Date();
|
|
94
98
|
exp.setSeconds(exp.getSeconds() + (tokenSet.expires_in ?? 0));
|
|
95
99
|
resolve(extendTokenSet(tokenSet));
|
|
96
100
|
server.close();
|
|
97
101
|
};
|
|
98
|
-
server.on(
|
|
102
|
+
server.on("request", (req, res) => {
|
|
99
103
|
void onRequest(req, res);
|
|
100
104
|
});
|
|
101
105
|
//
|
|
102
106
|
// Open browser to let user authenticate
|
|
103
107
|
//
|
|
104
|
-
const scopes = clientId == defaultClientID
|
|
108
|
+
const scopes = clientId == defaultClientID
|
|
109
|
+
? NEONCTL_SCOPES
|
|
110
|
+
: ALWAYS_PRESENT_SCOPES;
|
|
105
111
|
const authUrl = client.buildAuthorizationUrl(configuration, {
|
|
106
|
-
scope: scopes.join(
|
|
112
|
+
scope: scopes.join(" "),
|
|
107
113
|
state,
|
|
108
114
|
code_challenge: codeChallenge,
|
|
109
|
-
code_challenge_method:
|
|
115
|
+
code_challenge_method: "S256",
|
|
110
116
|
redirect_uri: REDIRECT_URI(listen_port),
|
|
111
117
|
});
|
|
112
|
-
log.info(
|
|
118
|
+
log.info("Awaiting authentication in web browser.");
|
|
113
119
|
log.info(`Auth Url: ${authUrl}`);
|
|
114
120
|
open(authUrl.href).catch((err) => {
|
|
115
121
|
const msg = `Failed to open web browser. Please copy & paste auth url to authenticate in browser.`;
|
package/dist/cli.js
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import { tryCurrentBranchFastPath } from "./current_branch_fast_path.js";
|
|
3
|
+
// Fast path for the offline `(config) status --current-branch` probe (used by shell
|
|
4
|
+
// prompts): read the pinned branch from `.neon` without loading the full command tree,
|
|
5
|
+
// api-client, and yargs (~200ms). Falls through to the full CLI for everything else, so
|
|
6
|
+
// the heavy `index.js` is imported lazily and only when actually needed.
|
|
7
|
+
if (!tryCurrentBranchFastPath(process.argv)) {
|
|
8
|
+
void import("./index.js");
|
|
9
|
+
}
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { getApiClient } from
|
|
5
|
-
import { auth, refreshToken } from
|
|
6
|
-
import { CREDENTIALS_FILE } from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
export const
|
|
12
|
-
export const
|
|
13
|
-
export const
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { getApiClient } from "../api.js";
|
|
5
|
+
import { auth, refreshToken } from "../auth.js";
|
|
6
|
+
import { CREDENTIALS_FILE } from "../config.js";
|
|
7
|
+
import { isConfigInit, isCurrentBranchProbe } from "../context.js";
|
|
8
|
+
import { isCi } from "../env.js";
|
|
9
|
+
import { log } from "../log.js";
|
|
10
|
+
import { extendTokenSet } from "../utils/auth.js";
|
|
11
|
+
export const command = "auth";
|
|
12
|
+
export const aliases = ["login"];
|
|
13
|
+
export const describe = "Authenticate";
|
|
14
|
+
export const builder = (yargs) => yargs.option("context-file", {
|
|
14
15
|
hidden: true,
|
|
15
16
|
});
|
|
16
17
|
export const handler = async (args) => {
|
|
17
18
|
await authFlow(args);
|
|
18
19
|
};
|
|
19
|
-
export const authFlow = async ({ configDir, oauthHost, clientId, apiHost, forceAuth,
|
|
20
|
+
export const authFlow = async ({ configDir, oauthHost, clientId, apiHost, forceAuth, "force-auth": forceAuthKebab, allowUnsafeTls, }) => {
|
|
20
21
|
const allowInteractiveAuth = forceAuth ?? forceAuthKebab;
|
|
21
22
|
if (!allowInteractiveAuth && isCi()) {
|
|
22
|
-
throw new Error(
|
|
23
|
+
throw new Error("Cannot run interactive auth in CI");
|
|
23
24
|
}
|
|
24
25
|
const tokenSet = await auth({
|
|
25
26
|
oauthHost: oauthHost,
|
|
@@ -29,16 +30,16 @@ export const authFlow = async ({ configDir, oauthHost, clientId, apiHost, forceA
|
|
|
29
30
|
const credentialsPath = join(configDir, CREDENTIALS_FILE);
|
|
30
31
|
try {
|
|
31
32
|
await preserveCredentials(credentialsPath, tokenSet, getApiClient({
|
|
32
|
-
apiKey: tokenSet.access_token ||
|
|
33
|
+
apiKey: tokenSet.access_token || "",
|
|
33
34
|
apiHost,
|
|
34
35
|
}));
|
|
35
36
|
}
|
|
36
37
|
catch {
|
|
37
|
-
log.error(
|
|
38
|
-
return
|
|
38
|
+
log.error("Failed to save credentials");
|
|
39
|
+
return "";
|
|
39
40
|
}
|
|
40
|
-
log.info(
|
|
41
|
-
return tokenSet.access_token ||
|
|
41
|
+
log.info("Auth complete");
|
|
42
|
+
return tokenSet.access_token || "";
|
|
42
43
|
};
|
|
43
44
|
const preserveCredentials = async (path, credentials, apiClient) => {
|
|
44
45
|
const { data: { id }, } = await apiClient.getCurrentUserInfo();
|
|
@@ -51,13 +52,13 @@ const preserveCredentials = async (path, credentials, apiClient) => {
|
|
|
51
52
|
writeFileSync(path, contents, {
|
|
52
53
|
mode: 0o700,
|
|
53
54
|
});
|
|
54
|
-
log.debug(
|
|
55
|
-
log.debug(
|
|
55
|
+
log.debug("Saved credentials to %s", path);
|
|
56
|
+
log.debug("Credentials MD5 hash: %s", md5hash(contents));
|
|
56
57
|
};
|
|
57
58
|
const handleExistingToken = async (tokenSet, props, credentialsPath) => {
|
|
58
59
|
// Use existing access_token, if present and valid
|
|
59
60
|
if (tokenSet.access_token && tokenSet.expires_at > Date.now()) {
|
|
60
|
-
log.debug(
|
|
61
|
+
log.debug("Using existing valid access_token");
|
|
61
62
|
const apiClient = getApiClient({
|
|
62
63
|
apiKey: tokenSet.access_token,
|
|
63
64
|
apiHost: props.apiHost,
|
|
@@ -66,10 +67,10 @@ const handleExistingToken = async (tokenSet, props, credentialsPath) => {
|
|
|
66
67
|
}
|
|
67
68
|
// Either access_token is missing or its expired. Refresh the token
|
|
68
69
|
log.debug(tokenSet.expires_at < Date.now()
|
|
69
|
-
?
|
|
70
|
-
:
|
|
70
|
+
? "Token is expired, attempting refresh"
|
|
71
|
+
: "Token is missing access_token, attempting refresh");
|
|
71
72
|
if (!tokenSet.refresh_token) {
|
|
72
|
-
log.debug(
|
|
73
|
+
log.debug("TokenSet is missing refresh_token, starting authentication");
|
|
73
74
|
return null;
|
|
74
75
|
}
|
|
75
76
|
try {
|
|
@@ -86,13 +87,13 @@ const handleExistingToken = async (tokenSet, props, credentialsPath) => {
|
|
|
86
87
|
apiHost: props.apiHost,
|
|
87
88
|
});
|
|
88
89
|
await preserveCredentials(credentialsPath, extendedTokenSet, apiClient);
|
|
89
|
-
log.debug(
|
|
90
|
+
log.debug("Token refresh successful");
|
|
90
91
|
return { apiKey, apiClient };
|
|
91
92
|
}
|
|
92
93
|
catch (err) {
|
|
93
|
-
const typedErr = err instanceof Error ? err : new Error(
|
|
94
|
-
log.debug(
|
|
95
|
-
throw new Error(
|
|
94
|
+
const typedErr = err instanceof Error ? err : new Error("Unknown error");
|
|
95
|
+
log.debug("Failed to refresh token: %s", typedErr.message);
|
|
96
|
+
throw new Error("AUTH_REFRESH_FAILED");
|
|
96
97
|
}
|
|
97
98
|
};
|
|
98
99
|
export const ensureAuth = async (props) => {
|
|
@@ -100,23 +101,34 @@ export const ensureAuth = async (props) => {
|
|
|
100
101
|
if (props._.length === 0 || props.help) {
|
|
101
102
|
return;
|
|
102
103
|
}
|
|
104
|
+
// `(config) status --current-branch` is a purely-local read of `.neon`; it must
|
|
105
|
+
// never refresh a token or pop a browser login. Skip auth entirely (the handler
|
|
106
|
+
// doesn't use an API client in this mode).
|
|
107
|
+
if (isCurrentBranchProbe(props)) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// `config init` only scaffolds a neon.ts and installs npm packages locally; it
|
|
111
|
+
// never calls the Neon API, so skip auth entirely — no token refresh, no login.
|
|
112
|
+
if (isConfigInit(props)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
103
115
|
// `dev` runs a function locally. It injects the selected branch's env vars
|
|
104
116
|
// when credentials happen to be available, but must never trigger an
|
|
105
117
|
// interactive login: use an API key or existing stored credentials if
|
|
106
118
|
// present, otherwise run with no API client (env injection is skipped).
|
|
107
|
-
const isLocalDev = props._[0] ===
|
|
119
|
+
const isLocalDev = props._[0] === "dev";
|
|
108
120
|
// `bootstrap` only copies a public template repo; it never calls the Neon
|
|
109
121
|
// API, so it must work without credentials and must never pop a browser
|
|
110
122
|
// login. It uses an API key / stored credentials when present (harmless),
|
|
111
123
|
// otherwise it proceeds with no API client.
|
|
112
|
-
const isBootstrap = props._[0] ===
|
|
124
|
+
const isBootstrap = props._[0] === "bootstrap";
|
|
113
125
|
// `init` manages its own auth flow (asks the user if they have an account,
|
|
114
126
|
// then triggers OAuth at the right time). Skip the global auth middleware.
|
|
115
|
-
const isInit = props._[0] ===
|
|
127
|
+
const isInit = props._[0] === "init";
|
|
116
128
|
// Use existing API key or handle auth command
|
|
117
|
-
if (props.apiKey || props._[0] ===
|
|
129
|
+
if (props.apiKey || props._[0] === "auth") {
|
|
118
130
|
if (props.apiKey) {
|
|
119
|
-
log.debug(
|
|
131
|
+
log.debug("Using an API key to authorize requests");
|
|
120
132
|
}
|
|
121
133
|
props.apiClient = getApiClient({
|
|
122
134
|
apiKey: props.apiKey,
|
|
@@ -127,10 +139,10 @@ export const ensureAuth = async (props) => {
|
|
|
127
139
|
const credentialsPath = join(props.configDir, CREDENTIALS_FILE);
|
|
128
140
|
// Handle case when credentials file exists
|
|
129
141
|
if (existsSync(credentialsPath)) {
|
|
130
|
-
log.debug(
|
|
142
|
+
log.debug("Trying to read credentials from %s", credentialsPath);
|
|
131
143
|
try {
|
|
132
|
-
const contents = readFileSync(credentialsPath,
|
|
133
|
-
log.debug(
|
|
144
|
+
const contents = readFileSync(credentialsPath, "utf8");
|
|
145
|
+
log.debug("Credentials MD5 hash: %s", md5hash(contents));
|
|
134
146
|
const tokenSet = JSON.parse(contents);
|
|
135
147
|
// Try to use existing token or refresh it
|
|
136
148
|
const result = await handleExistingToken(tokenSet, props, credentialsPath);
|
|
@@ -141,32 +153,33 @@ export const ensureAuth = async (props) => {
|
|
|
141
153
|
}
|
|
142
154
|
}
|
|
143
155
|
catch (err) {
|
|
144
|
-
if (!(err instanceof Error &&
|
|
145
|
-
err.
|
|
156
|
+
if (!(err instanceof Error &&
|
|
157
|
+
err.message === "AUTH_REFRESH_FAILED") &&
|
|
158
|
+
err.code !== "ENOENT" &&
|
|
146
159
|
!(err instanceof SyntaxError)) {
|
|
147
160
|
// Throw for any errors except auth refresh failure, missing file, or invalid credentials file
|
|
148
161
|
throw err;
|
|
149
162
|
}
|
|
150
163
|
// Fall through to new auth flow for auth failures
|
|
151
|
-
log.debug(
|
|
164
|
+
log.debug("Ensure auth failed, starting authentication", err);
|
|
152
165
|
}
|
|
153
166
|
}
|
|
154
167
|
else {
|
|
155
|
-
log.debug(
|
|
168
|
+
log.debug("Credentials file %s does not exist, starting authentication", credentialsPath);
|
|
156
169
|
}
|
|
157
170
|
// `dev` never launches the interactive browser flow. With no usable
|
|
158
171
|
// credentials it proceeds without an API client; env injection is skipped
|
|
159
172
|
// and the function still runs locally.
|
|
160
173
|
if (isLocalDev) {
|
|
161
|
-
log.debug(
|
|
174
|
+
log.debug("dev: no usable credentials; running without env injection");
|
|
162
175
|
return;
|
|
163
176
|
}
|
|
164
177
|
if (isBootstrap) {
|
|
165
|
-
log.debug(
|
|
178
|
+
log.debug("bootstrap: no usable credentials; continuing without auth");
|
|
166
179
|
return;
|
|
167
180
|
}
|
|
168
181
|
if (isInit) {
|
|
169
|
-
log.debug(
|
|
182
|
+
log.debug("init: skipping global auth; init manages its own auth flow");
|
|
170
183
|
return;
|
|
171
184
|
}
|
|
172
185
|
// Start new auth flow if no valid token exists or refresh failed
|
|
@@ -186,16 +199,16 @@ export const deleteCredentials = (configDir) => {
|
|
|
186
199
|
try {
|
|
187
200
|
if (existsSync(credentialsPath)) {
|
|
188
201
|
rmSync(credentialsPath);
|
|
189
|
-
log.info(
|
|
202
|
+
log.info("Deleted credentials from %s", credentialsPath);
|
|
190
203
|
}
|
|
191
204
|
else {
|
|
192
|
-
log.debug(
|
|
205
|
+
log.debug("Credentials file %s does not exist", credentialsPath);
|
|
193
206
|
}
|
|
194
207
|
}
|
|
195
208
|
catch (err) {
|
|
196
|
-
const typedErr = err instanceof Error ? err : new Error(
|
|
197
|
-
log.error(
|
|
198
|
-
throw new Error(
|
|
209
|
+
const typedErr = err instanceof Error ? err : new Error("Unknown error");
|
|
210
|
+
log.error("Failed to delete credentials: %s", typedErr.message);
|
|
211
|
+
throw new Error("CREDENTIALS_DELETE_FAILED");
|
|
199
212
|
}
|
|
200
213
|
};
|
|
201
|
-
const md5hash = (s) => createHash(
|
|
214
|
+
const md5hash = (s) => createHash("md5").update(s).digest("hex");
|