@neondatabase/config-runtime 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +178 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/lib/function-bundle.d.ts +34 -0
- package/dist/lib/function-bundle.d.ts.map +1 -0
- package/dist/lib/function-bundle.js +65 -0
- package/dist/lib/function-bundle.js.map +1 -0
- package/dist/lib/operations.d.ts +77 -0
- package/dist/lib/operations.d.ts.map +1 -0
- package/dist/lib/operations.js +61 -0
- package/dist/lib/operations.js.map +1 -0
- package/dist/lib/pull-config.d.ts +63 -0
- package/dist/lib/pull-config.d.ts.map +1 -0
- package/dist/lib/pull-config.js +113 -0
- package/dist/lib/pull-config.js.map +1 -0
- package/dist/lib/push-config.d.ts +111 -0
- package/dist/lib/push-config.d.ts.map +1 -0
- package/dist/lib/push-config.js +400 -0
- package/dist/lib/push-config.js.map +1 -0
- package/dist/v1.d.ts +6 -0
- package/dist/v1.js +6 -0
- package/package.json +69 -22
- package/e2e/conflict.e2e.test.ts +0 -34
- package/e2e/helpers.ts +0 -204
- package/e2e/lifecycle.e2e.test.ts +0 -50
- package/e2e/load-env.ts +0 -29
- package/e2e/setup.ts +0 -24
- package/src/index.ts +0 -5
- package/src/lib/fake-neon-api.ts +0 -782
- package/src/lib/function-bundle.test.ts +0 -60
- package/src/lib/function-bundle.ts +0 -104
- package/src/lib/operations.test.ts +0 -150
- package/src/lib/operations.ts +0 -103
- package/src/lib/pull-config.test.ts +0 -51
- package/src/lib/pull-config.ts +0 -215
- package/src/lib/push-config.test.ts +0 -421
- package/src/lib/push-config.ts +0 -619
- package/src/v1.test.ts +0 -30
- package/src/v1.ts +0 -74
- package/tsconfig.json +0 -4
- package/tsdown.config.ts +0 -22
- package/vitest.config.ts +0 -19
- package/vitest.e2e.config.ts +0 -29
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull-config.js","names":[],"sources":["../../src/lib/pull-config.ts"],"sourcesContent":["import {\n\ttype BranchConfig,\n\ttype BucketConfig,\n\ttype ComputeSettings,\n\tcreateNeonApiFromOptions,\n\tErrorCode,\n\ttype NeonApi,\n\ttype NeonBranchSnapshot,\n\ttype NeonBucketSnapshot,\n\ttype NeonDatabaseSnapshot,\n\ttype NeonEndpointSnapshot,\n\ttype NeonFunctionSnapshot,\n\ttype NeonProjectSnapshot,\n\tPlatformError,\n} from \"@neondatabase/config\";\n\nexport interface PullConfigOptions {\n\t/** Neon project id (`<project>`). Required — the API addresses branches by project. */\n\tprojectId: string;\n\t/** Neon branch id (`br-…`). Required. Resolve names to ids before calling. */\n\tbranchId: string;\n\t/** Neon API key. Falls back to `NEON_API_KEY` / neonctl credentials. */\n\tapiKey?: string;\n\t/** Inject a custom NeonApi adapter (primarily for tests). */\n\tapi?: NeonApi;\n}\n\n/**\n * Live Preview-feature state read back from a branch. Surfaced alongside `config` rather\n * than inside it because functions cannot round-trip: the remote only knows the deployed\n * bundle, not the local `source` path a {@link FunctionConfig} requires, so a pulled\n * function is reported as `{ slug, name }` (no `source`).\n */\nexport interface PulledPreview {\n\tbuckets: BucketConfig[];\n\tfunctions: Array<{ slug: string; name: string }>;\n\taiGatewayEnabled: boolean;\n}\n\nexport interface PulledBranchConfig {\n\tproject: {\n\t\tid: string;\n\t\tname: string;\n\t\tregion: string;\n\t\tpgVersion: number;\n\t\torgId?: string;\n\t};\n\tbranch: {\n\t\tid: string;\n\t\tname: string;\n\t\tparent?: string;\n\t\tisDefault: boolean;\n\t\tprotected: boolean;\n\t\texpiresAt?: string;\n\t};\n\tconfig: BranchConfig;\n\t/**\n\t * Live Preview-feature state, when the branch has any buckets/functions or an enabled\n\t * AI Gateway. Omitted entirely when there is nothing to report.\n\t */\n\tpreview?: PulledPreview;\n}\n\nexport async function pullConfig(\n\toptions: PullConfigOptions,\n): Promise<PulledBranchConfig> {\n\tconst api = options.api ?? createApiFromOptions(options);\n\tconst projectId = options.projectId;\n\tconst project = await api.getProject(projectId);\n\tconst [branches, endpoints] = await Promise.all([\n\t\tapi.listBranches(projectId),\n\t\tapi.listEndpoints(projectId),\n\t]);\n\tconst branch = resolveBranch(options.branchId, branches);\n\tconst endpoint = endpoints.find(\n\t\t(ep) => ep.type === \"read_write\" && ep.branchId === branch.id,\n\t);\n\n\t// Data API is enabled per branch + database, so resolve a database to probe.\n\tconst databases = await api.listBranchDatabases(projectId, branch.id);\n\tconst probeDatabase = pickProbeDatabase(databases);\n\n\tconst [buckets, functions, aiGatewayEnabled, auth, dataApi] =\n\t\tawait Promise.all([\n\t\t\tapi.listBranchBuckets(projectId, branch.id),\n\t\t\tapi.listBranchFunctions(projectId, branch.id),\n\t\t\tapi.getAiGatewayEnabled(projectId, branch.id),\n\t\t\tapi.getNeonAuth(projectId, branch.id),\n\t\t\tprobeDatabase\n\t\t\t\t? api.getNeonDataApi(projectId, branch.id, probeDatabase)\n\t\t\t\t: Promise.resolve(null),\n\t\t]);\n\n\treturn buildPulledBranchConfig(project, branch, branches, endpoint, {\n\t\tbuckets,\n\t\tfunctions,\n\t\taiGatewayEnabled,\n\t\tauthEnabled: auth !== null,\n\t\tdataApiEnabled: dataApi !== null,\n\t});\n}\n\n/**\n * Pick the database to probe for a Data API integration. Data API is enabled per\n * branch + database; for read-back we only need to know whether *any* database has it\n * on, so prefer the conventional default (`neondb`) and otherwise fall back to the first\n * database. Returns `undefined` when the branch has no databases.\n */\nfunction pickProbeDatabase(\n\tdatabases: NeonDatabaseSnapshot[],\n): string | undefined {\n\tif (databases.length === 0) return undefined;\n\tconst byName = databases.find((d) => d.name === \"neondb\");\n\tif (byName) return byName.name;\n\treturn databases[0].name;\n}\n\nfunction createApiFromOptions(options: PullConfigOptions): NeonApi {\n\treturn createNeonApiFromOptions(\"pullConfig\", {\n\t\t...(options.apiKey ? { apiKey: options.apiKey } : {}),\n\t});\n}\n\nexport function buildPulledBranchConfig(\n\tproject: NeonProjectSnapshot,\n\tbranch: NeonBranchSnapshot,\n\tbranches: NeonBranchSnapshot[],\n\tendpoint: NeonEndpointSnapshot | undefined,\n\tpreviewState?: {\n\t\tbuckets: NeonBucketSnapshot[];\n\t\tfunctions: NeonFunctionSnapshot[];\n\t\taiGatewayEnabled: boolean;\n\t\t/** Whether a Neon Auth integration is enabled on the branch. */\n\t\tauthEnabled?: boolean;\n\t\t/** Whether a Neon Data API integration is enabled on the branch. */\n\t\tdataApiEnabled?: boolean;\n\t},\n): PulledBranchConfig {\n\tconst parent = branch.parentId\n\t\t? branches.find((b) => b.id === branch.parentId)\n\t\t: undefined;\n\t// Auth/Data API live on the branch's service config (not under `preview`), so a\n\t// config pulled from a branch with them enabled round-trips through `resolveConfig`\n\t// / `fetchEnv` and the matching secrets get injected.\n\tconst config: BranchConfig = {\n\t\t...(previewState?.authEnabled ? { auth: {} } : {}),\n\t\t...(previewState?.dataApiEnabled ? { dataApi: {} } : {}),\n\t};\n\tif (parent) config.parent = parent.name;\n\tif (branch.expiresAt) config.ttl = branch.expiresAt;\n\tif (branch.protected) config.protected = true;\n\tif (endpoint) {\n\t\tconst compute = endpointToComputeSettings(endpoint, project);\n\t\tif (compute) config.postgres = { computeSettings: compute };\n\t}\n\tconst result: PulledBranchConfig = {\n\t\tproject: {\n\t\t\tid: project.id,\n\t\t\tname: project.name,\n\t\t\tregion: project.regionId,\n\t\t\tpgVersion: project.pgVersion,\n\t\t\t...(project.orgId ? { orgId: project.orgId } : {}),\n\t\t},\n\t\tbranch: {\n\t\t\tid: branch.id,\n\t\t\tname: branch.name,\n\t\t\t...(parent ? { parent: parent.name } : {}),\n\t\t\tisDefault: branch.isDefault,\n\t\t\tprotected: branch.protected,\n\t\t\t...(branch.expiresAt ? { expiresAt: branch.expiresAt } : {}),\n\t\t},\n\t\tconfig,\n\t};\n\tconst preview = previewState ? buildPulledPreview(previewState) : undefined;\n\tif (preview) result.preview = preview;\n\treturn result;\n}\n\n/**\n * Reverse-engineer the {@link PulledPreview} from remote snapshots. Returns `undefined` when\n * the branch has no Preview features so the field can be omitted entirely.\n */\nfunction buildPulledPreview(state: {\n\tbuckets: NeonBucketSnapshot[];\n\tfunctions: NeonFunctionSnapshot[];\n\taiGatewayEnabled: boolean;\n}): PulledPreview | undefined {\n\tif (\n\t\tstate.buckets.length === 0 &&\n\t\tstate.functions.length === 0 &&\n\t\t!state.aiGatewayEnabled\n\t) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\tbuckets: state.buckets.map((b) => ({\n\t\t\tname: b.name,\n\t\t\taccess: b.accessLevel,\n\t\t})),\n\t\tfunctions: state.functions.map((f) => ({\n\t\t\tslug: f.slug,\n\t\t\tname: f.name,\n\t\t})),\n\t\taiGatewayEnabled: state.aiGatewayEnabled,\n\t};\n}\n\nfunction resolveBranch(\n\tbranchId: string,\n\tbranches: NeonBranchSnapshot[],\n): NeonBranchSnapshot {\n\tconst match = branches.find((b) => b.id === branchId);\n\tif (match) return match;\n\tthrow new PlatformError(\n\t\tErrorCode.BranchNotFound,\n\t\t[\n\t\t\t`pullConfig: branch id ${JSON.stringify(branchId)} not found on project.`,\n\t\t\t`Available branches: ${branches.map((b) => `${b.name} (${b.id})`).join(\", \") || \"(none)\"}.`,\n\t\t].join(\" \"),\n\t\t{\n\t\t\tdetails: {\n\t\t\t\tbranchId,\n\t\t\t\tavailable: branches.map((b) => b.id),\n\t\t\t},\n\t\t},\n\t);\n}\n\nfunction endpointToComputeSettings(\n\tendpoint: NeonEndpointSnapshot,\n\tproject: NeonProjectSnapshot,\n): ComputeSettings | undefined {\n\tconst defaults = project.defaultEndpointSettings;\n\tconst out: ComputeSettings = {};\n\tif (\n\t\tendpoint.autoscalingLimitMinCu !== undefined &&\n\t\tendpoint.autoscalingLimitMinCu !== defaults?.autoscalingLimitMinCu\n\t) {\n\t\tout.autoscalingLimitMinCu = endpoint.autoscalingLimitMinCu;\n\t}\n\tif (\n\t\tendpoint.autoscalingLimitMaxCu !== undefined &&\n\t\tendpoint.autoscalingLimitMaxCu !== defaults?.autoscalingLimitMaxCu\n\t) {\n\t\tout.autoscalingLimitMaxCu = endpoint.autoscalingLimitMaxCu;\n\t}\n\tif (\n\t\tendpoint.suspendTimeout !== undefined &&\n\t\tendpoint.suspendTimeout !== defaults?.suspendTimeout\n\t) {\n\t\tout.suspendTimeout = endpoint.suspendTimeout;\n\t}\n\treturn Object.keys(out).length > 0 ? out : undefined;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { FunctionBundler } from "./function-bundle.js";
|
|
2
|
+
import { Config, NeonApi, PushResult } from "@neondatabase/config";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/push-config.d.ts
|
|
5
|
+
interface PushConfigOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Neon project id. **Required** — the management API addresses every branch through
|
|
8
|
+
* its project, so there is no way to push without it. `pushConfig` never creates a
|
|
9
|
+
* project; resolve the id yourself (e.g. via neonctl) and pass it in.
|
|
10
|
+
*/
|
|
11
|
+
projectId: string;
|
|
12
|
+
/**
|
|
13
|
+
* Neon branch id (`br-…`). **Required.** `pushConfig` never creates a branch — it must
|
|
14
|
+
* already exist on the project. Resolve names to ids before calling.
|
|
15
|
+
*/
|
|
16
|
+
branchId: string;
|
|
17
|
+
/** Neon API key. Falls back to `NEON_API_KEY` / neonctl credentials. Ignored when `api` is supplied. */
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Inject a custom NeonApi adapter. Primarily used by tests; production callers can rely
|
|
21
|
+
* on the default real adapter built from `apiKey`.
|
|
22
|
+
*/
|
|
23
|
+
api?: NeonApi;
|
|
24
|
+
/**
|
|
25
|
+
* Auto-confirm overriding existing remote settings.
|
|
26
|
+
*
|
|
27
|
+
* When `true`, mutable drift on the selected branch (TTL, `protected` flag, compute
|
|
28
|
+
* settings) is applied as actual mutations and the override-confirm prompt is
|
|
29
|
+
* skipped. When `false` (default) the behaviour depends on whether `confirm` is
|
|
30
|
+
* supplied:
|
|
31
|
+
* - With `confirm`: the callback is asked whether to apply the override.
|
|
32
|
+
* - Without `confirm`: drift is reported as a `PushConflictError` (legacy
|
|
33
|
+
* non-interactive default — preserved so programmatic SDK callers don't
|
|
34
|
+
* silently start mutating remote state).
|
|
35
|
+
*/
|
|
36
|
+
updateExisting?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Auto-confirm pushing to a protected branch.
|
|
39
|
+
*
|
|
40
|
+
* When `true`, no protected-branch confirmation is asked. When `false` (default):
|
|
41
|
+
* - With `confirm`: the callback is asked.
|
|
42
|
+
* - Without `confirm`: the push proceeds (legacy SDK default).
|
|
43
|
+
*/
|
|
44
|
+
allowProtectedBranch?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Optional confirmation callback. Invoked once with a single context object before
|
|
47
|
+
* any mutations run when the push needs confirmation: pushing to a protected
|
|
48
|
+
* branch (unless `allowProtectedBranch` is `true`) and/or applying mutable drift
|
|
49
|
+
* (unless `updateExisting` is `true`).
|
|
50
|
+
*
|
|
51
|
+
* Both prompts collapse into a single callback invocation when both apply, so the
|
|
52
|
+
* CLI can render one combined "are you sure?" prompt.
|
|
53
|
+
*
|
|
54
|
+
* Resolves to `true` to proceed, `false` to abort with {@link PushAbortedError}.
|
|
55
|
+
*
|
|
56
|
+
* Never invoked on `dryRun`.
|
|
57
|
+
*/
|
|
58
|
+
confirm?: (context: PushConfirmContext) => boolean | Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Custom bundler for function source. Defaults to {@link buildFunctionBundle}
|
|
61
|
+
* (esbuild). Inject your own to deploy functions without this package pulling
|
|
62
|
+
* esbuild's native binary into your build — see {@link FunctionBundler}.
|
|
63
|
+
*/
|
|
64
|
+
bundleFunction?: FunctionBundler;
|
|
65
|
+
/**
|
|
66
|
+
* When `true`, compute the full plan against the live remote state but **do not
|
|
67
|
+
* execute any mutations**. The resulting `PushResult.applied` array records every
|
|
68
|
+
* change that *would* run on a real push (with the same action / identifier / details
|
|
69
|
+
* shape, so the existing CLI summary formatter just works), and conflicts are
|
|
70
|
+
* reported instead of thrown.
|
|
71
|
+
*
|
|
72
|
+
* Used by `plan(config, branchId)` and any caller that wants a "would this push do
|
|
73
|
+
* something dangerous?" check before invoking `pushConfig` for real.
|
|
74
|
+
*/
|
|
75
|
+
dryRun?: boolean;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Context handed to a {@link PushConfigOptions.confirm} callback. Both flags can be
|
|
79
|
+
* `true` simultaneously when the push targets a protected branch *and* would override
|
|
80
|
+
* existing settings — render a single combined prompt covering both reasons.
|
|
81
|
+
*/
|
|
82
|
+
interface PushConfirmContext {
|
|
83
|
+
/** Name of the target branch on Neon. */
|
|
84
|
+
branchName: string;
|
|
85
|
+
/**
|
|
86
|
+
* `true` when the target branch has the `protected` flag on Neon and the caller
|
|
87
|
+
* did not pass `allowProtectedBranch: true`.
|
|
88
|
+
*/
|
|
89
|
+
protectedBranch: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* `true` when the plan would override existing remote settings (TTL, `protected`
|
|
92
|
+
* flag, compute settings on an existing endpoint) and the caller did not pass
|
|
93
|
+
* `updateExisting: true`. Additive operations (enabling Neon Auth / Data API for
|
|
94
|
+
* the first time) are **not** counted here — those are unambiguous and never
|
|
95
|
+
* prompt.
|
|
96
|
+
*/
|
|
97
|
+
overrideUpdates: boolean;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Push a Neon branch policy to a specific project + branch.
|
|
101
|
+
*
|
|
102
|
+
* Filesystem- and env-agnostic: the caller supplies an already-validated `Config` object
|
|
103
|
+
* (from `defineConfig` / `loadConfigFromFile`) and explicit `projectId` + `branch` in
|
|
104
|
+
* `options`. `pushConfig` performs no `.neon` lookups and reads no `NEON_*` env vars.
|
|
105
|
+
*
|
|
106
|
+
* It will **not** create a project or branch — both must already exist on Neon.
|
|
107
|
+
*/
|
|
108
|
+
declare function pushConfig(config: Config, options: PushConfigOptions): Promise<PushResult>;
|
|
109
|
+
//#endregion
|
|
110
|
+
export { PushConfigOptions, PushConfirmContext, pushConfig };
|
|
111
|
+
//# sourceMappingURL=push-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push-config.d.ts","names":[],"sources":["../../src/lib/push-config.ts"],"mappings":";;;;UAmCiB,iBAAA;;AAAjB;;;;WAqDsD,EAAA,MAAA;;AAMrB;AAmBjC;AA2BA;EAAgC,QAAA,EAAA,MAAA;;QAEtB,CAAA,EAAA,MAAA;;;AACA;;QA1FH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAmCc,iCAAiC;;;;;;mBAMpC;;;;;;;;;;;;;;;;;;UAmBD,kBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2BK,UAAA,SACb,iBACC,oBACP,QAAQ"}
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { ErrorCode, PlatformError, PushAbortedError, PushConflictError, createNeonApiFromOptions, diffConfig, resolveConfig } from "@neondatabase/config";
|
|
2
|
+
//#region src/lib/push-config.ts
|
|
3
|
+
/**
|
|
4
|
+
* Default function bundler (esbuild), loaded lazily so that `buildFunctionBundle`
|
|
5
|
+
* — and the esbuild it pulls in — only enters the module graph when a deploy
|
|
6
|
+
* actually needs it AND no custom `bundleFunction` was injected. A consumer that
|
|
7
|
+
* injects its own bundler never triggers this import, so esbuild can be dropped
|
|
8
|
+
* from their build entirely.
|
|
9
|
+
*/
|
|
10
|
+
const defaultBundleFunction = async (fn) => {
|
|
11
|
+
const { buildFunctionBundle } = await import("./function-bundle.js");
|
|
12
|
+
return buildFunctionBundle(fn);
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Push a Neon branch policy to a specific project + branch.
|
|
16
|
+
*
|
|
17
|
+
* Filesystem- and env-agnostic: the caller supplies an already-validated `Config` object
|
|
18
|
+
* (from `defineConfig` / `loadConfigFromFile`) and explicit `projectId` + `branch` in
|
|
19
|
+
* `options`. `pushConfig` performs no `.neon` lookups and reads no `NEON_*` env vars.
|
|
20
|
+
*
|
|
21
|
+
* It will **not** create a project or branch — both must already exist on Neon.
|
|
22
|
+
*/
|
|
23
|
+
async function pushConfig(config, options) {
|
|
24
|
+
const api = options.api ?? createApiFromOptions(options);
|
|
25
|
+
const projectId = options.projectId;
|
|
26
|
+
const dryRun = options.dryRun === true;
|
|
27
|
+
const updateExisting = options.updateExisting === true;
|
|
28
|
+
const allowProtectedBranch = options.allowProtectedBranch === true;
|
|
29
|
+
const remoteProject = await api.getProject(projectId);
|
|
30
|
+
const [branches, endpoints] = await Promise.all([api.listBranches(remoteProject.id), api.listEndpoints(remoteProject.id)]);
|
|
31
|
+
const branch = resolveRemoteBranch(options.branchId, branches);
|
|
32
|
+
const resolved = resolveConfig(config, {
|
|
33
|
+
name: branch.name,
|
|
34
|
+
id: branch.id,
|
|
35
|
+
exists: true,
|
|
36
|
+
...branch.parentId ? { parentId: branch.parentId } : {},
|
|
37
|
+
isDefault: branch.isDefault,
|
|
38
|
+
isProtected: branch.protected,
|
|
39
|
+
...branch.expiresAt ? { expiresAt: branch.expiresAt } : {}
|
|
40
|
+
});
|
|
41
|
+
const services = await resolveServiceState({
|
|
42
|
+
api,
|
|
43
|
+
projectId: remoteProject.id,
|
|
44
|
+
branch,
|
|
45
|
+
wantsAuth: resolved.authEnabled,
|
|
46
|
+
wantsDataApi: resolved.dataApiEnabled
|
|
47
|
+
});
|
|
48
|
+
const remote = {
|
|
49
|
+
projectId: remoteProject.id,
|
|
50
|
+
branch,
|
|
51
|
+
endpoint: endpoints.find((ep) => ep.type === "read_write" && ep.branchId === branch.id),
|
|
52
|
+
services
|
|
53
|
+
};
|
|
54
|
+
if (resolved.preview) remote.preview = await resolvePreviewState({
|
|
55
|
+
api,
|
|
56
|
+
projectId: remoteProject.id,
|
|
57
|
+
branchId: branch.id
|
|
58
|
+
});
|
|
59
|
+
const diff = diffConfig(resolved, remote, { updateExisting: true });
|
|
60
|
+
const needsOverrideConfirm = diff.plan.filter(isOverrideStep).length > 0 && !updateExisting;
|
|
61
|
+
const needsProtectedConfirm = branch.protected && !allowProtectedBranch;
|
|
62
|
+
if (!dryRun && diff.conflicts.length > 0) throw new PushConflictError(diff.conflicts);
|
|
63
|
+
if (!dryRun && (needsOverrideConfirm || needsProtectedConfirm)) {
|
|
64
|
+
if (options.confirm) {
|
|
65
|
+
if (!await options.confirm({
|
|
66
|
+
branchName: branch.name,
|
|
67
|
+
protectedBranch: needsProtectedConfirm,
|
|
68
|
+
overrideUpdates: needsOverrideConfirm
|
|
69
|
+
})) {
|
|
70
|
+
const reasons = [];
|
|
71
|
+
if (needsProtectedConfirm) reasons.push("protected-branch");
|
|
72
|
+
if (needsOverrideConfirm) reasons.push("override-updates");
|
|
73
|
+
throw new PushAbortedError(branch.name, reasons);
|
|
74
|
+
}
|
|
75
|
+
} else if (needsOverrideConfirm) throw new PushConflictError(diffConfig(resolved, remote, { updateExisting: false }).conflicts);
|
|
76
|
+
}
|
|
77
|
+
const applied = [{
|
|
78
|
+
kind: "branch",
|
|
79
|
+
action: "noop",
|
|
80
|
+
identifier: branch.name
|
|
81
|
+
}];
|
|
82
|
+
const branchById = new Map(branches.map((b) => [b.id, b]));
|
|
83
|
+
const branchByName = new Map(branches.map((b) => [b.name, b]));
|
|
84
|
+
for (const step of diff.plan) {
|
|
85
|
+
const change = dryRun ? synthesizeAppliedChange(step) : await applyStep(step, {
|
|
86
|
+
api,
|
|
87
|
+
remoteProjectId: remoteProject.id,
|
|
88
|
+
branchById,
|
|
89
|
+
branchByName,
|
|
90
|
+
bundleFunction: options.bundleFunction ?? defaultBundleFunction
|
|
91
|
+
});
|
|
92
|
+
applied.push(change);
|
|
93
|
+
}
|
|
94
|
+
const result = {
|
|
95
|
+
projectId: remoteProject.id,
|
|
96
|
+
branchId: branch.id,
|
|
97
|
+
branchName: branch.name,
|
|
98
|
+
dryRun,
|
|
99
|
+
applied,
|
|
100
|
+
conflicts: diff.conflicts
|
|
101
|
+
};
|
|
102
|
+
if (remoteProject.orgId) result.orgId = remoteProject.orgId;
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* `update-*` plan steps mutate existing remote state. `enable-*` steps are additive (no
|
|
107
|
+
* existing resource to override) and never trigger the override-confirm prompt.
|
|
108
|
+
*/
|
|
109
|
+
function isOverrideStep(step) {
|
|
110
|
+
return step.kind === "update-branch-ttl" || step.kind === "update-branch-protected" || step.kind === "update-endpoint";
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Build an {@link AppliedChange} from a {@link PlanStep} without calling the Neon API.
|
|
114
|
+
* Used by dry-run mode so callers see the same record shape they would on a live push,
|
|
115
|
+
* just with no side effects. Identifiers are the branch names from the plan; any
|
|
116
|
+
* sub-resource ids (`branchId`, `endpointId`) flow through unchanged when known.
|
|
117
|
+
*/
|
|
118
|
+
function synthesizeAppliedChange(step) {
|
|
119
|
+
switch (step.kind) {
|
|
120
|
+
case "update-branch-ttl": return {
|
|
121
|
+
kind: "branch",
|
|
122
|
+
action: "update",
|
|
123
|
+
identifier: step.branchName,
|
|
124
|
+
details: {
|
|
125
|
+
field: "ttl",
|
|
126
|
+
expiresAt: step.expiresAt
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
case "update-branch-protected": return {
|
|
130
|
+
kind: "branch",
|
|
131
|
+
action: "update",
|
|
132
|
+
identifier: step.branchName,
|
|
133
|
+
details: {
|
|
134
|
+
field: "protected",
|
|
135
|
+
protected: step.protected
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
case "update-endpoint": return {
|
|
139
|
+
kind: "branch",
|
|
140
|
+
action: "update",
|
|
141
|
+
identifier: step.branchName,
|
|
142
|
+
details: {
|
|
143
|
+
field: "computeSettings",
|
|
144
|
+
endpointId: step.endpointId,
|
|
145
|
+
settings: step.settings
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
case "enable-auth": return {
|
|
149
|
+
kind: "service",
|
|
150
|
+
action: "create",
|
|
151
|
+
identifier: "auth",
|
|
152
|
+
details: {
|
|
153
|
+
branchName: step.branchName,
|
|
154
|
+
...step.databaseName ? { databaseName: step.databaseName } : {}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
case "enable-data-api": return {
|
|
158
|
+
kind: "service",
|
|
159
|
+
action: "create",
|
|
160
|
+
identifier: "dataApi",
|
|
161
|
+
details: {
|
|
162
|
+
branchName: step.branchName,
|
|
163
|
+
databaseName: step.databaseName
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
case "create-bucket": return {
|
|
167
|
+
kind: "service",
|
|
168
|
+
action: "create",
|
|
169
|
+
identifier: `bucket:${step.bucketName}`,
|
|
170
|
+
details: {
|
|
171
|
+
branchName: step.branchName,
|
|
172
|
+
bucketName: step.bucketName,
|
|
173
|
+
accessLevel: step.accessLevel
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
case "create-function": return {
|
|
177
|
+
kind: "service",
|
|
178
|
+
action: "create",
|
|
179
|
+
identifier: `function:${step.fn.slug}`,
|
|
180
|
+
details: {
|
|
181
|
+
branchName: step.branchName,
|
|
182
|
+
slug: step.fn.slug,
|
|
183
|
+
name: step.fn.name
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
case "deploy-function": return {
|
|
187
|
+
kind: "service",
|
|
188
|
+
action: "update",
|
|
189
|
+
identifier: `function:${step.fn.slug}`,
|
|
190
|
+
details: {
|
|
191
|
+
branchName: step.branchName,
|
|
192
|
+
slug: step.fn.slug,
|
|
193
|
+
source: step.fn.source,
|
|
194
|
+
runtime: step.fn.runtime,
|
|
195
|
+
memoryMib: step.fn.memoryMib
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
case "enable-ai-gateway": return {
|
|
199
|
+
kind: "service",
|
|
200
|
+
action: "create",
|
|
201
|
+
identifier: "aiGateway",
|
|
202
|
+
details: { branchName: step.branchName }
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function createApiFromOptions(options) {
|
|
207
|
+
return createNeonApiFromOptions("pushConfig", { ...options.apiKey ? { apiKey: options.apiKey } : {} });
|
|
208
|
+
}
|
|
209
|
+
function resolveRemoteBranch(branchId, branches) {
|
|
210
|
+
const found = branches.find((b) => b.id === branchId);
|
|
211
|
+
if (found) return found;
|
|
212
|
+
throw new PlatformError(ErrorCode.BranchNotFound, [
|
|
213
|
+
`pushConfig: branch id ${JSON.stringify(branchId)} does not exist on the project.`,
|
|
214
|
+
`Available branches: ${branches.map((b) => `${b.name} (${b.id})`).join(", ") || "(none)"}.`,
|
|
215
|
+
"Pass an existing branch id, or create the branch first with the neonctl CLI."
|
|
216
|
+
].join(" "), { details: {
|
|
217
|
+
branchId,
|
|
218
|
+
available: branches.map((b) => b.id)
|
|
219
|
+
} });
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Pre-fetch the current state of branch-scoped integrations on the selected branch.
|
|
223
|
+
*/
|
|
224
|
+
async function resolveServiceState(args) {
|
|
225
|
+
const { api, projectId, branch, wantsAuth, wantsDataApi } = args;
|
|
226
|
+
if (!wantsAuth && !wantsDataApi) return {
|
|
227
|
+
databaseName: "neondb",
|
|
228
|
+
authEnabled: false,
|
|
229
|
+
dataApiEnabled: false
|
|
230
|
+
};
|
|
231
|
+
const databaseName = await pickServiceDatabaseName(api, projectId, branch.id);
|
|
232
|
+
const [auth, dataApi] = await Promise.all([wantsAuth ? api.getNeonAuth(projectId, branch.id) : Promise.resolve(null), wantsDataApi ? api.getNeonDataApi(projectId, branch.id, databaseName) : Promise.resolve(null)]);
|
|
233
|
+
return {
|
|
234
|
+
databaseName,
|
|
235
|
+
authEnabled: auth !== null,
|
|
236
|
+
dataApiEnabled: dataApi !== null
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Pre-fetch the current state of branch-scoped Preview features (buckets, functions, AI
|
|
241
|
+
* Gateway) so the diff can be computed additively. Only called when the policy has a
|
|
242
|
+
* `preview` block.
|
|
243
|
+
*/
|
|
244
|
+
async function resolvePreviewState(args) {
|
|
245
|
+
const { api, projectId, branchId } = args;
|
|
246
|
+
const [buckets, functions, aiGatewayEnabled] = await Promise.all([
|
|
247
|
+
api.listBranchBuckets(projectId, branchId),
|
|
248
|
+
api.listBranchFunctions(projectId, branchId),
|
|
249
|
+
api.getAiGatewayEnabled(projectId, branchId)
|
|
250
|
+
]);
|
|
251
|
+
return {
|
|
252
|
+
buckets,
|
|
253
|
+
functions,
|
|
254
|
+
aiGatewayEnabled
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Resolve the database name for a Data API integration. Auto-pick when the branch has
|
|
259
|
+
* exactly one database; otherwise fall back to Neon's default (`neondb`) so the call
|
|
260
|
+
* stays useful even on branches with multiple databases — push doesn't have a way to
|
|
261
|
+
* surface a "pick one" prompt the way `fetchEnv` does.
|
|
262
|
+
*/
|
|
263
|
+
async function pickServiceDatabaseName(api, projectId, branchId) {
|
|
264
|
+
const databases = await api.listBranchDatabases(projectId, branchId);
|
|
265
|
+
if (databases.length === 1) return databases[0].name;
|
|
266
|
+
const neondb = databases.find((d) => d.name === "neondb");
|
|
267
|
+
if (neondb) return neondb.name;
|
|
268
|
+
return databases[0]?.name ?? "neondb";
|
|
269
|
+
}
|
|
270
|
+
async function applyStep(step, ctx) {
|
|
271
|
+
switch (step.kind) {
|
|
272
|
+
case "update-branch-ttl": {
|
|
273
|
+
const updated = await ctx.api.updateBranch(ctx.remoteProjectId, step.branchId, { expiresAt: step.expiresAt ?? null });
|
|
274
|
+
ctx.branchById.set(updated.id, updated);
|
|
275
|
+
ctx.branchByName.set(updated.name, updated);
|
|
276
|
+
return {
|
|
277
|
+
kind: "branch",
|
|
278
|
+
action: "update",
|
|
279
|
+
identifier: updated.name,
|
|
280
|
+
details: {
|
|
281
|
+
field: "ttl",
|
|
282
|
+
expiresAt: step.expiresAt
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
case "update-branch-protected": {
|
|
287
|
+
const updated = await ctx.api.updateBranch(ctx.remoteProjectId, step.branchId, { protected: step.protected });
|
|
288
|
+
ctx.branchById.set(updated.id, updated);
|
|
289
|
+
ctx.branchByName.set(updated.name, updated);
|
|
290
|
+
return {
|
|
291
|
+
kind: "branch",
|
|
292
|
+
action: "update",
|
|
293
|
+
identifier: updated.name,
|
|
294
|
+
details: {
|
|
295
|
+
field: "protected",
|
|
296
|
+
protected: step.protected
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
case "update-endpoint": {
|
|
301
|
+
const updated = await ctx.api.updateEndpoint(ctx.remoteProjectId, step.endpointId, step.settings);
|
|
302
|
+
return {
|
|
303
|
+
kind: "branch",
|
|
304
|
+
action: "update",
|
|
305
|
+
identifier: step.branchName,
|
|
306
|
+
details: {
|
|
307
|
+
field: "computeSettings",
|
|
308
|
+
endpointId: updated.id,
|
|
309
|
+
settings: step.settings
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
case "enable-auth":
|
|
314
|
+
await ctx.api.enableNeonAuth(ctx.remoteProjectId, step.branchId, { ...step.databaseName ? { databaseName: step.databaseName } : {} });
|
|
315
|
+
return {
|
|
316
|
+
kind: "service",
|
|
317
|
+
action: "create",
|
|
318
|
+
identifier: "auth",
|
|
319
|
+
details: {
|
|
320
|
+
branchName: step.branchName,
|
|
321
|
+
...step.databaseName ? { databaseName: step.databaseName } : {}
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
case "enable-data-api":
|
|
325
|
+
await ctx.api.enableProjectBranchDataApi(ctx.remoteProjectId, step.branchId, step.databaseName);
|
|
326
|
+
return {
|
|
327
|
+
kind: "service",
|
|
328
|
+
action: "create",
|
|
329
|
+
identifier: "dataApi",
|
|
330
|
+
details: {
|
|
331
|
+
branchName: step.branchName,
|
|
332
|
+
databaseName: step.databaseName
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
case "create-bucket":
|
|
336
|
+
await ctx.api.createBranchBucket(ctx.remoteProjectId, step.branchId, {
|
|
337
|
+
name: step.bucketName,
|
|
338
|
+
accessLevel: step.accessLevel
|
|
339
|
+
});
|
|
340
|
+
return {
|
|
341
|
+
kind: "service",
|
|
342
|
+
action: "create",
|
|
343
|
+
identifier: `bucket:${step.bucketName}`,
|
|
344
|
+
details: {
|
|
345
|
+
branchName: step.branchName,
|
|
346
|
+
bucketName: step.bucketName,
|
|
347
|
+
accessLevel: step.accessLevel
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
case "create-function":
|
|
351
|
+
await ctx.api.createBranchFunction(ctx.remoteProjectId, step.branchId, {
|
|
352
|
+
slug: step.fn.slug,
|
|
353
|
+
name: step.fn.name
|
|
354
|
+
});
|
|
355
|
+
return {
|
|
356
|
+
kind: "service",
|
|
357
|
+
action: "create",
|
|
358
|
+
identifier: `function:${step.fn.slug}`,
|
|
359
|
+
details: {
|
|
360
|
+
branchName: step.branchName,
|
|
361
|
+
slug: step.fn.slug,
|
|
362
|
+
name: step.fn.name
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
case "deploy-function": {
|
|
366
|
+
const bundle = await ctx.bundleFunction(step.fn);
|
|
367
|
+
const deployment = await ctx.api.deployBranchFunction(ctx.remoteProjectId, step.branchId, step.fn.slug, {
|
|
368
|
+
bundle,
|
|
369
|
+
runtime: step.fn.runtime,
|
|
370
|
+
memoryMib: step.fn.memoryMib,
|
|
371
|
+
environment: step.fn.env
|
|
372
|
+
});
|
|
373
|
+
return {
|
|
374
|
+
kind: "service",
|
|
375
|
+
action: "update",
|
|
376
|
+
identifier: `function:${step.fn.slug}`,
|
|
377
|
+
details: {
|
|
378
|
+
branchName: step.branchName,
|
|
379
|
+
slug: step.fn.slug,
|
|
380
|
+
source: step.fn.source,
|
|
381
|
+
runtime: step.fn.runtime,
|
|
382
|
+
memoryMib: step.fn.memoryMib,
|
|
383
|
+
deploymentId: deployment.id
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
case "enable-ai-gateway":
|
|
388
|
+
await ctx.api.enableAiGateway(ctx.remoteProjectId, step.branchId);
|
|
389
|
+
return {
|
|
390
|
+
kind: "service",
|
|
391
|
+
action: "create",
|
|
392
|
+
identifier: "aiGateway",
|
|
393
|
+
details: { branchName: step.branchName }
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
//#endregion
|
|
398
|
+
export { pushConfig };
|
|
399
|
+
|
|
400
|
+
//# sourceMappingURL=push-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push-config.js","names":[],"sources":["../../src/lib/push-config.ts"],"sourcesContent":["import {\n\ttype AppliedChange,\n\ttype Config,\n\tcreateNeonApiFromOptions,\n\tdiffConfig,\n\tErrorCode,\n\ttype NeonApi,\n\ttype NeonBranchSnapshot,\n\ttype PlanStep,\n\tPlatformError,\n\tPushAbortedError,\n\tPushConflictError,\n\ttype PushResult,\n\ttype RemotePreviewState,\n\ttype RemoteServiceState,\n\ttype RemoteState,\n\ttype ResolvedFunctionConfig,\n\tresolveConfig,\n} from \"@neondatabase/config\";\nimport type { FunctionBundler } from \"./function-bundle.js\";\n\n/**\n * Default function bundler (esbuild), loaded lazily so that `buildFunctionBundle`\n * — and the esbuild it pulls in — only enters the module graph when a deploy\n * actually needs it AND no custom `bundleFunction` was injected. A consumer that\n * injects its own bundler never triggers this import, so esbuild can be dropped\n * from their build entirely.\n */\nconst defaultBundleFunction: FunctionBundler = async (\n\tfn: ResolvedFunctionConfig,\n): Promise<Uint8Array> => {\n\tconst { buildFunctionBundle } = await import(\"./function-bundle.js\");\n\treturn buildFunctionBundle(fn);\n};\n\nexport interface PushConfigOptions {\n\t/**\n\t * Neon project id. **Required** — the management API addresses every branch through\n\t * its project, so there is no way to push without it. `pushConfig` never creates a\n\t * project; resolve the id yourself (e.g. via neonctl) and pass it in.\n\t */\n\tprojectId: string;\n\t/**\n\t * Neon branch id (`br-…`). **Required.** `pushConfig` never creates a branch — it must\n\t * already exist on the project. Resolve names to ids before calling.\n\t */\n\tbranchId: string;\n\t/** Neon API key. Falls back to `NEON_API_KEY` / neonctl credentials. Ignored when `api` is supplied. */\n\tapiKey?: string;\n\t/**\n\t * Inject a custom NeonApi adapter. Primarily used by tests; production callers can rely\n\t * on the default real adapter built from `apiKey`.\n\t */\n\tapi?: NeonApi;\n\t/**\n\t * Auto-confirm overriding existing remote settings.\n\t *\n\t * When `true`, mutable drift on the selected branch (TTL, `protected` flag, compute\n\t * settings) is applied as actual mutations and the override-confirm prompt is\n\t * skipped. When `false` (default) the behaviour depends on whether `confirm` is\n\t * supplied:\n\t * - With `confirm`: the callback is asked whether to apply the override.\n\t * - Without `confirm`: drift is reported as a `PushConflictError` (legacy\n\t * non-interactive default — preserved so programmatic SDK callers don't\n\t * silently start mutating remote state).\n\t */\n\tupdateExisting?: boolean;\n\t/**\n\t * Auto-confirm pushing to a protected branch.\n\t *\n\t * When `true`, no protected-branch confirmation is asked. When `false` (default):\n\t * - With `confirm`: the callback is asked.\n\t * - Without `confirm`: the push proceeds (legacy SDK default).\n\t */\n\tallowProtectedBranch?: boolean;\n\t/**\n\t * Optional confirmation callback. Invoked once with a single context object before\n\t * any mutations run when the push needs confirmation: pushing to a protected\n\t * branch (unless `allowProtectedBranch` is `true`) and/or applying mutable drift\n\t * (unless `updateExisting` is `true`).\n\t *\n\t * Both prompts collapse into a single callback invocation when both apply, so the\n\t * CLI can render one combined \"are you sure?\" prompt.\n\t *\n\t * Resolves to `true` to proceed, `false` to abort with {@link PushAbortedError}.\n\t *\n\t * Never invoked on `dryRun`.\n\t */\n\tconfirm?: (context: PushConfirmContext) => boolean | Promise<boolean>;\n\t/**\n\t * Custom bundler for function source. Defaults to {@link buildFunctionBundle}\n\t * (esbuild). Inject your own to deploy functions without this package pulling\n\t * esbuild's native binary into your build — see {@link FunctionBundler}.\n\t */\n\tbundleFunction?: FunctionBundler;\n\t/**\n\t * When `true`, compute the full plan against the live remote state but **do not\n\t * execute any mutations**. The resulting `PushResult.applied` array records every\n\t * change that *would* run on a real push (with the same action / identifier / details\n\t * shape, so the existing CLI summary formatter just works), and conflicts are\n\t * reported instead of thrown.\n\t *\n\t * Used by `plan(config, branchId)` and any caller that wants a \"would this push do\n\t * something dangerous?\" check before invoking `pushConfig` for real.\n\t */\n\tdryRun?: boolean;\n}\n\n/**\n * Context handed to a {@link PushConfigOptions.confirm} callback. Both flags can be\n * `true` simultaneously when the push targets a protected branch *and* would override\n * existing settings — render a single combined prompt covering both reasons.\n */\nexport interface PushConfirmContext {\n\t/** Name of the target branch on Neon. */\n\tbranchName: string;\n\t/**\n\t * `true` when the target branch has the `protected` flag on Neon and the caller\n\t * did not pass `allowProtectedBranch: true`.\n\t */\n\tprotectedBranch: boolean;\n\t/**\n\t * `true` when the plan would override existing remote settings (TTL, `protected`\n\t * flag, compute settings on an existing endpoint) and the caller did not pass\n\t * `updateExisting: true`. Additive operations (enabling Neon Auth / Data API for\n\t * the first time) are **not** counted here — those are unambiguous and never\n\t * prompt.\n\t */\n\toverrideUpdates: boolean;\n}\n\n/**\n * Push a Neon branch policy to a specific project + branch.\n *\n * Filesystem- and env-agnostic: the caller supplies an already-validated `Config` object\n * (from `defineConfig` / `loadConfigFromFile`) and explicit `projectId` + `branch` in\n * `options`. `pushConfig` performs no `.neon` lookups and reads no `NEON_*` env vars.\n *\n * It will **not** create a project or branch — both must already exist on Neon.\n */\nexport async function pushConfig(\n\tconfig: Config,\n\toptions: PushConfigOptions,\n): Promise<PushResult> {\n\tconst api = options.api ?? createApiFromOptions(options);\n\tconst projectId = options.projectId;\n\n\tconst dryRun = options.dryRun === true;\n\tconst updateExisting = options.updateExisting === true;\n\tconst allowProtectedBranch = options.allowProtectedBranch === true;\n\n\tconst remoteProject = await api.getProject(projectId);\n\n\tconst [branches, endpoints] = await Promise.all([\n\t\tapi.listBranches(remoteProject.id),\n\t\tapi.listEndpoints(remoteProject.id),\n\t]);\n\tconst branch = resolveRemoteBranch(options.branchId, branches);\n\tconst resolved = resolveConfig(config, {\n\t\tname: branch.name,\n\t\tid: branch.id,\n\t\texists: true,\n\t\t...(branch.parentId ? { parentId: branch.parentId } : {}),\n\t\tisDefault: branch.isDefault,\n\t\tisProtected: branch.protected,\n\t\t...(branch.expiresAt ? { expiresAt: branch.expiresAt } : {}),\n\t});\n\tconst services = await resolveServiceState({\n\t\tapi,\n\t\tprojectId: remoteProject.id,\n\t\tbranch,\n\t\twantsAuth: resolved.authEnabled,\n\t\twantsDataApi: resolved.dataApiEnabled,\n\t});\n\tconst remote: RemoteState = {\n\t\tprojectId: remoteProject.id,\n\t\tbranch,\n\t\tendpoint: endpoints.find(\n\t\t\t(ep) => ep.type === \"read_write\" && ep.branchId === branch.id,\n\t\t),\n\t\tservices,\n\t};\n\t// Only fetch Preview state when the policy actually uses it — keeps pushes that don't\n\t// touch functions/buckets/aiGateway at the same number of API calls as before.\n\tif (resolved.preview) {\n\t\tremote.preview = await resolvePreviewState({\n\t\t\tapi,\n\t\t\tprojectId: remoteProject.id,\n\t\t\tbranchId: branch.id,\n\t\t});\n\t}\n\n\t// Always compute the plan with `updateExisting: true` so we can see what *would* be\n\t// overridden. The decision of whether to apply / prompt / fail is gated below using\n\t// the recorded steps.\n\tconst diff = diffConfig(resolved, remote, { updateExisting: true });\n\tconst overrideSteps = diff.plan.filter(isOverrideStep);\n\tconst needsOverrideConfirm = overrideSteps.length > 0 && !updateExisting;\n\tconst needsProtectedConfirm = branch.protected && !allowProtectedBranch;\n\n\tif (!dryRun && diff.conflicts.length > 0) {\n\t\tthrow new PushConflictError(diff.conflicts);\n\t}\n\n\tif (!dryRun && (needsOverrideConfirm || needsProtectedConfirm)) {\n\t\tif (options.confirm) {\n\t\t\tconst ok = await options.confirm({\n\t\t\t\tbranchName: branch.name,\n\t\t\t\tprotectedBranch: needsProtectedConfirm,\n\t\t\t\toverrideUpdates: needsOverrideConfirm,\n\t\t\t});\n\t\t\tif (!ok) {\n\t\t\t\tconst reasons: (\"protected-branch\" | \"override-updates\")[] = [];\n\t\t\t\tif (needsProtectedConfirm) reasons.push(\"protected-branch\");\n\t\t\t\tif (needsOverrideConfirm) reasons.push(\"override-updates\");\n\t\t\t\tthrow new PushAbortedError(branch.name, reasons);\n\t\t\t}\n\t\t} else if (needsOverrideConfirm) {\n\t\t\t// Legacy non-interactive fallback: surface the would-be drift as a\n\t\t\t// `PushConflictError` so programmatic callers that skipped both\n\t\t\t// `updateExisting` and `confirm` see the previous fail-fast behavior.\n\t\t\tconst legacy = diffConfig(resolved, remote, {\n\t\t\t\tupdateExisting: false,\n\t\t\t});\n\t\t\tthrow new PushConflictError(legacy.conflicts);\n\t\t}\n\t\t// Protected branch + no confirm callback: legacy default proceeds without\n\t\t// any extra check (no programmatic regression).\n\t}\n\n\tconst applied: AppliedChange[] = [\n\t\t{ kind: \"branch\", action: \"noop\", identifier: branch.name },\n\t];\n\n\tconst branchById = new Map(branches.map((b) => [b.id, b] as const));\n\tconst branchByName = new Map(branches.map((b) => [b.name, b] as const));\n\n\tfor (const step of diff.plan) {\n\t\tconst change = dryRun\n\t\t\t? synthesizeAppliedChange(step)\n\t\t\t: await applyStep(step, {\n\t\t\t\t\tapi,\n\t\t\t\t\tremoteProjectId: remoteProject.id,\n\t\t\t\t\tbranchById,\n\t\t\t\t\tbranchByName,\n\t\t\t\t\tbundleFunction:\n\t\t\t\t\t\toptions.bundleFunction ?? defaultBundleFunction,\n\t\t\t\t});\n\t\tapplied.push(change);\n\t}\n\n\tconst result: PushResult = {\n\t\tprojectId: remoteProject.id,\n\t\tbranchId: branch.id,\n\t\tbranchName: branch.name,\n\t\tdryRun,\n\t\tapplied,\n\t\tconflicts: diff.conflicts,\n\t};\n\tif (remoteProject.orgId) result.orgId = remoteProject.orgId;\n\treturn result;\n}\n\n/**\n * `update-*` plan steps mutate existing remote state. `enable-*` steps are additive (no\n * existing resource to override) and never trigger the override-confirm prompt.\n */\nfunction isOverrideStep(step: PlanStep): boolean {\n\treturn (\n\t\tstep.kind === \"update-branch-ttl\" ||\n\t\tstep.kind === \"update-branch-protected\" ||\n\t\tstep.kind === \"update-endpoint\"\n\t);\n}\n\n/**\n * Build an {@link AppliedChange} from a {@link PlanStep} without calling the Neon API.\n * Used by dry-run mode so callers see the same record shape they would on a live push,\n * just with no side effects. Identifiers are the branch names from the plan; any\n * sub-resource ids (`branchId`, `endpointId`) flow through unchanged when known.\n */\nfunction synthesizeAppliedChange(step: PlanStep): AppliedChange {\n\tswitch (step.kind) {\n\t\tcase \"update-branch-ttl\":\n\t\t\treturn {\n\t\t\t\tkind: \"branch\",\n\t\t\t\taction: \"update\",\n\t\t\t\tidentifier: step.branchName,\n\t\t\t\tdetails: { field: \"ttl\", expiresAt: step.expiresAt },\n\t\t\t};\n\t\tcase \"update-branch-protected\":\n\t\t\treturn {\n\t\t\t\tkind: \"branch\",\n\t\t\t\taction: \"update\",\n\t\t\t\tidentifier: step.branchName,\n\t\t\t\tdetails: { field: \"protected\", protected: step.protected },\n\t\t\t};\n\t\tcase \"update-endpoint\":\n\t\t\treturn {\n\t\t\t\tkind: \"branch\",\n\t\t\t\taction: \"update\",\n\t\t\t\tidentifier: step.branchName,\n\t\t\t\tdetails: {\n\t\t\t\t\tfield: \"computeSettings\",\n\t\t\t\t\tendpointId: step.endpointId,\n\t\t\t\t\tsettings: step.settings,\n\t\t\t\t},\n\t\t\t};\n\t\tcase \"enable-auth\":\n\t\t\treturn {\n\t\t\t\tkind: \"service\",\n\t\t\t\taction: \"create\",\n\t\t\t\tidentifier: \"auth\",\n\t\t\t\tdetails: {\n\t\t\t\t\tbranchName: step.branchName,\n\t\t\t\t\t...(step.databaseName\n\t\t\t\t\t\t? { databaseName: step.databaseName }\n\t\t\t\t\t\t: {}),\n\t\t\t\t},\n\t\t\t};\n\t\tcase \"enable-data-api\":\n\t\t\treturn {\n\t\t\t\tkind: \"service\",\n\t\t\t\taction: \"create\",\n\t\t\t\tidentifier: \"dataApi\",\n\t\t\t\tdetails: {\n\t\t\t\t\tbranchName: step.branchName,\n\t\t\t\t\tdatabaseName: step.databaseName,\n\t\t\t\t},\n\t\t\t};\n\t\tcase \"create-bucket\":\n\t\t\treturn {\n\t\t\t\tkind: \"service\",\n\t\t\t\taction: \"create\",\n\t\t\t\tidentifier: `bucket:${step.bucketName}`,\n\t\t\t\tdetails: {\n\t\t\t\t\tbranchName: step.branchName,\n\t\t\t\t\tbucketName: step.bucketName,\n\t\t\t\t\taccessLevel: step.accessLevel,\n\t\t\t\t},\n\t\t\t};\n\t\tcase \"create-function\":\n\t\t\treturn {\n\t\t\t\tkind: \"service\",\n\t\t\t\taction: \"create\",\n\t\t\t\tidentifier: `function:${step.fn.slug}`,\n\t\t\t\tdetails: {\n\t\t\t\t\tbranchName: step.branchName,\n\t\t\t\t\tslug: step.fn.slug,\n\t\t\t\t\tname: step.fn.name,\n\t\t\t\t},\n\t\t\t};\n\t\tcase \"deploy-function\":\n\t\t\treturn {\n\t\t\t\tkind: \"service\",\n\t\t\t\taction: \"update\",\n\t\t\t\tidentifier: `function:${step.fn.slug}`,\n\t\t\t\tdetails: {\n\t\t\t\t\tbranchName: step.branchName,\n\t\t\t\t\tslug: step.fn.slug,\n\t\t\t\t\tsource: step.fn.source,\n\t\t\t\t\truntime: step.fn.runtime,\n\t\t\t\t\tmemoryMib: step.fn.memoryMib,\n\t\t\t\t},\n\t\t\t};\n\t\tcase \"enable-ai-gateway\":\n\t\t\treturn {\n\t\t\t\tkind: \"service\",\n\t\t\t\taction: \"create\",\n\t\t\t\tidentifier: \"aiGateway\",\n\t\t\t\tdetails: { branchName: step.branchName },\n\t\t\t};\n\t}\n}\n\nfunction createApiFromOptions(options: PushConfigOptions): NeonApi {\n\treturn createNeonApiFromOptions(\"pushConfig\", {\n\t\t...(options.apiKey ? { apiKey: options.apiKey } : {}),\n\t});\n}\n\nfunction resolveRemoteBranch(\n\tbranchId: string,\n\tbranches: NeonBranchSnapshot[],\n): NeonBranchSnapshot {\n\tconst found = branches.find((b) => b.id === branchId);\n\tif (found) return found;\n\tthrow new PlatformError(\n\t\tErrorCode.BranchNotFound,\n\t\t[\n\t\t\t`pushConfig: branch id ${JSON.stringify(branchId)} does not exist on the project.`,\n\t\t\t`Available branches: ${branches.map((b) => `${b.name} (${b.id})`).join(\", \") || \"(none)\"}.`,\n\t\t\t\"Pass an existing branch id, or create the branch first with the neonctl CLI.\",\n\t\t].join(\" \"),\n\t\t{ details: { branchId, available: branches.map((b) => b.id) } },\n\t);\n}\n\n/**\n * Pre-fetch the current state of branch-scoped integrations on the selected branch.\n */\nasync function resolveServiceState(args: {\n\tapi: NeonApi;\n\tprojectId: string;\n\tbranch: NeonBranchSnapshot;\n\twantsAuth: boolean;\n\twantsDataApi: boolean;\n}): Promise<RemoteServiceState> {\n\tconst { api, projectId, branch, wantsAuth, wantsDataApi } = args;\n\tif (!wantsAuth && !wantsDataApi) {\n\t\treturn {\n\t\t\tdatabaseName: \"neondb\",\n\t\t\tauthEnabled: false,\n\t\t\tdataApiEnabled: false,\n\t\t};\n\t}\n\n\tconst databaseName = await pickServiceDatabaseName(\n\t\tapi,\n\t\tprojectId,\n\t\tbranch.id,\n\t);\n\n\tconst [auth, dataApi] = await Promise.all([\n\t\twantsAuth\n\t\t\t? api.getNeonAuth(projectId, branch.id)\n\t\t\t: Promise.resolve(null),\n\t\twantsDataApi\n\t\t\t? api.getNeonDataApi(projectId, branch.id, databaseName)\n\t\t\t: Promise.resolve(null),\n\t]);\n\treturn {\n\t\tdatabaseName,\n\t\tauthEnabled: auth !== null,\n\t\tdataApiEnabled: dataApi !== null,\n\t};\n}\n\n/**\n * Pre-fetch the current state of branch-scoped Preview features (buckets, functions, AI\n * Gateway) so the diff can be computed additively. Only called when the policy has a\n * `preview` block.\n */\nasync function resolvePreviewState(args: {\n\tapi: NeonApi;\n\tprojectId: string;\n\tbranchId: string;\n}): Promise<RemotePreviewState> {\n\tconst { api, projectId, branchId } = args;\n\tconst [buckets, functions, aiGatewayEnabled] = await Promise.all([\n\t\tapi.listBranchBuckets(projectId, branchId),\n\t\tapi.listBranchFunctions(projectId, branchId),\n\t\tapi.getAiGatewayEnabled(projectId, branchId),\n\t]);\n\treturn { buckets, functions, aiGatewayEnabled };\n}\n\n/**\n * Resolve the database name for a Data API integration. Auto-pick when the branch has\n * exactly one database; otherwise fall back to Neon's default (`neondb`) so the call\n * stays useful even on branches with multiple databases — push doesn't have a way to\n * surface a \"pick one\" prompt the way `fetchEnv` does.\n */\nasync function pickServiceDatabaseName(\n\tapi: NeonApi,\n\tprojectId: string,\n\tbranchId: string,\n): Promise<string> {\n\tconst databases = await api.listBranchDatabases(projectId, branchId);\n\tif (databases.length === 1) return databases[0].name;\n\tconst neondb = databases.find((d) => d.name === \"neondb\");\n\tif (neondb) return neondb.name;\n\treturn databases[0]?.name ?? \"neondb\";\n}\n\ninterface ApplyContext {\n\tapi: NeonApi;\n\tremoteProjectId: string;\n\tbranchById: Map<string, NeonBranchSnapshot>;\n\tbranchByName: Map<string, NeonBranchSnapshot>;\n\tbundleFunction: FunctionBundler;\n}\n\nasync function applyStep(\n\tstep: PlanStep,\n\tctx: ApplyContext,\n): Promise<AppliedChange> {\n\tswitch (step.kind) {\n\t\tcase \"update-branch-ttl\": {\n\t\t\tconst updated = await ctx.api.updateBranch(\n\t\t\t\tctx.remoteProjectId,\n\t\t\t\tstep.branchId,\n\t\t\t\t{\n\t\t\t\t\texpiresAt: step.expiresAt ?? null,\n\t\t\t\t},\n\t\t\t);\n\t\t\tctx.branchById.set(updated.id, updated);\n\t\t\tctx.branchByName.set(updated.name, updated);\n\t\t\treturn {\n\t\t\t\tkind: \"branch\",\n\t\t\t\taction: \"update\",\n\t\t\t\tidentifier: updated.name,\n\t\t\t\tdetails: { field: \"ttl\", expiresAt: step.expiresAt },\n\t\t\t};\n\t\t}\n\t\tcase \"update-branch-protected\": {\n\t\t\tconst updated = await ctx.api.updateBranch(\n\t\t\t\tctx.remoteProjectId,\n\t\t\t\tstep.branchId,\n\t\t\t\t{ protected: step.protected },\n\t\t\t);\n\t\t\tctx.branchById.set(updated.id, updated);\n\t\t\tctx.branchByName.set(updated.name, updated);\n\t\t\treturn {\n\t\t\t\tkind: \"branch\",\n\t\t\t\taction: \"update\",\n\t\t\t\tidentifier: updated.name,\n\t\t\t\tdetails: { field: \"protected\", protected: step.protected },\n\t\t\t};\n\t\t}\n\t\tcase \"update-endpoint\": {\n\t\t\tconst updated = await ctx.api.updateEndpoint(\n\t\t\t\tctx.remoteProjectId,\n\t\t\t\tstep.endpointId,\n\t\t\t\tstep.settings,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tkind: \"branch\",\n\t\t\t\taction: \"update\",\n\t\t\t\tidentifier: step.branchName,\n\t\t\t\tdetails: {\n\t\t\t\t\tfield: \"computeSettings\",\n\t\t\t\t\tendpointId: updated.id,\n\t\t\t\t\tsettings: step.settings,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tcase \"enable-auth\": {\n\t\t\tawait ctx.api.enableNeonAuth(ctx.remoteProjectId, step.branchId, {\n\t\t\t\t...(step.databaseName\n\t\t\t\t\t? { databaseName: step.databaseName }\n\t\t\t\t\t: {}),\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tkind: \"service\",\n\t\t\t\taction: \"create\",\n\t\t\t\tidentifier: \"auth\",\n\t\t\t\tdetails: {\n\t\t\t\t\tbranchName: step.branchName,\n\t\t\t\t\t...(step.databaseName\n\t\t\t\t\t\t? { databaseName: step.databaseName }\n\t\t\t\t\t\t: {}),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tcase \"enable-data-api\": {\n\t\t\tawait ctx.api.enableProjectBranchDataApi(\n\t\t\t\tctx.remoteProjectId,\n\t\t\t\tstep.branchId,\n\t\t\t\tstep.databaseName,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tkind: \"service\",\n\t\t\t\taction: \"create\",\n\t\t\t\tidentifier: \"dataApi\",\n\t\t\t\tdetails: {\n\t\t\t\t\tbranchName: step.branchName,\n\t\t\t\t\tdatabaseName: step.databaseName,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tcase \"create-bucket\": {\n\t\t\tawait ctx.api.createBranchBucket(\n\t\t\t\tctx.remoteProjectId,\n\t\t\t\tstep.branchId,\n\t\t\t\t{ name: step.bucketName, accessLevel: step.accessLevel },\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tkind: \"service\",\n\t\t\t\taction: \"create\",\n\t\t\t\tidentifier: `bucket:${step.bucketName}`,\n\t\t\t\tdetails: {\n\t\t\t\t\tbranchName: step.branchName,\n\t\t\t\t\tbucketName: step.bucketName,\n\t\t\t\t\taccessLevel: step.accessLevel,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tcase \"create-function\": {\n\t\t\tawait ctx.api.createBranchFunction(\n\t\t\t\tctx.remoteProjectId,\n\t\t\t\tstep.branchId,\n\t\t\t\t{ slug: step.fn.slug, name: step.fn.name },\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tkind: \"service\",\n\t\t\t\taction: \"create\",\n\t\t\t\tidentifier: `function:${step.fn.slug}`,\n\t\t\t\tdetails: {\n\t\t\t\t\tbranchName: step.branchName,\n\t\t\t\t\tslug: step.fn.slug,\n\t\t\t\t\tname: step.fn.name,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tcase \"deploy-function\": {\n\t\t\tconst bundle = await ctx.bundleFunction(step.fn);\n\t\t\tconst deployment = await ctx.api.deployBranchFunction(\n\t\t\t\tctx.remoteProjectId,\n\t\t\t\tstep.branchId,\n\t\t\t\tstep.fn.slug,\n\t\t\t\t{\n\t\t\t\t\tbundle,\n\t\t\t\t\truntime: step.fn.runtime,\n\t\t\t\t\tmemoryMib: step.fn.memoryMib,\n\t\t\t\t\tenvironment: step.fn.env,\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tkind: \"service\",\n\t\t\t\taction: \"update\",\n\t\t\t\tidentifier: `function:${step.fn.slug}`,\n\t\t\t\tdetails: {\n\t\t\t\t\tbranchName: step.branchName,\n\t\t\t\t\tslug: step.fn.slug,\n\t\t\t\t\tsource: step.fn.source,\n\t\t\t\t\truntime: step.fn.runtime,\n\t\t\t\t\tmemoryMib: step.fn.memoryMib,\n\t\t\t\t\tdeploymentId: deployment.id,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tcase \"enable-ai-gateway\": {\n\t\t\tawait ctx.api.enableAiGateway(ctx.remoteProjectId, step.branchId);\n\t\t\treturn {\n\t\t\t\tkind: \"service\",\n\t\t\t\taction: \"create\",\n\t\t\t\tidentifier: \"aiGateway\",\n\t\t\t\tdetails: { branchName: step.branchName },\n\t\t\t};\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
package/dist/v1.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { FunctionBundler, buildFunctionBundle } from "./lib/function-bundle.js";
|
|
2
|
+
import { PullConfigOptions, PulledBranchConfig, PulledPreview, pullConfig } from "./lib/pull-config.js";
|
|
3
|
+
import { PushConfigOptions, PushConfirmContext, pushConfig } from "./lib/push-config.js";
|
|
4
|
+
import { ApplyOptions, ConfigOperationOptions, apply, inspect, plan } from "./lib/operations.js";
|
|
5
|
+
import { AppliedChange, Config, ConfigLoadError, ConfigValidationError, ConflictReport, ErrorCode, LoadConfigOptions, MissingContextError, NeonApi, PlatformError, PushAbortedError, PushConflictError, PushResult, createNeonApiFromOptions, createRealNeonApi, defineConfig, loadConfigFromFile } from "@neondatabase/config";
|
|
6
|
+
export { type AppliedChange, type ApplyOptions, type Config, ConfigLoadError, type ConfigOperationOptions, ConfigValidationError, type ConflictReport, ErrorCode, type FunctionBundler, type LoadConfigOptions, MissingContextError, type NeonApi, PlatformError, type PullConfigOptions, type PulledBranchConfig, type PulledPreview, PushAbortedError, type PushConfigOptions, type PushConfirmContext, PushConflictError, type PushResult, apply, buildFunctionBundle, createNeonApiFromOptions, createRealNeonApi, defineConfig, inspect, loadConfigFromFile, plan, pullConfig, pushConfig };
|
package/dist/v1.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { buildFunctionBundle } from "./lib/function-bundle.js";
|
|
2
|
+
import { pullConfig } from "./lib/pull-config.js";
|
|
3
|
+
import { pushConfig } from "./lib/push-config.js";
|
|
4
|
+
import { apply, inspect, plan } from "./lib/operations.js";
|
|
5
|
+
import { ConfigLoadError, ConfigValidationError, ErrorCode, MissingContextError, PlatformError, PushAbortedError, PushConflictError, createNeonApiFromOptions, createRealNeonApi, defineConfig, loadConfigFromFile } from "@neondatabase/config";
|
|
6
|
+
export { ConfigLoadError, ConfigValidationError, ErrorCode, MissingContextError, PlatformError, PushAbortedError, PushConflictError, apply, buildFunctionBundle, createNeonApiFromOptions, createRealNeonApi, defineConfig, inspect, loadConfigFromFile, plan, pullConfig, pushConfig };
|