@neondatabase/env 0.15.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -29
- package/dist/cli.js +971 -6
- package/dist/cli.js.map +1 -1
- package/dist/{lib/env.js → env.js} +70 -219
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +528 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +197 -1
- package/dist/index.js.map +1 -0
- package/package.json +11 -14
- package/dist/_shared/auth_selection.js +0 -76
- package/dist/_shared/auth_selection.js.map +0 -1
- package/dist/_shared/credentials.js +0 -131
- package/dist/_shared/credentials.js.map +0 -1
- package/dist/_shared/paths.js +0 -131
- package/dist/_shared/paths.js.map +0 -1
- package/dist/_shared/profiles.d.ts +0 -7
- package/dist/_shared/profiles.d.ts.map +0 -1
- package/dist/_shared/profiles.js +0 -124
- package/dist/_shared/profiles.js.map +0 -1
- package/dist/config/dist/lib/define-config.d.ts +0 -20
- package/dist/config/dist/lib/define-config.d.ts.map +0 -1
- package/dist/config/dist/lib/neon-api.d.ts +0 -375
- package/dist/config/dist/lib/neon-api.d.ts.map +0 -1
- package/dist/config/dist/lib/types.d.ts +0 -544
- package/dist/config/dist/lib/types.d.ts.map +0 -1
- package/dist/config/dist/v1.d.ts +0 -5
- package/dist/lib/cli/commands.d.ts +0 -68
- package/dist/lib/cli/commands.d.ts.map +0 -1
- package/dist/lib/cli/commands.js +0 -233
- package/dist/lib/cli/commands.js.map +0 -1
- package/dist/lib/cli/resolve-api-key.d.ts +0 -29
- package/dist/lib/cli/resolve-api-key.d.ts.map +0 -1
- package/dist/lib/cli/resolve-api-key.js +0 -74
- package/dist/lib/cli/resolve-api-key.js.map +0 -1
- package/dist/lib/cli/resolve-context.d.ts +0 -34
- package/dist/lib/cli/resolve-context.d.ts.map +0 -1
- package/dist/lib/cli/resolve-context.js +0 -88
- package/dist/lib/cli/resolve-context.js.map +0 -1
- package/dist/lib/env.d.ts +0 -509
- package/dist/lib/env.d.ts.map +0 -1
- package/dist/lib/env.js.map +0 -1
- package/dist/lib/reuse-secrets.d.ts +0 -95
- package/dist/lib/reuse-secrets.d.ts.map +0 -1
- package/dist/lib/reuse-secrets.js +0 -181
- package/dist/lib/reuse-secrets.js.map +0 -1
- package/dist/runtime.d.ts +0 -2
- package/dist/runtime.js +0 -2
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { NeonApi } from "../../config/dist/lib/neon-api.js";
|
|
2
|
-
import "../../config/dist/v1.js";
|
|
3
|
-
|
|
4
|
-
//#region src/lib/cli/commands.d.ts
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Cross-cutting environment a CLI command is allowed to touch. Injected so tests can drive
|
|
8
|
-
* the handler with a custom NeonApi and a controlled `cwd` without spawning child
|
|
9
|
-
* processes.
|
|
10
|
-
*/
|
|
11
|
-
interface CommandEnv {
|
|
12
|
-
cwd: string;
|
|
13
|
-
/**
|
|
14
|
-
* When set, used directly as the NeonApi. When omitted, the real adapter is built from
|
|
15
|
-
* the key {@link resolveApiKey} resolves (`--api-key` → `NEON_API_KEY` → the Neon CLI's
|
|
16
|
-
* stored credentials).
|
|
17
|
-
*/
|
|
18
|
-
api?: NeonApi;
|
|
19
|
-
}
|
|
20
|
-
interface CommandResult {
|
|
21
|
-
/** Process exit code. `0` for success, non-zero for failure. */
|
|
22
|
-
exitCode: number;
|
|
23
|
-
/** Text intended for stdout. */
|
|
24
|
-
stdout: string;
|
|
25
|
-
/** Text intended for stderr (human-readable status / error messages). */
|
|
26
|
-
stderr: string;
|
|
27
|
-
/** Optional structured debug payload — printed only when `--debug` is passed. */
|
|
28
|
-
debugInfo?: string;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Inputs needed to resolve a branch and fetch its env, shared by `run` and `export`: an
|
|
32
|
-
* optional explicit `neon.ts` path, project/branch overrides, and an API key. Everything
|
|
33
|
-
* ambient — `.neon`, `NEON_*` env, the Neon CLI's stored credentials — is resolved by the
|
|
34
|
-
* CLI (see `resolveContext` and `resolveApiKey`), never by the library.
|
|
35
|
-
*/
|
|
36
|
-
interface EnvResolveOptions {
|
|
37
|
-
configPath?: string;
|
|
38
|
-
projectId?: string;
|
|
39
|
-
branch?: string;
|
|
40
|
-
apiKey?: string;
|
|
41
|
-
/** Neon CLI profile whose stored credential to use. `--profile`, else `NEON_PROFILE`. */
|
|
42
|
-
profile?: string;
|
|
43
|
-
}
|
|
44
|
-
interface EnvRunCommandOptions extends EnvResolveOptions {
|
|
45
|
-
/** The user command to spawn (after `--`). The first element is the executable. */
|
|
46
|
-
command: string[];
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Implementation of `neon-env run -- <cmd...>`. Loads `neon.ts`, fetches the env from
|
|
50
|
-
* Neon, then spawns the user-supplied command with the env vars injected on top of the
|
|
51
|
-
* inherited `process.env`. Stdio is inherited so interactive dev servers keep working.
|
|
52
|
-
* The parent process exits with the child's exit code.
|
|
53
|
-
*/
|
|
54
|
-
declare function runEnvRun(options: EnvRunCommandOptions, ctx: CommandEnv): Promise<CommandResult>;
|
|
55
|
-
interface EnvExportCommandOptions extends EnvResolveOptions {
|
|
56
|
-
/** Output format. `dotenv` (KEY=value lines) by default; `json` for tooling / bulk loaders. */
|
|
57
|
-
format?: "dotenv" | "json";
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Implementation of `neon-env export`. Resolves the branch's Neon env the same way `run`
|
|
61
|
-
* does (neon.ts policy + linked branch), then writes it to stdout — as dotenv lines or JSON —
|
|
62
|
-
* instead of spawning a process, so other env tools can consume it. For example, varlock can
|
|
63
|
-
* bulk-load it with `@setValuesBulk(exec("neon-env export --format json"), format=json)`.
|
|
64
|
-
*/
|
|
65
|
-
declare function runEnvExport(options: EnvExportCommandOptions, ctx: CommandEnv): Promise<CommandResult>;
|
|
66
|
-
//#endregion
|
|
67
|
-
export { CommandEnv, CommandResult, EnvExportCommandOptions, EnvResolveOptions, EnvRunCommandOptions, runEnvExport, runEnvRun };
|
|
68
|
-
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","names":[],"sources":["../../../src/lib/cli/commands.ts"],"mappings":";;;;;;;;AAuBA;AAUA;AAiBiB,UA3BA,UAAA,CA2BiB;EASjB,GAAA,EAAA,MAAA;EAWK;AAAS;AACrB;AACJ;AACK;EAAR,GAAA,CAAA,EA3CI,OA2CJ;AAAO;AA2CO,UAnFA,aAAA,CAmFwB;EAWnB;EAAY,QAAA,EAAA,MAAA;EACxB;EACJ,MAAA,EAAA,MAAA;EACK;EAAR,MAAA,EAAA,MAAA;EAAO;;;;;;;;;UAhFO,iBAAA;;;;;;;;UASA,oBAAA,SAA6B;;;;;;;;;;iBAWxB,SAAA,UACZ,2BACJ,aACH,QAAQ;UA2CM,uBAAA,SAAgC;;;;;;;;;;iBAW3B,YAAA,UACZ,8BACJ,aACH,QAAQ"}
|
package/dist/lib/cli/commands.js
DELETED
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import { fetchEnvReusingSecrets } from "../reuse-secrets.js";
|
|
2
|
-
import { resolveApiKey } from "./resolve-api-key.js";
|
|
3
|
-
import { resolveContext } from "./resolve-context.js";
|
|
4
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
-
import { spawn } from "node:child_process";
|
|
6
|
-
import { dirname, join } from "node:path";
|
|
7
|
-
import { ConfigLoadError, ErrorCode, MissingContextError, PlatformError, loadConfigFromFile } from "@neon/config/v1";
|
|
8
|
-
//#region src/lib/cli/commands.ts
|
|
9
|
-
/** File `env run` reads to layer one-time auth keys. Matches the Vercel/Next.js convention. */
|
|
10
|
-
const DEFAULT_ENV_FILE = ".env.local";
|
|
11
|
-
/**
|
|
12
|
-
* Implementation of `neon-env run -- <cmd...>`. Loads `neon.ts`, fetches the env from
|
|
13
|
-
* Neon, then spawns the user-supplied command with the env vars injected on top of the
|
|
14
|
-
* inherited `process.env`. Stdio is inherited so interactive dev servers keep working.
|
|
15
|
-
* The parent process exits with the child's exit code.
|
|
16
|
-
*/
|
|
17
|
-
async function runEnvRun(options, ctx) {
|
|
18
|
-
if (options.command.length === 0) return failure([
|
|
19
|
-
"`env run` requires a command to spawn.",
|
|
20
|
-
"Usage: neon-env run -- <command> [args...]",
|
|
21
|
-
"Example: neon-env run -- npm run dev"
|
|
22
|
-
].join("\n"));
|
|
23
|
-
const resolved = resolveContext({
|
|
24
|
-
cwd: ctx.cwd,
|
|
25
|
-
...options.projectId ? { projectId: options.projectId } : {},
|
|
26
|
-
...options.branch ? { branch: options.branch } : {}
|
|
27
|
-
});
|
|
28
|
-
if (!resolved.ok) return failure(["`env run` could not resolve the Neon project and branch:", ...resolved.missing.map((m) => ` - ${m}`)].join("\n"), 3);
|
|
29
|
-
let injected;
|
|
30
|
-
try {
|
|
31
|
-
injected = await loadConfigAndFetchEnv(options, ctx, resolved.context);
|
|
32
|
-
} catch (err) {
|
|
33
|
-
return handleError(err);
|
|
34
|
-
}
|
|
35
|
-
const [executable, ...args] = options.command;
|
|
36
|
-
return {
|
|
37
|
-
exitCode: await spawnAndWait(executable, args, {
|
|
38
|
-
cwd: ctx.cwd,
|
|
39
|
-
env: {
|
|
40
|
-
...process.env,
|
|
41
|
-
...injected
|
|
42
|
-
}
|
|
43
|
-
}),
|
|
44
|
-
stdout: "",
|
|
45
|
-
stderr: ""
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Implementation of `neon-env export`. Resolves the branch's Neon env the same way `run`
|
|
50
|
-
* does (neon.ts policy + linked branch), then writes it to stdout — as dotenv lines or JSON —
|
|
51
|
-
* instead of spawning a process, so other env tools can consume it. For example, varlock can
|
|
52
|
-
* bulk-load it with `@setValuesBulk(exec("neon-env export --format json"), format=json)`.
|
|
53
|
-
*/
|
|
54
|
-
async function runEnvExport(options, ctx) {
|
|
55
|
-
const resolved = resolveContext({
|
|
56
|
-
cwd: ctx.cwd,
|
|
57
|
-
...options.projectId ? { projectId: options.projectId } : {},
|
|
58
|
-
...options.branch ? { branch: options.branch } : {}
|
|
59
|
-
});
|
|
60
|
-
if (!resolved.ok) return failure(["`env export` could not resolve the Neon project and branch:", ...resolved.missing.map((m) => ` - ${m}`)].join("\n"), 3);
|
|
61
|
-
let entries;
|
|
62
|
-
try {
|
|
63
|
-
entries = await loadConfigAndFetchEnv(options, ctx, resolved.context);
|
|
64
|
-
} catch (err) {
|
|
65
|
-
return handleError(err);
|
|
66
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
exitCode: 0,
|
|
69
|
-
stdout: options.format === "json" ? `${JSON.stringify(entries, null, 2)}\n` : toDotenv(entries),
|
|
70
|
-
stderr: ""
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
/** Render an env map as dotenv `KEY=value` lines, quoting values that need it. */
|
|
74
|
-
function toDotenv(entries) {
|
|
75
|
-
const lines = Object.entries(entries).map(([key, value]) => formatDotenvLine(key, value));
|
|
76
|
-
return lines.length > 0 ? `${lines.join("\n")}\n` : "";
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Render a single `KEY=value` dotenv line, double-quoting (and escaping) values that contain
|
|
80
|
-
* whitespace, `#`, quotes, or `=` so connection strings round-trip through dotenv parsers.
|
|
81
|
-
*/
|
|
82
|
-
function formatDotenvLine(key, value) {
|
|
83
|
-
if (!/[\s#"'=]/.test(value)) return `${key}=${value}`;
|
|
84
|
-
return `${key}="${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Load `neon.ts`, then resolve the branch env for the explicitly-resolved project + branch.
|
|
88
|
-
* Layers `.env.local` (next to the config file) into the env source so re-runs keep the
|
|
89
|
-
* one-time secrets the Neon API only returns once — the branch credential's, and any Auth
|
|
90
|
-
* values a pre-`base_url` integration can no longer report. Uses
|
|
91
|
-
* {@link fetchEnvReusingSecrets} rather than a bare `fetchEnv` so a run that already has a
|
|
92
|
-
* working credential verifies and keeps it instead of minting another one per invocation.
|
|
93
|
-
*/
|
|
94
|
-
async function loadConfigAndFetchEnv(options, ctx, resolved) {
|
|
95
|
-
const { config, resolvedPath } = await loadConfigFromFile({
|
|
96
|
-
...options.configPath ? { path: options.configPath } : {},
|
|
97
|
-
cwd: ctx.cwd
|
|
98
|
-
});
|
|
99
|
-
const envFileSource = join(dirname(resolvedPath), DEFAULT_ENV_FILE);
|
|
100
|
-
const fileEnv = existsSync(envFileSource) ? parseEnvFile(readFileSync(envFileSource, "utf-8")) : {};
|
|
101
|
-
const apiKey = resolveApiKey({
|
|
102
|
-
...options.apiKey ? { apiKey: options.apiKey } : {},
|
|
103
|
-
...options.profile ? { profile: options.profile } : {}
|
|
104
|
-
});
|
|
105
|
-
const { vars } = await fetchEnvReusingSecrets(config, {
|
|
106
|
-
projectId: resolved.projectId,
|
|
107
|
-
branch: resolved.branch,
|
|
108
|
-
env: {
|
|
109
|
-
...process.env,
|
|
110
|
-
...fileEnv
|
|
111
|
-
},
|
|
112
|
-
...ctx.api ? { api: ctx.api } : {},
|
|
113
|
-
...apiKey ? { apiKey } : {}
|
|
114
|
-
});
|
|
115
|
-
return vars;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Spawn a child process with stdio inherited so dev servers stay interactive. Resolves
|
|
119
|
-
* with the child's exit code (treating signal terminations as code 1 so the CLI surfaces
|
|
120
|
-
* a non-zero exit consistently).
|
|
121
|
-
*/
|
|
122
|
-
function spawnAndWait(command, args, options) {
|
|
123
|
-
return new Promise((resolve) => {
|
|
124
|
-
const child = spawn(command, args, {
|
|
125
|
-
cwd: options.cwd,
|
|
126
|
-
env: options.env,
|
|
127
|
-
stdio: "inherit"
|
|
128
|
-
});
|
|
129
|
-
child.on("error", (err) => {
|
|
130
|
-
process.stderr.write(`neon-env run: failed to spawn '${command}': ${err.message}\n`);
|
|
131
|
-
resolve(1);
|
|
132
|
-
});
|
|
133
|
-
child.on("exit", (code, signal) => {
|
|
134
|
-
if (typeof code === "number") {
|
|
135
|
-
resolve(code);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
if (signal) {
|
|
139
|
-
process.stderr.write(`neon-env run: child terminated by signal ${signal}\n`);
|
|
140
|
-
resolve(1);
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
resolve(1);
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
function parseEnvFile(body) {
|
|
148
|
-
const out = {};
|
|
149
|
-
for (const line of body.split("\n")) {
|
|
150
|
-
const parsed = parseEnvLine(line);
|
|
151
|
-
if (parsed) out[parsed.key] = parsed.value;
|
|
152
|
-
}
|
|
153
|
-
return out;
|
|
154
|
-
}
|
|
155
|
-
function parseEnvLine(line) {
|
|
156
|
-
const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
157
|
-
const key = match?.[1];
|
|
158
|
-
const rawValue = match?.[2];
|
|
159
|
-
if (key === void 0 || rawValue === void 0) return null;
|
|
160
|
-
return {
|
|
161
|
-
key,
|
|
162
|
-
value: unescapeEnvValue(rawValue.trim())
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
function unescapeEnvValue(value) {
|
|
166
|
-
if (value.length >= 2 && value.startsWith("\"") && value.endsWith("\"")) return value.slice(1, -1).replace(/\\"/g, "\"").replace(/\\\\/g, "\\");
|
|
167
|
-
if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
168
|
-
return value;
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Stable exit code per `PlatformError` code. Mirrors the table in the config package so
|
|
172
|
-
* shell pipelines can branch on the specific failure mode without parsing free text.
|
|
173
|
-
*/
|
|
174
|
-
const EXIT_CODE_BY_PLATFORM_ERROR_CODE = {
|
|
175
|
-
[ErrorCode.MissingApiKey]: 1,
|
|
176
|
-
[ErrorCode.Unauthorized]: 6,
|
|
177
|
-
[ErrorCode.Forbidden]: 7,
|
|
178
|
-
[ErrorCode.NotFound]: 8,
|
|
179
|
-
[ErrorCode.RateLimited]: 9,
|
|
180
|
-
[ErrorCode.NetworkError]: 10,
|
|
181
|
-
[ErrorCode.ServerError]: 11,
|
|
182
|
-
[ErrorCode.Locked]: 11,
|
|
183
|
-
[ErrorCode.InternalError]: 99
|
|
184
|
-
};
|
|
185
|
-
function handleError(err) {
|
|
186
|
-
if (err instanceof MissingContextError) return errorResult(err, `Missing context: ${err.message}`, 3);
|
|
187
|
-
if (err instanceof ConfigLoadError) return errorResult(err, `Failed to load config: ${err.message}`, 4);
|
|
188
|
-
if (err instanceof PlatformError && err.code === ErrorCode.MissingApiKey) return errorResult(err, [
|
|
189
|
-
"No Neon API key. `neon-env` looks for one in this order:",
|
|
190
|
-
" - the `--api-key` flag",
|
|
191
|
-
" - the `NEON_API_KEY` environment variable",
|
|
192
|
-
" - `credentials.json` in `NEONCTL_CONFIG_DIR` (else `~/.config/neonctl`) — run `neon auth` to create it"
|
|
193
|
-
].join("\n"), EXIT_CODE_BY_PLATFORM_ERROR_CODE[ErrorCode.MissingApiKey] ?? 1);
|
|
194
|
-
if (err instanceof PlatformError) {
|
|
195
|
-
const exitCode = EXIT_CODE_BY_PLATFORM_ERROR_CODE[err.code];
|
|
196
|
-
if (exitCode !== void 0) return errorResult(err, err.message, exitCode);
|
|
197
|
-
return errorResult(err, `[${err.code}] ${err.message}`, 5);
|
|
198
|
-
}
|
|
199
|
-
if (err instanceof Error) return errorResult(err, err.message, 1);
|
|
200
|
-
return failure(String(err), 1);
|
|
201
|
-
}
|
|
202
|
-
function errorResult(err, message, exitCode) {
|
|
203
|
-
const result = {
|
|
204
|
-
exitCode,
|
|
205
|
-
stdout: "",
|
|
206
|
-
stderr: `${message}\n`
|
|
207
|
-
};
|
|
208
|
-
const debug = buildDebugInfo(err);
|
|
209
|
-
if (debug) result.debugInfo = debug;
|
|
210
|
-
return result;
|
|
211
|
-
}
|
|
212
|
-
function buildDebugInfo(err) {
|
|
213
|
-
if (!(err instanceof Error)) return void 0;
|
|
214
|
-
const lines = [];
|
|
215
|
-
if (err instanceof PlatformError) {
|
|
216
|
-
lines.push(`code : ${err.code}`);
|
|
217
|
-
if (Object.keys(err.details).length > 0) lines.push(`details : ${JSON.stringify(err.details, null, 2)}`);
|
|
218
|
-
}
|
|
219
|
-
if (err.cause instanceof Error) lines.push(`cause : ${err.cause.name}: ${err.cause.message}`);
|
|
220
|
-
if (err.stack) lines.push(err.stack);
|
|
221
|
-
return lines.length > 0 ? lines.join("\n") : void 0;
|
|
222
|
-
}
|
|
223
|
-
function failure(message, exitCode = 1) {
|
|
224
|
-
return {
|
|
225
|
-
exitCode,
|
|
226
|
-
stdout: "",
|
|
227
|
-
stderr: `${message}\n`
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
//#endregion
|
|
231
|
-
export { runEnvExport, runEnvRun };
|
|
232
|
-
|
|
233
|
-
//# sourceMappingURL=commands.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"commands.js","names":[],"sources":["../../../src/lib/cli/commands.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport {\n\tConfigLoadError,\n\tErrorCode,\n\tloadConfigFromFile,\n\tMissingContextError,\n\ttype NeonApi,\n\tPlatformError,\n} from \"@neon/config/v1\";\nimport { fetchEnvReusingSecrets } from \"../reuse-secrets.js\";\nimport { resolveApiKey } from \"./resolve-api-key.js\";\nimport { resolveContext } from \"./resolve-context.js\";\n\n/** File `env run` reads to layer one-time auth keys. Matches the Vercel/Next.js convention. */\nconst DEFAULT_ENV_FILE = \".env.local\";\n\n/**\n * Cross-cutting environment a CLI command is allowed to touch. Injected so tests can drive\n * the handler with a custom NeonApi and a controlled `cwd` without spawning child\n * processes.\n */\nexport interface CommandEnv {\n\tcwd: string;\n\t/**\n\t * When set, used directly as the NeonApi. When omitted, the real adapter is built from\n\t * the key {@link resolveApiKey} resolves (`--api-key` → `NEON_API_KEY` → the Neon CLI's\n\t * stored credentials).\n\t */\n\tapi?: NeonApi;\n}\n\nexport interface CommandResult {\n\t/** Process exit code. `0` for success, non-zero for failure. */\n\texitCode: number;\n\t/** Text intended for stdout. */\n\tstdout: string;\n\t/** Text intended for stderr (human-readable status / error messages). */\n\tstderr: string;\n\t/** Optional structured debug payload — printed only when `--debug` is passed. */\n\tdebugInfo?: string;\n}\n\n/**\n * Inputs needed to resolve a branch and fetch its env, shared by `run` and `export`: an\n * optional explicit `neon.ts` path, project/branch overrides, and an API key. Everything\n * ambient — `.neon`, `NEON_*` env, the Neon CLI's stored credentials — is resolved by the\n * CLI (see `resolveContext` and `resolveApiKey`), never by the library.\n */\nexport interface EnvResolveOptions {\n\tconfigPath?: string;\n\tprojectId?: string;\n\tbranch?: string;\n\tapiKey?: string;\n\t/** Neon CLI profile whose stored credential to use. `--profile`, else `NEON_PROFILE`. */\n\tprofile?: string;\n}\n\nexport interface EnvRunCommandOptions extends EnvResolveOptions {\n\t/** The user command to spawn (after `--`). The first element is the executable. */\n\tcommand: string[];\n}\n\n/**\n * Implementation of `neon-env run -- <cmd...>`. Loads `neon.ts`, fetches the env from\n * Neon, then spawns the user-supplied command with the env vars injected on top of the\n * inherited `process.env`. Stdio is inherited so interactive dev servers keep working.\n * The parent process exits with the child's exit code.\n */\nexport async function runEnvRun(\n\toptions: EnvRunCommandOptions,\n\tctx: CommandEnv,\n): Promise<CommandResult> {\n\tif (options.command.length === 0) {\n\t\treturn failure(\n\t\t\t[\n\t\t\t\t\"`env run` requires a command to spawn.\",\n\t\t\t\t\"Usage: neon-env run -- <command> [args...]\",\n\t\t\t\t\"Example: neon-env run -- npm run dev\",\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\t// The CLI owns project/branch resolution (flags → NEON_* env → .neon file) so the\n\t// library functions stay filesystem/env-agnostic.\n\tconst resolved = resolveContext({\n\t\tcwd: ctx.cwd,\n\t\t...(options.projectId ? { projectId: options.projectId } : {}),\n\t\t...(options.branch ? { branch: options.branch } : {}),\n\t});\n\tif (!resolved.ok) {\n\t\treturn failure(\n\t\t\t[\n\t\t\t\t\"`env run` could not resolve the Neon project and branch:\",\n\t\t\t\t...resolved.missing.map((m) => ` - ${m}`),\n\t\t\t].join(\"\\n\"),\n\t\t\t3,\n\t\t);\n\t}\n\n\tlet injected: Record<string, string>;\n\ttry {\n\t\tinjected = await loadConfigAndFetchEnv(options, ctx, resolved.context);\n\t} catch (err) {\n\t\treturn handleError(err);\n\t}\n\n\tconst [executable, ...args] = options.command;\n\tconst exitCode = await spawnAndWait(executable, args, {\n\t\tcwd: ctx.cwd,\n\t\tenv: { ...process.env, ...injected },\n\t});\n\treturn { exitCode, stdout: \"\", stderr: \"\" };\n}\n\nexport interface EnvExportCommandOptions extends EnvResolveOptions {\n\t/** Output format. `dotenv` (KEY=value lines) by default; `json` for tooling / bulk loaders. */\n\tformat?: \"dotenv\" | \"json\";\n}\n\n/**\n * Implementation of `neon-env export`. Resolves the branch's Neon env the same way `run`\n * does (neon.ts policy + linked branch), then writes it to stdout — as dotenv lines or JSON —\n * instead of spawning a process, so other env tools can consume it. For example, varlock can\n * bulk-load it with `@setValuesBulk(exec(\"neon-env export --format json\"), format=json)`.\n */\nexport async function runEnvExport(\n\toptions: EnvExportCommandOptions,\n\tctx: CommandEnv,\n): Promise<CommandResult> {\n\tconst resolved = resolveContext({\n\t\tcwd: ctx.cwd,\n\t\t...(options.projectId ? { projectId: options.projectId } : {}),\n\t\t...(options.branch ? { branch: options.branch } : {}),\n\t});\n\tif (!resolved.ok) {\n\t\treturn failure(\n\t\t\t[\n\t\t\t\t\"`env export` could not resolve the Neon project and branch:\",\n\t\t\t\t...resolved.missing.map((m) => ` - ${m}`),\n\t\t\t].join(\"\\n\"),\n\t\t\t3,\n\t\t);\n\t}\n\n\tlet entries: Record<string, string>;\n\ttry {\n\t\tentries = await loadConfigAndFetchEnv(options, ctx, resolved.context);\n\t} catch (err) {\n\t\treturn handleError(err);\n\t}\n\n\tconst stdout =\n\t\toptions.format === \"json\"\n\t\t\t? `${JSON.stringify(entries, null, 2)}\\n`\n\t\t\t: toDotenv(entries);\n\treturn { exitCode: 0, stdout, stderr: \"\" };\n}\n\n/** Render an env map as dotenv `KEY=value` lines, quoting values that need it. */\nfunction toDotenv(entries: Record<string, string>): string {\n\tconst lines = Object.entries(entries).map(([key, value]) =>\n\t\tformatDotenvLine(key, value),\n\t);\n\treturn lines.length > 0 ? `${lines.join(\"\\n\")}\\n` : \"\";\n}\n\n/**\n * Render a single `KEY=value` dotenv line, double-quoting (and escaping) values that contain\n * whitespace, `#`, quotes, or `=` so connection strings round-trip through dotenv parsers.\n */\nfunction formatDotenvLine(key: string, value: string): string {\n\tif (!/[\\s#\"'=]/.test(value)) return `${key}=${value}`;\n\tconst escaped = value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n\treturn `${key}=\"${escaped}\"`;\n}\n\n/**\n * Load `neon.ts`, then resolve the branch env for the explicitly-resolved project + branch.\n * Layers `.env.local` (next to the config file) into the env source so re-runs keep the\n * one-time secrets the Neon API only returns once — the branch credential's, and any Auth\n * values a pre-`base_url` integration can no longer report. Uses\n * {@link fetchEnvReusingSecrets} rather than a bare `fetchEnv` so a run that already has a\n * working credential verifies and keeps it instead of minting another one per invocation.\n */\nasync function loadConfigAndFetchEnv(\n\toptions: EnvResolveOptions,\n\tctx: CommandEnv,\n\tresolved: { projectId: string; branch: string },\n): Promise<Record<string, string>> {\n\tconst { config, resolvedPath } = await loadConfigFromFile({\n\t\t...(options.configPath ? { path: options.configPath } : {}),\n\t\tcwd: ctx.cwd,\n\t});\n\tconst envFileSource = join(dirname(resolvedPath), DEFAULT_ENV_FILE);\n\tconst fileEnv = existsSync(envFileSource)\n\t\t? parseEnvFile(readFileSync(envFileSource, \"utf-8\"))\n\t\t: {};\n\tconst apiKey = resolveApiKey({\n\t\t...(options.apiKey ? { apiKey: options.apiKey } : {}),\n\t\t...(options.profile ? { profile: options.profile } : {}),\n\t});\n\tconst { vars } = await fetchEnvReusingSecrets(config, {\n\t\tprojectId: resolved.projectId,\n\t\tbranch: resolved.branch,\n\t\tenv: { ...process.env, ...fileEnv },\n\t\t...(ctx.api ? { api: ctx.api } : {}),\n\t\t...(apiKey ? { apiKey } : {}),\n\t});\n\treturn vars;\n}\n\n/**\n * Spawn a child process with stdio inherited so dev servers stay interactive. Resolves\n * with the child's exit code (treating signal terminations as code 1 so the CLI surfaces\n * a non-zero exit consistently).\n */\nfunction spawnAndWait(\n\tcommand: string,\n\targs: string[],\n\toptions: { cwd: string; env: Record<string, string | undefined> },\n): Promise<number> {\n\treturn new Promise((resolve) => {\n\t\tconst child = spawn(command, args, {\n\t\t\tcwd: options.cwd,\n\t\t\tenv: options.env,\n\t\t\tstdio: \"inherit\",\n\t\t});\n\t\tchild.on(\"error\", (err) => {\n\t\t\tprocess.stderr.write(\n\t\t\t\t`neon-env run: failed to spawn '${command}': ${err.message}\\n`,\n\t\t\t);\n\t\t\tresolve(1);\n\t\t});\n\t\tchild.on(\"exit\", (code, signal) => {\n\t\t\tif (typeof code === \"number\") {\n\t\t\t\tresolve(code);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (signal) {\n\t\t\t\tprocess.stderr.write(\n\t\t\t\t\t`neon-env run: child terminated by signal ${signal}\\n`,\n\t\t\t\t);\n\t\t\t\tresolve(1);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve(1);\n\t\t});\n\t});\n}\n\nfunction parseEnvFile(body: string): NodeJS.ProcessEnv {\n\tconst out: NodeJS.ProcessEnv = {};\n\tfor (const line of body.split(\"\\n\")) {\n\t\tconst parsed = parseEnvLine(line);\n\t\tif (parsed) out[parsed.key] = parsed.value;\n\t}\n\treturn out;\n}\n\nfunction parseEnvLine(line: string): { key: string; value: string } | null {\n\tconst match = line.match(\n\t\t/^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=\\s*(.*)$/,\n\t);\n\tconst key = match?.[1];\n\tconst rawValue = match?.[2];\n\tif (key === undefined || rawValue === undefined) return null;\n\treturn { key, value: unescapeEnvValue(rawValue.trim()) };\n}\n\nfunction unescapeEnvValue(value: string): string {\n\tif (value.length >= 2 && value.startsWith('\"') && value.endsWith('\"')) {\n\t\treturn value.slice(1, -1).replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, \"\\\\\");\n\t}\n\tif (value.length >= 2 && value.startsWith(\"'\") && value.endsWith(\"'\")) {\n\t\treturn value.slice(1, -1);\n\t}\n\treturn value;\n}\n\n/**\n * Stable exit code per `PlatformError` code. Mirrors the table in the config package so\n * shell pipelines can branch on the specific failure mode without parsing free text.\n */\nconst EXIT_CODE_BY_PLATFORM_ERROR_CODE: Readonly<Record<string, number>> = {\n\t[ErrorCode.MissingApiKey]: 1,\n\t[ErrorCode.Unauthorized]: 6,\n\t[ErrorCode.Forbidden]: 7,\n\t[ErrorCode.NotFound]: 8,\n\t[ErrorCode.RateLimited]: 9,\n\t[ErrorCode.NetworkError]: 10,\n\t[ErrorCode.ServerError]: 11,\n\t[ErrorCode.Locked]: 11,\n\t[ErrorCode.InternalError]: 99,\n};\n\nfunction handleError(err: unknown): CommandResult {\n\tif (err instanceof MissingContextError)\n\t\treturn errorResult(err, `Missing context: ${err.message}`, 3);\n\tif (err instanceof ConfigLoadError)\n\t\treturn errorResult(err, `Failed to load config: ${err.message}`, 4);\n\t// The library's own wording is right for a library (\"this package never reads\n\t// NEON_API_KEY on your behalf\") and wrong here: `neon-env` does read it. Render the\n\t// chain this CLI actually implements, the same way an unresolved context is rendered.\n\tif (err instanceof PlatformError && err.code === ErrorCode.MissingApiKey) {\n\t\treturn errorResult(\n\t\t\terr,\n\t\t\t[\n\t\t\t\t\"No Neon API key. `neon-env` looks for one in this order:\",\n\t\t\t\t\" - the `--api-key` flag\",\n\t\t\t\t\" - the `NEON_API_KEY` environment variable\",\n\t\t\t\t\" - `credentials.json` in `NEONCTL_CONFIG_DIR` (else `~/.config/neonctl`) — run `neon auth` to create it\",\n\t\t\t].join(\"\\n\"),\n\t\t\tEXIT_CODE_BY_PLATFORM_ERROR_CODE[ErrorCode.MissingApiKey] ?? 1,\n\t\t);\n\t}\n\tif (err instanceof PlatformError) {\n\t\tconst exitCode = EXIT_CODE_BY_PLATFORM_ERROR_CODE[err.code];\n\t\tif (exitCode !== undefined)\n\t\t\treturn errorResult(err, err.message, exitCode);\n\t\treturn errorResult(err, `[${err.code}] ${err.message}`, 5);\n\t}\n\tif (err instanceof Error) return errorResult(err, err.message, 1);\n\treturn failure(String(err), 1);\n}\n\nfunction errorResult(\n\terr: unknown,\n\tmessage: string,\n\texitCode: number,\n): CommandResult {\n\tconst result: CommandResult = {\n\t\texitCode,\n\t\tstdout: \"\",\n\t\tstderr: `${message}\\n`,\n\t};\n\tconst debug = buildDebugInfo(err);\n\tif (debug) result.debugInfo = debug;\n\treturn result;\n}\n\nfunction buildDebugInfo(err: unknown): string | undefined {\n\tif (!(err instanceof Error)) return undefined;\n\tconst lines: string[] = [];\n\tif (err instanceof PlatformError) {\n\t\tlines.push(`code : ${err.code}`);\n\t\tif (Object.keys(err.details).length > 0) {\n\t\t\tlines.push(`details : ${JSON.stringify(err.details, null, 2)}`);\n\t\t}\n\t}\n\tif (err.cause instanceof Error) {\n\t\tlines.push(`cause : ${err.cause.name}: ${err.cause.message}`);\n\t}\n\tif (err.stack) {\n\t\tlines.push(err.stack);\n\t}\n\treturn lines.length > 0 ? lines.join(\"\\n\") : undefined;\n}\n\nfunction failure(message: string, exitCode = 1): CommandResult {\n\treturn { exitCode, stdout: \"\", stderr: `${message}\\n` };\n}\n"],"mappings":"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"}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_PROFILE } from "../../_shared/profiles.js";
|
|
2
|
-
|
|
3
|
-
//#region src/lib/cli/resolve-api-key.d.ts
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Resolve the Neon API key for a `neon-env` CLI invocation.
|
|
7
|
-
*
|
|
8
|
-
* Precedence is the `neon` CLI's, from the same module: **an explicit flag beats an ambient
|
|
9
|
-
* environment variable.** `--api-key` and `--profile` together is an error; `--profile` beats
|
|
10
|
-
* `NEON_API_KEY`; `--api-key` beats `NEON_PROFILE`; two ambient sources resolve to the key.
|
|
11
|
-
*
|
|
12
|
-
* Sharing that decision rather than restating it is the point. An earlier version of this file
|
|
13
|
-
* checked `NEON_API_KEY` before the selected profile, so `NEON_API_KEY=… neon-env run --profile
|
|
14
|
-
* work` silently used the wrong account — the very bug this feature fixes in `neon`.
|
|
15
|
-
*
|
|
16
|
-
* The CLI owns the resolution because `@neon/config` and `@neon/env`'s root export are
|
|
17
|
-
* deliberately environment- and filesystem-agnostic: they accept an explicit `apiKey` and
|
|
18
|
-
* nothing else, so the ambient sources a *user* expects have to be read out here.
|
|
19
|
-
*/
|
|
20
|
-
declare function resolveApiKey(options: {
|
|
21
|
-
apiKey?: string;
|
|
22
|
-
profile?: string;
|
|
23
|
-
env?: NodeJS.ProcessEnv;
|
|
24
|
-
/** Where to say that an exported key displaced an exported profile. Defaults to stderr. */
|
|
25
|
-
warn?: (message: string) => void;
|
|
26
|
-
}): string | undefined;
|
|
27
|
-
//#endregion
|
|
28
|
-
export { DEFAULT_PROFILE, resolveApiKey };
|
|
29
|
-
//# sourceMappingURL=resolve-api-key.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-api-key.d.ts","names":[],"sources":["../../../src/lib/cli/resolve-api-key.ts"],"mappings":";;;;;;AA0BA;;;;;;;;;;;;;iBAAgB,aAAA;;;QAGT,MAAA,CAAO"}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { configDir, resolveConfigFile } from "../../_shared/paths.js";
|
|
2
|
-
import { DEFAULT_PROFILE, resolveProfile } from "../../_shared/profiles.js";
|
|
3
|
-
import { displacedProfileWarning, selectCredential } from "../../_shared/auth_selection.js";
|
|
4
|
-
import { inspectCredentials, interpretCredentials } from "../../_shared/credentials.js";
|
|
5
|
-
//#region src/lib/cli/resolve-api-key.ts
|
|
6
|
-
/**
|
|
7
|
-
* Resolve the Neon API key for a `neon-env` CLI invocation.
|
|
8
|
-
*
|
|
9
|
-
* Precedence is the `neon` CLI's, from the same module: **an explicit flag beats an ambient
|
|
10
|
-
* environment variable.** `--api-key` and `--profile` together is an error; `--profile` beats
|
|
11
|
-
* `NEON_API_KEY`; `--api-key` beats `NEON_PROFILE`; two ambient sources resolve to the key.
|
|
12
|
-
*
|
|
13
|
-
* Sharing that decision rather than restating it is the point. An earlier version of this file
|
|
14
|
-
* checked `NEON_API_KEY` before the selected profile, so `NEON_API_KEY=… neon-env run --profile
|
|
15
|
-
* work` silently used the wrong account — the very bug this feature fixes in `neon`.
|
|
16
|
-
*
|
|
17
|
-
* The CLI owns the resolution because `@neon/config` and `@neon/env`'s root export are
|
|
18
|
-
* deliberately environment- and filesystem-agnostic: they accept an explicit `apiKey` and
|
|
19
|
-
* nothing else, so the ambient sources a *user* expects have to be read out here.
|
|
20
|
-
*/
|
|
21
|
-
function resolveApiKey(options) {
|
|
22
|
-
const env = options.env ?? process.env;
|
|
23
|
-
const selection = selectCredential({
|
|
24
|
-
...options.apiKey !== void 0 ? { apiKeyFlag: options.apiKey } : {},
|
|
25
|
-
...options.profile !== void 0 ? { profileFlag: options.profile } : {},
|
|
26
|
-
...env.NEON_API_KEY !== void 0 ? { apiKeyEnv: env.NEON_API_KEY } : {},
|
|
27
|
-
...env.NEON_PROFILE !== void 0 ? { profileEnv: env.NEON_PROFILE } : {}
|
|
28
|
-
});
|
|
29
|
-
const displaced = displacedProfileWarning(selection);
|
|
30
|
-
if (displaced !== null) (options.warn ?? ((message) => process.stderr.write(`${message}\n`)))(displaced);
|
|
31
|
-
if (selection.source !== "profile") return selection.apiKey;
|
|
32
|
-
return readStoredCredential(selection, env);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* The credential stored for the selected profile.
|
|
36
|
-
*
|
|
37
|
-
* Two different situations, deliberately not merged. A **missing** credential under `DEFAULT` is
|
|
38
|
-
* the ordinary not-signed-in state and resolves to no key; under a profile the user named it is
|
|
39
|
-
* an error, because reporting a missing credential would hide that the real problem is the name
|
|
40
|
-
* they typed. A **damaged** credential is always an error: the file is there, it is not an
|
|
41
|
-
* absence, and no amount of signing in elsewhere explains it.
|
|
42
|
-
*/
|
|
43
|
-
function readStoredCredential(selection, env) {
|
|
44
|
-
const { profile, explicit } = selection;
|
|
45
|
-
/**
|
|
46
|
-
* An *absence* is only an error when the user named the profile. Not being signed in under
|
|
47
|
-
* `DEFAULT` is the ordinary state, and the library's `PLATFORM_MISSING_API_KEY` says it
|
|
48
|
-
* better than a stack trace.
|
|
49
|
-
*/
|
|
50
|
-
const absent = (reason) => {
|
|
51
|
-
if (explicit) throw new Error(reason);
|
|
52
|
-
};
|
|
53
|
-
let path;
|
|
54
|
-
try {
|
|
55
|
-
path = profile === "DEFAULT" ? resolveConfigFile("credentials.json", { env }).path : resolveProfile(configDir({ env }), profile).credentialsPath;
|
|
56
|
-
} catch (err) {
|
|
57
|
-
throw err instanceof Error ? err : new Error(String(err));
|
|
58
|
-
}
|
|
59
|
-
const read = inspectCredentials(path);
|
|
60
|
-
if (read.kind === "absent") return absent(`Profile "${profile}" has no stored credential at ${path}. Sign in with \`neon profile create ${profile}\`.`);
|
|
61
|
-
if (read.kind === "unusable") throw new Error(`${read.reason}. Replace it deliberately with \`neon profile create ${profile} --force\`, or delete the file.`);
|
|
62
|
-
const credential = interpretCredentials(read.credentials, {
|
|
63
|
-
path,
|
|
64
|
-
profile
|
|
65
|
-
});
|
|
66
|
-
if (credential.kind === "api_key") return credential.apiKey;
|
|
67
|
-
const token = read.credentials.access_token;
|
|
68
|
-
if (typeof token === "string" && token.trim() !== "") return token.trim();
|
|
69
|
-
throw new Error(`Profile "${profile}" holds a browser sign-in with no usable token at ${path}. Sign in again with \`neon auth --profile ${profile}\`.`);
|
|
70
|
-
}
|
|
71
|
-
//#endregion
|
|
72
|
-
export { DEFAULT_PROFILE, resolveApiKey };
|
|
73
|
-
|
|
74
|
-
//# sourceMappingURL=resolve-api-key.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-api-key.js","names":[],"sources":["../../../src/lib/cli/resolve-api-key.ts"],"sourcesContent":["import {\n\tdisplacedProfileWarning,\n\tselectCredential,\n} from \"../../_shared/auth_selection.js\";\nimport {\n\tinspectCredentials,\n\tinterpretCredentials,\n} from \"../../_shared/credentials.js\";\nimport { configDir, resolveConfigFile } from \"../../_shared/paths.js\";\nimport { DEFAULT_PROFILE, resolveProfile } from \"../../_shared/profiles.js\";\n\n/**\n * Resolve the Neon API key for a `neon-env` CLI invocation.\n *\n * Precedence is the `neon` CLI's, from the same module: **an explicit flag beats an ambient\n * environment variable.** `--api-key` and `--profile` together is an error; `--profile` beats\n * `NEON_API_KEY`; `--api-key` beats `NEON_PROFILE`; two ambient sources resolve to the key.\n *\n * Sharing that decision rather than restating it is the point. An earlier version of this file\n * checked `NEON_API_KEY` before the selected profile, so `NEON_API_KEY=… neon-env run --profile\n * work` silently used the wrong account — the very bug this feature fixes in `neon`.\n *\n * The CLI owns the resolution because `@neon/config` and `@neon/env`'s root export are\n * deliberately environment- and filesystem-agnostic: they accept an explicit `apiKey` and\n * nothing else, so the ambient sources a *user* expects have to be read out here.\n */\nexport function resolveApiKey(options: {\n\tapiKey?: string;\n\tprofile?: string;\n\tenv?: NodeJS.ProcessEnv;\n\t/** Where to say that an exported key displaced an exported profile. Defaults to stderr. */\n\twarn?: (message: string) => void;\n}): string | undefined {\n\tconst env = options.env ?? process.env;\n\tconst selection = selectCredential({\n\t\t...(options.apiKey !== undefined ? { apiKeyFlag: options.apiKey } : {}),\n\t\t...(options.profile !== undefined\n\t\t\t? { profileFlag: options.profile }\n\t\t\t: {}),\n\t\t...(env.NEON_API_KEY !== undefined\n\t\t\t? { apiKeyEnv: env.NEON_API_KEY }\n\t\t\t: {}),\n\t\t...(env.NEON_PROFILE !== undefined\n\t\t\t? { profileEnv: env.NEON_PROFILE }\n\t\t\t: {}),\n\t});\n\n\t// Sharing the decision is only half of it. The disclosure is the half that keeps a\n\t// displaced profile from being the original bug in a quieter form: `NEON_API_KEY=…\n\t// NEON_PROFILE=work neon-env run` legitimately uses the key, and saying nothing leaves the\n\t// user believing they ran as `work`. `neon` has warned here from the start; this did not.\n\tconst displaced = displacedProfileWarning(selection);\n\tif (displaced !== null) {\n\t\tconst warn =\n\t\t\toptions.warn ??\n\t\t\t((message: string) => process.stderr.write(`${message}\\n`));\n\t\twarn(displaced);\n\t}\n\n\tif (selection.source !== \"profile\") return selection.apiKey;\n\treturn readStoredCredential(selection, env);\n}\n\n/**\n * The credential stored for the selected profile.\n *\n * Two different situations, deliberately not merged. A **missing** credential under `DEFAULT` is\n * the ordinary not-signed-in state and resolves to no key; under a profile the user named it is\n * an error, because reporting a missing credential would hide that the real problem is the name\n * they typed. A **damaged** credential is always an error: the file is there, it is not an\n * absence, and no amount of signing in elsewhere explains it.\n */\nfunction readStoredCredential(\n\tselection: { profile: string; explicit: boolean },\n\tenv: NodeJS.ProcessEnv,\n): string | undefined {\n\tconst { profile, explicit } = selection;\n\t/**\n\t * An *absence* is only an error when the user named the profile. Not being signed in under\n\t * `DEFAULT` is the ordinary state, and the library's `PLATFORM_MISSING_API_KEY` says it\n\t * better than a stack trace.\n\t */\n\tconst absent = (reason: string): undefined => {\n\t\tif (explicit) throw new Error(reason);\n\t\treturn undefined;\n\t};\n\n\tlet path: string;\n\ttry {\n\t\tpath =\n\t\t\tprofile === DEFAULT_PROFILE\n\t\t\t\t? // Per *file*, so an install predating the rename still finds its\n\t\t\t\t\t// `credentials.json` in the legacy `neonctl` directory, in place.\n\t\t\t\t\tresolveConfigFile(\"credentials.json\", { env }).path\n\t\t\t\t: // From the config root, not from wherever `credentials.json` happens to live:\n\t\t\t\t\t// that file can still be in `neonctl/` while `profiles.json` is in `neon/`,\n\t\t\t\t\t// and deriving one from the other loses every named profile.\n\t\t\t\t\tresolveProfile(configDir({ env }), profile).credentialsPath;\n\t} catch (err) {\n\t\t// An unknown profile name is a naming error whoever typed it can fix, so it is fatal\n\t\t// either way — it cannot be reported as \"not signed in\".\n\t\tthrow err instanceof Error ? err : new Error(String(err));\n\t}\n\n\tconst read = inspectCredentials(path);\n\tif (read.kind === \"absent\") {\n\t\treturn absent(\n\t\t\t`Profile \"${profile}\" has no stored credential at ${path}. Sign in with \\`neon profile create ${profile}\\`.`,\n\t\t);\n\t}\n\n\t// A file that exists but cannot be read is never an absence, named or not. Returning\n\t// `undefined` here reported \"no API key\" for a credential that is present and broken,\n\t// which sends the reader looking for a missing login instead of at the damaged file — and\n\t// under `neon` the same file is a hard error, so the two CLIs disagreed about it.\n\tif (read.kind === \"unusable\") {\n\t\tthrow new Error(\n\t\t\t`${read.reason}. Replace it deliberately with \\`neon profile create ${profile} --force\\`, or delete the file.`,\n\t\t);\n\t}\n\n\tconst credential = interpretCredentials(read.credentials, {\n\t\tpath,\n\t\tprofile,\n\t});\n\tif (credential.kind === \"api_key\") return credential.apiKey;\n\tconst token = read.credentials.access_token;\n\tif (typeof token === \"string\" && token.trim() !== \"\") return token.trim();\n\tthrow new Error(\n\t\t`Profile \"${profile}\" holds a browser sign-in with no usable token at ${path}. Sign in again with \\`neon auth --profile ${profile}\\`.`,\n\t);\n}\n\nexport { DEFAULT_PROFILE };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,cAAc,SAMP;CACtB,MAAM,MAAM,QAAQ,OAAO,QAAQ;CACnC,MAAM,YAAY,iBAAiB;EAClC,GAAI,QAAQ,WAAW,KAAA,IAAY,EAAE,YAAY,QAAQ,OAAO,IAAI,CAAC;EACrE,GAAI,QAAQ,YAAY,KAAA,IACrB,EAAE,aAAa,QAAQ,QAAQ,IAC/B,CAAC;EACJ,GAAI,IAAI,iBAAiB,KAAA,IACtB,EAAE,WAAW,IAAI,aAAa,IAC9B,CAAC;EACJ,GAAI,IAAI,iBAAiB,KAAA,IACtB,EAAE,YAAY,IAAI,aAAa,IAC/B,CAAC;CACL,CAAC;CAMD,MAAM,YAAY,wBAAwB,SAAS;CACnD,IAAI,cAAc,MAIjB,CAFC,QAAQ,UACN,YAAoB,QAAQ,OAAO,MAAM,GAAG,QAAQ,GAAG,GAAA,CACrD,SAAS;CAGf,IAAI,UAAU,WAAW,WAAW,OAAO,UAAU;CACrD,OAAO,qBAAqB,WAAW,GAAG;AAC3C;;;;;;;;;;AAWA,SAAS,qBACR,WACA,KACqB;CACrB,MAAM,EAAE,SAAS,aAAa;;;;;;CAM9B,MAAM,UAAU,WAA8B;EAC7C,IAAI,UAAU,MAAM,IAAI,MAAM,MAAM;CAErC;CAEA,IAAI;CACJ,IAAI;EACH,OACC,YAAA,YAGE,kBAAkB,oBAAoB,EAAE,IAAI,CAAC,CAAC,CAAC,OAI/C,eAAe,UAAU,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;CAChD,SAAS,KAAK;EAGb,MAAM,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;CACzD;CAEA,MAAM,OAAO,mBAAmB,IAAI;CACpC,IAAI,KAAK,SAAS,UACjB,OAAO,OACN,YAAY,QAAQ,gCAAgC,KAAK,uCAAuC,QAAQ,IACzG;CAOD,IAAI,KAAK,SAAS,YACjB,MAAM,IAAI,MACT,GAAG,KAAK,OAAO,uDAAuD,QAAQ,gCAC/E;CAGD,MAAM,aAAa,qBAAqB,KAAK,aAAa;EACzD;EACA;CACD,CAAC;CACD,IAAI,WAAW,SAAS,WAAW,OAAO,WAAW;CACrD,MAAM,QAAQ,KAAK,YAAY;CAC/B,IAAI,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,IAAI,OAAO,MAAM,KAAK;CACxE,MAAM,IAAI,MACT,YAAY,QAAQ,oDAAoD,KAAK,6CAA6C,QAAQ,IACnI;AACD"}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
//#region src/lib/cli/resolve-context.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Resolved project + branch context for the `neon-env` CLI. The CLI owns this resolution
|
|
4
|
-
* (flags → `NEON_*` env → `.neon[/project.json]` file) so the `@neon/env` library
|
|
5
|
-
* functions can stay filesystem- and env-agnostic.
|
|
6
|
-
*/
|
|
7
|
-
interface ResolvedContext {
|
|
8
|
-
projectId: string;
|
|
9
|
-
/** Branch ref — a name (preferred for readability) or an id (`br-…`). */
|
|
10
|
-
branch: string;
|
|
11
|
-
}
|
|
12
|
-
interface ResolveContextOptions {
|
|
13
|
-
projectId?: string;
|
|
14
|
-
branch?: string;
|
|
15
|
-
cwd: string;
|
|
16
|
-
env?: NodeJS.ProcessEnv;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Resolve `projectId` and `branch` for a CLI invocation. Precedence (each wins over the
|
|
20
|
-
* next): explicit flag → `NEON_*` env var → `.neon[/project.json]` walked up from `cwd`.
|
|
21
|
-
*
|
|
22
|
-
* Returns the resolved values plus a list of human-readable reasons for any field that
|
|
23
|
-
* could not be resolved (so the caller can render one combined error).
|
|
24
|
-
*/
|
|
25
|
-
declare function resolveContext(options: ResolveContextOptions): {
|
|
26
|
-
ok: true;
|
|
27
|
-
context: ResolvedContext;
|
|
28
|
-
} | {
|
|
29
|
-
ok: false;
|
|
30
|
-
missing: string[];
|
|
31
|
-
};
|
|
32
|
-
//#endregion
|
|
33
|
-
export { ResolveContextOptions, ResolvedContext, resolveContext };
|
|
34
|
-
//# sourceMappingURL=resolve-context.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-context.d.ts","names":[],"sources":["../../../src/lib/cli/resolve-context.ts"],"mappings":";;AASA;AAMA;AAcA;AAA8B;AACpB,UArBO,eAAA,CAqBP;EACc,SAAA,EAAA,MAAA;EAAe;;;UAhBtB,qBAAA;;;;QAIV,MAAA,CAAO;;;;;;;;;iBAUE,cAAA,UACN;;WACc"}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { dirname, resolve } from "node:path";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
//#region src/lib/cli/resolve-context.ts
|
|
5
|
-
/**
|
|
6
|
-
* Resolve `projectId` and `branch` for a CLI invocation. Precedence (each wins over the
|
|
7
|
-
* next): explicit flag → `NEON_*` env var → `.neon[/project.json]` walked up from `cwd`.
|
|
8
|
-
*
|
|
9
|
-
* Returns the resolved values plus a list of human-readable reasons for any field that
|
|
10
|
-
* could not be resolved (so the caller can render one combined error).
|
|
11
|
-
*/
|
|
12
|
-
function resolveContext(options) {
|
|
13
|
-
const env = options.env ?? process.env;
|
|
14
|
-
const file = findNeonFile(options.cwd);
|
|
15
|
-
const projectId = nonEmpty(options.projectId) ?? nonEmpty(env.NEON_PROJECT_ID) ?? file?.projectId;
|
|
16
|
-
const branch = nonEmpty(options.branch) ?? nonEmpty(env.NEON_BRANCH) ?? nonEmpty(env.NEON_BRANCH_ID) ?? file?.branch;
|
|
17
|
-
const missing = [];
|
|
18
|
-
if (!projectId) missing.push("project id — pass `--project-id`, set `NEON_PROJECT_ID`, or add `projectId` to `.neon` (run `npx neon link`).");
|
|
19
|
-
if (!branch) missing.push("branch — pass `--branch`, set `NEON_BRANCH`/`NEON_BRANCH_ID`, or add `branch` to `.neon` (run `npx neon link` / `neon checkout <branch>`).");
|
|
20
|
-
if (!projectId || !branch) return {
|
|
21
|
-
ok: false,
|
|
22
|
-
missing
|
|
23
|
-
};
|
|
24
|
-
return {
|
|
25
|
-
ok: true,
|
|
26
|
-
context: {
|
|
27
|
-
projectId,
|
|
28
|
-
branch
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Walk up from `cwd` looking for `.neon/project.json` (preferred) or `.neon` (neonctl
|
|
34
|
-
* convention). Stops at the first `.git` directory or the home directory. Read-only.
|
|
35
|
-
*/
|
|
36
|
-
function findNeonFile(cwd) {
|
|
37
|
-
let current = resolve(cwd);
|
|
38
|
-
const stop = resolve(homedir());
|
|
39
|
-
let lastSeen = null;
|
|
40
|
-
while (true) {
|
|
41
|
-
const parsed = readNeonFileAt(resolve(current, ".neon", "project.json")) ?? readNeonFileAt(resolve(current, ".neon"));
|
|
42
|
-
if (parsed) return parsed;
|
|
43
|
-
if (current === stop) return null;
|
|
44
|
-
if (existsSync(resolve(current, ".git"))) return null;
|
|
45
|
-
const parent = dirname(current);
|
|
46
|
-
if (parent === current || parent === lastSeen) return null;
|
|
47
|
-
lastSeen = current;
|
|
48
|
-
current = parent;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function readNeonFileAt(path) {
|
|
52
|
-
if (!isFile(path)) return null;
|
|
53
|
-
let raw;
|
|
54
|
-
try {
|
|
55
|
-
raw = readFileSync(path, "utf-8");
|
|
56
|
-
} catch {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
let parsed;
|
|
60
|
-
try {
|
|
61
|
-
parsed = JSON.parse(raw);
|
|
62
|
-
} catch {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
66
|
-
const obj = parsed;
|
|
67
|
-
const out = {};
|
|
68
|
-
if (typeof obj.projectId === "string" && obj.projectId !== "") out.projectId = obj.projectId;
|
|
69
|
-
const branch = typeof obj.branch === "string" && obj.branch !== "" ? obj.branch : typeof obj.branchId === "string" && obj.branchId !== "" ? obj.branchId : void 0;
|
|
70
|
-
if (branch) out.branch = branch;
|
|
71
|
-
return out;
|
|
72
|
-
}
|
|
73
|
-
function isFile(path) {
|
|
74
|
-
try {
|
|
75
|
-
return statSync(path).isFile();
|
|
76
|
-
} catch {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function nonEmpty(value) {
|
|
81
|
-
if (typeof value !== "string") return void 0;
|
|
82
|
-
const trimmed = value.trim();
|
|
83
|
-
return trimmed === "" ? void 0 : trimmed;
|
|
84
|
-
}
|
|
85
|
-
//#endregion
|
|
86
|
-
export { resolveContext };
|
|
87
|
-
|
|
88
|
-
//# sourceMappingURL=resolve-context.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-context.js","names":[],"sources":["../../../src/lib/cli/resolve-context.ts"],"sourcesContent":["import { existsSync, readFileSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, resolve } from \"node:path\";\n\n/**\n * Resolved project + branch context for the `neon-env` CLI. The CLI owns this resolution\n * (flags → `NEON_*` env → `.neon[/project.json]` file) so the `@neon/env` library\n * functions can stay filesystem- and env-agnostic.\n */\nexport interface ResolvedContext {\n\tprojectId: string;\n\t/** Branch ref — a name (preferred for readability) or an id (`br-…`). */\n\tbranch: string;\n}\n\nexport interface ResolveContextOptions {\n\tprojectId?: string;\n\tbranch?: string;\n\tcwd: string;\n\tenv?: NodeJS.ProcessEnv;\n}\n\n/**\n * Resolve `projectId` and `branch` for a CLI invocation. Precedence (each wins over the\n * next): explicit flag → `NEON_*` env var → `.neon[/project.json]` walked up from `cwd`.\n *\n * Returns the resolved values plus a list of human-readable reasons for any field that\n * could not be resolved (so the caller can render one combined error).\n */\nexport function resolveContext(\n\toptions: ResolveContextOptions,\n): { ok: true; context: ResolvedContext } | { ok: false; missing: string[] } {\n\tconst env = options.env ?? process.env;\n\tconst file = findNeonFile(options.cwd);\n\n\tconst projectId =\n\t\tnonEmpty(options.projectId) ??\n\t\tnonEmpty(env.NEON_PROJECT_ID) ??\n\t\tfile?.projectId;\n\n\t// A branch ref — name (preferred) or id. `NEON_BRANCH` carries the name; `NEON_BRANCH_ID`\n\t// is the legacy id-only var. The `.neon` file pins `branch` (name) via `neonctl link`,\n\t// with legacy `branchId` still honored. fetchEnv resolves either form by name or id.\n\tconst branch =\n\t\tnonEmpty(options.branch) ??\n\t\tnonEmpty(env.NEON_BRANCH) ??\n\t\tnonEmpty(env.NEON_BRANCH_ID) ??\n\t\tfile?.branch;\n\n\tconst missing: string[] = [];\n\tif (!projectId) {\n\t\tmissing.push(\n\t\t\t\"project id — pass `--project-id`, set `NEON_PROJECT_ID`, or add `projectId` to `.neon` (run `npx neon link`).\",\n\t\t);\n\t}\n\tif (!branch) {\n\t\tmissing.push(\n\t\t\t\"branch — pass `--branch`, set `NEON_BRANCH`/`NEON_BRANCH_ID`, or add `branch` to `.neon` (run `npx neon link` / `neon checkout <branch>`).\",\n\t\t);\n\t}\n\tif (!projectId || !branch) return { ok: false, missing };\n\n\treturn {\n\t\tok: true,\n\t\tcontext: { projectId, branch },\n\t};\n}\n\ninterface NeonFile {\n\tprojectId?: string;\n\t/** Branch ref — name (preferred) or id. Reads `branch`, falling back to legacy `branchId`. */\n\tbranch?: string;\n}\n\n/**\n * Walk up from `cwd` looking for `.neon/project.json` (preferred) or `.neon` (neonctl\n * convention). Stops at the first `.git` directory or the home directory. Read-only.\n */\nfunction findNeonFile(cwd: string): NeonFile | null {\n\tlet current = resolve(cwd);\n\tconst stop = resolve(homedir());\n\tlet lastSeen: string | null = null;\n\n\twhile (true) {\n\t\tconst parsed =\n\t\t\treadNeonFileAt(resolve(current, \".neon\", \"project.json\")) ??\n\t\t\treadNeonFileAt(resolve(current, \".neon\"));\n\t\tif (parsed) return parsed;\n\n\t\tif (current === stop) return null;\n\t\tif (existsSync(resolve(current, \".git\"))) return null;\n\n\t\tconst parent = dirname(current);\n\t\tif (parent === current || parent === lastSeen) return null;\n\t\tlastSeen = current;\n\t\tcurrent = parent;\n\t}\n}\n\nfunction readNeonFileAt(path: string): NeonFile | null {\n\tif (!isFile(path)) return null;\n\tlet raw: string;\n\ttry {\n\t\traw = readFileSync(path, \"utf-8\");\n\t} catch {\n\t\treturn null;\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\treturn null;\n\t}\n\tif (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed))\n\t\treturn null;\n\tconst obj = parsed as Record<string, unknown>;\n\tconst out: NeonFile = {};\n\tif (typeof obj.projectId === \"string\" && obj.projectId !== \"\")\n\t\tout.projectId = obj.projectId;\n\t// Prefer the `branch` field (name or id, written by `neonctl link`); fall back to the\n\t// legacy id-only `branchId`.\n\tconst branch =\n\t\ttypeof obj.branch === \"string\" && obj.branch !== \"\"\n\t\t\t? obj.branch\n\t\t\t: typeof obj.branchId === \"string\" && obj.branchId !== \"\"\n\t\t\t\t? obj.branchId\n\t\t\t\t: undefined;\n\tif (branch) out.branch = branch;\n\treturn out;\n}\n\nfunction isFile(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction nonEmpty(value: string | undefined): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\treturn trimmed === \"\" ? undefined : trimmed;\n}\n"],"mappings":";;;;;;;;;;;AA6BA,SAAgB,eACf,SAC4E;CAC5E,MAAM,MAAM,QAAQ,OAAO,QAAQ;CACnC,MAAM,OAAO,aAAa,QAAQ,GAAG;CAErC,MAAM,YACL,SAAS,QAAQ,SAAS,KAC1B,SAAS,IAAI,eAAe,KAC5B,MAAM;CAKP,MAAM,SACL,SAAS,QAAQ,MAAM,KACvB,SAAS,IAAI,WAAW,KACxB,SAAS,IAAI,cAAc,KAC3B,MAAM;CAEP,MAAM,UAAoB,CAAC;CAC3B,IAAI,CAAC,WACJ,QAAQ,KACP,+GACD;CAED,IAAI,CAAC,QACJ,QAAQ,KACP,4IACD;CAED,IAAI,CAAC,aAAa,CAAC,QAAQ,OAAO;EAAE,IAAI;EAAO;CAAQ;CAEvD,OAAO;EACN,IAAI;EACJ,SAAS;GAAE;GAAW;EAAO;CAC9B;AACD;;;;;AAYA,SAAS,aAAa,KAA8B;CACnD,IAAI,UAAU,QAAQ,GAAG;CACzB,MAAM,OAAO,QAAQ,QAAQ,CAAC;CAC9B,IAAI,WAA0B;CAE9B,OAAO,MAAM;EACZ,MAAM,SACL,eAAe,QAAQ,SAAS,SAAS,cAAc,CAAC,KACxD,eAAe,QAAQ,SAAS,OAAO,CAAC;EACzC,IAAI,QAAQ,OAAO;EAEnB,IAAI,YAAY,MAAM,OAAO;EAC7B,IAAI,WAAW,QAAQ,SAAS,MAAM,CAAC,GAAG,OAAO;EAEjD,MAAM,SAAS,QAAQ,OAAO;EAC9B,IAAI,WAAW,WAAW,WAAW,UAAU,OAAO;EACtD,WAAW;EACX,UAAU;CACX;AACD;AAEA,SAAS,eAAe,MAA+B;CACtD,IAAI,CAAC,OAAO,IAAI,GAAG,OAAO;CAC1B,IAAI;CACJ,IAAI;EACH,MAAM,aAAa,MAAM,OAAO;CACjC,QAAQ;EACP,OAAO;CACR;CACA,IAAI;CACJ,IAAI;EACH,SAAS,KAAK,MAAM,GAAG;CACxB,QAAQ;EACP,OAAO;CACR;CACA,IAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GACxE,OAAO;CACR,MAAM,MAAM;CACZ,MAAM,MAAgB,CAAC;CACvB,IAAI,OAAO,IAAI,cAAc,YAAY,IAAI,cAAc,IAC1D,IAAI,YAAY,IAAI;CAGrB,MAAM,SACL,OAAO,IAAI,WAAW,YAAY,IAAI,WAAW,KAC9C,IAAI,SACJ,OAAO,IAAI,aAAa,YAAY,IAAI,aAAa,KACpD,IAAI,WACJ,KAAA;CACL,IAAI,QAAQ,IAAI,SAAS;CACzB,OAAO;AACR;AAEA,SAAS,OAAO,MAAuB;CACtC,IAAI;EACH,OAAO,SAAS,IAAI,CAAC,CAAC,OAAO;CAC9B,QAAQ;EACP,OAAO;CACR;AACD;AAEA,SAAS,SAAS,OAA+C;CAChE,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;CACtC,MAAM,UAAU,MAAM,KAAK;CAC3B,OAAO,YAAY,KAAK,KAAA,IAAY;AACrC"}
|