@neon/config-runtime 0.0.0 → 0.8.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/README.md +24 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/lib/function-bundle.d.ts +40 -0
- package/dist/lib/function-bundle.d.ts.map +1 -0
- package/dist/lib/function-bundle.js +78 -0
- package/dist/lib/function-bundle.js.map +1 -0
- package/dist/lib/operations.d.ts +125 -0
- package/dist/lib/operations.d.ts.map +1 -0
- package/dist/lib/operations.js +133 -0
- package/dist/lib/operations.js.map +1 -0
- package/dist/lib/pull-config.d.ts +81 -0
- package/dist/lib/pull-config.d.ts.map +1 -0
- package/dist/lib/pull-config.js +132 -0
- package/dist/lib/pull-config.js.map +1 -0
- package/dist/lib/push-config.d.ts +124 -0
- package/dist/lib/push-config.d.ts.map +1 -0
- package/dist/lib/push-config.js +405 -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 +58 -18
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { BucketAccessLevel, Config, NeonApi, NeonBranchSnapshot, NeonBucketSnapshot, NeonCredentialMeta, NeonEndpointSnapshot, NeonFunctionSnapshot, NeonProjectSnapshot } from "@neon/config";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/pull-config.d.ts
|
|
4
|
+
interface PullConfigOptions {
|
|
5
|
+
/** Neon project id (`<project>`). Required — the API addresses branches by project. */
|
|
6
|
+
projectId: string;
|
|
7
|
+
/** Neon branch id (`br-…`). Required. Resolve names to ids before calling. */
|
|
8
|
+
branchId: string;
|
|
9
|
+
/** Neon API key. Falls back to `NEON_API_KEY` / neonctl credentials. */
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
/** Neon API base URL. Falls back to `NEON_API_HOST`, then production. */
|
|
12
|
+
apiHost?: string;
|
|
13
|
+
/** Inject a custom NeonApi adapter (primarily for tests). */
|
|
14
|
+
api?: NeonApi;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Live Preview-feature state read back from a branch. Surfaced alongside `config` rather
|
|
18
|
+
* than inside it because functions cannot round-trip: the remote only knows the deployed
|
|
19
|
+
* bundle, not the local `source` path a {@link FunctionConfig} requires, so a pulled
|
|
20
|
+
* function is reported as `{ slug, name }` (no `source`).
|
|
21
|
+
*/
|
|
22
|
+
interface PulledPreview {
|
|
23
|
+
buckets: Array<{
|
|
24
|
+
name: string;
|
|
25
|
+
access: BucketAccessLevel;
|
|
26
|
+
}>;
|
|
27
|
+
functions: Array<{
|
|
28
|
+
slug: string;
|
|
29
|
+
name: string;
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* Secret-free metadata for the credentials issued on the branch (Preview). Surfaced so
|
|
33
|
+
* `config status` can show issued credentials (id, scopes, last used) without ever
|
|
34
|
+
* exposing the one-time `api_token` / `s3_secret_access_key`. Empty when none / the
|
|
35
|
+
* credentials endpoint is unavailable for the project.
|
|
36
|
+
*/
|
|
37
|
+
credentials: NeonCredentialMeta[];
|
|
38
|
+
}
|
|
39
|
+
interface PulledBranchConfig {
|
|
40
|
+
project: {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
region: string;
|
|
44
|
+
pgVersion: number;
|
|
45
|
+
orgId?: string;
|
|
46
|
+
};
|
|
47
|
+
branch: {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
parent?: string;
|
|
51
|
+
isDefault: boolean;
|
|
52
|
+
protected: boolean;
|
|
53
|
+
expiresAt?: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* The branch's live state expressed as a {@link Config}: static `auth` / `dataApi`
|
|
57
|
+
* toggles plus a `branch` closure carrying the branch's lifecycle/compute tuning.
|
|
58
|
+
* Preview functions/buckets are reported separately in {@link PulledBranchConfig.preview}
|
|
59
|
+
* because functions cannot round-trip (the remote has no `source` path).
|
|
60
|
+
*/
|
|
61
|
+
config: Config;
|
|
62
|
+
/**
|
|
63
|
+
* Live Preview-feature state, when the branch has any buckets/functions or issued
|
|
64
|
+
* credentials. Omitted entirely when there is nothing to report. The AI Gateway is not
|
|
65
|
+
* included: it is always available (credential-gated), not per-branch state.
|
|
66
|
+
*/
|
|
67
|
+
preview?: PulledPreview;
|
|
68
|
+
}
|
|
69
|
+
declare function pullConfig(options: PullConfigOptions): Promise<PulledBranchConfig>;
|
|
70
|
+
declare function buildPulledBranchConfig(project: NeonProjectSnapshot, branch: NeonBranchSnapshot, branches: NeonBranchSnapshot[], endpoint: NeonEndpointSnapshot | undefined, previewState?: {
|
|
71
|
+
buckets: NeonBucketSnapshot[];
|
|
72
|
+
functions: NeonFunctionSnapshot[];
|
|
73
|
+
credentials?: NeonCredentialMeta[];
|
|
74
|
+
/** Whether a Neon Auth integration is enabled on the branch. */
|
|
75
|
+
authEnabled?: boolean;
|
|
76
|
+
/** Whether a Neon Data API integration is enabled on the branch. */
|
|
77
|
+
dataApiEnabled?: boolean;
|
|
78
|
+
}): PulledBranchConfig;
|
|
79
|
+
//#endregion
|
|
80
|
+
export { PullConfigOptions, PulledBranchConfig, PulledPreview, buildPulledBranchConfig, pullConfig };
|
|
81
|
+
//# sourceMappingURL=pull-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull-config.d.ts","names":[],"sources":["../../src/lib/pull-config.ts"],"mappings":";;;UAkBiB,iBAAA;;EAAA,SAAA,EAAA,MAAA;EAmBA;EAAa,QAAA,EAAA,MAAA;;QACpB,CAAA,EAAA,MAAA;;SAQI,CAAA,EAAA,MAAA;EAAkB;EAGf,GAAA,CAAA,EArBV,OAqBU;;;;AA4BO;AAGxB;;;AAEW,UA7CM,aAAA,CA6CN;SAAR,EA5CO,KA4CP,CAAA;IAAO,IAAA,EAAA,MAAA;IA4FM,MAAA,EAxIwB,iBAwID;EAAA,CAAA,CAAA;WAC7B,EAxIE,KAwIF,CAAA;IACD,IAAA,EAAA,MAAA;IACE,IAAA,EAAA,MAAA;;;;;;AAWU;;eA9IP;;UAGG,kBAAA;;;;;;;;;;;;;;;;;;;;;;UAsBR;;;;;;YAME;;iBAGW,UAAA,UACZ,oBACP,QAAQ;iBA4FK,uBAAA,UACN,6BACD,8BACE,gCACA;WAEA;aACE;gBACG;;;;;IAMb"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { ErrorCode, PlatformError, createNeonApiFromOptions } from "@neon/config";
|
|
2
|
+
//#region src/lib/pull-config.ts
|
|
3
|
+
async function pullConfig(options) {
|
|
4
|
+
const api = options.api ?? createApiFromOptions(options);
|
|
5
|
+
const projectId = options.projectId;
|
|
6
|
+
const project = await api.getProject(projectId);
|
|
7
|
+
const [branches, endpoints] = await Promise.all([api.listBranches(projectId), api.listEndpoints(projectId)]);
|
|
8
|
+
const branch = resolveBranch(options.branchId, branches);
|
|
9
|
+
const endpoint = endpoints.find((ep) => ep.type === "read_write" && ep.branchId === branch.id);
|
|
10
|
+
const probeDatabase = pickProbeDatabase(await api.listBranchDatabases(projectId, branch.id));
|
|
11
|
+
const [buckets, functions, credentials, auth, dataApi] = await Promise.all([
|
|
12
|
+
degradeUnavailable(() => api.listBranchBuckets(projectId, branch.id), []),
|
|
13
|
+
degradeUnavailable(() => api.listBranchFunctions(projectId, branch.id), []),
|
|
14
|
+
degradeUnavailable(() => api.listCredentials(projectId, branch.id), []),
|
|
15
|
+
api.getNeonAuth(projectId, branch.id),
|
|
16
|
+
probeDatabase ? api.getNeonDataApi(projectId, branch.id, probeDatabase) : Promise.resolve(null)
|
|
17
|
+
]);
|
|
18
|
+
return buildPulledBranchConfig(project, branch, branches, endpoint, {
|
|
19
|
+
buckets,
|
|
20
|
+
functions,
|
|
21
|
+
credentials,
|
|
22
|
+
authEnabled: auth !== null,
|
|
23
|
+
dataApiEnabled: dataApi !== null
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Pick the database to probe for a Data API integration. Data API is enabled per
|
|
28
|
+
* branch + database; for read-back we only need to know whether *any* database has it
|
|
29
|
+
* on, so prefer the conventional default (`neondb`) and otherwise fall back to the first
|
|
30
|
+
* database. Returns `undefined` when the branch has no databases.
|
|
31
|
+
*/
|
|
32
|
+
function pickProbeDatabase(databases) {
|
|
33
|
+
if (databases.length === 0) return void 0;
|
|
34
|
+
const byName = databases.find((d) => d.name === "neondb");
|
|
35
|
+
if (byName) return byName.name;
|
|
36
|
+
return databases[0].name;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Run a Preview-feature read, returning `fallback` if the feature is unavailable for the
|
|
40
|
+
* project/region (a {@link ErrorCode.FeatureUnavailable} from the adapter). Other errors
|
|
41
|
+
* propagate. Used by `pullConfig` so a branch without a Preview feature still mirrors
|
|
42
|
+
* cleanly for env resolution / `inspect`, rather than aborting on an unrelated capability.
|
|
43
|
+
*/
|
|
44
|
+
async function degradeUnavailable(read, fallback) {
|
|
45
|
+
try {
|
|
46
|
+
return await read();
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (err instanceof PlatformError && err.code === ErrorCode.FeatureUnavailable) return fallback;
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function createApiFromOptions(options) {
|
|
53
|
+
return createNeonApiFromOptions("pullConfig", {
|
|
54
|
+
...options.apiKey ? { apiKey: options.apiKey } : {},
|
|
55
|
+
...options.apiHost ? { apiHost: options.apiHost } : {}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function buildPulledBranchConfig(project, branch, branches, endpoint, previewState) {
|
|
59
|
+
const parent = branch.parentId ? branches.find((b) => b.id === branch.parentId) : void 0;
|
|
60
|
+
const tuning = {};
|
|
61
|
+
if (parent) tuning.parent = parent.name;
|
|
62
|
+
if (branch.protected) tuning.protected = true;
|
|
63
|
+
if (endpoint) {
|
|
64
|
+
const compute = endpointToComputeSettings(endpoint, project);
|
|
65
|
+
if (compute) tuning.postgres = { computeSettings: compute };
|
|
66
|
+
}
|
|
67
|
+
const config = {
|
|
68
|
+
...previewState?.authEnabled ? { auth: true } : {},
|
|
69
|
+
...previewState?.dataApiEnabled ? { dataApi: true } : {},
|
|
70
|
+
...Object.keys(tuning).length > 0 ? { branch: () => tuning } : {}
|
|
71
|
+
};
|
|
72
|
+
const result = {
|
|
73
|
+
project: {
|
|
74
|
+
id: project.id,
|
|
75
|
+
name: project.name,
|
|
76
|
+
region: project.regionId,
|
|
77
|
+
pgVersion: project.pgVersion,
|
|
78
|
+
...project.orgId ? { orgId: project.orgId } : {}
|
|
79
|
+
},
|
|
80
|
+
branch: {
|
|
81
|
+
id: branch.id,
|
|
82
|
+
name: branch.name,
|
|
83
|
+
...parent ? { parent: parent.name } : {},
|
|
84
|
+
isDefault: branch.isDefault,
|
|
85
|
+
protected: branch.protected,
|
|
86
|
+
...branch.expiresAt ? { expiresAt: branch.expiresAt } : {}
|
|
87
|
+
},
|
|
88
|
+
config
|
|
89
|
+
};
|
|
90
|
+
const preview = previewState ? buildPulledPreview(previewState) : void 0;
|
|
91
|
+
if (preview) result.preview = preview;
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Reverse-engineer the {@link PulledPreview} from remote snapshots. Returns `undefined` when
|
|
96
|
+
* the branch has no Preview features so the field can be omitted entirely.
|
|
97
|
+
*/
|
|
98
|
+
function buildPulledPreview(state) {
|
|
99
|
+
const credentials = state.credentials ?? [];
|
|
100
|
+
if (state.buckets.length === 0 && state.functions.length === 0 && credentials.length === 0) return;
|
|
101
|
+
return {
|
|
102
|
+
buckets: state.buckets.map((b) => ({
|
|
103
|
+
name: b.name,
|
|
104
|
+
access: b.accessLevel
|
|
105
|
+
})),
|
|
106
|
+
functions: state.functions.map((f) => ({
|
|
107
|
+
slug: f.slug,
|
|
108
|
+
name: f.name
|
|
109
|
+
})),
|
|
110
|
+
credentials
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function resolveBranch(branchId, branches) {
|
|
114
|
+
const match = branches.find((b) => b.id === branchId);
|
|
115
|
+
if (match) return match;
|
|
116
|
+
throw new PlatformError(ErrorCode.BranchNotFound, [`pullConfig: branch id ${JSON.stringify(branchId)} not found on project.`, `Available branches: ${branches.map((b) => `${b.name} (${b.id})`).join(", ") || "(none)"}.`].join(" "), { details: {
|
|
117
|
+
branchId,
|
|
118
|
+
available: branches.map((b) => b.id)
|
|
119
|
+
} });
|
|
120
|
+
}
|
|
121
|
+
function endpointToComputeSettings(endpoint, project) {
|
|
122
|
+
const defaults = project.defaultEndpointSettings;
|
|
123
|
+
const out = {};
|
|
124
|
+
if (endpoint.autoscalingLimitMinCu !== void 0 && endpoint.autoscalingLimitMinCu !== defaults?.autoscalingLimitMinCu) out.autoscalingLimitMinCu = endpoint.autoscalingLimitMinCu;
|
|
125
|
+
if (endpoint.autoscalingLimitMaxCu !== void 0 && endpoint.autoscalingLimitMaxCu !== defaults?.autoscalingLimitMaxCu) out.autoscalingLimitMaxCu = endpoint.autoscalingLimitMaxCu;
|
|
126
|
+
if (endpoint.suspendTimeout !== void 0 && endpoint.suspendTimeout !== defaults?.suspendTimeout) out.suspendTimeout = endpoint.suspendTimeout;
|
|
127
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { buildPulledBranchConfig, pullConfig };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=pull-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull-config.js","names":[],"sources":["../../src/lib/pull-config.ts"],"sourcesContent":["import {\n\ttype BranchTuning,\n\ttype BucketAccessLevel,\n\ttype ComputeSettings,\n\ttype Config,\n\tcreateNeonApiFromOptions,\n\tErrorCode,\n\ttype NeonApi,\n\ttype NeonBranchSnapshot,\n\ttype NeonBucketSnapshot,\n\ttype NeonCredentialMeta,\n\ttype NeonDatabaseSnapshot,\n\ttype NeonEndpointSnapshot,\n\ttype NeonFunctionSnapshot,\n\ttype NeonProjectSnapshot,\n\tPlatformError,\n} from \"@neon/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/** Neon API base URL. Falls back to `NEON_API_HOST`, then production. */\n\tapiHost?: 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: Array<{ name: string; access: BucketAccessLevel }>;\n\tfunctions: Array<{ slug: string; name: string }>;\n\t/**\n\t * Secret-free metadata for the credentials issued on the branch (Preview). Surfaced so\n\t * `config status` can show issued credentials (id, scopes, last used) without ever\n\t * exposing the one-time `api_token` / `s3_secret_access_key`. Empty when none / the\n\t * credentials endpoint is unavailable for the project.\n\t */\n\tcredentials: NeonCredentialMeta[];\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\t/**\n\t * The branch's live state expressed as a {@link Config}: static `auth` / `dataApi`\n\t * toggles plus a `branch` closure carrying the branch's lifecycle/compute tuning.\n\t * Preview functions/buckets are reported separately in {@link PulledBranchConfig.preview}\n\t * because functions cannot round-trip (the remote has no `source` path).\n\t */\n\tconfig: Config;\n\t/**\n\t * Live Preview-feature state, when the branch has any buckets/functions or issued\n\t * credentials. Omitted entirely when there is nothing to report. The AI Gateway is not\n\t * included: it is always available (credential-gated), not per-branch state.\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\t// Preview reads degrade to \"none / disabled\" when the feature isn't available for the\n\t// project/region. `pullConfig` mirrors the branch for env resolution (`neon dev`,\n\t// `neon env pull`) and `inspect` — an unavailable Preview feature should not break those\n\t// (env comes from auth/dataApi/postgres). `pushConfig` is the place that fails on an\n\t// unavailable feature, and only when the policy declares it.\n\tconst [buckets, functions, credentials, auth, dataApi] = await Promise.all([\n\t\tdegradeUnavailable(\n\t\t\t() => api.listBranchBuckets(projectId, branch.id),\n\t\t\t[],\n\t\t),\n\t\tdegradeUnavailable(\n\t\t\t() => api.listBranchFunctions(projectId, branch.id),\n\t\t\t[],\n\t\t),\n\t\tdegradeUnavailable(() => api.listCredentials(projectId, branch.id), []),\n\t\tapi.getNeonAuth(projectId, branch.id),\n\t\tprobeDatabase\n\t\t\t? api.getNeonDataApi(projectId, branch.id, probeDatabase)\n\t\t\t: Promise.resolve(null),\n\t]);\n\n\treturn buildPulledBranchConfig(project, branch, branches, endpoint, {\n\t\tbuckets,\n\t\tfunctions,\n\t\tcredentials,\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\n/**\n * Run a Preview-feature read, returning `fallback` if the feature is unavailable for the\n * project/region (a {@link ErrorCode.FeatureUnavailable} from the adapter). Other errors\n * propagate. Used by `pullConfig` so a branch without a Preview feature still mirrors\n * cleanly for env resolution / `inspect`, rather than aborting on an unrelated capability.\n */\nasync function degradeUnavailable<T>(\n\tread: () => Promise<T>,\n\tfallback: T,\n): Promise<T> {\n\ttry {\n\t\treturn await read();\n\t} catch (err) {\n\t\tif (\n\t\t\terr instanceof PlatformError &&\n\t\t\terr.code === ErrorCode.FeatureUnavailable\n\t\t) {\n\t\t\treturn fallback;\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nfunction createApiFromOptions(options: PullConfigOptions): NeonApi {\n\treturn createNeonApiFromOptions(\"pullConfig\", {\n\t\t...(options.apiKey ? { apiKey: options.apiKey } : {}),\n\t\t...(options.apiHost ? { apiHost: options.apiHost } : {}),\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\tcredentials?: NeonCredentialMeta[];\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 are static top-level toggles, so a config pulled from a branch with\n\t// them enabled round-trips through `resolveConfig` / `fetchEnv` and the matching\n\t// secrets get injected. Branch lifecycle/compute is per-branch tuning, so it goes in\n\t// the `branch` closure.\n\tconst tuning: BranchTuning = {};\n\tif (parent) tuning.parent = parent.name;\n\t// Deliberately NOT emitting `ttl` from `branch.expiresAt`: policy `ttl` is a\n\t// creation-time *duration*, while `expiresAt` is an absolute timestamp. Feeding the\n\t// timestamp through `parseDuration` (in `resolveConfig`) would throw, breaking\n\t// `fetchEnv` / `neon dev` / `neon env pull` for any branch that has a TTL. The expiry\n\t// is reported faithfully on `branch.expiresAt` above.\n\tif (branch.protected) tuning.protected = true;\n\tif (endpoint) {\n\t\tconst compute = endpointToComputeSettings(endpoint, project);\n\t\tif (compute) tuning.postgres = { computeSettings: compute };\n\t}\n\tconst config: Config = {\n\t\t...(previewState?.authEnabled ? { auth: true } : {}),\n\t\t...(previewState?.dataApiEnabled ? { dataApi: true } : {}),\n\t\t...(Object.keys(tuning).length > 0 ? { branch: () => tuning } : {}),\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\tcredentials?: NeonCredentialMeta[];\n}): PulledPreview | undefined {\n\tconst credentials = state.credentials ?? [];\n\tif (\n\t\tstate.buckets.length === 0 &&\n\t\tstate.functions.length === 0 &&\n\t\tcredentials.length === 0\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\tcredentials,\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,124 @@
|
|
|
1
|
+
import { FunctionBundler } from "./function-bundle.js";
|
|
2
|
+
import { Config, NeonApi, PushResult } from "@neon/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
|
+
/** Neon API base URL. Falls back to `NEON_API_HOST`, then production. */
|
|
20
|
+
apiHost?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Inject a custom NeonApi adapter. Primarily used by tests; production callers can rely
|
|
23
|
+
* on the default real adapter built from `apiKey`.
|
|
24
|
+
*/
|
|
25
|
+
api?: NeonApi;
|
|
26
|
+
/**
|
|
27
|
+
* Whether to evaluate the policy as if the target branch **already exists** (the value of
|
|
28
|
+
* `branch.exists` passed to the `defineConfig({ branch: (branch) => … })` closure). Defaults to `true`.
|
|
29
|
+
*
|
|
30
|
+
* Set to `false` to evaluate the policy as a **branch creation** — used by
|
|
31
|
+
* {@link createBranch} right after it provisions a new branch, so creation-time tuning
|
|
32
|
+
* gated on `!branch.exists` (TTL, compute settings, `parent`) actually resolves instead of
|
|
33
|
+
* hitting the "existing branch, leave as-is" path. Only affects policy evaluation; the
|
|
34
|
+
* branch must still physically exist on Neon (`pushConfig` never creates one).
|
|
35
|
+
*/
|
|
36
|
+
branchExists?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Auto-confirm overriding existing remote settings.
|
|
39
|
+
*
|
|
40
|
+
* When `true`, mutable drift on the selected branch (TTL, `protected` flag, compute
|
|
41
|
+
* settings) is applied as actual mutations and the override-confirm prompt is
|
|
42
|
+
* skipped. When `false` (default) the behaviour depends on whether `confirm` is
|
|
43
|
+
* supplied:
|
|
44
|
+
* - With `confirm`: the callback is asked whether to apply the override.
|
|
45
|
+
* - Without `confirm`: drift is reported as a `PushConflictError` (legacy
|
|
46
|
+
* non-interactive default — preserved so programmatic SDK callers don't
|
|
47
|
+
* silently start mutating remote state).
|
|
48
|
+
*/
|
|
49
|
+
updateExisting?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Auto-confirm pushing to a protected branch.
|
|
52
|
+
*
|
|
53
|
+
* When `true`, no protected-branch confirmation is asked. When `false` (default):
|
|
54
|
+
* - With `confirm`: the callback is asked.
|
|
55
|
+
* - Without `confirm`: the push proceeds (legacy SDK default).
|
|
56
|
+
*/
|
|
57
|
+
allowProtectedBranch?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Optional confirmation callback. Invoked once with a single context object before
|
|
60
|
+
* any mutations run when the push needs confirmation: pushing to a protected
|
|
61
|
+
* branch (unless `allowProtectedBranch` is `true`) and/or applying mutable drift
|
|
62
|
+
* (unless `updateExisting` is `true`).
|
|
63
|
+
*
|
|
64
|
+
* Both prompts collapse into a single callback invocation when both apply, so the
|
|
65
|
+
* CLI can render one combined "are you sure?" prompt.
|
|
66
|
+
*
|
|
67
|
+
* Resolves to `true` to proceed, `false` to abort with {@link PushAbortedError}.
|
|
68
|
+
*
|
|
69
|
+
* Never invoked on `dryRun`.
|
|
70
|
+
*/
|
|
71
|
+
confirm?: (context: PushConfirmContext) => boolean | Promise<boolean>;
|
|
72
|
+
/**
|
|
73
|
+
* Custom bundler for function source. Defaults to {@link buildFunctionBundle}
|
|
74
|
+
* (esbuild). Inject your own to deploy functions without this package pulling
|
|
75
|
+
* esbuild's native binary into your build — see {@link FunctionBundler}.
|
|
76
|
+
*/
|
|
77
|
+
bundleFunction?: FunctionBundler;
|
|
78
|
+
/**
|
|
79
|
+
* When `true`, compute the full plan against the live remote state but **do not
|
|
80
|
+
* execute any mutations**. The resulting `PushResult.applied` array records every
|
|
81
|
+
* change that *would* run on a real push (with the same action / identifier / details
|
|
82
|
+
* shape, so the existing CLI summary formatter just works), and conflicts are
|
|
83
|
+
* reported instead of thrown.
|
|
84
|
+
*
|
|
85
|
+
* Used by `plan(config, branchId)` and any caller that wants a "would this push do
|
|
86
|
+
* something dangerous?" check before invoking `pushConfig` for real.
|
|
87
|
+
*/
|
|
88
|
+
dryRun?: boolean;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Context handed to a {@link PushConfigOptions.confirm} callback. Both flags can be
|
|
92
|
+
* `true` simultaneously when the push targets a protected branch *and* would override
|
|
93
|
+
* existing settings — render a single combined prompt covering both reasons.
|
|
94
|
+
*/
|
|
95
|
+
interface PushConfirmContext {
|
|
96
|
+
/** Name of the target branch on Neon. */
|
|
97
|
+
branchName: string;
|
|
98
|
+
/**
|
|
99
|
+
* `true` when the target branch has the `protected` flag on Neon and the caller
|
|
100
|
+
* did not pass `allowProtectedBranch: true`.
|
|
101
|
+
*/
|
|
102
|
+
protectedBranch: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* `true` when the plan would override existing remote settings (TTL, `protected`
|
|
105
|
+
* flag, compute settings on an existing endpoint) and the caller did not pass
|
|
106
|
+
* `updateExisting: true`. Additive operations (enabling Neon Auth / Data API for
|
|
107
|
+
* the first time) are **not** counted here — those are unambiguous and never
|
|
108
|
+
* prompt.
|
|
109
|
+
*/
|
|
110
|
+
overrideUpdates: boolean;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Push a Neon branch policy to a specific project + branch.
|
|
114
|
+
*
|
|
115
|
+
* Filesystem- and env-agnostic: the caller supplies an already-validated `Config` object
|
|
116
|
+
* (from `defineConfig` / `loadConfigFromFile`) and explicit `projectId` + `branch` in
|
|
117
|
+
* `options`. `pushConfig` performs no `.neon` lookups and reads no `NEON_*` env vars except the API credential/host resolution documented on `apiKey`/`apiHost`.
|
|
118
|
+
*
|
|
119
|
+
* It will **not** create a project or branch — both must already exist on Neon.
|
|
120
|
+
*/
|
|
121
|
+
declare function pushConfig(config: Config, options: PushConfigOptions): Promise<PushResult>;
|
|
122
|
+
//#endregion
|
|
123
|
+
export { PushConfigOptions, PushConfirmContext, pushConfig };
|
|
124
|
+
//# 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":";;;;UAoCiB,iBAAA;;AAAjB;;;;WAkEsD,EAAA,MAAA;;AAMrB;AAmBjC;AA2BA;EAAgC,QAAA,EAAA,MAAA;;QAEtB,CAAA,EAAA,MAAA;;SACP,CAAA,EAAA,MAAA;EAAO;;;;QArGH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA8Cc,iCAAiC;;;;;;mBAMpC;;;;;;;;;;;;;;;;;;UAmBD,kBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2BK,UAAA,SACb,iBACC,oBACP,QAAQ"}
|