@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
|
@@ -1,421 +0,0 @@
|
|
|
1
|
-
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { defineConfig, ErrorCode } from "@neondatabase/config";
|
|
5
|
-
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
6
|
-
import { FakeNeonApi } from "./fake-neon-api.js";
|
|
7
|
-
import { pushConfig } from "./push-config.js";
|
|
8
|
-
|
|
9
|
-
// A real on-disk function source so `buildFunctionBundle` (real esbuild) has something to
|
|
10
|
-
// bundle. We avoid mocks per the project's no-mocks rule: the deploy path runs esbuild for
|
|
11
|
-
// real against this trivial handler.
|
|
12
|
-
let fnSource: string;
|
|
13
|
-
let fnTmpDir: string;
|
|
14
|
-
beforeAll(() => {
|
|
15
|
-
fnTmpDir = mkdtempSync(join(tmpdir(), "neon-fn-"));
|
|
16
|
-
fnSource = join(fnTmpDir, "hello-world.ts");
|
|
17
|
-
writeFileSync(
|
|
18
|
-
fnSource,
|
|
19
|
-
"export default { fetch(_req: Request): Response { return new Response('ok'); } };\n",
|
|
20
|
-
);
|
|
21
|
-
});
|
|
22
|
-
afterAll(() => {
|
|
23
|
-
rmSync(fnTmpDir, { recursive: true, force: true });
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
function seededFake(opts?: { protected?: boolean }) {
|
|
27
|
-
const api = new FakeNeonApi();
|
|
28
|
-
const projectId = "proj-push";
|
|
29
|
-
api.seedProject({
|
|
30
|
-
project: {
|
|
31
|
-
id: projectId,
|
|
32
|
-
name: "push-test",
|
|
33
|
-
regionId: "aws-us-east-1",
|
|
34
|
-
pgVersion: 17,
|
|
35
|
-
orgId: "org-push",
|
|
36
|
-
},
|
|
37
|
-
branches: [
|
|
38
|
-
{
|
|
39
|
-
branch: {
|
|
40
|
-
id: "br-main",
|
|
41
|
-
name: "main",
|
|
42
|
-
isDefault: true,
|
|
43
|
-
protected: opts?.protected ?? false,
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
});
|
|
48
|
-
return { api, projectId };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
describe("pushConfig", () => {
|
|
52
|
-
test("applies branch-scoped service enables to the selected branch", async () => {
|
|
53
|
-
const { api, projectId } = seededFake();
|
|
54
|
-
const config = defineConfig((branch) => ({
|
|
55
|
-
postgres: {
|
|
56
|
-
computeSettings: {
|
|
57
|
-
autoscalingLimitMaxCu: branch.name === "main" ? 4 : 1,
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
auth: {},
|
|
61
|
-
dataApi: {},
|
|
62
|
-
}));
|
|
63
|
-
|
|
64
|
-
const result = await pushConfig(config, {
|
|
65
|
-
api,
|
|
66
|
-
projectId,
|
|
67
|
-
branchId: "br-main",
|
|
68
|
-
updateExisting: true,
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
expect(result.branchName).toBe("main");
|
|
72
|
-
expect(result.applied).toEqual(
|
|
73
|
-
expect.arrayContaining([
|
|
74
|
-
expect.objectContaining({
|
|
75
|
-
kind: "service",
|
|
76
|
-
identifier: "auth",
|
|
77
|
-
}),
|
|
78
|
-
expect.objectContaining({
|
|
79
|
-
kind: "service",
|
|
80
|
-
identifier: "dataApi",
|
|
81
|
-
}),
|
|
82
|
-
]),
|
|
83
|
-
);
|
|
84
|
-
expect(
|
|
85
|
-
api.history.some(
|
|
86
|
-
(h) => h.method === "enableNeonAuth" && h.args[1] === "br-main",
|
|
87
|
-
),
|
|
88
|
-
).toBe(true);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("reports mutable branch drift as conflict without updateExisting", async () => {
|
|
92
|
-
const { api, projectId } = seededFake();
|
|
93
|
-
const config = defineConfig(() => ({
|
|
94
|
-
postgres: { computeSettings: { autoscalingLimitMaxCu: 4 } },
|
|
95
|
-
}));
|
|
96
|
-
|
|
97
|
-
await expect(
|
|
98
|
-
pushConfig(config, { api, projectId, branchId: "br-main" }),
|
|
99
|
-
).rejects.toMatchObject({
|
|
100
|
-
code: ErrorCode.PushConflict,
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("confirm callback applies mutable drift when user accepts", async () => {
|
|
105
|
-
const { api, projectId } = seededFake();
|
|
106
|
-
const config = defineConfig(() => ({
|
|
107
|
-
postgres: { computeSettings: { autoscalingLimitMaxCu: 4 } },
|
|
108
|
-
}));
|
|
109
|
-
|
|
110
|
-
const calls: Array<{
|
|
111
|
-
protectedBranch: boolean;
|
|
112
|
-
overrideUpdates: boolean;
|
|
113
|
-
}> = [];
|
|
114
|
-
const result = await pushConfig(config, {
|
|
115
|
-
api,
|
|
116
|
-
projectId,
|
|
117
|
-
branchId: "br-main",
|
|
118
|
-
confirm: (ctx) => {
|
|
119
|
-
calls.push({
|
|
120
|
-
protectedBranch: ctx.protectedBranch,
|
|
121
|
-
overrideUpdates: ctx.overrideUpdates,
|
|
122
|
-
});
|
|
123
|
-
return true;
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
expect(calls).toEqual([
|
|
128
|
-
{ protectedBranch: false, overrideUpdates: true },
|
|
129
|
-
]);
|
|
130
|
-
expect(
|
|
131
|
-
result.applied.some(
|
|
132
|
-
(c) =>
|
|
133
|
-
c.kind === "branch" &&
|
|
134
|
-
c.action === "update" &&
|
|
135
|
-
c.identifier === "main",
|
|
136
|
-
),
|
|
137
|
-
).toBe(true);
|
|
138
|
-
expect(api.history.some((h) => h.method === "updateEndpoint")).toBe(
|
|
139
|
-
true,
|
|
140
|
-
);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test("confirm callback returning false aborts with PushAbortedError", async () => {
|
|
144
|
-
const { api, projectId } = seededFake();
|
|
145
|
-
const config = defineConfig(() => ({
|
|
146
|
-
postgres: { computeSettings: { autoscalingLimitMaxCu: 4 } },
|
|
147
|
-
}));
|
|
148
|
-
|
|
149
|
-
await expect(
|
|
150
|
-
pushConfig(config, {
|
|
151
|
-
api,
|
|
152
|
-
projectId,
|
|
153
|
-
branchId: "br-main",
|
|
154
|
-
confirm: () => false,
|
|
155
|
-
}),
|
|
156
|
-
).rejects.toMatchObject({ code: ErrorCode.PushAborted });
|
|
157
|
-
expect(api.history.some((h) => h.method === "updateEndpoint")).toBe(
|
|
158
|
-
false,
|
|
159
|
-
);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test("protected branch triggers confirm even when no drift", async () => {
|
|
163
|
-
const { api, projectId } = seededFake({ protected: true });
|
|
164
|
-
const config = defineConfig(() => ({ auth: {} }));
|
|
165
|
-
|
|
166
|
-
const ctxs: Array<{
|
|
167
|
-
protectedBranch: boolean;
|
|
168
|
-
overrideUpdates: boolean;
|
|
169
|
-
}> = [];
|
|
170
|
-
const result = await pushConfig(config, {
|
|
171
|
-
api,
|
|
172
|
-
projectId,
|
|
173
|
-
branchId: "br-main",
|
|
174
|
-
confirm: (ctx) => {
|
|
175
|
-
ctxs.push({
|
|
176
|
-
protectedBranch: ctx.protectedBranch,
|
|
177
|
-
overrideUpdates: ctx.overrideUpdates,
|
|
178
|
-
});
|
|
179
|
-
return true;
|
|
180
|
-
},
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
expect(ctxs).toEqual([
|
|
184
|
-
{ protectedBranch: true, overrideUpdates: false },
|
|
185
|
-
]);
|
|
186
|
-
expect(
|
|
187
|
-
result.applied.some(
|
|
188
|
-
(c) => c.kind === "service" && c.identifier === "auth",
|
|
189
|
-
),
|
|
190
|
-
).toBe(true);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test("protected branch + drift collapses into a single confirm call", async () => {
|
|
194
|
-
const { api, projectId } = seededFake({ protected: true });
|
|
195
|
-
const config = defineConfig(() => ({
|
|
196
|
-
postgres: { computeSettings: { autoscalingLimitMaxCu: 4 } },
|
|
197
|
-
}));
|
|
198
|
-
|
|
199
|
-
const ctxs: Array<{
|
|
200
|
-
protectedBranch: boolean;
|
|
201
|
-
overrideUpdates: boolean;
|
|
202
|
-
}> = [];
|
|
203
|
-
await pushConfig(config, {
|
|
204
|
-
api,
|
|
205
|
-
projectId,
|
|
206
|
-
branchId: "br-main",
|
|
207
|
-
confirm: (ctx) => {
|
|
208
|
-
ctxs.push({
|
|
209
|
-
protectedBranch: ctx.protectedBranch,
|
|
210
|
-
overrideUpdates: ctx.overrideUpdates,
|
|
211
|
-
});
|
|
212
|
-
return true;
|
|
213
|
-
},
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
expect(ctxs).toEqual([
|
|
217
|
-
{ protectedBranch: true, overrideUpdates: true },
|
|
218
|
-
]);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
test("allowProtectedBranch + updateExisting skip the confirm callback", async () => {
|
|
222
|
-
const { api, projectId } = seededFake({ protected: true });
|
|
223
|
-
const config = defineConfig(() => ({
|
|
224
|
-
postgres: { computeSettings: { autoscalingLimitMaxCu: 4 } },
|
|
225
|
-
}));
|
|
226
|
-
|
|
227
|
-
let called = false;
|
|
228
|
-
await pushConfig(config, {
|
|
229
|
-
api,
|
|
230
|
-
projectId,
|
|
231
|
-
branchId: "br-main",
|
|
232
|
-
allowProtectedBranch: true,
|
|
233
|
-
updateExisting: true,
|
|
234
|
-
confirm: () => {
|
|
235
|
-
called = true;
|
|
236
|
-
return true;
|
|
237
|
-
},
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
expect(called).toBe(false);
|
|
241
|
-
expect(api.history.some((h) => h.method === "updateEndpoint")).toBe(
|
|
242
|
-
true,
|
|
243
|
-
);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
test("dryRun never invokes the confirm callback", async () => {
|
|
247
|
-
const { api, projectId } = seededFake({ protected: true });
|
|
248
|
-
const config = defineConfig(() => ({
|
|
249
|
-
postgres: { computeSettings: { autoscalingLimitMaxCu: 4 } },
|
|
250
|
-
}));
|
|
251
|
-
|
|
252
|
-
let called = false;
|
|
253
|
-
const result = await pushConfig(config, {
|
|
254
|
-
api,
|
|
255
|
-
projectId,
|
|
256
|
-
branchId: "br-main",
|
|
257
|
-
dryRun: true,
|
|
258
|
-
confirm: () => {
|
|
259
|
-
called = true;
|
|
260
|
-
return true;
|
|
261
|
-
},
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
expect(called).toBe(false);
|
|
265
|
-
expect(result.dryRun).toBe(true);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
test("creates buckets, creates + deploys functions, and enables AI Gateway", async () => {
|
|
269
|
-
const { api, projectId } = seededFake();
|
|
270
|
-
const config = defineConfig(() => ({
|
|
271
|
-
preview: {
|
|
272
|
-
functions: [
|
|
273
|
-
{
|
|
274
|
-
name: "Hello World",
|
|
275
|
-
slug: "hello-world",
|
|
276
|
-
source: fnSource,
|
|
277
|
-
env: { RESEND_API_KEY: "re_abc" },
|
|
278
|
-
},
|
|
279
|
-
],
|
|
280
|
-
buckets: [{ name: "uploads" }],
|
|
281
|
-
aiGateway: {},
|
|
282
|
-
},
|
|
283
|
-
}));
|
|
284
|
-
|
|
285
|
-
const result = await pushConfig(config, {
|
|
286
|
-
api,
|
|
287
|
-
projectId,
|
|
288
|
-
branchId: "br-main",
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
expect(result.applied).toEqual(
|
|
292
|
-
expect.arrayContaining([
|
|
293
|
-
expect.objectContaining({
|
|
294
|
-
kind: "service",
|
|
295
|
-
action: "create",
|
|
296
|
-
identifier: "bucket:uploads",
|
|
297
|
-
}),
|
|
298
|
-
expect.objectContaining({
|
|
299
|
-
kind: "service",
|
|
300
|
-
action: "create",
|
|
301
|
-
identifier: "function:hello-world",
|
|
302
|
-
}),
|
|
303
|
-
expect.objectContaining({
|
|
304
|
-
kind: "service",
|
|
305
|
-
action: "update",
|
|
306
|
-
identifier: "function:hello-world",
|
|
307
|
-
}),
|
|
308
|
-
expect.objectContaining({
|
|
309
|
-
kind: "service",
|
|
310
|
-
action: "create",
|
|
311
|
-
identifier: "aiGateway",
|
|
312
|
-
}),
|
|
313
|
-
]),
|
|
314
|
-
);
|
|
315
|
-
// The function exists and now has an active deployment on the branch.
|
|
316
|
-
const functions = await api.listBranchFunctions(projectId, "br-main");
|
|
317
|
-
expect(functions).toEqual([
|
|
318
|
-
expect.objectContaining({
|
|
319
|
-
slug: "hello-world",
|
|
320
|
-
activeDeploymentId: 1,
|
|
321
|
-
}),
|
|
322
|
-
]);
|
|
323
|
-
expect(await api.getAiGatewayEnabled(projectId, "br-main")).toBe(true);
|
|
324
|
-
expect(
|
|
325
|
-
(await api.listBranchBuckets(projectId, "br-main")).map(
|
|
326
|
-
(b) => b.name,
|
|
327
|
-
),
|
|
328
|
-
).toEqual(["uploads"]);
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
test("re-deploys an existing function but does not recreate it", async () => {
|
|
332
|
-
const { api, projectId } = seededFake();
|
|
333
|
-
api.seedFunction(projectId, "br-main", {
|
|
334
|
-
id: "fn-existing",
|
|
335
|
-
slug: "hello-world",
|
|
336
|
-
name: "Hello World",
|
|
337
|
-
invocationUrl: "https://x/functions/hello-world",
|
|
338
|
-
});
|
|
339
|
-
const config = defineConfig(() => ({
|
|
340
|
-
preview: {
|
|
341
|
-
functions: [
|
|
342
|
-
{
|
|
343
|
-
name: "Hello World",
|
|
344
|
-
slug: "hello-world",
|
|
345
|
-
source: fnSource,
|
|
346
|
-
},
|
|
347
|
-
],
|
|
348
|
-
},
|
|
349
|
-
}));
|
|
350
|
-
|
|
351
|
-
await pushConfig(config, { api, projectId, branchId: "br-main" });
|
|
352
|
-
|
|
353
|
-
expect(
|
|
354
|
-
api.history.filter((h) => h.method === "createBranchFunction"),
|
|
355
|
-
).toHaveLength(0);
|
|
356
|
-
expect(
|
|
357
|
-
api.history.filter((h) => h.method === "deployBranchFunction"),
|
|
358
|
-
).toHaveLength(1);
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
test("dryRun plans preview steps without mutating", async () => {
|
|
362
|
-
const { api, projectId } = seededFake();
|
|
363
|
-
const config = defineConfig(() => ({
|
|
364
|
-
preview: { buckets: [{ name: "uploads" }], aiGateway: {} },
|
|
365
|
-
}));
|
|
366
|
-
|
|
367
|
-
const result = await pushConfig(config, {
|
|
368
|
-
api,
|
|
369
|
-
projectId,
|
|
370
|
-
branchId: "br-main",
|
|
371
|
-
dryRun: true,
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
expect(result.applied).toEqual(
|
|
375
|
-
expect.arrayContaining([
|
|
376
|
-
expect.objectContaining({ identifier: "bucket:uploads" }),
|
|
377
|
-
expect.objectContaining({ identifier: "aiGateway" }),
|
|
378
|
-
]),
|
|
379
|
-
);
|
|
380
|
-
expect(api.history.some((h) => h.method === "createBranchBucket")).toBe(
|
|
381
|
-
false,
|
|
382
|
-
);
|
|
383
|
-
expect(api.history.some((h) => h.method === "enableAiGateway")).toBe(
|
|
384
|
-
false,
|
|
385
|
-
);
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
test("dryRun surfaces selected branch plan without mutating", async () => {
|
|
389
|
-
const { api, projectId } = seededFake();
|
|
390
|
-
const config = defineConfig(() => ({
|
|
391
|
-
protected: true,
|
|
392
|
-
auth: {},
|
|
393
|
-
}));
|
|
394
|
-
|
|
395
|
-
const result = await pushConfig(config, {
|
|
396
|
-
api,
|
|
397
|
-
projectId,
|
|
398
|
-
branchId: "br-main",
|
|
399
|
-
dryRun: true,
|
|
400
|
-
updateExisting: true,
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
expect(result.dryRun).toBe(true);
|
|
404
|
-
expect(result.applied).toEqual(
|
|
405
|
-
expect.arrayContaining([
|
|
406
|
-
expect.objectContaining({
|
|
407
|
-
kind: "branch",
|
|
408
|
-
action: "update",
|
|
409
|
-
identifier: "main",
|
|
410
|
-
}),
|
|
411
|
-
expect.objectContaining({
|
|
412
|
-
kind: "service",
|
|
413
|
-
identifier: "auth",
|
|
414
|
-
}),
|
|
415
|
-
]),
|
|
416
|
-
);
|
|
417
|
-
expect(api.history.some((h) => h.method === "updateBranch")).toBe(
|
|
418
|
-
false,
|
|
419
|
-
);
|
|
420
|
-
});
|
|
421
|
-
});
|