neon 3.5.1 → 4.0.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 +19 -30
- package/dist/_chunks/{auth_selection-CV_3n1du.js → auth_selection-Bx2NAgkk.js} +3 -1
- package/dist/api.js +1 -1
- package/dist/commands/api_keys.js +2 -7
- package/dist/commands/auth.js +1 -1
- package/dist/commands/bootstrap.js +1 -1
- package/dist/commands/init.js +0 -6
- package/dist/commands/neon_auth.js +27 -49
- package/dist/commands/profile.js +12 -8
- package/dist/human_table.js +239 -0
- package/dist/init/auth.js +6 -9
- package/dist/init/enrich_output.js +7 -2
- package/dist/init/interactive.js +4 -3
- package/dist/init/neonctl.js +4 -11
- package/dist/init/phases/status.js +8 -4
- package/dist/init/profile_cli.js +56 -0
- package/dist/init/resolve_context.js +7 -12
- package/dist/utils/middlewares.js +11 -2
- package/dist/writer.js +19 -26
- package/package.json +5 -7
package/README.md
CHANGED
|
@@ -720,8 +720,7 @@ That message reports where parsing stopped and nothing more. `--data` carries wh
|
|
|
720
720
|
| `--data <json>` | Route to one phase, with that phase's options |
|
|
721
721
|
| `--skip-migrations` | Leave the migrations phase out of the flow |
|
|
722
722
|
| `--preview` | Enable preview features (scaffolding a project from a template) |
|
|
723
|
-
|
|
724
|
-
`neon init` refuses `--profile`; see [Which credential an invocation uses](#which-credential-an-invocation-uses).
|
|
723
|
+
| `--profile <name>` | Run as that stored account. See [Which credential an invocation uses](#which-credential-an-invocation-uses). |
|
|
725
724
|
|
|
726
725
|
## Snapshots (`snapshots`)
|
|
727
726
|
|
|
@@ -842,22 +841,18 @@ neon profile remove work
|
|
|
842
841
|
```console
|
|
843
842
|
$ neon profile list
|
|
844
843
|
Profiles
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
├────────┼─────────┼───────────────────────┼─────────┼────────────────┼──────┼─────────┼────────────────────────┤
|
|
850
|
-
│ │ work │ me@example.com │ api key │ account │ ok │ keyring │ keyring │
|
|
851
|
-
├────────┼─────────┼───────────────────────┼─────────┼────────────────┼──────┼─────────┼────────────────────────┤
|
|
852
|
-
│ │ ci │ org-abc-123 │ api key │ project proj-1 │ ok │ file │ credentials.ci.json │
|
|
853
|
-
└────────┴─────────┴───────────────────────┴─────────┴────────────────┴──────┴─────────┴────────────────────────┘
|
|
844
|
+
Active Name Account Auth Credentials Scope
|
|
845
|
+
* DEFAULT me@example.com oauth credentials.json account
|
|
846
|
+
work me@example.com api key keyring account
|
|
847
|
+
ci org-abc-123 api key credentials.ci.json project proj-1
|
|
854
848
|
```
|
|
855
849
|
|
|
856
|
-
`Scope` is what
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
850
|
+
`Scope` is what the credential can reach. An OAuth session and an unscoped user API key both
|
|
851
|
+
show `account`. A project or org key names that project or org. `Credentials` is the path
|
|
852
|
+
relative to the config directory, or `keyring`. A path outside the config directory is shown
|
|
853
|
+
absolute. `--output json` keeps the absolute path, and also includes `file` (`ok`, `invalid`,
|
|
854
|
+
`missing`, or `unreadable`) and `storage` (`file` or `keyring`). A keyring get of null is
|
|
855
|
+
`unreadable`, not missing: the addon cannot tell those apart.
|
|
861
856
|
|
|
862
857
|
Select one per invocation with `--profile`, or per shell with `NEON_PROFILE`. There is no `profile use` command and nothing is stored about which profile is "current", so what you type is always what runs.
|
|
863
858
|
|
|
@@ -999,7 +994,7 @@ When both are only environment variables the key wins, which keeps a CI pipeline
|
|
|
999
994
|
|
|
1000
995
|
`neon auth` and the `profile` subcommands are outside all of this, because they read the same flags to mean something else: `neon auth --profile work` names where to write a credential, and `neon profile create work --api-key …` names one to store.
|
|
1001
996
|
|
|
1002
|
-
`neon init`
|
|
997
|
+
`neon init` follows the same profile selection: `--profile` and `NEON_PROFILE` pick the stored account. Every `npx neon` it runs, and every command it tells an agent to run (`npx neon …`, `neon init --agent …`), includes `--profile <name>` when a profile was named by `--profile` or `NEON_PROFILE`, and `--config-dir <path>` when you passed `--config-dir`; nothing is added when those were not set. It still ignores `--api-key` and `NEON_API_KEY` for its own credential read and runs as that stored profile (`DEFAULT` when a key was named with no profile). Those keys are also not written onto the emitted commands — putting a key in agent JSON would print it. An ambient `NEON_API_KEY` is inherited by the subprocesses.
|
|
1003
998
|
|
|
1004
999
|
## API keys (`api-keys`)
|
|
1005
1000
|
|
|
@@ -1014,16 +1009,14 @@ neon api-keys create --name agent --project-id frosty-… # can access only th
|
|
|
1014
1009
|
neon api-keys revoke <id> [--org-id org-…]
|
|
1015
1010
|
```
|
|
1016
1011
|
|
|
1017
|
-
The key is returned once, on create, and cannot be retrieved again. It prints on its own line below the
|
|
1012
|
+
The key is returned once, on create, and cannot be retrieved again. It prints on its own line below the metadata, so it can be selected in one gesture regardless of terminal width — and `… | tail -1` on stdout yields exactly the key, since both notices go to stderr.
|
|
1018
1013
|
|
|
1019
1014
|
```console
|
|
1020
1015
|
$ neon api-keys create --name agent --project-id proj-in-org
|
|
1021
1016
|
API key
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
│ 303 │ agent │ proj-in-org │
|
|
1026
|
-
└─────┴───────┴─────────────┘
|
|
1017
|
+
Id 303
|
|
1018
|
+
Name agent
|
|
1019
|
+
Project proj-in-org
|
|
1027
1020
|
|
|
1028
1021
|
napi_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
1029
1022
|
WARNING: Store this key now: it is not shown again.
|
|
@@ -1062,13 +1055,9 @@ NEON_API_KEY=napi_… neon deploy # then the agent, reaching only that pro
|
|
|
1062
1055
|
```console
|
|
1063
1056
|
$ neon api-keys list --org-id org-7
|
|
1064
1057
|
API keys in org-7
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
│ 301 │ scoped │ proj-in-org │ 2026-01-02T00:00:00Z │ │ │
|
|
1069
|
-
├─────┼──────────┼────────────────┼──────────────────────┼──────────────────────┼─────────────────────┤
|
|
1070
|
-
│ 302 │ org-wide │ (all projects) │ 2026-01-03T00:00:00Z │ 2026-02-03T00:00:00Z │ 203.0.113.9 │
|
|
1071
|
-
└─────┴──────────┴────────────────┴──────────────────────┴──────────────────────┴─────────────────────┘
|
|
1058
|
+
Id Name Project Created At Last Used At Last Used From Addr
|
|
1059
|
+
301 scoped proj-in-org 2026-01-02T00:00:00Z
|
|
1060
|
+
302 org-wide (all projects) 2026-01-03T00:00:00Z 2026-02-03T00:00:00Z 203.0.113.9
|
|
1072
1061
|
```
|
|
1073
1062
|
|
|
1074
1063
|
`last_used_at` and `last_used_from_addr` are how you spot a key worth revoking.
|
package/dist/api.js
CHANGED
|
@@ -796,7 +796,7 @@ const getApiClient = ({ apiKey, apiHost, requestTimeoutMs = REQUEST_TIMEOUT_MS }
|
|
|
796
796
|
},
|
|
797
797
|
body: data
|
|
798
798
|
})),
|
|
799
|
-
|
|
799
|
+
sendNeonAuthEmailProviderTest: (projectId, branchId, data) => call(() => raw.sendNeonAuthEmailProviderTest({
|
|
800
800
|
client,
|
|
801
801
|
path: {
|
|
802
802
|
project_id: projectId,
|
|
@@ -171,13 +171,8 @@ const create = async (props) => {
|
|
|
171
171
|
log.info("Limited to %s: it cannot create projects, mint API keys, or read any other project. It can still change and delete everything inside that project.", scopeTo);
|
|
172
172
|
};
|
|
173
173
|
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* In a terminal the secret goes on its own line rather than into a table cell: `cli-table`
|
|
177
|
-
* neither wraps nor truncates, so a 50-odd character key makes the row wider than most
|
|
178
|
-
* terminals and the wrapped remainder ends up beside box-drawing characters. Since this is
|
|
179
|
-
* the only time the key is ever shown, it has to be selectable in one gesture. Structured
|
|
180
|
-
* output keeps the key in the object, where a script expects it.
|
|
174
|
+
* The key is shown only once, so human output keeps it on a selectable line.
|
|
175
|
+
* Structured output keeps it in the object for scripts.
|
|
181
176
|
*/
|
|
182
177
|
const report = (props, data, fields, tableFields) => {
|
|
183
178
|
const out = writer(props);
|
package/dist/commands/auth.js
CHANGED
|
@@ -9,7 +9,7 @@ import { setAuthContext } from "../auth_context.js";
|
|
|
9
9
|
import { isConfigInit, isCurrentBranchProbe, isProfileCommand } from "../context.js";
|
|
10
10
|
import { extendTokenSet } from "../utils/auth.js";
|
|
11
11
|
import { auth, refreshToken } from "../auth.js";
|
|
12
|
-
import { i as selectCredential, n as displacedProfileWarning, t as credentialInputs } from "../_chunks/auth_selection-
|
|
12
|
+
import { i as selectCredential, n as displacedProfileWarning, t as credentialInputs } from "../_chunks/auth_selection-Bx2NAgkk.js";
|
|
13
13
|
import { readOutgoingCredential, retirePreviousCredential } from "../retire_credential.js";
|
|
14
14
|
import { existsSync } from "node:fs";
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
@@ -2,7 +2,7 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
|
2
2
|
import { isCi } from "../env.js";
|
|
3
3
|
import { log } from "../log.js";
|
|
4
4
|
import { getCliName } from "../utils/cli_name.js";
|
|
5
|
-
import { t as credentialInputs } from "../_chunks/auth_selection-
|
|
5
|
+
import { t as credentialInputs } from "../_chunks/auth_selection-Bx2NAgkk.js";
|
|
6
6
|
import { BootstrapInputError, FALLBACK_TEMPLATES, ensureTargetUsable, fetchTemplates, findTemplate, scaffoldTemplate, templateIds } from "../init/bootstrap.js";
|
|
7
7
|
import { DO_NOT_SUBSTITUTE_HINT, formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
|
|
8
8
|
import { existsSync } from "node:fs";
|
package/dist/commands/init.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { closeAnalytics, sendError } from "../analytics.js";
|
|
3
|
-
import { t as credentialInputs } from "../_chunks/auth_selection-CV_3n1du.js";
|
|
4
3
|
import { detectAgent } from "../init/detect_agent.js";
|
|
5
4
|
import { enrichResponse } from "../init/enrich_output.js";
|
|
6
5
|
import { interactiveInit } from "../init/interactive.js";
|
|
@@ -62,11 +61,6 @@ const handler = async (argv) => {
|
|
|
62
61
|
const agent = (argv.agent || !process.stdin.isTTY ? detectAgent() : null) || void 0;
|
|
63
62
|
const isAgentMode = argv.agent || agent !== void 0;
|
|
64
63
|
try {
|
|
65
|
-
const selectedProfile = argv.profile?.trim() || credentialInputs().profileEnv.trim();
|
|
66
|
-
if (selectedProfile) {
|
|
67
|
-
const how = argv.profile?.trim() ? "--profile was passed, so" : "NEON_PROFILE is set, so";
|
|
68
|
-
throw new Error(`${how} \`neon init\` would run as the default account instead of "${selectedProfile}", and it does not support profile selection yet. Run it without one, or set the project up with \`neon --profile ${selectedProfile} link\`.`);
|
|
69
|
-
}
|
|
70
64
|
if (argv.data && isAgentMode) {
|
|
71
65
|
let data;
|
|
72
66
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { codeFromBody, isNeonApiError, retryOnLock } from "../api.js";
|
|
3
3
|
import { writer } from "../writer.js";
|
|
4
|
+
import { noPassthrough, single } from "../utils/flags.js";
|
|
4
5
|
import { NeonAuthEmailVerificationMethod, NeonAuthOauthProviderId, NeonAuthOauthProviderType, NeonAuthSupportedAuthProvider } from "../utils/api_enums.js";
|
|
5
6
|
import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
|
|
6
7
|
import chalk from "chalk";
|
|
@@ -241,43 +242,17 @@ const builder = (argv) => {
|
|
|
241
242
|
}
|
|
242
243
|
}), async (args) => {
|
|
243
244
|
await emailProviderUpdate(args);
|
|
244
|
-
}).command("test", "Send a test
|
|
245
|
-
"
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
port: {
|
|
256
|
-
describe: "SMTP port",
|
|
257
|
-
type: "number",
|
|
258
|
-
demandOption: true
|
|
259
|
-
},
|
|
260
|
-
username: {
|
|
261
|
-
describe: "SMTP username",
|
|
262
|
-
type: "string",
|
|
263
|
-
demandOption: true
|
|
264
|
-
},
|
|
265
|
-
password: {
|
|
266
|
-
describe: "SMTP password",
|
|
267
|
-
type: "string",
|
|
268
|
-
demandOption: true
|
|
269
|
-
},
|
|
270
|
-
"sender-email": {
|
|
271
|
-
describe: "Sender email address",
|
|
272
|
-
type: "string",
|
|
273
|
-
demandOption: true
|
|
274
|
-
},
|
|
275
|
-
"sender-name": {
|
|
276
|
-
describe: "Sender display name",
|
|
277
|
-
type: "string",
|
|
278
|
-
demandOption: true
|
|
279
|
-
}
|
|
280
|
-
}), async (args) => {
|
|
245
|
+
}).command("test", "Send a test message through the saved custom SMTP provider", (yargs) => yargs.options({ "recipient-email": {
|
|
246
|
+
describe: "Email address to deliver the test message to",
|
|
247
|
+
type: "string",
|
|
248
|
+
demandOption: true,
|
|
249
|
+
coerce: single("recipient-email", { required: true })
|
|
250
|
+
} }).strict().check(noPassthrough("neon-auth config email-provider test")).epilogue([
|
|
251
|
+
"Save the provider first with",
|
|
252
|
+
"`neon neon-auth config email-provider update --type standard`.",
|
|
253
|
+
"A shared provider, a missing configuration, or a",
|
|
254
|
+
"non-Better-Auth integration is rejected by the API."
|
|
255
|
+
].join("\n")), async (args) => {
|
|
281
256
|
await emailProviderTest(args);
|
|
282
257
|
});
|
|
283
258
|
}).command("organization", "Manage organization plugin settings", (yargs) => {
|
|
@@ -663,18 +638,21 @@ const emailProviderUpdate = async (props) => {
|
|
|
663
638
|
};
|
|
664
639
|
const emailProviderTest = async (props) => {
|
|
665
640
|
const branchId = await resolveBranch(props);
|
|
666
|
-
const { data } = await props.apiClient.
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
641
|
+
const { data } = await props.apiClient.sendNeonAuthEmailProviderTest(props.projectId, branchId, { recipient_email: props.recipientEmail });
|
|
642
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
643
|
+
writer(props).end(data, { fields: TEST_EMAIL_FIELDS });
|
|
644
|
+
if (!data.success) throw new Error(emailProviderTestFailureMessage(props.recipientEmail, data.error_message));
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (data.success) {
|
|
648
|
+
printMessage(`Test email dispatched to ${props.recipientEmail} using the custom SMTP provider saved on this branch.`);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
throw new Error(emailProviderTestFailureMessage(props.recipientEmail, data.error_message));
|
|
652
|
+
};
|
|
653
|
+
const emailProviderTestFailureMessage = (recipientEmail, errorMessage) => {
|
|
654
|
+
const header = `Test email could NOT be sent to ${recipientEmail} using the custom SMTP provider saved on this branch.`;
|
|
655
|
+
return errorMessage ? `${header}\nUpstream error: ${errorMessage}` : header;
|
|
678
656
|
};
|
|
679
657
|
const printOrganizationEntries = (data) => [
|
|
680
658
|
["Enabled: ", String(data.enabled)],
|
package/dist/commands/profile.js
CHANGED
|
@@ -7,7 +7,7 @@ import { getApiClient, isNeonApiError } from "../api.js";
|
|
|
7
7
|
import { o as isOwnedCredentialPath, r as credentialsPath } from "../_chunks/paths-g3DRqJiD.js";
|
|
8
8
|
import { setAuthContext } from "../auth_context.js";
|
|
9
9
|
import { auth, revokeToken } from "../auth.js";
|
|
10
|
-
import { t as credentialInputs } from "../_chunks/auth_selection-
|
|
10
|
+
import { t as credentialInputs } from "../_chunks/auth_selection-Bx2NAgkk.js";
|
|
11
11
|
import { readOutgoingCredential, retirePreviousCredential, revokeTokenSet, withdrawKey } from "../retire_credential.js";
|
|
12
12
|
import { authFlow, usableCredential } from "./auth.js";
|
|
13
13
|
import { writer } from "../writer.js";
|
|
@@ -15,7 +15,7 @@ import { noPassthrough, single } from "../utils/flags.js";
|
|
|
15
15
|
import { orgIdForProject } from "./api_keys.js";
|
|
16
16
|
import { identityFromAuthDetails, isApiKeyMethod, mintedKeyName, notAnApiKeyMessage } from "../profile_keys.js";
|
|
17
17
|
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
18
|
-
import {
|
|
18
|
+
import { isAbsolute, relative } from "node:path";
|
|
19
19
|
import prompts from "prompts";
|
|
20
20
|
//#region src/commands/profile.ts
|
|
21
21
|
var profile_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -173,10 +173,10 @@ const list = async (props) => {
|
|
|
173
173
|
name: p.name,
|
|
174
174
|
account: p.label ?? p.userId ?? storedUserId ?? "-",
|
|
175
175
|
auth,
|
|
176
|
-
scope: stored !== null && auth === "api key" ? describeScope(scopeOf(stored)) : "-",
|
|
176
|
+
scope: auth === "oauth" ? describeScope({}) : stored !== null && auth === "api key" ? describeScope(scopeOf(stored)) : "-",
|
|
177
177
|
file,
|
|
178
178
|
storage,
|
|
179
|
-
|
|
179
|
+
credentials: props.output === "table" ? credentialsListValue(p, props.configDir) : credentialsDisplay(p)
|
|
180
180
|
};
|
|
181
181
|
});
|
|
182
182
|
writer(props).end(rows, {
|
|
@@ -186,13 +186,17 @@ const list = async (props) => {
|
|
|
186
186
|
"name",
|
|
187
187
|
"account",
|
|
188
188
|
"auth",
|
|
189
|
-
"
|
|
190
|
-
"
|
|
191
|
-
"storage",
|
|
192
|
-
"credentials"
|
|
189
|
+
"credentials",
|
|
190
|
+
"scope"
|
|
193
191
|
]
|
|
194
192
|
});
|
|
195
193
|
};
|
|
194
|
+
const credentialsListValue = (profile, configDir) => {
|
|
195
|
+
if (profile.storage === "keyring") return KEYRING_CREDENTIALS;
|
|
196
|
+
const rel = relative(configDir, profile.credentialsPath);
|
|
197
|
+
if (rel.startsWith("..") || isAbsolute(rel)) return profile.credentialsPath;
|
|
198
|
+
return rel;
|
|
199
|
+
};
|
|
196
200
|
const locationForCreate = (configDir, name, keyring) => {
|
|
197
201
|
assertProfilesUsable(configDir, name);
|
|
198
202
|
const declared = readProfiles(configDir)?.profiles[name];
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { isObject } from "./utils/string.js";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
//#region src/human_table.ts
|
|
4
|
+
const GUTTER = 2;
|
|
5
|
+
const MIN_COL = 3;
|
|
6
|
+
const ELLIPSIS = "...";
|
|
7
|
+
const ANSI_RE = /\u001b\[[0-9;]*m/g;
|
|
8
|
+
function resolveOutputWidth(out, override, deps = {}) {
|
|
9
|
+
if (override === null) return;
|
|
10
|
+
if (typeof override === "number" && Number.isFinite(override) && override > 0) return Math.floor(override);
|
|
11
|
+
if ("columns" in out) {
|
|
12
|
+
const columns = Reflect.get(out, "columns");
|
|
13
|
+
if (typeof columns === "number" && Number.isFinite(columns) && columns > 0) return Math.floor(columns);
|
|
14
|
+
}
|
|
15
|
+
const stdout = deps.stdout;
|
|
16
|
+
if (stdout !== void 0 && out === stdout) return parseColumns(deps.envColumns);
|
|
17
|
+
}
|
|
18
|
+
function parseColumns(value) {
|
|
19
|
+
if (value === void 0 || value === "") return;
|
|
20
|
+
if (!/^\d+$/.test(value)) return;
|
|
21
|
+
const n = Number(value);
|
|
22
|
+
if (!Number.isFinite(n) || n <= 0) return;
|
|
23
|
+
return n;
|
|
24
|
+
}
|
|
25
|
+
function displayWidth(s) {
|
|
26
|
+
const plain = s.replace(ANSI_RE, "");
|
|
27
|
+
let width = 0;
|
|
28
|
+
for (const char of plain) {
|
|
29
|
+
const code = char.codePointAt(0);
|
|
30
|
+
if (code === void 0) continue;
|
|
31
|
+
width += charWidth(code);
|
|
32
|
+
}
|
|
33
|
+
return width;
|
|
34
|
+
}
|
|
35
|
+
function formatHumanChunk(chunk) {
|
|
36
|
+
const arrayData = Array.isArray(chunk.data) ? chunk.data : [chunk.data];
|
|
37
|
+
const isList = Array.isArray(chunk.data);
|
|
38
|
+
if (!arrayData.length && chunk.emptyMessage) return `\n${chunk.emptyMessage}\n`;
|
|
39
|
+
let out = "";
|
|
40
|
+
if (chunk.title) {
|
|
41
|
+
if (chunk.leadingBlank) out += "\n";
|
|
42
|
+
out += formatTitle(chunk.title, chunk.colorTitle) + "\n";
|
|
43
|
+
}
|
|
44
|
+
if (!arrayData.length) return out;
|
|
45
|
+
const fields = chunk.fields.filter((field) => arrayData.some((item) => {
|
|
46
|
+
const value = fieldValue(item, field);
|
|
47
|
+
return value !== void 0 && value !== "";
|
|
48
|
+
}));
|
|
49
|
+
if (!fields.length) return out;
|
|
50
|
+
const headers = fields.map(titleCaseField);
|
|
51
|
+
const rows = arrayData.map((item) => fields.map((field) => cellText(item, field, chunk.renderColumns)));
|
|
52
|
+
const body = isList ? formatList(headers, rows, chunk.width) : formatStacked(headers, rows, chunk.width);
|
|
53
|
+
if (body) out += body + "\n";
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
function formatTitle(title, colorTitle) {
|
|
57
|
+
return colorTitle ? chalk.bold(title) : title;
|
|
58
|
+
}
|
|
59
|
+
function formatList(headers, rows, width) {
|
|
60
|
+
if (headers.length === 1) return formatOneColumn(headers[0] ?? "", rows.map((row) => row[0] ?? ""));
|
|
61
|
+
if (width === void 0) return formatColumns(headers, rows, naturalWidths(headers, rows));
|
|
62
|
+
for (let n = headers.length; n >= 2; n--) {
|
|
63
|
+
const subsetHeaders = headers.slice(0, n);
|
|
64
|
+
const subsetRows = rows.map((row) => row.slice(0, n));
|
|
65
|
+
const natural = naturalWidths(subsetHeaders, subsetRows);
|
|
66
|
+
if (rowWidth(natural) <= width) return formatColumns(subsetHeaders, subsetRows, natural);
|
|
67
|
+
const fitted = tryFit(subsetHeaders, subsetRows, width);
|
|
68
|
+
if (fitted === void 0) continue;
|
|
69
|
+
if (!natural.slice(0, n - 1).every((col, i) => fitted[i] === col)) continue;
|
|
70
|
+
return formatColumns(subsetHeaders.map((cell, i) => truncateTo(cell, fitted[i] ?? 0)), subsetRows.map((row) => row.map((cell, i) => truncateTo(cell, fitted[i] ?? 0))), fitted);
|
|
71
|
+
}
|
|
72
|
+
return formatStacked(headers, rows, width);
|
|
73
|
+
}
|
|
74
|
+
function rowWidth(widths) {
|
|
75
|
+
if (widths.length === 0) return 0;
|
|
76
|
+
return widths.reduce((sum, col) => sum + col, 0) + (widths.length - 1) * GUTTER;
|
|
77
|
+
}
|
|
78
|
+
function formatOneColumn(header, values) {
|
|
79
|
+
const lines = [chalk.green(header)];
|
|
80
|
+
for (const value of values) lines.push(value);
|
|
81
|
+
return lines.join("\n");
|
|
82
|
+
}
|
|
83
|
+
function formatColumns(headers, rows, widths) {
|
|
84
|
+
const joinRow = (cells) => cells.map((cell, i) => {
|
|
85
|
+
const width = widths[i] ?? 0;
|
|
86
|
+
return i === cells.length - 1 ? truncateTo(cell, width) : padEndWidth(cell, width);
|
|
87
|
+
}).join(" ".repeat(GUTTER)).trimEnd();
|
|
88
|
+
const lines = [chalk.green(joinRow(headers))];
|
|
89
|
+
for (const row of rows) lines.push(joinRow(row));
|
|
90
|
+
return lines.join("\n");
|
|
91
|
+
}
|
|
92
|
+
function formatStacked(headers, rows, width) {
|
|
93
|
+
const naturalLabel = Math.max(0, ...headers.map((header) => displayWidth(header)));
|
|
94
|
+
let labelWidth = naturalLabel;
|
|
95
|
+
if (width !== void 0) {
|
|
96
|
+
const maxLabel = width - GUTTER - MIN_COL;
|
|
97
|
+
labelWidth = maxLabel < 1 ? Math.min(naturalLabel, width) : Math.min(naturalLabel, maxLabel);
|
|
98
|
+
}
|
|
99
|
+
return rows.map((row) => headers.map((header, i) => stackedLine(header, row[i] ?? "", labelWidth)).join("\n")).join("\n\n");
|
|
100
|
+
}
|
|
101
|
+
function stackedLine(header, value, labelWidth) {
|
|
102
|
+
return `${padEndWidth(header, labelWidth)}${" ".repeat(GUTTER)}${value}`;
|
|
103
|
+
}
|
|
104
|
+
function tryFit(headers, rows, width) {
|
|
105
|
+
const n = headers.length;
|
|
106
|
+
if (n === 0) return [];
|
|
107
|
+
const gutters = (n - 1) * GUTTER;
|
|
108
|
+
if (width <= gutters) return;
|
|
109
|
+
const budget = width - gutters;
|
|
110
|
+
if (budget < n * MIN_COL) return;
|
|
111
|
+
const widths = naturalWidths(headers, rows);
|
|
112
|
+
let overflow = widths.reduce((sum, col) => sum + col, 0) - budget;
|
|
113
|
+
if (overflow <= 0) return widths;
|
|
114
|
+
for (let i = n - 1; i >= 0 && overflow > 0; i--) {
|
|
115
|
+
const current = widths[i] ?? 0;
|
|
116
|
+
const take = Math.min(Math.max(current - MIN_COL, 0), overflow);
|
|
117
|
+
widths[i] = current - take;
|
|
118
|
+
overflow -= take;
|
|
119
|
+
}
|
|
120
|
+
if (overflow > 0) return;
|
|
121
|
+
return widths;
|
|
122
|
+
}
|
|
123
|
+
function naturalWidths(headers, rows) {
|
|
124
|
+
return headers.map((header, i) => Math.max(displayWidth(header), ...rows.map((row) => displayWidth(row[i] ?? ""))));
|
|
125
|
+
}
|
|
126
|
+
function padEndWidth(s, width) {
|
|
127
|
+
const clipped = truncateTo(s, width);
|
|
128
|
+
const pad = width - displayWidth(clipped);
|
|
129
|
+
return pad > 0 ? clipped + " ".repeat(pad) : clipped;
|
|
130
|
+
}
|
|
131
|
+
function truncateTo(s, max) {
|
|
132
|
+
if (max <= 0) return "";
|
|
133
|
+
if (displayWidth(s) <= max) return s;
|
|
134
|
+
if (max <= 3) return ELLIPSIS.slice(0, max);
|
|
135
|
+
const budget = max - 3;
|
|
136
|
+
let out = "";
|
|
137
|
+
let used = 0;
|
|
138
|
+
for (const char of s.replace(ANSI_RE, "")) {
|
|
139
|
+
const code = char.codePointAt(0);
|
|
140
|
+
if (code === void 0) continue;
|
|
141
|
+
const w = charWidth(code);
|
|
142
|
+
if (used + w > budget) break;
|
|
143
|
+
out += char;
|
|
144
|
+
used += w;
|
|
145
|
+
}
|
|
146
|
+
return out + ELLIPSIS;
|
|
147
|
+
}
|
|
148
|
+
function cellText(item, field, renderColumns) {
|
|
149
|
+
if (renderColumns) {
|
|
150
|
+
const render = Reflect.get(renderColumns, field);
|
|
151
|
+
if (typeof render === "function") return flattenCell(render(item));
|
|
152
|
+
}
|
|
153
|
+
return flattenCell(fieldValue(item, field));
|
|
154
|
+
}
|
|
155
|
+
function fieldValue(item, field) {
|
|
156
|
+
if (item === null || typeof item !== "object") return;
|
|
157
|
+
return Reflect.get(item, field);
|
|
158
|
+
}
|
|
159
|
+
function flattenCell(value) {
|
|
160
|
+
if (value === void 0 || value === null) return "";
|
|
161
|
+
if (Array.isArray(value)) return value.map((entry) => flattenCell(entry)).join(", ");
|
|
162
|
+
if (isObject(value)) return JSON.stringify(value);
|
|
163
|
+
return String(value).replace(/\s+/g, " ").trim();
|
|
164
|
+
}
|
|
165
|
+
function titleCaseField(field) {
|
|
166
|
+
return field.split("_").map((word) => word ? word[0]?.toUpperCase() + word.slice(1) : word).join(" ");
|
|
167
|
+
}
|
|
168
|
+
const WIDE_RANGES = [
|
|
169
|
+
[4352, 4447],
|
|
170
|
+
[8986, 8987],
|
|
171
|
+
[9001, 9002],
|
|
172
|
+
[9193, 9196],
|
|
173
|
+
[9200, 9200],
|
|
174
|
+
[9203, 9203],
|
|
175
|
+
[9725, 9726],
|
|
176
|
+
[9748, 9749],
|
|
177
|
+
[9800, 9811],
|
|
178
|
+
[9855, 9855],
|
|
179
|
+
[9875, 9875],
|
|
180
|
+
[9889, 9889],
|
|
181
|
+
[9898, 9899],
|
|
182
|
+
[9917, 9918],
|
|
183
|
+
[9924, 9925],
|
|
184
|
+
[9934, 9934],
|
|
185
|
+
[9940, 9940],
|
|
186
|
+
[9962, 9962],
|
|
187
|
+
[9970, 9971],
|
|
188
|
+
[9973, 9973],
|
|
189
|
+
[9978, 9978],
|
|
190
|
+
[9981, 9981],
|
|
191
|
+
[9989, 9989],
|
|
192
|
+
[9994, 9995],
|
|
193
|
+
[10024, 10024],
|
|
194
|
+
[10060, 10060],
|
|
195
|
+
[10062, 10062],
|
|
196
|
+
[10067, 10069],
|
|
197
|
+
[10071, 10071],
|
|
198
|
+
[10133, 10135],
|
|
199
|
+
[10160, 10160],
|
|
200
|
+
[10175, 10175],
|
|
201
|
+
[11035, 11036],
|
|
202
|
+
[11088, 11088],
|
|
203
|
+
[11093, 11093],
|
|
204
|
+
[11904, 12350],
|
|
205
|
+
[12352, 42191],
|
|
206
|
+
[43360, 43391],
|
|
207
|
+
[44032, 55203],
|
|
208
|
+
[55216, 55295],
|
|
209
|
+
[63744, 64255],
|
|
210
|
+
[65040, 65049],
|
|
211
|
+
[65072, 65135],
|
|
212
|
+
[65280, 65376],
|
|
213
|
+
[65504, 65510],
|
|
214
|
+
[126976, 129791],
|
|
215
|
+
[131072, 196605],
|
|
216
|
+
[196608, 262141]
|
|
217
|
+
];
|
|
218
|
+
function charWidth(code) {
|
|
219
|
+
if (code >= 768 && code <= 879) return 0;
|
|
220
|
+
let lo = 0;
|
|
221
|
+
let hi = WIDE_RANGES.length - 1;
|
|
222
|
+
while (lo <= hi) {
|
|
223
|
+
const mid = lo + hi >> 1;
|
|
224
|
+
const range = WIDE_RANGES[mid];
|
|
225
|
+
if (range === void 0) break;
|
|
226
|
+
if (code < range[0]) {
|
|
227
|
+
hi = mid - 1;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (code > range[1]) {
|
|
231
|
+
lo = mid + 1;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
return 2;
|
|
235
|
+
}
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
export { displayWidth, formatHumanChunk, parseColumns, resolveOutputWidth };
|
package/dist/init/auth.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { l as locationForName
|
|
1
|
+
import { l as locationForName } from "../_chunks/profiles-CVXNvU2r.js";
|
|
2
2
|
import { l as interpretCredentials, t as storeFor } from "../_chunks/credential_io-D2vw1UOw.js";
|
|
3
|
-
import {
|
|
3
|
+
import { npxNeonArgs, selectedConfigDir, selectedProfileName } from "./profile_cli.js";
|
|
4
4
|
import { log } from "@clack/prompts";
|
|
5
5
|
import { execa } from "execa";
|
|
6
6
|
//#region src/init/auth.ts
|
|
@@ -12,11 +12,7 @@ async function ensureNeonctlAuth(options) {
|
|
|
12
12
|
const quiet = options?.json === true;
|
|
13
13
|
if (await getNeonctlAccessToken()) return true;
|
|
14
14
|
try {
|
|
15
|
-
await execa("npx", [
|
|
16
|
-
"-y",
|
|
17
|
-
"neon",
|
|
18
|
-
"me"
|
|
19
|
-
], {
|
|
15
|
+
await execa("npx", npxNeonArgs(["me"]), {
|
|
20
16
|
stdio: "inherit",
|
|
21
17
|
env: {
|
|
22
18
|
...process.env,
|
|
@@ -44,8 +40,9 @@ async function isAuthenticated() {
|
|
|
44
40
|
* Unreadable credentials throw so browser sign-in cannot overwrite them.
|
|
45
41
|
*/
|
|
46
42
|
async function getNeonctlAccessToken() {
|
|
47
|
-
const
|
|
48
|
-
const
|
|
43
|
+
const dir = selectedConfigDir();
|
|
44
|
+
const at = locationForName(dir, selectedProfileName());
|
|
45
|
+
const loaded = storeFor(dir).read(at);
|
|
49
46
|
if (loaded === null) return null;
|
|
50
47
|
const credential = interpretCredentials(loaded.credentials, at, loaded.backend);
|
|
51
48
|
if (credential.kind === "api_key") return credential.apiKey;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { neonInitAgentCmd } from "./profile_cli.js";
|
|
1
2
|
//#region src/init/enrich_output.ts
|
|
2
3
|
/**
|
|
3
4
|
* Converts a phase's `args` (e.g. ["neon-auth", "--json", "--setup"]) into the
|
|
@@ -14,11 +15,15 @@ function argsToCommand(args) {
|
|
|
14
15
|
const arg = args[i];
|
|
15
16
|
if (arg.startsWith("--")) {
|
|
16
17
|
const key = arg.slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
18
|
+
const next = args[i + 1];
|
|
17
19
|
if (key === "json") {
|
|
18
20
|
i += 1;
|
|
19
21
|
continue;
|
|
20
22
|
}
|
|
21
|
-
|
|
23
|
+
if (key === "profile" || key === "configDir") {
|
|
24
|
+
i += next !== void 0 && !next.startsWith("-") ? 2 : 1;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
22
27
|
if (next !== void 0 && !next.startsWith("-")) {
|
|
23
28
|
data[key] = next;
|
|
24
29
|
i += 2;
|
|
@@ -28,7 +33,7 @@ function argsToCommand(args) {
|
|
|
28
33
|
}
|
|
29
34
|
} else i += 1;
|
|
30
35
|
}
|
|
31
|
-
return
|
|
36
|
+
return neonInitAgentCmd(data);
|
|
32
37
|
}
|
|
33
38
|
/**
|
|
34
39
|
* Walks a phase response object and:
|
package/dist/init/interactive.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FALLBACK_TEMPLATES, fetchTemplates, scaffoldTemplate } from "./bootstrap.js";
|
|
2
2
|
import { detectAgent, detectIde } from "./detect_agent.js";
|
|
3
|
+
import { neonInitAgentCmd } from "./profile_cli.js";
|
|
3
4
|
import { agentSupportsProjectMcp, detectInstalledAgents, getAgentDisplayName, getSkillsAgentName, mcpPickerOptions, tryResolveAddMcpAgentId } from "./agents.js";
|
|
4
5
|
import { ensureNeonctlAuth, isAuthenticated } from "./auth.js";
|
|
5
6
|
import { installExtension, isExtensionInstalled } from "./extension.js";
|
|
@@ -266,7 +267,7 @@ async function interactiveInitInner(options) {
|
|
|
266
267
|
initialValue: "no"
|
|
267
268
|
});
|
|
268
269
|
if (isCancel(authResult) || authResult === "no") {
|
|
269
|
-
outro(dim(`Your project is configured with Neon. You can set up Neon Auth later by having your agent run:
|
|
270
|
+
outro(dim(`Your project is configured with Neon. You can set up Neon Auth later by having your agent run: ${neonInitAgentCmd({ step: "neon-auth" })}`));
|
|
270
271
|
return;
|
|
271
272
|
}
|
|
272
273
|
let projectId = null;
|
|
@@ -480,10 +481,10 @@ async function interactiveInitInner(options) {
|
|
|
480
481
|
if (inspection.migrationDir && inspection.migrationDir !== "none") gettingStartedData.migrationDir = inspection.migrationDir;
|
|
481
482
|
if (selectedFeatures.length > 0) gettingStartedData.features = selectedFeatures;
|
|
482
483
|
if (options.preview) gettingStartedData.preview = true;
|
|
483
|
-
const cmd =
|
|
484
|
+
const cmd = neonInitAgentCmd({
|
|
484
485
|
step: "getting-started",
|
|
485
486
|
...gettingStartedData
|
|
486
|
-
})
|
|
487
|
+
});
|
|
487
488
|
const cols = (process.stdout.columns || 80) - 3;
|
|
488
489
|
const promptText = `To finish setting up Neon using Neon's agent-guided onboarding experience, have your agent run this shell command: ${cmd}`;
|
|
489
490
|
log.step("Next steps");
|
package/dist/init/neonctl.js
CHANGED
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
import { globalInstallCommand, resolveInvokingPackageManager } from "../utils/package_manager.js";
|
|
2
|
+
import { explicitConfigDirCli, explicitProfileCli } from "./profile_cli.js";
|
|
2
3
|
import { lstatSync } from "node:fs";
|
|
3
4
|
import { execa } from "execa";
|
|
4
5
|
//#region src/init/neonctl.ts
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* The CLI reads NEON_API_HOST and NEON_OAUTH_HOST from the environment
|
|
10
|
-
* directly, so no extra flags are needed. The published package is `neon`
|
|
11
|
-
* (its binary is `neon`; the older `neonctl` alias is no longer shipped). `npx`
|
|
12
|
-
* runs it with zero global-install assumptions, and the leading `CI=` keeps the
|
|
13
|
-
* CLI non-interactive.
|
|
14
|
-
*
|
|
15
|
-
* Usage: `${neonctlCmd()} orgs list --output json`
|
|
7
|
+
* `npx -y neon` avoids global-install assumptions, and `CI=` keeps emitted calls
|
|
8
|
+
* non-interactive. API and OAuth hosts stay ambient because the CLI reads them directly.
|
|
16
9
|
*/
|
|
17
10
|
function neonctlCmd() {
|
|
18
|
-
return
|
|
11
|
+
return `CI= npx -y neon${explicitProfileCli()}${explicitConfigDirCli()}`;
|
|
19
12
|
}
|
|
20
13
|
/**
|
|
21
14
|
* The global binaries that indicate the Neon CLI is installed. The current
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { neonInitAgentCmd } from "../profile_cli.js";
|
|
1
2
|
import { supportsSkills, tryResolveAddMcpAgentId } from "../agents.js";
|
|
2
3
|
import { isAuthenticated } from "../auth.js";
|
|
3
4
|
import { inspectProject } from "../inspect.js";
|
|
@@ -35,23 +36,26 @@ async function handleStatusPhase(options) {
|
|
|
35
36
|
if (!authed) recommendations.push({
|
|
36
37
|
priority: "high",
|
|
37
38
|
message: "Not authenticated with Neon",
|
|
38
|
-
command:
|
|
39
|
+
command: neonInitAgentCmd({ step: "auth" })
|
|
39
40
|
});
|
|
40
41
|
if (!hasDatabaseUrl) recommendations.push({
|
|
41
42
|
priority: "high",
|
|
42
43
|
message: "No DATABASE_URL found in .env",
|
|
43
|
-
command:
|
|
44
|
+
command: neonInitAgentCmd({ step: "db" })
|
|
44
45
|
});
|
|
45
46
|
const statusAgent = options.agent ? tryResolveAddMcpAgentId(options.agent) : void 0;
|
|
46
47
|
if (!skillsInstalled && (!statusAgent || supportsSkills(statusAgent))) recommendations.push({
|
|
47
48
|
priority: "medium",
|
|
48
49
|
message: "Neon agent skills not detected in this project",
|
|
49
|
-
command:
|
|
50
|
+
command: neonInitAgentCmd({
|
|
51
|
+
step: "skills",
|
|
52
|
+
install: true
|
|
53
|
+
})
|
|
50
54
|
});
|
|
51
55
|
if (migrationTool && !hasMigrations) recommendations.push({
|
|
52
56
|
priority: "medium",
|
|
53
57
|
message: `${migrationTool} detected but no migrations found`,
|
|
54
|
-
command:
|
|
58
|
+
command: neonInitAgentCmd({ step: "migrations" })
|
|
55
59
|
});
|
|
56
60
|
return {
|
|
57
61
|
auth: { authenticated: authed },
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { t as DEFAULT_PROFILE } from "../_chunks/profiles-CVXNvU2r.js";
|
|
2
|
+
import { n as configDir } from "../_chunks/paths-g3DRqJiD.js";
|
|
3
|
+
import { i as selectCredential, t as credentialInputs } from "../_chunks/auth_selection-Bx2NAgkk.js";
|
|
4
|
+
//#region src/init/profile_cli.ts
|
|
5
|
+
/**
|
|
6
|
+
* API-key selections fall back to DEFAULT because init authenticates from stored credentials.
|
|
7
|
+
*/
|
|
8
|
+
function selectedProfileName() {
|
|
9
|
+
const selection = selectCredential(credentialInputs());
|
|
10
|
+
return selection.source === "profile" ? selection.profile : DEFAULT_PROFILE;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* An explicit `--config-dir` must override `NEON_CONFIG_DIR` and the home path.
|
|
14
|
+
*/
|
|
15
|
+
function selectedConfigDir() {
|
|
16
|
+
const recorded = credentialInputs().configDir;
|
|
17
|
+
return recorded !== "" ? recorded : configDir();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Omitting implicit DEFAULT preserves command compatibility; retaining explicit DEFAULT
|
|
21
|
+
* prevents ambient `NEON_PROFILE` from overriding it in subprocesses.
|
|
22
|
+
*/
|
|
23
|
+
function explicitProfileArgs() {
|
|
24
|
+
const selection = selectCredential(credentialInputs());
|
|
25
|
+
if (selection.source === "profile" && selection.explicit) return ["--profile", selection.profile];
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
function explicitProfileCli() {
|
|
29
|
+
const args = explicitProfileArgs();
|
|
30
|
+
return args.length === 0 ? "" : ` ${args.join(" ")}`;
|
|
31
|
+
}
|
|
32
|
+
function explicitConfigDirArgs() {
|
|
33
|
+
const dir = credentialInputs().configDir;
|
|
34
|
+
return dir !== "" ? ["--config-dir", dir] : [];
|
|
35
|
+
}
|
|
36
|
+
function posixSingleQuote(value) {
|
|
37
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
38
|
+
}
|
|
39
|
+
function explicitConfigDirCli() {
|
|
40
|
+
const dir = credentialInputs().configDir;
|
|
41
|
+
return dir === "" ? "" : ` --config-dir ${posixSingleQuote(dir)}`;
|
|
42
|
+
}
|
|
43
|
+
function npxNeonArgs(command) {
|
|
44
|
+
return [
|
|
45
|
+
"-y",
|
|
46
|
+
"neon",
|
|
47
|
+
...explicitProfileArgs(),
|
|
48
|
+
...explicitConfigDirArgs(),
|
|
49
|
+
...command
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
function neonInitAgentCmd(data) {
|
|
53
|
+
return `neon init --agent${explicitProfileCli()}${explicitConfigDirCli()} --data '${JSON.stringify(data)}'`;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { explicitConfigDirCli, explicitProfileArgs, explicitProfileCli, neonInitAgentCmd, npxNeonArgs, selectedConfigDir, selectedProfileName };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { npxNeonArgs } from "./profile_cli.js";
|
|
1
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
3
|
import { resolve } from "node:path";
|
|
3
4
|
import { execa } from "execa";
|
|
@@ -40,14 +41,12 @@ async function resolveNeonContext(cwd) {
|
|
|
40
41
|
if (!neonHost) return null;
|
|
41
42
|
let orgs;
|
|
42
43
|
try {
|
|
43
|
-
const result = await execa("npx", [
|
|
44
|
-
"-y",
|
|
45
|
-
"neon",
|
|
44
|
+
const result = await execa("npx", npxNeonArgs([
|
|
46
45
|
"orgs",
|
|
47
46
|
"list",
|
|
48
47
|
"--output",
|
|
49
48
|
"json"
|
|
50
|
-
], {
|
|
49
|
+
]), {
|
|
51
50
|
stdio: "pipe",
|
|
52
51
|
timeout: 3e4,
|
|
53
52
|
env: {
|
|
@@ -63,16 +62,14 @@ async function resolveNeonContext(cwd) {
|
|
|
63
62
|
for (const org of orgs) {
|
|
64
63
|
let projects;
|
|
65
64
|
try {
|
|
66
|
-
const result = await execa("npx", [
|
|
67
|
-
"-y",
|
|
68
|
-
"neon",
|
|
65
|
+
const result = await execa("npx", npxNeonArgs([
|
|
69
66
|
"projects",
|
|
70
67
|
"list",
|
|
71
68
|
"--org-id",
|
|
72
69
|
org.id,
|
|
73
70
|
"--output",
|
|
74
71
|
"json"
|
|
75
|
-
], {
|
|
72
|
+
]), {
|
|
76
73
|
stdio: "pipe",
|
|
77
74
|
timeout: 3e4,
|
|
78
75
|
env: {
|
|
@@ -85,13 +82,11 @@ async function resolveNeonContext(cwd) {
|
|
|
85
82
|
continue;
|
|
86
83
|
}
|
|
87
84
|
for (const project of projects) try {
|
|
88
|
-
if ((await execa("npx", [
|
|
89
|
-
"-y",
|
|
90
|
-
"neon",
|
|
85
|
+
if ((await execa("npx", npxNeonArgs([
|
|
91
86
|
"connection-string",
|
|
92
87
|
"--project-id",
|
|
93
88
|
project.id
|
|
94
|
-
], {
|
|
89
|
+
]), {
|
|
95
90
|
stdio: "pipe",
|
|
96
91
|
timeout: 15e3,
|
|
97
92
|
env: {
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import { r as recordCredentialInputs } from "../_chunks/auth_selection-
|
|
1
|
+
import { r as recordCredentialInputs } from "../_chunks/auth_selection-Bx2NAgkk.js";
|
|
2
2
|
//#region src/utils/middlewares.ts
|
|
3
3
|
/**
|
|
4
|
+
* Forwarding Yargs's default would turn ambient configuration into an explicit child override.
|
|
5
|
+
*/
|
|
6
|
+
function configDirFromArgv(parsed) {
|
|
7
|
+
if (!process.argv.some((arg) => arg === "--config-dir" || arg.startsWith("--config-dir=")) || typeof parsed !== "string") return "";
|
|
8
|
+
return parsed;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
4
11
|
* Resolves `--api-key` from `NEON_API_KEY` when the flag is absent, leaving it an
|
|
5
12
|
* empty string when neither is set.
|
|
6
13
|
*
|
|
@@ -20,7 +27,9 @@ const resolveApiKeyFromEnv = (args) => {
|
|
|
20
27
|
recordCredentialInputs({
|
|
21
28
|
apiKeyFlag: fromFlag,
|
|
22
29
|
apiKeyEnv: fromEnv,
|
|
23
|
-
profileEnv: process.env.NEON_PROFILE ?? ""
|
|
30
|
+
profileEnv: process.env.NEON_PROFILE ?? "",
|
|
31
|
+
profileFlag: typeof args.profile === "string" ? args.profile : "",
|
|
32
|
+
configDir: configDirFromArgv(args.configDir)
|
|
24
33
|
});
|
|
25
34
|
if (fromFlag !== "") return;
|
|
26
35
|
args.apiKey = fromEnv;
|
package/dist/writer.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { isCi } from "./env.js";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { toSnakeCase } from "./utils/string.js";
|
|
3
|
+
import { formatHumanChunk, resolveOutputWidth } from "./human_table.js";
|
|
4
4
|
import YAML from "yaml";
|
|
5
|
-
import Table from "cli-table";
|
|
6
5
|
//#region src/writer.ts
|
|
7
6
|
const writeYaml = (chunks) => {
|
|
8
7
|
return YAML.stringify(chunks.length === 1 ? chunks[0].data : Object.fromEntries(chunks.map(({ config, data }, idx) => [config.title ? toSnakeCase(config.title) : idx, data])), null, 2);
|
|
@@ -10,28 +9,18 @@ const writeYaml = (chunks) => {
|
|
|
10
9
|
const writeJson = (chunks) => {
|
|
11
10
|
return JSON.stringify(chunks.length === 1 ? chunks[0].data : Object.fromEntries(chunks.map(({ config, data }, idx) => [config.title ? toSnakeCase(config.title) : idx, data])), null, 2);
|
|
12
11
|
};
|
|
13
|
-
const writeTable = (chunks, out) => {
|
|
14
|
-
chunks.forEach(({ data, config
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
25
|
-
arrayData.forEach((item) => {
|
|
26
|
-
table.push(fieldsFiltered.map((field) => {
|
|
27
|
-
const value = item[field];
|
|
28
|
-
if (renderColumns[field]) return renderColumns[field]?.(item);
|
|
29
|
-
return Array.isArray(value) ? value.join("\n") : isObject(value) ? JSON.stringify(value, null, 2) : value ?? "";
|
|
30
|
-
}));
|
|
31
|
-
});
|
|
32
|
-
if (title) out.write((isCi() ? title : chalk.bold(title)) + "\n");
|
|
33
|
-
out.write(table.toString());
|
|
34
|
-
out.write("\n");
|
|
12
|
+
const writeTable = (chunks, out, width) => {
|
|
13
|
+
chunks.forEach(({ data, config }, i) => {
|
|
14
|
+
out.write(formatHumanChunk({
|
|
15
|
+
data,
|
|
16
|
+
fields: config.fields,
|
|
17
|
+
title: config.title,
|
|
18
|
+
emptyMessage: config.emptyMessage,
|
|
19
|
+
renderColumns: config.renderColumns,
|
|
20
|
+
width,
|
|
21
|
+
colorTitle: !isCi(),
|
|
22
|
+
leadingBlank: i > 0 && Boolean(config.title)
|
|
23
|
+
}));
|
|
35
24
|
});
|
|
36
25
|
};
|
|
37
26
|
/**
|
|
@@ -50,6 +39,10 @@ const writeTable = (chunks, out) => {
|
|
|
50
39
|
*/
|
|
51
40
|
const writer = (props) => {
|
|
52
41
|
const out = props.out ?? process.stdout;
|
|
42
|
+
const width = resolveOutputWidth(out, props.columns, {
|
|
43
|
+
stdout: process.stdout,
|
|
44
|
+
envColumns: process.env.COLUMNS
|
|
45
|
+
});
|
|
53
46
|
const chunks = [];
|
|
54
47
|
return {
|
|
55
48
|
write(data, config) {
|
|
@@ -69,7 +62,7 @@ const writer = (props) => {
|
|
|
69
62
|
});
|
|
70
63
|
if (props.output == "yaml") return out.write(writeYaml(chunks));
|
|
71
64
|
if (props.output == "json") return out.write(writeJson(chunks));
|
|
72
|
-
writeTable(chunks, out);
|
|
65
|
+
writeTable(chunks, out, width);
|
|
73
66
|
}
|
|
74
67
|
};
|
|
75
68
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -49,7 +49,6 @@
|
|
|
49
49
|
"add-mcp": "2.0.0",
|
|
50
50
|
"chalk": "5.3.0",
|
|
51
51
|
"chokidar": "5.0.0",
|
|
52
|
-
"cli-table": "0.3.11",
|
|
53
52
|
"cliui": "8.0.1",
|
|
54
53
|
"diff": "5.2.0",
|
|
55
54
|
"execa": "^9.5.2",
|
|
@@ -64,9 +63,9 @@
|
|
|
64
63
|
"yaml": "^2.9.0",
|
|
65
64
|
"yargs": "17.7.2",
|
|
66
65
|
"yoctocolors": "^2.1.2",
|
|
67
|
-
"@neon/config-runtime": "1.0.
|
|
68
|
-
"@neon/config": "1.0.
|
|
69
|
-
"@neon/sdk": "2.
|
|
66
|
+
"@neon/config-runtime": "1.0.2",
|
|
67
|
+
"@neon/config": "1.0.2",
|
|
68
|
+
"@neon/sdk": "2.2.0"
|
|
70
69
|
},
|
|
71
70
|
"optionalDependencies": {
|
|
72
71
|
"@napi-rs/keyring": "1.3.0",
|
|
@@ -78,7 +77,6 @@
|
|
|
78
77
|
"@rollup/plugin-json": "6.1.0",
|
|
79
78
|
"@rollup/plugin-node-resolve": "15.2.3",
|
|
80
79
|
"@testcontainers/postgresql": "^11.14.0",
|
|
81
|
-
"@types/cli-table": "0.3.4",
|
|
82
80
|
"@types/diff": "5.2.1",
|
|
83
81
|
"@types/express": "4.17.21",
|
|
84
82
|
"@types/node": "^20.19.0",
|
|
@@ -98,8 +96,8 @@
|
|
|
98
96
|
"tsx": "4.22.3",
|
|
99
97
|
"typescript": "^5.9.0",
|
|
100
98
|
"vitest": "^3.0.9",
|
|
101
|
-
"@neon-internals/env-core": "0.0.1",
|
|
102
99
|
"@neon-internals/cli-core": "0.0.0",
|
|
100
|
+
"@neon-internals/env-core": "0.0.2",
|
|
103
101
|
"@neon/e2e-harness": "0.0.0"
|
|
104
102
|
},
|
|
105
103
|
"publishConfig": {
|