neon 3.0.0 → 3.1.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 +54 -0
- package/dist/_shared/auth_selection.js +76 -79
- package/dist/_shared/credentials.js +166 -187
- package/dist/_shared/env-core/env.js +354 -517
- package/dist/_shared/env-core/reuse-secrets.js +159 -203
- package/dist/_shared/paths.js +129 -126
- package/dist/_shared/profiles.js +192 -242
- package/dist/_shared/secure_file.js +36 -38
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +573 -690
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +243 -303
- package/dist/commands/functions.js +275 -355
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +614 -845
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +313 -394
- package/dist/dev/functions.js +54 -68
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +36 -38
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +727 -983
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +1293 -2082
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +547 -1099
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +261 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +762 -1180
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1302
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +626 -815
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +1414 -1927
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +2 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +130 -189
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +37 -44
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +7 -5
package/dist/context.js
CHANGED
|
@@ -1,268 +1,216 @@
|
|
|
1
|
+
import { log } from "./log.js";
|
|
1
2
|
import { accessSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, normalize, resolve } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
//#region src/context.ts
|
|
5
6
|
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
* The branch pinned in a context, reading the current `branch` field and
|
|
8
|
+
* falling back to the legacy `branchId` so pre-migration `.neon` files keep
|
|
9
|
+
* working.
|
|
10
|
+
*/
|
|
11
|
+
const contextBranch = (context) => context.branch ?? context.branchId;
|
|
11
12
|
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
(args._[0] === "status" ||
|
|
26
|
-
(args._[0] === "config" && args._[1] === "status"));
|
|
13
|
+
* True when the invocation is the offline "current branch" probe:
|
|
14
|
+
* `(config) status --current-branch`. This mode only reads the pinned branch
|
|
15
|
+
* from the local `.neon` file (for shell prompts like starship), so it MUST
|
|
16
|
+
* NOT touch the network — several middlewares (auth, analytics, single-project
|
|
17
|
+
* resolution) consult this to early-return and skip their API calls / login.
|
|
18
|
+
*
|
|
19
|
+
* Gated on the exact command as well as the flag so an accidental
|
|
20
|
+
* `--current-branch` on an unrelated command (e.g. `config plan`, where the flag
|
|
21
|
+
* is undefined but non-strict yargs still parses it) can't silently skip
|
|
22
|
+
* auth/analytics. The probe is only `status` (the top-level alias) or
|
|
23
|
+
* `config status` (`_ = ['config', 'status']`).
|
|
24
|
+
*/
|
|
25
|
+
const isCurrentBranchProbe = (args) => args.currentBranch === true && (args._[0] === "status" || args._[0] === "config" && args._[1] === "status");
|
|
27
26
|
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
args._[1] === "init" &&
|
|
41
|
-
args.fromBranch !== true &&
|
|
42
|
-
!process.argv.includes("--from-branch");
|
|
27
|
+
* `config init` only scaffolds a local `neon.ts` and installs npm packages — it
|
|
28
|
+
* never calls the Neon API. Gated on the exact command path so the global auth
|
|
29
|
+
* middleware and the single-project resolver can skip it (it runs with no API
|
|
30
|
+
* client), mirroring {@link isCurrentBranchProbe}.
|
|
31
|
+
*
|
|
32
|
+
* `--from-branch` is the exception: it seeds the policy from a branch's live state, so it
|
|
33
|
+
* needs both credentials and a resolved project. The raw argv is checked alongside the parsed
|
|
34
|
+
* flag because this runs from middleware that executes before validation, where the parsed
|
|
35
|
+
* value may not be populated yet (the same reason `analytics.ts` scans argv for
|
|
36
|
+
* `--current-branch`).
|
|
37
|
+
*/
|
|
38
|
+
const isConfigInit = (args) => args._[0] === "config" && args._[1] === "init" && args.fromBranch !== true && !process.argv.includes("--from-branch");
|
|
43
39
|
/**
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
* `neon profile …` manages credentials on disk and never calls the Neon API, so the global
|
|
41
|
+
* auth middleware must skip it — mirroring {@link isConfigInit}.
|
|
42
|
+
*
|
|
43
|
+
* More than a nicety: without this, listing your profiles would launch a browser login, and
|
|
44
|
+
* removing a broken profile would demand you sign into it first. Removing a profile whose
|
|
45
|
+
* access has already lapsed is the main reason to remove one.
|
|
46
|
+
*/
|
|
47
|
+
const isProfileCommand = (args) => args._[0] === "profile" || args._[0] === "profiles";
|
|
52
48
|
/**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
* `neon api-keys …`, under either spelling. Exempts the group from context enrichment: how
|
|
50
|
+
* far a credential reaches must come from an explicit flag, never from `.neon`.
|
|
51
|
+
*/
|
|
52
|
+
const isApiKeysCommand = (args) => args._[0] === "api-keys" || args._[0] === "api-key";
|
|
57
53
|
const CONTEXT_FILE = ".neon";
|
|
58
54
|
const GITIGNORE_FILE = ".gitignore";
|
|
59
55
|
const canAccessFile = (file) => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
56
|
+
try {
|
|
57
|
+
accessSync(file);
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
67
62
|
};
|
|
68
63
|
/**
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
currentDir = parentDir;
|
|
87
|
-
}
|
|
88
|
-
return resolvePath(cwd, CONTEXT_FILE);
|
|
64
|
+
* Walk upward to find an existing `.neon` file.
|
|
65
|
+
*
|
|
66
|
+
* The walk keeps the established home and POSIX-root boundaries, then also
|
|
67
|
+
* stops when resolving a parent makes no progress. That second guard handles
|
|
68
|
+
* Windows drive and UNC-share roots, whose paths do not equal `normalize("/")`.
|
|
69
|
+
*/
|
|
70
|
+
const walkContextFile = (cwd, root, home, resolvePath, canAccess) => {
|
|
71
|
+
let currentDir = cwd;
|
|
72
|
+
while (currentDir !== root && currentDir !== home) {
|
|
73
|
+
const contextFile = resolvePath(currentDir, CONTEXT_FILE);
|
|
74
|
+
if (canAccess(contextFile)) return contextFile;
|
|
75
|
+
const parentDir = resolvePath(currentDir, "..");
|
|
76
|
+
if (parentDir === currentDir) break;
|
|
77
|
+
currentDir = parentDir;
|
|
78
|
+
}
|
|
79
|
+
return resolvePath(cwd, CONTEXT_FILE);
|
|
89
80
|
};
|
|
90
81
|
/**
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
82
|
+
* Resolve the default `.neon` path for the current working directory.
|
|
83
|
+
*
|
|
84
|
+
* Walks UP from `cwd` looking ONLY for an already-existing `.neon` file so
|
|
85
|
+
* commands run from a sub-directory of a linked project still pick up the
|
|
86
|
+
* project's context. If no `.neon` is found, the path defaults to
|
|
87
|
+
* `<cwd>/.neon`, which makes `neonctl link` and `neonctl set-context`
|
|
88
|
+
* predictable: they always write the context file into the directory they
|
|
89
|
+
* were invoked from.
|
|
90
|
+
*
|
|
91
|
+
* Historically the walk also considered `package.json` and `.git` as project
|
|
92
|
+
* markers, but that led to surprising behaviour when running `link` from a
|
|
93
|
+
* fresh sub-directory inside an unrelated repo (the new link would land in
|
|
94
|
+
* the parent repo's root instead of the cwd).
|
|
95
|
+
*
|
|
96
|
+
* `cwd` is overridable so tests can exercise the walk-up without mutating
|
|
97
|
+
* `process.cwd()` (which would race with other tests running in parallel).
|
|
98
|
+
*/
|
|
99
|
+
const currentContextFile = (cwd = process.cwd()) => walkContextFile(cwd, normalize("/"), homedir(), resolve, canAccessFile);
|
|
100
|
+
const readContextFile = (file) => {
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(readFileSync(file, "utf-8"));
|
|
103
|
+
} catch {
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
116
106
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// user typed — never something inherited from whichever project happens to be checked
|
|
126
|
-
// out. Enriched here, `api-keys create --name ci` in a linked directory would quietly
|
|
127
|
-
// produce a key scoped to that project instead of the account key it asked for.
|
|
128
|
-
if (isApiKeysCommand(args)) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
// `profile create --mint` mints one too, and for the same reason must take its scope only
|
|
132
|
-
// from what was typed: enriched here, running it inside a linked directory would quietly
|
|
133
|
-
// produce a key scoped to that project rather than the account or organization asked for.
|
|
134
|
-
// No `profile` subcommand has any use for a project or branch.
|
|
135
|
-
if (isProfileCommand(args)) {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
const context = readContextFile(args.contextFile);
|
|
139
|
-
if (!args.orgId) {
|
|
140
|
-
args.orgId = context.orgId;
|
|
141
|
-
}
|
|
142
|
-
if (!args.projectId) {
|
|
143
|
-
args.projectId = context.projectId;
|
|
144
|
-
}
|
|
145
|
-
if (!args.branch &&
|
|
146
|
-
!args.id &&
|
|
147
|
-
!args.name &&
|
|
148
|
-
context.projectId === args.projectId) {
|
|
149
|
-
args.branch = contextBranch(context);
|
|
150
|
-
}
|
|
107
|
+
const enrichFromContext = (args) => {
|
|
108
|
+
if (args._[0] === "link" || args._[0] === "set-context") return;
|
|
109
|
+
if (isApiKeysCommand(args)) return;
|
|
110
|
+
if (isProfileCommand(args)) return;
|
|
111
|
+
const context = readContextFile(args.contextFile);
|
|
112
|
+
if (!args.orgId) args.orgId = context.orgId;
|
|
113
|
+
if (!args.projectId) args.projectId = context.projectId;
|
|
114
|
+
if (!args.branch && !args.id && !args.name && context.projectId === args.projectId) args.branch = contextBranch(context);
|
|
151
115
|
};
|
|
152
|
-
|
|
153
|
-
|
|
116
|
+
const updateContextFile = (file, context) => {
|
|
117
|
+
writeFileSync(file, JSON.stringify(context, null, 2));
|
|
154
118
|
};
|
|
155
119
|
/**
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
ensureGitignored(file);
|
|
172
|
-
}
|
|
120
|
+
* Shared primitive used by `link`, the deprecated `set-context`, and `checkout`
|
|
121
|
+
* to persist context. Mirrors the destructive write semantics of
|
|
122
|
+
* `updateContextFile` — any field not present in `context` is dropped from the
|
|
123
|
+
* file.
|
|
124
|
+
*
|
|
125
|
+
* `.gitignore` scaffolding only happens when the context file is being
|
|
126
|
+
* *created* (it didn't exist before this write). On updates to an existing
|
|
127
|
+
* `.neon` we never touch `.gitignore`, so a user who deliberately un-ignored
|
|
128
|
+
* the file (e.g. to commit shared context) won't have the entry re-added on
|
|
129
|
+
* every subsequent command.
|
|
130
|
+
*/
|
|
131
|
+
const applyContext = (file, context) => {
|
|
132
|
+
const isNewFile = !existsSync(file);
|
|
133
|
+
updateContextFile(file, context);
|
|
134
|
+
if (isNewFile) ensureGitignored(file);
|
|
173
135
|
};
|
|
174
136
|
/**
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
137
|
+
* Low-level writer for callers that already hold the resolved identifiers and
|
|
138
|
+
* just need to record them — e.g. `init` or `projects create`, which create a
|
|
139
|
+
* project and want to link it without the resolution, verification, prompting,
|
|
140
|
+
* or env-pull that `link` performs.
|
|
141
|
+
*
|
|
142
|
+
* Unlike the loose {@link applyContext}, this enforces at the type level that
|
|
143
|
+
* `orgId` and `projectId` are present, so the `.neon` file never ends up with a
|
|
144
|
+
* dangling project that has no org. The branch stays optional. It writes through
|
|
145
|
+
* {@link applyContext}, so the same `.gitignore` scaffolding applies.
|
|
146
|
+
*/
|
|
147
|
+
const setContext = (file, context) => {
|
|
148
|
+
applyContext(file, {
|
|
149
|
+
orgId: context.orgId,
|
|
150
|
+
projectId: context.projectId,
|
|
151
|
+
branch: context.branch
|
|
152
|
+
});
|
|
191
153
|
};
|
|
192
154
|
/**
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
catch (err) {
|
|
222
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
223
|
-
log.debug("Failed to update .gitignore next to %s: %s", file, message);
|
|
224
|
-
}
|
|
155
|
+
* Make sure the `.gitignore` next to `file` covers the file's basename — used for the `.neon`
|
|
156
|
+
* context file and for a `.env` we create (both carry credentials that must not be committed).
|
|
157
|
+
* Creates the `.gitignore` if it doesn't exist, otherwise appends the entry only when nothing
|
|
158
|
+
* there already covers it: an exact line, or a basename glob such as `.env*` / `*.local`
|
|
159
|
+
* (see {@link gitignoreCovers}), so a repo that already ignores env files doesn't collect a
|
|
160
|
+
* redundant line per pull.
|
|
161
|
+
*
|
|
162
|
+
* Best-effort: a failure here (e.g. read-only filesystem) is logged at debug
|
|
163
|
+
* level and swallowed; persisting the context file is the primary goal and
|
|
164
|
+
* must not be blocked by a `.gitignore` write error.
|
|
165
|
+
*/
|
|
166
|
+
const ensureGitignored = (file) => {
|
|
167
|
+
try {
|
|
168
|
+
const dir = dirname(file);
|
|
169
|
+
const entry = basenameOf(file);
|
|
170
|
+
const gitignorePath = resolve(dir, GITIGNORE_FILE);
|
|
171
|
+
if (!existsSync(gitignorePath)) {
|
|
172
|
+
writeFileSync(gitignorePath, `${entry}\n`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const current = readFileSync(gitignorePath, "utf-8");
|
|
176
|
+
if (hasGitignoreEntry(current, entry)) return;
|
|
177
|
+
const addition = `${current.length > 0 && !current.endsWith("\n") ? "\n" : ""}${entry}\n`;
|
|
178
|
+
writeFileSync(gitignorePath, current + addition);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
181
|
+
log.debug("Failed to update .gitignore next to %s: %s", file, message);
|
|
182
|
+
}
|
|
225
183
|
};
|
|
226
184
|
const basenameOf = (file) => {
|
|
227
|
-
|
|
228
|
-
|
|
185
|
+
const parts = file.split(/[\\/]/);
|
|
186
|
+
return parts[parts.length - 1] || CONTEXT_FILE;
|
|
229
187
|
};
|
|
230
188
|
const hasGitignoreEntry = (content, entry) => {
|
|
231
|
-
|
|
232
|
-
.split(/\r?\n/)
|
|
233
|
-
.some((line) => gitignoreCovers(line.trim(), entry));
|
|
189
|
+
return content.split(/\r?\n/).some((line) => gitignoreCovers(line.trim(), entry));
|
|
234
190
|
};
|
|
235
191
|
/**
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
192
|
+
* Whether a single `.gitignore` line already ignores `entry` (a bare basename like `.neon` or
|
|
193
|
+
* `.env.local`).
|
|
194
|
+
*
|
|
195
|
+
* Deliberately narrow: an exact match, or a glob **without a path separator** — the
|
|
196
|
+
* `.env*` / `*.local` / `.env.?` shapes that repos actually use for env files — matched
|
|
197
|
+
* against the whole basename. Everything else returns false, which at worst appends an entry
|
|
198
|
+
* git already covers (harmless) rather than skipping one it doesn't (a committed credential).
|
|
199
|
+
* That's why path-scoped patterns (`config/.env`), negations (`!.env.local`), character
|
|
200
|
+
* classes, and comments are all treated as "does not cover".
|
|
201
|
+
*/
|
|
246
202
|
const gitignoreCovers = (line, entry) => {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (pattern === entry)
|
|
254
|
-
return true;
|
|
255
|
-
if (pattern.includes("/") || pattern.includes("["))
|
|
256
|
-
return false;
|
|
257
|
-
if (!pattern.includes("*") && !pattern.includes("?"))
|
|
258
|
-
return false;
|
|
259
|
-
return globToRegExp(pattern).test(entry);
|
|
203
|
+
if (line === "" || line.startsWith("#") || line.startsWith("!")) return false;
|
|
204
|
+
const pattern = line.replace(/\/$/, "").replace(/^\//, "");
|
|
205
|
+
if (pattern === entry) return true;
|
|
206
|
+
if (pattern.includes("/") || pattern.includes("[")) return false;
|
|
207
|
+
if (!pattern.includes("*") && !pattern.includes("?")) return false;
|
|
208
|
+
return globToRegExp(pattern).test(entry);
|
|
260
209
|
};
|
|
261
210
|
/** Compile a separator-free `.gitignore` glob (`*` / `?` only) into an anchored RegExp. */
|
|
262
211
|
const globToRegExp = (pattern) => {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
.replace(/\*/g, "[^/]*")
|
|
266
|
-
.replace(/\?/g, "[^/]");
|
|
267
|
-
return new RegExp(`^${source}$`);
|
|
212
|
+
const source = pattern.replace(/[.+^${}()|\\]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
|
|
213
|
+
return new RegExp(`^${source}$`);
|
|
268
214
|
};
|
|
215
|
+
//#endregion
|
|
216
|
+
export { applyContext, contextBranch, currentContextFile, enrichFromContext, ensureGitignored, isApiKeysCommand, isConfigInit, isCurrentBranchProbe, isProfileCommand, readContextFile, setContext, updateContextFile, walkContextFile };
|
|
@@ -1,56 +1,47 @@
|
|
|
1
|
-
import { contextBranch, currentContextFile, readContextFile, } from "./context.js";
|
|
2
1
|
import { log } from "./log.js";
|
|
2
|
+
import { contextBranch, currentContextFile, readContextFile } from "./context.js";
|
|
3
3
|
import { getCliName } from "./utils/cli_name.js";
|
|
4
|
+
//#region src/current_branch_fast_path.ts
|
|
4
5
|
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (branch) {
|
|
36
|
-
process.stdout.write(`${branch}\n`);
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
log.info(`No branch pinned. Run \`${getCliName()} checkout <branch>\` to pin a branch and pull its env vars.`);
|
|
40
|
-
process.exitCode = 1;
|
|
41
|
-
}
|
|
42
|
-
return true;
|
|
6
|
+
* Offline fast path for `(config) status --current-branch` (used by shell prompts).
|
|
7
|
+
*
|
|
8
|
+
* Reading the pinned branch out of the local `.neon` file does not need the CLI's
|
|
9
|
+
* full command tree, `@neondatabase/api-client`, or yargs — importing those is ~200ms,
|
|
10
|
+
* which dwarfs the actual work. So the entry point ({@link file://./cli.ts}) calls this
|
|
11
|
+
* BEFORE importing `index.js`, and only falls through to the full CLI when this returns
|
|
12
|
+
* `false`. On the fast path the process loads only this module + `context.js`/`log.js`
|
|
13
|
+
* (~25ms total incl. Node startup) instead of ~230ms.
|
|
14
|
+
*
|
|
15
|
+
* It mirrors the `--current-branch` short-circuit in `status()` (commands/config.ts):
|
|
16
|
+
* print the pinned branch to stdout and exit 0, or print nothing + a `neonctl checkout`
|
|
17
|
+
* hint on stderr and exit non-zero when no branch is pinned.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately conservative: only the EXACT bare invocation is handled —
|
|
20
|
+
* `status --current-branch` or `config status --current-branch` with no other args.
|
|
21
|
+
* Anything else (extra flags like `--context-file`/`--output`, more args, etc.) returns
|
|
22
|
+
* `false` and flows through the normal yargs pipeline, so behavior can never diverge —
|
|
23
|
+
* the worst case is "not faster", never "wrong".
|
|
24
|
+
*
|
|
25
|
+
* @returns `true` if it handled the invocation (caller should NOT load the full CLI).
|
|
26
|
+
*/
|
|
27
|
+
const tryCurrentBranchFastPath = (argv, cwd = process.cwd()) => {
|
|
28
|
+
if (!isExactCurrentBranchInvocation(argv.slice(2))) return false;
|
|
29
|
+
const branch = contextBranch(readContextFile(currentContextFile(cwd)));
|
|
30
|
+
if (branch) process.stdout.write(`${branch}\n`);
|
|
31
|
+
else {
|
|
32
|
+
log.info(`No branch pinned. Run \`${getCliName()} checkout <branch>\` to pin a branch and pull its env vars.`);
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
43
36
|
};
|
|
44
37
|
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
38
|
+
* True only for `status --current-branch` or `config status --current-branch` with no
|
|
39
|
+
* other arguments. Any extra token (another flag, `--context-file`, `=`-style flags,
|
|
40
|
+
* positional args) makes this false so the full CLI handles it.
|
|
41
|
+
*/
|
|
49
42
|
const isExactCurrentBranchInvocation = (args) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
: args[0] === "config" && args[1] === "status"
|
|
53
|
-
? args.slice(2)
|
|
54
|
-
: null;
|
|
55
|
-
return rest !== null && rest.length === 1 && rest[0] === "--current-branch";
|
|
43
|
+
const rest = args[0] === "status" ? args.slice(1) : args[0] === "config" && args[1] === "status" ? args.slice(2) : null;
|
|
44
|
+
return rest !== null && rest.length === 1 && rest[0] === "--current-branch";
|
|
56
45
|
};
|
|
46
|
+
//#endregion
|
|
47
|
+
export { tryCurrentBranchFastPath };
|