@neondatabase/env 0.0.0 → 0.1.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/LICENSE.md +178 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +61 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/dist/lib/neon-api.d.ts +264 -0
- package/dist/config/dist/lib/neon-api.d.ts.map +1 -0
- package/dist/config/dist/lib/types.d.ts +209 -0
- package/dist/config/dist/lib/types.d.ts.map +1 -0
- package/dist/config/dist/v1.d.ts +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/lib/cli/commands.d.ts +46 -0
- package/dist/lib/cli/commands.d.ts.map +1 -0
- package/dist/lib/cli/commands.js +181 -0
- package/dist/lib/cli/commands.js.map +1 -0
- package/dist/lib/cli/resolve-context.d.ts +35 -0
- package/dist/lib/cli/resolve-context.d.ts.map +1 -0
- package/dist/lib/cli/resolve-context.js +90 -0
- package/dist/lib/cli/resolve-context.js.map +1 -0
- package/dist/lib/env.d.ts +194 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/env.js +263 -0
- package/dist/lib/env.js.map +1 -0
- package/dist/v1.d.ts +2 -0
- package/dist/v1.js +2 -0
- package/package.json +72 -21
- package/.env.example +0 -5
- package/e2e/env.e2e.test.ts +0 -36
- package/e2e/helpers.ts +0 -188
- package/e2e/load-env.ts +0 -29
- package/e2e/setup.ts +0 -24
- package/src/cli.ts +0 -107
- package/src/index.ts +0 -5
- package/src/lib/cli/commands.test.ts +0 -101
- package/src/lib/cli/commands.ts +0 -267
- package/src/lib/cli/resolve-context.test.ts +0 -242
- package/src/lib/cli/resolve-context.ts +0 -142
- package/src/lib/env.test.ts +0 -172
- package/src/lib/env.ts +0 -610
- package/src/lib/fake-neon-api.ts +0 -782
- package/src/lib/test-utils.ts +0 -83
- package/src/v1.ts +0 -32
- package/tsconfig.json +0 -4
- package/tsdown.config.ts +0 -20
- package/vitest.config.ts +0 -19
- package/vitest.e2e.config.ts +0 -29
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
//#region ../config/dist/lib/types.d.ts
|
|
2
|
+
//#region src/lib/types.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Valid Neon Compute Unit values.
|
|
5
|
+
* Most plans support 0.25, 0.5, 1, 2, 4, 8. Higher values may be available on Business plans.
|
|
6
|
+
*/
|
|
7
|
+
type ComputeUnit = 0.25 | 0.5 | 1 | 2 | 4 | 8;
|
|
8
|
+
/**
|
|
9
|
+
* Compute settings applied to the read/write endpoint of a branch.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors the subset of {@link https://api-docs.neon.tech/reference/getting-started-with-neon-api Neon endpoint}
|
|
12
|
+
* fields that we expose as IaC primitives. Anything left undefined falls back to the project's
|
|
13
|
+
* `default_endpoint_settings` (which themselves fall back to Neon platform defaults).
|
|
14
|
+
*/
|
|
15
|
+
interface ComputeSettings {
|
|
16
|
+
/**
|
|
17
|
+
* Minimum number of Compute Units. Set to 0.25 for true scale-to-zero.
|
|
18
|
+
* @example 0.25 // scale-to-zero
|
|
19
|
+
* @example 1 // always-on with 1 CU minimum
|
|
20
|
+
*/
|
|
21
|
+
autoscalingLimitMinCu?: ComputeUnit;
|
|
22
|
+
/**
|
|
23
|
+
* Maximum number of Compute Units for autoscaling.
|
|
24
|
+
* @example 2
|
|
25
|
+
* @example 8
|
|
26
|
+
*/
|
|
27
|
+
autoscalingLimitMaxCu?: ComputeUnit;
|
|
28
|
+
/**
|
|
29
|
+
* How long to wait before suspending an idle compute.
|
|
30
|
+
*
|
|
31
|
+
* - `false` — never suspend (always-on compute)
|
|
32
|
+
* - `"5m"` — duration string (supports "30s", "5m", "1h", "7d", etc)
|
|
33
|
+
* - `300` — custom timeout in seconds (60-604800)
|
|
34
|
+
* - `undefined` — use Neon platform default (currently 300s / 5 minutes)
|
|
35
|
+
*
|
|
36
|
+
* @example false // never suspend
|
|
37
|
+
* @example "5m" // 5 minutes
|
|
38
|
+
* @example "1h" // 1 hour
|
|
39
|
+
* @example 300 // 5 minutes in seconds
|
|
40
|
+
*/
|
|
41
|
+
suspendTimeout?: false | "5m" | "1h" | string | number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Read-only descriptor of the branch a {@link Config} policy is being evaluated for — the
|
|
45
|
+
* `branch` argument passed to your `defineConfig((branch) => …)` callback. It describes
|
|
46
|
+
* **which** branch this invocation decides for; it is not a live branch handle and must not
|
|
47
|
+
* be mutated. Switch on its fields and return the desired {@link BranchConfig}.
|
|
48
|
+
*/
|
|
49
|
+
interface BranchTarget {
|
|
50
|
+
/** Branch name being evaluated. For `branch dev`, this is the generated branch name. */
|
|
51
|
+
name: string;
|
|
52
|
+
/** Neon branch id when the branch already exists. Undefined during pre-create eval. */
|
|
53
|
+
id?: string;
|
|
54
|
+
/** Whether this branch already exists on Neon. */
|
|
55
|
+
exists: boolean;
|
|
56
|
+
/** Parent branch id from Neon when known. */
|
|
57
|
+
parentId?: string;
|
|
58
|
+
/** Whether Neon marks this branch as the project default. */
|
|
59
|
+
isDefault?: boolean;
|
|
60
|
+
/** Whether Neon currently marks this branch protected. */
|
|
61
|
+
isProtected?: boolean;
|
|
62
|
+
/** Current expiration timestamp from Neon, when set. */
|
|
63
|
+
expiresAt?: string;
|
|
64
|
+
}
|
|
65
|
+
interface ServiceToggle {
|
|
66
|
+
/** Defaults to `true` when the service namespace is present. Set `false` to opt out. */
|
|
67
|
+
enabled?: boolean;
|
|
68
|
+
}
|
|
69
|
+
interface PostgresConfig {
|
|
70
|
+
computeSettings?: ComputeSettings;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Supported function runtimes. Mirrors the Neon Functions deploy API `runtime` enum.
|
|
74
|
+
* Only `nodejs24` exists today; kept as a union so adding runtimes later is a
|
|
75
|
+
* non-breaking, type-checked change.
|
|
76
|
+
*/
|
|
77
|
+
type FunctionRuntime = "nodejs24";
|
|
78
|
+
/**
|
|
79
|
+
* Memory sizes (MiB) accepted by the Neon Functions deploy API. Mirrors the
|
|
80
|
+
* `memory_mib` enum in the spec.
|
|
81
|
+
*/
|
|
82
|
+
type FunctionMemoryMib = 256 | 512 | 1024 | 2048 | 4096 | 8192;
|
|
83
|
+
/**
|
|
84
|
+
* Local-development settings for a function, used by `neon dev` when it serves every
|
|
85
|
+
* function declared in `neon.ts` (i.e. invoked with no `--source`). Never affects deploy.
|
|
86
|
+
*
|
|
87
|
+
* Typed as a discriminated union so the `portless` ⇒ `port` requirement is enforced at
|
|
88
|
+
* compile time: a `portless` route needs a concrete port to map its `slug.localhost`
|
|
89
|
+
* name to, so `port` is mandatory when `portless: true`.
|
|
90
|
+
*
|
|
91
|
+
* - `{ portless: true; port }` — wrap this function with `portless run <slug> …` so it gets
|
|
92
|
+
* a stable `slug.localhost` URL. `port` is required.
|
|
93
|
+
* - `{ portless?: false; port? }` — serve directly. `port` is optional: when set it is bound
|
|
94
|
+
* exactly (and `neon dev` fails loudly if it is taken); when omitted a free port is found.
|
|
95
|
+
*/
|
|
96
|
+
type FunctionDevConfig = {
|
|
97
|
+
portless: true;
|
|
98
|
+
port: number;
|
|
99
|
+
} | {
|
|
100
|
+
portless?: false;
|
|
101
|
+
port?: number;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* A single Neon Function deployed to a branch (Preview feature).
|
|
105
|
+
*
|
|
106
|
+
* A function is invoked like a Cloudflare/Vercel handler — its source module
|
|
107
|
+
* `export default { fetch }` or `export async function handler(req): Response`. The
|
|
108
|
+
* `source` path is bundled (esbuild) and uploaded as a deployment; the newest
|
|
109
|
+
* deployment becomes active.
|
|
110
|
+
*/
|
|
111
|
+
interface FunctionConfig {
|
|
112
|
+
/**
|
|
113
|
+
* Branch-unique, lowercase DNS-label used as the path segment in the function's
|
|
114
|
+
* invocation URL. Immutable once created. 1–40 chars, `^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$`.
|
|
115
|
+
* @example "hello-world"
|
|
116
|
+
*/
|
|
117
|
+
slug: string;
|
|
118
|
+
/** Free-form display name. @example "Hello World" */
|
|
119
|
+
name: string;
|
|
120
|
+
/**
|
|
121
|
+
* Path to the function's entry module, **relative to `neon.ts`** (or absolute). The
|
|
122
|
+
* module's default export (`{ fetch }`) or `handler` export is the function entry. This
|
|
123
|
+
* path is resolved against the loaded `neon.ts` location and bundled with esbuild at
|
|
124
|
+
* deploy time.
|
|
125
|
+
*
|
|
126
|
+
* We require a string path rather than an imported handler because a JS function value
|
|
127
|
+
* carries no reference back to its source file, so esbuild has nothing to bundle from.
|
|
128
|
+
* @example "./functions/hello-world.ts"
|
|
129
|
+
*/
|
|
130
|
+
source: string;
|
|
131
|
+
/**
|
|
132
|
+
* Environment variables injected into the deployed function. Every value must be a
|
|
133
|
+
* defined string — a `process.env.X` that is `undefined` (unset) errors at validation
|
|
134
|
+
* time rather than silently shipping `undefined`.
|
|
135
|
+
* @example { RESEND_API_KEY: process.env.RESEND_API_KEY }
|
|
136
|
+
*/
|
|
137
|
+
env?: Record<string, string>;
|
|
138
|
+
/** Runtime to execute the function with. Defaults to `"nodejs24"`. */
|
|
139
|
+
runtime?: FunctionRuntime;
|
|
140
|
+
/** Memory allotted to each invocation, in MiB. Defaults to `512`. */
|
|
141
|
+
memoryMib?: FunctionMemoryMib;
|
|
142
|
+
/**
|
|
143
|
+
* Local-development settings used by `neon dev` when serving every function from
|
|
144
|
+
* `neon.ts`. Ignored at deploy time. See {@link FunctionDevConfig}.
|
|
145
|
+
*/
|
|
146
|
+
dev?: FunctionDevConfig;
|
|
147
|
+
}
|
|
148
|
+
/** Anonymous-access level for a branchable object-storage bucket. */
|
|
149
|
+
type BucketAccessLevel = "private" | "public_read";
|
|
150
|
+
/**
|
|
151
|
+
* A branchable object-storage bucket on a branch (Preview feature).
|
|
152
|
+
*/
|
|
153
|
+
interface BucketConfig {
|
|
154
|
+
/** Bucket name, unique within a branch. 1–255 chars. */
|
|
155
|
+
name: string;
|
|
156
|
+
/**
|
|
157
|
+
* Anonymous access level. `private` (default) requires authenticated reads/writes;
|
|
158
|
+
* `public_read` allows anonymous GetObject/HeadObject.
|
|
159
|
+
*/
|
|
160
|
+
access?: BucketAccessLevel;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Branch-scoped Preview features. Grouped under `preview` to signal they are backed by
|
|
164
|
+
* Neon `x-stability-level: beta` endpoints and may change before GA.
|
|
165
|
+
*/
|
|
166
|
+
interface PreviewConfig {
|
|
167
|
+
/** Functions to deploy on the branch. */
|
|
168
|
+
functions?: FunctionConfig[];
|
|
169
|
+
/** Object-storage buckets to create on the branch. */
|
|
170
|
+
buckets?: BucketConfig[];
|
|
171
|
+
/** Enable/disable the AI Gateway on the branch (toggle, like auth / dataApi). */
|
|
172
|
+
aiGateway?: ServiceToggle;
|
|
173
|
+
}
|
|
174
|
+
interface BranchConfigBase {
|
|
175
|
+
/** Parent branch name used when creating a new branch. Not a Postgres setting. */
|
|
176
|
+
parent?: string;
|
|
177
|
+
/** Time-to-live applied when creating a new branch, or reconciled on existing branches. */
|
|
178
|
+
ttl?: string | number;
|
|
179
|
+
/** Whether the selected branch should be protected. Undefined means "leave as-is". */
|
|
180
|
+
protected?: boolean;
|
|
181
|
+
postgres?: PostgresConfig;
|
|
182
|
+
/**
|
|
183
|
+
* Branch-scoped Preview features (functions, object-storage buckets, AI Gateway).
|
|
184
|
+
* Backed by Neon `x-stability-level: beta` endpoints — see {@link PreviewConfig}.
|
|
185
|
+
*/
|
|
186
|
+
preview?: PreviewConfig;
|
|
187
|
+
}
|
|
188
|
+
type BranchServiceConfig = {
|
|
189
|
+
auth?: never;
|
|
190
|
+
dataApi?: never;
|
|
191
|
+
} | {
|
|
192
|
+
auth: ServiceToggle;
|
|
193
|
+
dataApi?: never;
|
|
194
|
+
} | {
|
|
195
|
+
auth?: never;
|
|
196
|
+
dataApi: ServiceToggle;
|
|
197
|
+
} | {
|
|
198
|
+
auth: ServiceToggle;
|
|
199
|
+
dataApi: ServiceToggle;
|
|
200
|
+
};
|
|
201
|
+
type BranchConfig = BranchConfigBase & BranchServiceConfig;
|
|
202
|
+
type Config = (branch: BranchTarget) => BranchConfig;
|
|
203
|
+
/**
|
|
204
|
+
* A function with all deploy defaults applied. `resolveConfig` fills in `runtime` and
|
|
205
|
+
* `memoryMib` so downstream diff/apply never has to re-derive them.
|
|
206
|
+
*/
|
|
207
|
+
//#endregion
|
|
208
|
+
export { BranchConfig, BranchTarget, BucketAccessLevel, BucketConfig, ComputeSettings, Config, FunctionConfig, FunctionDevConfig, FunctionMemoryMib, FunctionRuntime, PostgresConfig, PreviewConfig, ServiceToggle };
|
|
209
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":["ComputeUnit","ComputeSettings","BranchTarget","ServiceToggle","PostgresConfig","FunctionRuntime","FunctionMemoryMib","FunctionDevConfig","FunctionConfig","Record","BucketAccessLevel","BucketConfig","PreviewConfig","BranchConfigBase","BranchServiceConfig","BranchConfig","Config","ResolvedFunctionConfig","ResolvedBucketConfig","ResolvedPreviewConfig","ResolvedBranchConfig","AppliedChange","ConflictReport","PushResult"],"sources":["../../../../../config/dist/lib/types.d.ts"],"sourcesContent":["//#region src/lib/types.d.ts\n/**\n * Valid Neon Compute Unit values.\n * Most plans support 0.25, 0.5, 1, 2, 4, 8. Higher values may be available on Business plans.\n */\ntype ComputeUnit = 0.25 | 0.5 | 1 | 2 | 4 | 8;\n/**\n * Compute settings applied to the read/write endpoint of a branch.\n *\n * Mirrors the subset of {@link https://api-docs.neon.tech/reference/getting-started-with-neon-api Neon endpoint}\n * fields that we expose as IaC primitives. Anything left undefined falls back to the project's\n * `default_endpoint_settings` (which themselves fall back to Neon platform defaults).\n */\ninterface ComputeSettings {\n /**\n * Minimum number of Compute Units. Set to 0.25 for true scale-to-zero.\n * @example 0.25 // scale-to-zero\n * @example 1 // always-on with 1 CU minimum\n */\n autoscalingLimitMinCu?: ComputeUnit;\n /**\n * Maximum number of Compute Units for autoscaling.\n * @example 2\n * @example 8\n */\n autoscalingLimitMaxCu?: ComputeUnit;\n /**\n * How long to wait before suspending an idle compute.\n *\n * - `false` — never suspend (always-on compute)\n * - `\"5m\"` — duration string (supports \"30s\", \"5m\", \"1h\", \"7d\", etc)\n * - `300` — custom timeout in seconds (60-604800)\n * - `undefined` — use Neon platform default (currently 300s / 5 minutes)\n *\n * @example false // never suspend\n * @example \"5m\" // 5 minutes\n * @example \"1h\" // 1 hour\n * @example 300 // 5 minutes in seconds\n */\n suspendTimeout?: false | \"5m\" | \"1h\" | string | number;\n}\n/**\n * Read-only descriptor of the branch a {@link Config} policy is being evaluated for — the\n * `branch` argument passed to your `defineConfig((branch) => …)` callback. It describes\n * **which** branch this invocation decides for; it is not a live branch handle and must not\n * be mutated. Switch on its fields and return the desired {@link BranchConfig}.\n */\ninterface BranchTarget {\n /** Branch name being evaluated. For `branch dev`, this is the generated branch name. */\n name: string;\n /** Neon branch id when the branch already exists. Undefined during pre-create eval. */\n id?: string;\n /** Whether this branch already exists on Neon. */\n exists: boolean;\n /** Parent branch id from Neon when known. */\n parentId?: string;\n /** Whether Neon marks this branch as the project default. */\n isDefault?: boolean;\n /** Whether Neon currently marks this branch protected. */\n isProtected?: boolean;\n /** Current expiration timestamp from Neon, when set. */\n expiresAt?: string;\n}\ninterface ServiceToggle {\n /** Defaults to `true` when the service namespace is present. Set `false` to opt out. */\n enabled?: boolean;\n}\ninterface PostgresConfig {\n computeSettings?: ComputeSettings;\n}\n/**\n * Supported function runtimes. Mirrors the Neon Functions deploy API `runtime` enum.\n * Only `nodejs24` exists today; kept as a union so adding runtimes later is a\n * non-breaking, type-checked change.\n */\ntype FunctionRuntime = \"nodejs24\";\n/**\n * Memory sizes (MiB) accepted by the Neon Functions deploy API. Mirrors the\n * `memory_mib` enum in the spec.\n */\ntype FunctionMemoryMib = 256 | 512 | 1024 | 2048 | 4096 | 8192;\n/**\n * Local-development settings for a function, used by `neon dev` when it serves every\n * function declared in `neon.ts` (i.e. invoked with no `--source`). Never affects deploy.\n *\n * Typed as a discriminated union so the `portless` ⇒ `port` requirement is enforced at\n * compile time: a `portless` route needs a concrete port to map its `slug.localhost`\n * name to, so `port` is mandatory when `portless: true`.\n *\n * - `{ portless: true; port }` — wrap this function with `portless run <slug> …` so it gets\n * a stable `slug.localhost` URL. `port` is required.\n * - `{ portless?: false; port? }` — serve directly. `port` is optional: when set it is bound\n * exactly (and `neon dev` fails loudly if it is taken); when omitted a free port is found.\n */\ntype FunctionDevConfig = {\n portless: true;\n port: number;\n} | {\n portless?: false;\n port?: number;\n};\n/**\n * A single Neon Function deployed to a branch (Preview feature).\n *\n * A function is invoked like a Cloudflare/Vercel handler — its source module\n * `export default { fetch }` or `export async function handler(req): Response`. The\n * `source` path is bundled (esbuild) and uploaded as a deployment; the newest\n * deployment becomes active.\n */\ninterface FunctionConfig {\n /**\n * Branch-unique, lowercase DNS-label used as the path segment in the function's\n * invocation URL. Immutable once created. 1–40 chars, `^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$`.\n * @example \"hello-world\"\n */\n slug: string;\n /** Free-form display name. @example \"Hello World\" */\n name: string;\n /**\n * Path to the function's entry module, **relative to `neon.ts`** (or absolute). The\n * module's default export (`{ fetch }`) or `handler` export is the function entry. This\n * path is resolved against the loaded `neon.ts` location and bundled with esbuild at\n * deploy time.\n *\n * We require a string path rather than an imported handler because a JS function value\n * carries no reference back to its source file, so esbuild has nothing to bundle from.\n * @example \"./functions/hello-world.ts\"\n */\n source: string;\n /**\n * Environment variables injected into the deployed function. Every value must be a\n * defined string — a `process.env.X` that is `undefined` (unset) errors at validation\n * time rather than silently shipping `undefined`.\n * @example { RESEND_API_KEY: process.env.RESEND_API_KEY }\n */\n env?: Record<string, string>;\n /** Runtime to execute the function with. Defaults to `\"nodejs24\"`. */\n runtime?: FunctionRuntime;\n /** Memory allotted to each invocation, in MiB. Defaults to `512`. */\n memoryMib?: FunctionMemoryMib;\n /**\n * Local-development settings used by `neon dev` when serving every function from\n * `neon.ts`. Ignored at deploy time. See {@link FunctionDevConfig}.\n */\n dev?: FunctionDevConfig;\n}\n/** Anonymous-access level for a branchable object-storage bucket. */\ntype BucketAccessLevel = \"private\" | \"public_read\";\n/**\n * A branchable object-storage bucket on a branch (Preview feature).\n */\ninterface BucketConfig {\n /** Bucket name, unique within a branch. 1–255 chars. */\n name: string;\n /**\n * Anonymous access level. `private` (default) requires authenticated reads/writes;\n * `public_read` allows anonymous GetObject/HeadObject.\n */\n access?: BucketAccessLevel;\n}\n/**\n * Branch-scoped Preview features. Grouped under `preview` to signal they are backed by\n * Neon `x-stability-level: beta` endpoints and may change before GA.\n */\ninterface PreviewConfig {\n /** Functions to deploy on the branch. */\n functions?: FunctionConfig[];\n /** Object-storage buckets to create on the branch. */\n buckets?: BucketConfig[];\n /** Enable/disable the AI Gateway on the branch (toggle, like auth / dataApi). */\n aiGateway?: ServiceToggle;\n}\ninterface BranchConfigBase {\n /** Parent branch name used when creating a new branch. Not a Postgres setting. */\n parent?: string;\n /** Time-to-live applied when creating a new branch, or reconciled on existing branches. */\n ttl?: string | number;\n /** Whether the selected branch should be protected. Undefined means \"leave as-is\". */\n protected?: boolean;\n postgres?: PostgresConfig;\n /**\n * Branch-scoped Preview features (functions, object-storage buckets, AI Gateway).\n * Backed by Neon `x-stability-level: beta` endpoints — see {@link PreviewConfig}.\n */\n preview?: PreviewConfig;\n}\ntype BranchServiceConfig = {\n auth?: never;\n dataApi?: never;\n} | {\n auth: ServiceToggle;\n dataApi?: never;\n} | {\n auth?: never;\n dataApi: ServiceToggle;\n} | {\n auth: ServiceToggle;\n dataApi: ServiceToggle;\n};\ntype BranchConfig = BranchConfigBase & BranchServiceConfig;\ntype Config = (branch: BranchTarget) => BranchConfig;\n/**\n * A function with all deploy defaults applied. `resolveConfig` fills in `runtime` and\n * `memoryMib` so downstream diff/apply never has to re-derive them.\n */\ninterface ResolvedFunctionConfig {\n slug: string;\n name: string;\n source: string;\n env: Record<string, string>;\n runtime: FunctionRuntime;\n memoryMib: FunctionMemoryMib;\n /**\n * Local-development settings, passed through untouched from {@link FunctionConfig.dev}\n * (no defaults applied). Only consumed by `neon dev`; deploy ignores it.\n */\n dev?: FunctionDevConfig;\n}\n/** A bucket with its access level defaulted to `private`. */\ninterface ResolvedBucketConfig {\n name: string;\n access: BucketAccessLevel;\n}\n/**\n * Normalized {@link PreviewConfig}. Only present on {@link ResolvedBranchConfig} when the\n * policy returned a `preview` block. `aiGatewayEnabled` follows the same\n * \"present-and-not-`false`\" semantics as `authEnabled` / `dataApiEnabled`.\n */\ninterface ResolvedPreviewConfig {\n functions: ResolvedFunctionConfig[];\n buckets: ResolvedBucketConfig[];\n aiGatewayEnabled: boolean;\n}\ninterface ResolvedBranchConfig {\n parent?: string;\n ttlSeconds?: number;\n protected?: boolean;\n postgres?: PostgresConfig;\n authEnabled: boolean;\n dataApiEnabled: boolean;\n preview?: ResolvedPreviewConfig;\n}\n/**\n * One concrete change `pushConfig` made (or, in dry-run, would make) on the remote.\n */\ninterface AppliedChange {\n /**\n * `service` covers branch-scoped integrations driven by the branch policy (e.g.\n * Neon Auth, Data API).\n */\n kind: \"branch\" | \"service\";\n action: \"create\" | \"update\" | \"noop\";\n identifier: string;\n details?: Record<string, unknown>;\n}\n/**\n * A diff entry that conflicts with the desired config. `pushConfig` throws\n * {@link PushConflictError} on the first call when conflicts exist; pass\n * `updateExisting: true` to apply mutable drift (settings, `protected`, TTL, project\n * rename). Immutable fields (region, Postgres major version) are always conflicts —\n * recreate the project to change them.\n */\ninterface ConflictReport {\n kind: \"branch\";\n identifier: string;\n field: string;\n current: unknown;\n desired: unknown;\n reason: string;\n}\n/**\n * Result of a `pushConfig` invocation.\n */\ninterface PushResult {\n projectId: string;\n orgId?: string;\n branchId: string;\n branchName: string;\n /**\n * `true` when `pushConfig` was called with `{ dryRun: true }`. `applied` then records\n * what **would** be applied on a real push; no API mutations were performed.\n */\n dryRun: boolean;\n applied: AppliedChange[];\n conflicts: ConflictReport[];\n}\n//#endregion\nexport { AppliedChange, BranchConfig, BranchTarget, BucketAccessLevel, BucketConfig, ComputeSettings, ComputeUnit, Config, ConflictReport, FunctionConfig, FunctionDevConfig, FunctionMemoryMib, FunctionRuntime, PostgresConfig, PreviewConfig, PushResult, ResolvedBranchConfig, ResolvedBucketConfig, ResolvedFunctionConfig, ResolvedPreviewConfig, ServiceToggle };\n//# sourceMappingURL=types.d.ts.map"],"mappings":";;;AAKgB;;;KAAXA,WAAAA,GAoBqBA,IAAAA,GAAAA,GAAAA,GAAAA,CAAAA,GAAAA,CAAAA,GAAAA,CAAAA,GAAAA,CAAAA;AAAW;AAAA;AAsBf;AAgBC;AAKY;AAOf;AAKE;AAcA,UAjFZC,eAAAA,CAgGc;EAAA;;;;;EAmCC,qBAAA,CAAA,EA7HCD,WA6HD;EAAA;AAGH;AAWM;;;uBAUhBW,CAAAA,EA/IcX,WA+IdW;;AAEe;AAAA;;;;AAcF;AAAA;;;;;;EAaD,cAAA,CAAA,EAAA,KAAA,GAAA,IAAA,GAAA,IAAA,GAAA,MAAA,GAAA,MAAA;AAAA;;;;AAEkC;AAAA;;UAxJhDT,YAAAA,CAyJaA;;EAA6B,IAAA,EAAA,MAAA;;;;;;;;;;;;;;UAzI1CC,aAAAA;;;;UAIAC,cAAAA;oBACUH;;;;;;;KAOfI,eAAAA;;;;;KAKAC,iBAAAA;;;;;;;;;;;;;;KAcAC,iBAAAA;;;;;;;;;;;;;;;UAeKC,cAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;QA0BFC;;YAEIJ;;cAEEC;;;;;QAKNC;;;KAGHG,iBAAAA;;;;UAIKC,YAAAA;;;;;;;WAOCD;;;;;;UAMDE,aAAAA;;cAEIJ;;YAEFG;;cAEER;;UAEJU,gBAAAA;;;;;;;aAOGT;;;;;YAKDQ;;KAEPE,mBAAAA;;;;QAIGX;;;;WAIGA;;QAEHA;WACGA;;KAENY,YAAAA,GAAeF,mBAAmBC;KAClCE,MAAAA,YAAkBd,iBAAiBa"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { BranchConfig, BranchTarget, BucketAccessLevel, BucketConfig, ComputeSettings, Config, FunctionConfig, FunctionDevConfig, FunctionMemoryMib, FunctionRuntime, PostgresConfig, PreviewConfig, ServiceToggle } from "./lib/types.js";
|
|
2
|
+
import { CreateBranchInput, CreateBucketInput, CreateProjectInput, DeployFunctionInput, GetConnectionUriInput, NeonApi, NeonAuthSnapshot, NeonBranchSnapshot, NeonBucketSnapshot, NeonDataApiSnapshot, NeonDatabaseSnapshot, NeonEndpointSnapshot, NeonFunctionDeploymentSnapshot, NeonFunctionSnapshot, NeonProjectSnapshot, NeonRoleSnapshot, UpdateBranchInput } from "./lib/neon-api.js";
|
|
3
|
+
import "zod";
|
|
4
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { FetchEnvOptions, NEON_ENV_VAR_KEYS, NeonAuthEnv, NeonDataApiEnv, NeonEnv, NeonPostgresEnv, fetchEnv, parseEnv, toEntries } from "./lib/env.js";
|
|
2
|
+
export { FetchEnvOptions, NEON_ENV_VAR_KEYS, NeonAuthEnv, NeonDataApiEnv, NeonEnv, NeonPostgresEnv, fetchEnv, parseEnv, toEntries };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
* `options.apiKey ?? NEON_API_KEY` inside `fetchEnv`.
|
|
16
|
+
*/
|
|
17
|
+
api?: NeonApi;
|
|
18
|
+
}
|
|
19
|
+
interface CommandResult {
|
|
20
|
+
/** Process exit code. `0` for success, non-zero for failure. */
|
|
21
|
+
exitCode: number;
|
|
22
|
+
/** Text intended for stdout. */
|
|
23
|
+
stdout: string;
|
|
24
|
+
/** Text intended for stderr (human-readable status / error messages). */
|
|
25
|
+
stderr: string;
|
|
26
|
+
/** Optional structured debug payload — printed only when `--debug` is passed. */
|
|
27
|
+
debugInfo?: string;
|
|
28
|
+
}
|
|
29
|
+
interface EnvRunCommandOptions {
|
|
30
|
+
/** The user command to spawn (after `--`). The first element is the executable. */
|
|
31
|
+
command: string[];
|
|
32
|
+
configPath?: string;
|
|
33
|
+
projectId?: string;
|
|
34
|
+
branch?: string;
|
|
35
|
+
apiKey?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Implementation of `neon-env run -- <cmd...>`. Loads `neon.ts`, fetches the env from
|
|
39
|
+
* Neon, then spawns the user-supplied command with the env vars injected on top of the
|
|
40
|
+
* inherited `process.env`. Stdio is inherited so interactive dev servers keep working.
|
|
41
|
+
* The parent process exits with the child's exit code.
|
|
42
|
+
*/
|
|
43
|
+
declare function runEnvRun(options: EnvRunCommandOptions, ctx: CommandEnv): Promise<CommandResult>;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { CommandEnv, CommandResult, EnvRunCommandOptions, runEnvRun };
|
|
46
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","names":[],"sources":["../../../src/lib/cli/commands.ts"],"mappings":";;;;;;;;AAsBA;AASA;AAWiB,UApBA,UAAA,CAoBoB;EAef,GAAA,EAAA,MAAA;EAAS;;;;KAG5B,CAAA,EAhCI,OAgCJ;AAAO;UA7BO,aAAA;;;;;;;;;;UAWA,oBAAA;;;;;;;;;;;;;;iBAeK,SAAA,UACZ,2BACJ,aACH,QAAQ"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { fetchEnv, toEntries } from "../env.js";
|
|
2
|
+
import { resolveContext } from "./resolve-context.js";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { ConfigLoadError, ErrorCode, MissingContextError, PlatformError, loadConfigFromFile } from "@neondatabase/config/v1";
|
|
7
|
+
//#region src/lib/cli/commands.ts
|
|
8
|
+
/** File `env run` reads to layer one-time auth keys. Matches the Vercel/Next.js convention. */
|
|
9
|
+
const DEFAULT_ENV_FILE = ".env.local";
|
|
10
|
+
/**
|
|
11
|
+
* Implementation of `neon-env run -- <cmd...>`. Loads `neon.ts`, fetches the env from
|
|
12
|
+
* Neon, then spawns the user-supplied command with the env vars injected on top of the
|
|
13
|
+
* inherited `process.env`. Stdio is inherited so interactive dev servers keep working.
|
|
14
|
+
* The parent process exits with the child's exit code.
|
|
15
|
+
*/
|
|
16
|
+
async function runEnvRun(options, ctx) {
|
|
17
|
+
if (options.command.length === 0) return failure([
|
|
18
|
+
"`env run` requires a command to spawn.",
|
|
19
|
+
"Usage: neon-env run -- <command> [args...]",
|
|
20
|
+
"Example: neon-env run -- npm run dev"
|
|
21
|
+
].join("\n"));
|
|
22
|
+
const resolved = resolveContext({
|
|
23
|
+
cwd: ctx.cwd,
|
|
24
|
+
...options.projectId ? { projectId: options.projectId } : {},
|
|
25
|
+
...options.branch ? { branch: options.branch } : {}
|
|
26
|
+
});
|
|
27
|
+
if (!resolved.ok) return failure(["`env run` could not resolve the Neon project and branch:", ...resolved.missing.map((m) => ` - ${m}`)].join("\n"), 3);
|
|
28
|
+
let injected;
|
|
29
|
+
try {
|
|
30
|
+
injected = toEntries(await loadConfigAndFetchEnv(options, ctx, resolved.context));
|
|
31
|
+
} catch (err) {
|
|
32
|
+
return handleError(err);
|
|
33
|
+
}
|
|
34
|
+
const [executable, ...args] = options.command;
|
|
35
|
+
return {
|
|
36
|
+
exitCode: await spawnAndWait(executable, args, {
|
|
37
|
+
cwd: ctx.cwd,
|
|
38
|
+
env: {
|
|
39
|
+
...process.env,
|
|
40
|
+
...injected
|
|
41
|
+
}
|
|
42
|
+
}),
|
|
43
|
+
stdout: "",
|
|
44
|
+
stderr: ""
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Load `neon.ts`, then call `fetchEnv` with the explicitly-resolved project + branch.
|
|
49
|
+
* Layers any one-time Auth keys from `.env.local` (next to the config file) into the env
|
|
50
|
+
* source so re-runs keep round-tripping values the Neon API only returns once at
|
|
51
|
+
* integration-creation time.
|
|
52
|
+
*/
|
|
53
|
+
async function loadConfigAndFetchEnv(options, ctx, resolved) {
|
|
54
|
+
const { config, resolvedPath } = await loadConfigFromFile({
|
|
55
|
+
...options.configPath ? { path: options.configPath } : {},
|
|
56
|
+
cwd: ctx.cwd
|
|
57
|
+
});
|
|
58
|
+
const envFileSource = join(dirname(resolvedPath), DEFAULT_ENV_FILE);
|
|
59
|
+
const fileEnv = existsSync(envFileSource) ? parseEnvFile(readFileSync(envFileSource, "utf-8")) : {};
|
|
60
|
+
return fetchEnv(config, {
|
|
61
|
+
projectId: resolved.projectId,
|
|
62
|
+
branchId: resolved.branchId,
|
|
63
|
+
env: {
|
|
64
|
+
...process.env,
|
|
65
|
+
...fileEnv
|
|
66
|
+
},
|
|
67
|
+
...ctx.api ? { api: ctx.api } : {},
|
|
68
|
+
...options.apiKey ? { apiKey: options.apiKey } : {}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Spawn a child process with stdio inherited so dev servers stay interactive. Resolves
|
|
73
|
+
* with the child's exit code (treating signal terminations as code 1 so the CLI surfaces
|
|
74
|
+
* a non-zero exit consistently).
|
|
75
|
+
*/
|
|
76
|
+
function spawnAndWait(command, args, options) {
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
const child = spawn(command, args, {
|
|
79
|
+
cwd: options.cwd,
|
|
80
|
+
env: options.env,
|
|
81
|
+
stdio: "inherit"
|
|
82
|
+
});
|
|
83
|
+
child.on("error", (err) => {
|
|
84
|
+
process.stderr.write(`neon-env run: failed to spawn '${command}': ${err.message}\n`);
|
|
85
|
+
resolve(1);
|
|
86
|
+
});
|
|
87
|
+
child.on("exit", (code, signal) => {
|
|
88
|
+
if (typeof code === "number") {
|
|
89
|
+
resolve(code);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (signal) {
|
|
93
|
+
process.stderr.write(`neon-env run: child terminated by signal ${signal}\n`);
|
|
94
|
+
resolve(1);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
resolve(1);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function parseEnvFile(body) {
|
|
102
|
+
const out = {};
|
|
103
|
+
for (const line of body.split("\n")) {
|
|
104
|
+
const parsed = parseEnvLine(line);
|
|
105
|
+
if (parsed) out[parsed.key] = parsed.value;
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
function parseEnvLine(line) {
|
|
110
|
+
const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
111
|
+
const key = match?.[1];
|
|
112
|
+
const rawValue = match?.[2];
|
|
113
|
+
if (key === void 0 || rawValue === void 0) return null;
|
|
114
|
+
return {
|
|
115
|
+
key,
|
|
116
|
+
value: unescapeEnvValue(rawValue.trim())
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function unescapeEnvValue(value) {
|
|
120
|
+
if (value.length >= 2 && value.startsWith("\"") && value.endsWith("\"")) return value.slice(1, -1).replace(/\\"/g, "\"").replace(/\\\\/g, "\\");
|
|
121
|
+
if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Stable exit code per `PlatformError` code. Mirrors the table in the config package so
|
|
126
|
+
* shell pipelines can branch on the specific failure mode without parsing free text.
|
|
127
|
+
*/
|
|
128
|
+
const EXIT_CODE_BY_PLATFORM_ERROR_CODE = {
|
|
129
|
+
[ErrorCode.MissingApiKey]: 1,
|
|
130
|
+
[ErrorCode.Unauthorized]: 6,
|
|
131
|
+
[ErrorCode.Forbidden]: 7,
|
|
132
|
+
[ErrorCode.NotFound]: 8,
|
|
133
|
+
[ErrorCode.RateLimited]: 9,
|
|
134
|
+
[ErrorCode.NetworkError]: 10,
|
|
135
|
+
[ErrorCode.ServerError]: 11,
|
|
136
|
+
[ErrorCode.Locked]: 11,
|
|
137
|
+
[ErrorCode.InternalError]: 99
|
|
138
|
+
};
|
|
139
|
+
function handleError(err) {
|
|
140
|
+
if (err instanceof MissingContextError) return errorResult(err, `Missing context: ${err.message}`, 3);
|
|
141
|
+
if (err instanceof ConfigLoadError) return errorResult(err, `Failed to load config: ${err.message}`, 4);
|
|
142
|
+
if (err instanceof PlatformError) {
|
|
143
|
+
const exitCode = EXIT_CODE_BY_PLATFORM_ERROR_CODE[err.code];
|
|
144
|
+
if (exitCode !== void 0) return errorResult(err, err.message, exitCode);
|
|
145
|
+
return errorResult(err, `[${err.code}] ${err.message}`, 5);
|
|
146
|
+
}
|
|
147
|
+
if (err instanceof Error) return errorResult(err, err.message, 1);
|
|
148
|
+
return failure(String(err), 1);
|
|
149
|
+
}
|
|
150
|
+
function errorResult(err, message, exitCode) {
|
|
151
|
+
const result = {
|
|
152
|
+
exitCode,
|
|
153
|
+
stdout: "",
|
|
154
|
+
stderr: `${message}\n`
|
|
155
|
+
};
|
|
156
|
+
const debug = buildDebugInfo(err);
|
|
157
|
+
if (debug) result.debugInfo = debug;
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
function buildDebugInfo(err) {
|
|
161
|
+
if (!(err instanceof Error)) return void 0;
|
|
162
|
+
const lines = [];
|
|
163
|
+
if (err instanceof PlatformError) {
|
|
164
|
+
lines.push(`code : ${err.code}`);
|
|
165
|
+
if (Object.keys(err.details).length > 0) lines.push(`details : ${JSON.stringify(err.details, null, 2)}`);
|
|
166
|
+
}
|
|
167
|
+
if (err.cause instanceof Error) lines.push(`cause : ${err.cause.name}: ${err.cause.message}`);
|
|
168
|
+
if (err.stack) lines.push(err.stack);
|
|
169
|
+
return lines.length > 0 ? lines.join("\n") : void 0;
|
|
170
|
+
}
|
|
171
|
+
function failure(message, exitCode = 1) {
|
|
172
|
+
return {
|
|
173
|
+
exitCode,
|
|
174
|
+
stdout: "",
|
|
175
|
+
stderr: `${message}\n`
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
export { runEnvRun };
|
|
180
|
+
|
|
181
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 \"@neondatabase/config/v1\";\nimport { fetchEnv, toEntries } from \"../env.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 * `options.apiKey ?? NEON_API_KEY` inside `fetchEnv`.\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\nexport interface EnvRunCommandOptions {\n\t/** The user command to spawn (after `--`). The first element is the executable. */\n\tcommand: string[];\n\tconfigPath?: string;\n\tprojectId?: string;\n\tbranch?: string;\n\tapiKey?: 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\tconst env = await loadConfigAndFetchEnv(options, ctx, resolved.context);\n\t\tinjected = toEntries(env);\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\n/**\n * Load `neon.ts`, then call `fetchEnv` with the explicitly-resolved project + branch.\n * Layers any one-time Auth keys from `.env.local` (next to the config file) into the env\n * source so re-runs keep round-tripping values the Neon API only returns once at\n * integration-creation time.\n */\nasync function loadConfigAndFetchEnv(\n\toptions: EnvRunCommandOptions,\n\tctx: CommandEnv,\n\tresolved: { projectId: string; branchId: string },\n): Promise<Awaited<ReturnType<typeof fetchEnv>>> {\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\treturn fetchEnv(config, {\n\t\tprojectId: resolved.projectId,\n\t\tbranchId: resolved.branchId,\n\t\tenv: { ...process.env, ...fileEnv },\n\t\t...(ctx.api ? { api: ctx.api } : {}),\n\t\t...(options.apiKey ? { apiKey: options.apiKey } : {}),\n\t});\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\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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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
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 `@neondatabase/env` library
|
|
5
|
+
* functions can stay filesystem- and env-agnostic.
|
|
6
|
+
*/
|
|
7
|
+
interface ResolvedContext {
|
|
8
|
+
projectId: string;
|
|
9
|
+
branchId: string;
|
|
10
|
+
/** Branch name for `parseEnv`-style policy evaluation, when known. */
|
|
11
|
+
branchName?: string;
|
|
12
|
+
}
|
|
13
|
+
interface ResolveContextOptions {
|
|
14
|
+
projectId?: string;
|
|
15
|
+
branch?: string;
|
|
16
|
+
cwd: string;
|
|
17
|
+
env?: NodeJS.ProcessEnv;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve `projectId` and `branch` for a CLI invocation. Precedence (each wins over the
|
|
21
|
+
* next): explicit flag → `NEON_*` env var → `.neon[/project.json]` walked up from `cwd`.
|
|
22
|
+
*
|
|
23
|
+
* Returns the resolved values plus a list of human-readable reasons for any field that
|
|
24
|
+
* could not be resolved (so the caller can render one combined error).
|
|
25
|
+
*/
|
|
26
|
+
declare function resolveContext(options: ResolveContextOptions): {
|
|
27
|
+
ok: true;
|
|
28
|
+
context: ResolvedContext;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
missing: string[];
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
export { ResolveContextOptions, ResolvedContext, resolveContext };
|
|
35
|
+
//# sourceMappingURL=resolve-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-context.d.ts","names":[],"sources":["../../../src/lib/cli/resolve-context.ts"],"mappings":";;AASA;AAOA;AAcA;;AACU,UAtBO,eAAA,CAsBP;WACc,EAAA,MAAA;EAAe,QAAA,EAAA,MAAA;;;;UAhBtB,qBAAA;;;;QAIV,MAAA,CAAO;;;;;;;;;iBAUE,cAAA,UACN;;WACc"}
|
|
@@ -0,0 +1,90 @@
|
|
|
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 branchId = nonEmpty(options.branch) ?? nonEmpty(env.NEON_BRANCH_ID) ?? file?.branchId;
|
|
17
|
+
const branchName = nonEmpty(env.NEON_BRANCH_NAME) ?? file?.branchName;
|
|
18
|
+
const missing = [];
|
|
19
|
+
if (!projectId) missing.push("project id — pass `--project-id`, set `NEON_PROJECT_ID`, or add `projectId` to `.neon/project.json` (run `npx neonctl link`).");
|
|
20
|
+
if (!branchId) missing.push("branch — pass `--branch`, set `NEON_BRANCH_ID`, or add `branchId` to `.neon/project.json` (run `npx neonctl checkout <branch>`).");
|
|
21
|
+
if (!projectId || !branchId) return {
|
|
22
|
+
ok: false,
|
|
23
|
+
missing
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
ok: true,
|
|
27
|
+
context: {
|
|
28
|
+
projectId,
|
|
29
|
+
branchId,
|
|
30
|
+
...branchName ? { branchName } : {}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Walk up from `cwd` looking for `.neon/project.json` (preferred) or `.neon` (neonctl
|
|
36
|
+
* convention). Stops at the first `.git` directory or the home directory. Read-only.
|
|
37
|
+
*/
|
|
38
|
+
function findNeonFile(cwd) {
|
|
39
|
+
let current = resolve(cwd);
|
|
40
|
+
const stop = resolve(homedir());
|
|
41
|
+
let lastSeen = null;
|
|
42
|
+
while (true) {
|
|
43
|
+
const parsed = readNeonFileAt(resolve(current, ".neon", "project.json")) ?? readNeonFileAt(resolve(current, ".neon"));
|
|
44
|
+
if (parsed) return parsed;
|
|
45
|
+
if (current === stop) return null;
|
|
46
|
+
if (existsSync(resolve(current, ".git"))) return null;
|
|
47
|
+
const parent = dirname(current);
|
|
48
|
+
if (parent === current || parent === lastSeen) return null;
|
|
49
|
+
lastSeen = current;
|
|
50
|
+
current = parent;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function readNeonFileAt(path) {
|
|
54
|
+
if (!isFile(path)) return null;
|
|
55
|
+
let raw;
|
|
56
|
+
try {
|
|
57
|
+
raw = readFileSync(path, "utf-8");
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
let parsed;
|
|
62
|
+
try {
|
|
63
|
+
parsed = JSON.parse(raw);
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
68
|
+
const obj = parsed;
|
|
69
|
+
const out = {};
|
|
70
|
+
if (typeof obj.projectId === "string" && obj.projectId !== "") out.projectId = obj.projectId;
|
|
71
|
+
if (typeof obj.branchId === "string" && obj.branchId !== "") out.branchId = obj.branchId;
|
|
72
|
+
if (typeof obj.branchName === "string" && obj.branchName !== "") out.branchName = obj.branchName;
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
function isFile(path) {
|
|
76
|
+
try {
|
|
77
|
+
return statSync(path).isFile();
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function nonEmpty(value) {
|
|
83
|
+
if (typeof value !== "string") return void 0;
|
|
84
|
+
const trimmed = value.trim();
|
|
85
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { resolveContext };
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=resolve-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 `@neondatabase/env` library\n * functions can stay filesystem- and env-agnostic.\n */\nexport interface ResolvedContext {\n\tprojectId: string;\n\tbranchId: string;\n\t/** Branch name for `parseEnv`-style policy evaluation, when known. */\n\tbranchName?: 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\tconst branchId =\n\t\tnonEmpty(options.branch) ??\n\t\tnonEmpty(env.NEON_BRANCH_ID) ??\n\t\tfile?.branchId;\n\n\tconst branchName = nonEmpty(env.NEON_BRANCH_NAME) ?? file?.branchName;\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/project.json` (run `npx neonctl link`).\",\n\t\t);\n\t}\n\tif (!branchId) {\n\t\tmissing.push(\n\t\t\t\"branch — pass `--branch`, set `NEON_BRANCH_ID`, or add `branchId` to `.neon/project.json` (run `npx neonctl checkout <branch>`).\",\n\t\t);\n\t}\n\tif (!projectId || !branchId) return { ok: false, missing };\n\n\treturn {\n\t\tok: true,\n\t\tcontext: {\n\t\t\tprojectId,\n\t\t\tbranchId,\n\t\t\t...(branchName ? { branchName } : {}),\n\t\t},\n\t};\n}\n\ninterface NeonFile {\n\tprojectId?: string;\n\tbranchId?: string;\n\tbranchName?: 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\tif (typeof obj.branchId === \"string\" && obj.branchId !== \"\")\n\t\tout.branchId = obj.branchId;\n\tif (typeof obj.branchName === \"string\" && obj.branchName !== \"\")\n\t\tout.branchName = obj.branchName;\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":";;;;;;;;;;;AA8BA,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;CAEP,MAAM,WACL,SAAS,QAAQ,MAAM,KACvB,SAAS,IAAI,cAAc,KAC3B,MAAM;CAEP,MAAM,aAAa,SAAS,IAAI,gBAAgB,KAAK,MAAM;CAE3D,MAAM,UAAoB,CAAC;CAC3B,IAAI,CAAC,WACJ,QAAQ,KACP,+HACD;CAED,IAAI,CAAC,UACJ,QAAQ,KACP,kIACD;CAED,IAAI,CAAC,aAAa,CAAC,UAAU,OAAO;EAAE,IAAI;EAAO;CAAQ;CAEzD,OAAO;EACN,IAAI;EACJ,SAAS;GACR;GACA;GACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;EACpC;CACD;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;CACrB,IAAI,OAAO,IAAI,aAAa,YAAY,IAAI,aAAa,IACxD,IAAI,WAAW,IAAI;CACpB,IAAI,OAAO,IAAI,eAAe,YAAY,IAAI,eAAe,IAC5D,IAAI,aAAa,IAAI;CACtB,OAAO;AACR;AAEA,SAAS,OAAO,MAAuB;CACtC,IAAI;EACH,OAAO,SAAS,IAAI,EAAE,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"}
|