@zuplo/cli 7.9.11 → 7.9.13

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.
Files changed (100) hide show
  1. package/dist/__tests__/integration/source-upgrade.integration.test.d.ts +2 -0
  2. package/dist/__tests__/integration/source-upgrade.integration.test.d.ts.map +1 -0
  3. package/dist/__tests__/integration/source-upgrade.integration.test.js +100 -0
  4. package/dist/__tests__/integration/source-upgrade.integration.test.js.map +1 -0
  5. package/dist/cmds/source/upgrade.d.ts.map +1 -1
  6. package/dist/cmds/source/upgrade.js +45 -2
  7. package/dist/cmds/source/upgrade.js.map +1 -1
  8. package/dist/source/ai-gateway/catalog.json +411 -0
  9. package/dist/source/ai-gateway/files.d.ts +16 -0
  10. package/dist/source/ai-gateway/files.d.ts.map +1 -0
  11. package/dist/source/ai-gateway/files.js +174 -0
  12. package/dist/source/ai-gateway/files.js.map +1 -0
  13. package/dist/source/ai-gateway/files.test.d.ts +2 -0
  14. package/dist/source/ai-gateway/files.test.d.ts.map +1 -0
  15. package/dist/source/ai-gateway/files.test.js +135 -0
  16. package/dist/source/ai-gateway/files.test.js.map +1 -0
  17. package/dist/source/ai-gateway/handler.d.ts +31 -0
  18. package/dist/source/ai-gateway/handler.d.ts.map +1 -0
  19. package/dist/source/ai-gateway/handler.js +140 -0
  20. package/dist/source/ai-gateway/handler.js.map +1 -0
  21. package/dist/source/ai-gateway/handler.test.d.ts +2 -0
  22. package/dist/source/ai-gateway/handler.test.d.ts.map +1 -0
  23. package/dist/source/ai-gateway/handler.test.js +113 -0
  24. package/dist/source/ai-gateway/handler.test.js.map +1 -0
  25. package/dist/source/ai-gateway/package.test.d.ts +2 -0
  26. package/dist/source/ai-gateway/package.test.d.ts.map +1 -0
  27. package/dist/source/ai-gateway/package.test.js +30 -0
  28. package/dist/source/ai-gateway/package.test.js.map +1 -0
  29. package/dist/source/ai-gateway/planner.d.ts +132 -0
  30. package/dist/source/ai-gateway/planner.d.ts.map +1 -0
  31. package/dist/source/ai-gateway/planner.js +365 -0
  32. package/dist/source/ai-gateway/planner.js.map +1 -0
  33. package/dist/source/ai-gateway/planner.test.d.ts +2 -0
  34. package/dist/source/ai-gateway/planner.test.d.ts.map +1 -0
  35. package/dist/source/ai-gateway/planner.test.js +282 -0
  36. package/dist/source/ai-gateway/planner.test.js.map +1 -0
  37. package/dist/source/ai-gateway/release.d.ts +19 -0
  38. package/dist/source/ai-gateway/release.d.ts.map +1 -0
  39. package/dist/source/ai-gateway/release.js +75 -0
  40. package/dist/source/ai-gateway/release.js.map +1 -0
  41. package/dist/source/ai-gateway/release.test.d.ts +2 -0
  42. package/dist/source/ai-gateway/release.test.d.ts.map +1 -0
  43. package/dist/source/ai-gateway/release.test.js +62 -0
  44. package/dist/source/ai-gateway/release.test.js.map +1 -0
  45. package/dist/tsconfig.tsbuildinfo +1 -1
  46. package/node_modules/@posthog/core/dist/featureFlagUtils.d.ts.map +1 -1
  47. package/node_modules/@posthog/core/dist/featureFlagUtils.js +2 -1
  48. package/node_modules/@posthog/core/dist/featureFlagUtils.mjs +2 -1
  49. package/node_modules/@posthog/core/dist/index.d.ts +1 -0
  50. package/node_modules/@posthog/core/dist/index.d.ts.map +1 -1
  51. package/node_modules/@posthog/core/dist/index.js +66 -10
  52. package/node_modules/@posthog/core/dist/index.mjs +1 -0
  53. package/node_modules/@posthog/core/dist/privacy.d.ts +35 -0
  54. package/node_modules/@posthog/core/dist/privacy.d.ts.map +1 -0
  55. package/node_modules/@posthog/core/dist/privacy.js +366 -0
  56. package/node_modules/@posthog/core/dist/privacy.mjs +319 -0
  57. package/node_modules/@posthog/core/package.json +2 -2
  58. package/node_modules/@posthog/core/src/featureFlagUtils.ts +2 -0
  59. package/node_modules/@posthog/core/src/index.ts +1 -0
  60. package/node_modules/@posthog/core/src/privacy.ts +696 -0
  61. package/node_modules/@posthog/types/dist/index.d.ts +1 -1
  62. package/node_modules/@posthog/types/dist/index.d.ts.map +1 -1
  63. package/node_modules/@posthog/types/dist/posthog-config.d.ts +39 -8
  64. package/node_modules/@posthog/types/dist/posthog-config.d.ts.map +1 -1
  65. package/node_modules/@posthog/types/package.json +1 -1
  66. package/node_modules/@posthog/types/src/index.ts +1 -0
  67. package/node_modules/@posthog/types/src/posthog-config.ts +41 -8
  68. package/node_modules/@zuplo/core/customer.cli.minified.js +215 -215
  69. package/node_modules/@zuplo/core/index.minified.js +220 -220
  70. package/node_modules/@zuplo/core/package.json +1 -1
  71. package/node_modules/@zuplo/graphql/package.json +1 -1
  72. package/node_modules/@zuplo/openapi-tools/package.json +1 -1
  73. package/node_modules/@zuplo/otel/package.json +1 -1
  74. package/node_modules/@zuplo/runtime/out/esm/chunk-2XSPVUOE.js +411 -0
  75. package/node_modules/@zuplo/runtime/out/esm/chunk-2XSPVUOE.js.map +1 -0
  76. package/node_modules/@zuplo/runtime/out/esm/chunk-PBDT4FDO.js +37 -0
  77. package/node_modules/@zuplo/runtime/out/esm/chunk-PBDT4FDO.js.map +1 -0
  78. package/node_modules/@zuplo/runtime/out/esm/{chunk-QTXBMT6O.js → chunk-VTO3F6DK.js} +2 -2
  79. package/node_modules/@zuplo/runtime/out/esm/{chunk-7AHIER5R.js → chunk-YJ2BHVUH.js} +2 -2
  80. package/node_modules/@zuplo/runtime/out/esm/index.js +1 -1
  81. package/node_modules/@zuplo/runtime/out/esm/index.js.map +1 -1
  82. package/node_modules/@zuplo/runtime/out/esm/internal/index.js +1 -1
  83. package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js +1 -1
  84. package/node_modules/@zuplo/runtime/out/esm/mocks/index.js +1 -1
  85. package/node_modules/@zuplo/runtime/out/esm/sdks/aws/index.js +1 -1
  86. package/node_modules/@zuplo/runtime/out/types/index.d.ts +152 -54
  87. package/node_modules/@zuplo/runtime/out/types/internal/index.d.ts +32 -0
  88. package/node_modules/@zuplo/runtime/package.json +1 -1
  89. package/node_modules/express-rate-limit/dist/index.cjs +9 -7
  90. package/node_modules/express-rate-limit/dist/index.mjs +9 -7
  91. package/node_modules/express-rate-limit/package.json +9 -9
  92. package/package.json +11 -11
  93. package/node_modules/@zuplo/runtime/out/esm/chunk-DDUIJEX3.js +0 -411
  94. package/node_modules/@zuplo/runtime/out/esm/chunk-DDUIJEX3.js.map +0 -1
  95. package/node_modules/@zuplo/runtime/out/esm/chunk-KESHY2P3.js +0 -37
  96. package/node_modules/@zuplo/runtime/out/esm/chunk-KESHY2P3.js.map +0 -1
  97. /package/node_modules/@zuplo/runtime/out/esm/{chunk-DDUIJEX3.js.LEGAL.txt → chunk-2XSPVUOE.js.LEGAL.txt} +0 -0
  98. /package/node_modules/@zuplo/runtime/out/esm/{chunk-KESHY2P3.js.LEGAL.txt → chunk-PBDT4FDO.js.LEGAL.txt} +0 -0
  99. /package/node_modules/@zuplo/runtime/out/esm/{chunk-QTXBMT6O.js.map → chunk-VTO3F6DK.js.map} +0 -0
  100. /package/node_modules/@zuplo/runtime/out/esm/{chunk-7AHIER5R.js.map → chunk-YJ2BHVUH.js.map} +0 -0
@@ -0,0 +1,282 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { AI_OAS, catalog, POLICIES, PROVENANCE, planUpgrade, readJson, } from "./planner.js";
4
+ const oldest = catalog.snapshots[1];
5
+ const original = catalog.snapshots[2];
6
+ const latest = catalog.snapshots.find((s) => s.revision === catalog.target);
7
+ const files = () => ({ ...original.files });
8
+ function change(file, mutate) {
9
+ const input = files();
10
+ const value = JSON.parse(input[file]);
11
+ mutate(value);
12
+ input[file] = JSON.stringify(value, null, 2);
13
+ return input;
14
+ }
15
+ function blocked(input, expected) {
16
+ const before = structuredClone(input);
17
+ const result = planUpgrade(input, "1.0.0");
18
+ assert.match(result.blockers.join("\n"), expected);
19
+ assert.deepEqual(result.after, {});
20
+ assert.deepEqual(input, before);
21
+ }
22
+ describe("conservative versioned gateway planner", () => {
23
+ it("upgrades several historical snapshots without changing existing definitions", () => {
24
+ for (const snapshot of catalog.snapshots.slice(0, 4)) {
25
+ const result = planUpgrade(snapshot.files, "1.0.0");
26
+ assert.deepEqual(result.blockers, []);
27
+ assert.ok(result.additions.length);
28
+ const before = JSON.parse(snapshot.files[AI_OAS]);
29
+ const after = JSON.parse(result.after[AI_OAS] ?? snapshot.files[AI_OAS]);
30
+ for (const [key, value] of Object.entries(before.paths))
31
+ assert.deepEqual(after.paths[key], value);
32
+ assert.deepEqual(Object.keys(after.paths), [
33
+ "/u/:app_id/(.*)",
34
+ "/a/:app_id/(.*)",
35
+ "/:app_id/(.*)",
36
+ ]);
37
+ const oldPolicies = JSON.parse(snapshot.files[POLICIES]).policies;
38
+ const newPolicies = JSON.parse(result.after[POLICIES] ?? snapshot.files[POLICIES]).policies;
39
+ assert.deepEqual(newPolicies.slice(0, oldPolicies.length), oldPolicies);
40
+ const again = planUpgrade({ ...snapshot.files, ...result.after }, "1.0.0");
41
+ assert.deepEqual(again.blockers, []);
42
+ assert.deepEqual(again.additions, []);
43
+ assert.deepEqual(again.after, {});
44
+ }
45
+ });
46
+ it("preserves the original loader defaults and rejects customized historical loaders", () => {
47
+ const historical = catalog.snapshots[0];
48
+ const input = { ...historical.files };
49
+ const result = planUpgrade(input, "1.0.0");
50
+ assert.deepEqual(result.blockers, []);
51
+ const loader = (raw) => JSON.parse(raw).policies.find((p) => p.name === "ai-gateway-configuration-loader-v2-inbound");
52
+ assert.deepEqual(loader(result.after[POLICIES]).handler.options, {});
53
+ assert.deepEqual(loader(result.after[POLICIES]), loader(input[POLICIES]));
54
+ const policies = JSON.parse(input[POLICIES]);
55
+ policies.policies.find((p) => p.name === "ai-gateway-configuration-loader-v2-inbound").handler.options.cacheTtlSeconds = 45;
56
+ input[POLICIES] = JSON.stringify(policies);
57
+ blocked(input, /required policy.*customized/);
58
+ });
59
+ it("preserves the previous template after the Internal Only rename", () => {
60
+ const previous = catalog.snapshots[4];
61
+ const result = planUpgrade(previous.files, "1.0.0");
62
+ assert.deepEqual(result.blockers, []);
63
+ assert.deepEqual(result.additions, []);
64
+ assert.deepEqual(result.after, {});
65
+ const historical = planUpgrade(files(), "1.0.0");
66
+ assert.deepEqual(historical.blockers, []);
67
+ assert.ok(!historical.additions.some((a) => a.key === "internal-only-inbound"));
68
+ });
69
+ it("does not restore a deliberately removed Internal Only policy under its new name", () => {
70
+ const previous = catalog.snapshots[4];
71
+ const input = {
72
+ ...previous.files,
73
+ [PROVENANCE]: JSON.stringify({
74
+ formatVersion: 1,
75
+ template: "ai-gateway",
76
+ origin: { revision: previous.revision, release: "1.0.0" },
77
+ appliedSteps: [],
78
+ }),
79
+ };
80
+ const policies = JSON.parse(input[POLICIES]);
81
+ policies.policies = policies.policies.filter((p) => p.name !== "ai-gateway-internal-only-inbound");
82
+ input[POLICIES] = JSON.stringify(policies);
83
+ blocked(input, /internal-only-inbound.*previously present/);
84
+ });
85
+ it("reuses historical dependency names rather than adding aliases", () => {
86
+ const result = planUpgrade(files(), "1.0.0");
87
+ const after = JSON.parse(result.after[AI_OAS]);
88
+ const inbound = after.paths["/u/:app_id/(.*)"]["get,post,put,patch,delete,head"]["x-zuplo-route"].policies.inbound;
89
+ assert.deepEqual(inbound, [
90
+ "ai-gateway-configuration-loader-v2-inbound",
91
+ "ai-gateway-configuration-executor-v2-inbound",
92
+ ]);
93
+ assert.equal(result.additions.filter((a) => a.key.includes("configuration-loader"))
94
+ .length, 0);
95
+ });
96
+ it("does not modify an up-to-date project's bytes or create provenance", () => {
97
+ assert.deepEqual(planUpgrade(latest.files, "1.0.0").after, {});
98
+ });
99
+ it("preserves unrelated policy text and whitespace", () => {
100
+ const input = files();
101
+ assert.ok(input[POLICIES].startsWith("{"));
102
+ input[POLICIES] = `{\n\n${input[POLICIES].slice(1)}`;
103
+ const result = planUpgrade(input, "1.0.0");
104
+ assert.deepEqual(result.blockers, []);
105
+ assert.ok(result.after[POLICIES].startsWith("{\n\n"));
106
+ });
107
+ it("blocks customized catch-all authentication", () => {
108
+ blocked(change(AI_OAS, (v) => v.paths["/:app_id/(.*)"]["get,post,put,patch,delete,head"]["x-zuplo-route"].policies.inbound.unshift("my-auth")), /custom authentication/);
109
+ });
110
+ it("blocks reordered prerequisite chains", () => {
111
+ blocked(change(AI_OAS, (v) => v.paths["/:app_id/(.*)"]["get,post,put,patch,delete,head"]["x-zuplo-route"].policies.inbound.reverse()), /supported template/);
112
+ });
113
+ it("blocks customized required policies", () => {
114
+ blocked(change(POLICIES, (v) => {
115
+ v.policies.find((p) => p.name.includes("configuration-loader")).handler.options.cacheTtlSeconds = 45;
116
+ }), /required policy.*customized/);
117
+ });
118
+ it("blocks ambiguous canonical policy aliases", () => {
119
+ blocked(change(POLICIES, (v) => v.policies.push({
120
+ ...v.policies.find((p) => p.name.includes("configuration-loader")),
121
+ name: "ai-gateway-configuration-loader-inbound",
122
+ })), /duplicate policy identity/);
123
+ });
124
+ it("blocks cross-file overlapping and unsupported routes but allows disjoint literal prefixes", () => {
125
+ for (const key of ["/u/custom", "/:prefix/foo", "/(.*)"]) {
126
+ blocked({
127
+ ...files(),
128
+ "config/custom.oas.json": JSON.stringify({
129
+ openapi: "3.1.0",
130
+ paths: { [key]: { get: { operationId: "custom" } } },
131
+ }),
132
+ }, /cannot prove route/);
133
+ }
134
+ const result = planUpgrade({
135
+ ...files(),
136
+ "config/custom.oas.json": JSON.stringify({
137
+ openapi: "3.1.0",
138
+ paths: { "/health": { get: { operationId: "health" } } },
139
+ }),
140
+ }, "1.0.0");
141
+ assert.deepEqual(result.blockers, []);
142
+ });
143
+ for (const method of ["get", "GET,POST", "Get,pOsT", " GET, POST "]) {
144
+ it(`blocks duplicate operation IDs across files for ${method}`, () => {
145
+ blocked({
146
+ ...files(),
147
+ "config/custom.oas.json": JSON.stringify({
148
+ openapi: "3.1.0",
149
+ paths: {
150
+ "/health": {
151
+ [method]: { operationId: "aiGatewayUserAppCatchAll" },
152
+ },
153
+ },
154
+ }),
155
+ }, /operationId.*already in use/);
156
+ });
157
+ }
158
+ it("blocks custom runtime extensions and document-level security", () => {
159
+ blocked({
160
+ ...files(),
161
+ "modules/zuplo.runtime.ts": "export function runtimeInit(runtime) { runtime.addPlugin(custom); }",
162
+ }, /custom runtime/);
163
+ blocked(change(AI_OAS, (v) => {
164
+ v.security = [{ apiKey: [] }];
165
+ }), /document-level/);
166
+ });
167
+ it("blocks malformed JSON and duplicate keys, retaining manual snippets", () => {
168
+ for (const text of ['{"paths":', '{"policies": [], "policies": []}']) {
169
+ const result = planUpgrade({ ...files(), [POLICIES]: text }, "1.0.0");
170
+ assert.ok(result.blockers.length);
171
+ assert.ok(result.additions.length);
172
+ assert.deepEqual(result.after, {});
173
+ }
174
+ assert.throws(() => readJson('{"a":{"b":1,"b":2}}', "x"), /duplicate/);
175
+ });
176
+ it("refuses to silently restore deliberate deletion using origin or applied steps", () => {
177
+ const first = planUpgrade(files(), "1.0.0");
178
+ const input = { ...files(), ...first.after };
179
+ const oas = JSON.parse(input[AI_OAS]);
180
+ delete oas.paths["/u/:app_id/(.*)"];
181
+ input[AI_OAS] = JSON.stringify(oas);
182
+ blocked(input, /previously present/);
183
+ const known = {
184
+ ...latest.files,
185
+ [AI_OAS]: JSON.stringify(oas),
186
+ [PROVENANCE]: JSON.stringify({
187
+ formatVersion: 1,
188
+ template: "ai-gateway",
189
+ origin: { revision: latest.revision, release: "1.0.0" },
190
+ appliedSteps: [],
191
+ }),
192
+ };
193
+ blocked(known, /previously present/);
194
+ });
195
+ it("never invents a whole-project origin for legacy projects", () => {
196
+ const result = planUpgrade(files(), "1.0.0");
197
+ assert.equal(JSON.parse(result.after[PROVENANCE]).origin, null);
198
+ });
199
+ it("fails closed for unsupported metadata formats and missing snapshots", () => {
200
+ blocked({ ...files(), [PROVENANCE]: '{"formatVersion":999}' }, /formatVersion/);
201
+ blocked({
202
+ ...files(),
203
+ [PROVENANCE]: JSON.stringify({
204
+ formatVersion: 1,
205
+ template: "ai-gateway",
206
+ origin: { revision: "unknown", release: "1.0.0" },
207
+ appliedSteps: [],
208
+ }),
209
+ }, /originating snapshot/);
210
+ });
211
+ it("supports a future assessed release, including upgrading across skipped releases", () => {
212
+ const future = structuredClone(catalog);
213
+ const snapshot = structuredClone(latest);
214
+ snapshot.revision = "future-test-revision";
215
+ const policyFile = JSON.parse(snapshot.files[POLICIES]);
216
+ const policy = {
217
+ name: "future-policy",
218
+ handler: {
219
+ module: "$import(@zuplo/runtime)",
220
+ export: "FuturePolicy",
221
+ options: {},
222
+ },
223
+ };
224
+ policyFile.policies.push(policy);
225
+ snapshot.files[POLICIES] = JSON.stringify(policyFile);
226
+ future.snapshots.push(snapshot);
227
+ future.target = snapshot.revision;
228
+ future.steps.push(...catalog.steps
229
+ .filter((s) => s.target === catalog.target)
230
+ .map((s) => ({
231
+ ...s,
232
+ id: `next:${s.id}`,
233
+ target: snapshot.revision,
234
+ })), {
235
+ id: "next:future",
236
+ target: snapshot.revision,
237
+ kind: "policy",
238
+ key: policy.name,
239
+ runtime: "release",
240
+ instructions: "Add the future declaration.",
241
+ });
242
+ future.assessments.push({
243
+ revision: snapshot.revision,
244
+ classification: "additions",
245
+ instructions: "New declaration only.",
246
+ });
247
+ const direct = planUpgrade(oldest.files, "2.0.0", future);
248
+ assert.deepEqual(direct.blockers, []);
249
+ assert.ok(direct.additions.some((a) => a.key === policy.name));
250
+ const first = planUpgrade(original.files, "1.0.0");
251
+ const next = planUpgrade({ ...original.files, ...first.after }, "2.0.0", future);
252
+ assert.deepEqual(next.blockers, []);
253
+ assert.deepEqual(next.additions.map((a) => a.key), [policy.name]);
254
+ });
255
+ it("cannot bypass a required manual migration by skipping a release", () => {
256
+ const future = structuredClone(catalog);
257
+ future.assessments.unshift({
258
+ revision: catalog.snapshots[3].revision,
259
+ classification: "manual",
260
+ instructions: "Required manual authentication migration.",
261
+ });
262
+ const result = planUpgrade(original.files, "2.0.0", future);
263
+ assert.match(result.blockers.join("\n"), /manual authentication migration/);
264
+ assert.deepEqual(result.after, {});
265
+ });
266
+ it("checks executor prerequisites for policy-only additions", () => {
267
+ const input = { ...latest.files };
268
+ const policies = JSON.parse(input[POLICIES]);
269
+ policies.policies = policies.policies.filter((p) => p.name !== "ai-gateway-smart-router-inbound");
270
+ policies.policies.find((p) => p.name.includes("configuration-executor")).handler.export = "CustomExecutor";
271
+ input[POLICIES] = JSON.stringify(policies);
272
+ blocked(input, /required policy.*customized/);
273
+ });
274
+ it("blocks a future release classified as requiring manual changes", () => {
275
+ const future = structuredClone(catalog);
276
+ future.assessments[0].classification = "manual";
277
+ const result = planUpgrade(files(), "2.0.0", future);
278
+ assert.ok(result.blockers.length);
279
+ assert.deepEqual(result.after, {});
280
+ });
281
+ });
282
+ //# sourceMappingURL=planner.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planner.test.js","sourceRoot":"","sources":["../../../src/source/ai-gateway/planner.test.ts"],"names":[],"mappings":"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","sourcesContent":["// biome-ignore-all lint/suspicious/noExplicitAny: Tests deliberately mutate fixture configuration shapes.\nimport assert from \"node:assert/strict\";\nimport { describe, it } from \"node:test\";\nimport {\n AI_OAS,\n catalog,\n POLICIES,\n PROVENANCE,\n planUpgrade,\n readJson,\n} from \"./planner.js\";\n\nconst oldest = catalog.snapshots[1];\nconst original = catalog.snapshots[2];\nconst latest = catalog.snapshots.find((s) => s.revision === catalog.target)!;\nconst files = () => ({ ...original.files });\nfunction change(file: string, mutate: (value: any) => void) {\n const input = files();\n const value = JSON.parse(input[file]);\n mutate(value);\n input[file] = JSON.stringify(value, null, 2);\n return input;\n}\nfunction blocked(input: Record<string, string>, expected: RegExp) {\n const before = structuredClone(input);\n const result = planUpgrade(input, \"1.0.0\");\n assert.match(result.blockers.join(\"\\n\"), expected);\n assert.deepEqual(result.after, {});\n assert.deepEqual(input, before);\n}\n\ndescribe(\"conservative versioned gateway planner\", () => {\n it(\"upgrades several historical snapshots without changing existing definitions\", () => {\n for (const snapshot of catalog.snapshots.slice(0, 4)) {\n const result = planUpgrade(snapshot.files, \"1.0.0\");\n assert.deepEqual(result.blockers, []);\n assert.ok(result.additions.length);\n const before = JSON.parse(snapshot.files[AI_OAS]);\n const after = JSON.parse(result.after[AI_OAS] ?? snapshot.files[AI_OAS]);\n for (const [key, value] of Object.entries(before.paths))\n assert.deepEqual(after.paths[key], value);\n assert.deepEqual(Object.keys(after.paths), [\n \"/u/:app_id/(.*)\",\n \"/a/:app_id/(.*)\",\n \"/:app_id/(.*)\",\n ]);\n const oldPolicies = JSON.parse(snapshot.files[POLICIES]).policies;\n const newPolicies = JSON.parse(\n result.after[POLICIES] ?? snapshot.files[POLICIES]\n ).policies;\n assert.deepEqual(newPolicies.slice(0, oldPolicies.length), oldPolicies);\n const again = planUpgrade(\n { ...snapshot.files, ...result.after },\n \"1.0.0\"\n );\n assert.deepEqual(again.blockers, []);\n assert.deepEqual(again.additions, []);\n assert.deepEqual(again.after, {});\n }\n });\n it(\"preserves the original loader defaults and rejects customized historical loaders\", () => {\n const historical = catalog.snapshots[0];\n const input = { ...historical.files };\n const result = planUpgrade(input, \"1.0.0\");\n assert.deepEqual(result.blockers, []);\n const loader = (raw: string) =>\n JSON.parse(raw).policies.find(\n (p: any) => p.name === \"ai-gateway-configuration-loader-v2-inbound\"\n );\n assert.deepEqual(loader(result.after[POLICIES]).handler.options, {});\n assert.deepEqual(loader(result.after[POLICIES]), loader(input[POLICIES]));\n const policies = JSON.parse(input[POLICIES]);\n policies.policies.find(\n (p: any) => p.name === \"ai-gateway-configuration-loader-v2-inbound\"\n ).handler.options.cacheTtlSeconds = 45;\n input[POLICIES] = JSON.stringify(policies);\n blocked(input, /required policy.*customized/);\n });\n it(\"preserves the previous template after the Internal Only rename\", () => {\n const previous = catalog.snapshots[4];\n const result = planUpgrade(previous.files, \"1.0.0\");\n assert.deepEqual(result.blockers, []);\n assert.deepEqual(result.additions, []);\n assert.deepEqual(result.after, {});\n const historical = planUpgrade(files(), \"1.0.0\");\n assert.deepEqual(historical.blockers, []);\n assert.ok(\n !historical.additions.some((a) => a.key === \"internal-only-inbound\")\n );\n });\n it(\"does not restore a deliberately removed Internal Only policy under its new name\", () => {\n const previous = catalog.snapshots[4];\n const input: Record<string, string> = {\n ...previous.files,\n [PROVENANCE]: JSON.stringify({\n formatVersion: 1,\n template: \"ai-gateway\",\n origin: { revision: previous.revision, release: \"1.0.0\" },\n appliedSteps: [],\n }),\n };\n const policies = JSON.parse(input[POLICIES]);\n policies.policies = policies.policies.filter(\n (p: any) => p.name !== \"ai-gateway-internal-only-inbound\"\n );\n input[POLICIES] = JSON.stringify(policies);\n blocked(input, /internal-only-inbound.*previously present/);\n });\n it(\"reuses historical dependency names rather than adding aliases\", () => {\n const result = planUpgrade(files(), \"1.0.0\");\n const after = JSON.parse(result.after[AI_OAS]);\n const inbound =\n after.paths[\"/u/:app_id/(.*)\"][\"get,post,put,patch,delete,head\"][\n \"x-zuplo-route\"\n ].policies.inbound;\n assert.deepEqual(inbound, [\n \"ai-gateway-configuration-loader-v2-inbound\",\n \"ai-gateway-configuration-executor-v2-inbound\",\n ]);\n assert.equal(\n result.additions.filter((a) => a.key.includes(\"configuration-loader\"))\n .length,\n 0\n );\n });\n it(\"does not modify an up-to-date project's bytes or create provenance\", () => {\n assert.deepEqual(planUpgrade(latest.files, \"1.0.0\").after, {});\n });\n it(\"preserves unrelated policy text and whitespace\", () => {\n const input = files();\n assert.ok(input[POLICIES].startsWith(\"{\"));\n input[POLICIES] = `{\\n\\n${input[POLICIES].slice(1)}`;\n const result = planUpgrade(input, \"1.0.0\");\n assert.deepEqual(result.blockers, []);\n assert.ok(result.after[POLICIES].startsWith(\"{\\n\\n\"));\n });\n it(\"blocks customized catch-all authentication\", () => {\n blocked(\n change(AI_OAS, (v) =>\n v.paths[\"/:app_id/(.*)\"][\"get,post,put,patch,delete,head\"][\n \"x-zuplo-route\"\n ].policies.inbound.unshift(\"my-auth\")\n ),\n /custom authentication/\n );\n });\n it(\"blocks reordered prerequisite chains\", () => {\n blocked(\n change(AI_OAS, (v) =>\n v.paths[\"/:app_id/(.*)\"][\"get,post,put,patch,delete,head\"][\n \"x-zuplo-route\"\n ].policies.inbound.reverse()\n ),\n /supported template/\n );\n });\n it(\"blocks customized required policies\", () => {\n blocked(\n change(POLICIES, (v) => {\n v.policies.find((p: any) =>\n p.name.includes(\"configuration-loader\")\n ).handler.options.cacheTtlSeconds = 45;\n }),\n /required policy.*customized/\n );\n });\n it(\"blocks ambiguous canonical policy aliases\", () => {\n blocked(\n change(POLICIES, (v) =>\n v.policies.push({\n ...v.policies.find((p: any) =>\n p.name.includes(\"configuration-loader\")\n ),\n name: \"ai-gateway-configuration-loader-inbound\",\n })\n ),\n /duplicate policy identity/\n );\n });\n it(\"blocks cross-file overlapping and unsupported routes but allows disjoint literal prefixes\", () => {\n for (const key of [\"/u/custom\", \"/:prefix/foo\", \"/(.*)\"]) {\n blocked(\n {\n ...files(),\n \"config/custom.oas.json\": JSON.stringify({\n openapi: \"3.1.0\",\n paths: { [key]: { get: { operationId: \"custom\" } } },\n }),\n },\n /cannot prove route/\n );\n }\n const result = planUpgrade(\n {\n ...files(),\n \"config/custom.oas.json\": JSON.stringify({\n openapi: \"3.1.0\",\n paths: { \"/health\": { get: { operationId: \"health\" } } },\n }),\n },\n \"1.0.0\"\n );\n assert.deepEqual(result.blockers, []);\n });\n for (const method of [\"get\", \"GET,POST\", \"Get,pOsT\", \" GET, POST \"]) {\n it(`blocks duplicate operation IDs across files for ${method}`, () => {\n blocked(\n {\n ...files(),\n \"config/custom.oas.json\": JSON.stringify({\n openapi: \"3.1.0\",\n paths: {\n \"/health\": {\n [method]: { operationId: \"aiGatewayUserAppCatchAll\" },\n },\n },\n }),\n },\n /operationId.*already in use/\n );\n });\n }\n it(\"blocks custom runtime extensions and document-level security\", () => {\n blocked(\n {\n ...files(),\n \"modules/zuplo.runtime.ts\":\n \"export function runtimeInit(runtime) { runtime.addPlugin(custom); }\",\n },\n /custom runtime/\n );\n blocked(\n change(AI_OAS, (v) => {\n v.security = [{ apiKey: [] }];\n }),\n /document-level/\n );\n });\n it(\"blocks malformed JSON and duplicate keys, retaining manual snippets\", () => {\n for (const text of ['{\"paths\":', '{\"policies\": [], \"policies\": []}']) {\n const result = planUpgrade({ ...files(), [POLICIES]: text }, \"1.0.0\");\n assert.ok(result.blockers.length);\n assert.ok(result.additions.length);\n assert.deepEqual(result.after, {});\n }\n assert.throws(() => readJson('{\"a\":{\"b\":1,\"b\":2}}', \"x\"), /duplicate/);\n });\n it(\"refuses to silently restore deliberate deletion using origin or applied steps\", () => {\n const first = planUpgrade(files(), \"1.0.0\");\n const input = { ...files(), ...first.after };\n const oas = JSON.parse(input[AI_OAS]);\n delete oas.paths[\"/u/:app_id/(.*)\"];\n input[AI_OAS] = JSON.stringify(oas);\n blocked(input, /previously present/);\n const known = {\n ...latest.files,\n [AI_OAS]: JSON.stringify(oas),\n [PROVENANCE]: JSON.stringify({\n formatVersion: 1,\n template: \"ai-gateway\",\n origin: { revision: latest.revision, release: \"1.0.0\" },\n appliedSteps: [],\n }),\n };\n blocked(known, /previously present/);\n });\n it(\"never invents a whole-project origin for legacy projects\", () => {\n const result = planUpgrade(files(), \"1.0.0\");\n assert.equal(JSON.parse(result.after[PROVENANCE]).origin, null);\n });\n it(\"fails closed for unsupported metadata formats and missing snapshots\", () => {\n blocked(\n { ...files(), [PROVENANCE]: '{\"formatVersion\":999}' },\n /formatVersion/\n );\n blocked(\n {\n ...files(),\n [PROVENANCE]: JSON.stringify({\n formatVersion: 1,\n template: \"ai-gateway\",\n origin: { revision: \"unknown\", release: \"1.0.0\" },\n appliedSteps: [],\n }),\n },\n /originating snapshot/\n );\n });\n it(\"supports a future assessed release, including upgrading across skipped releases\", () => {\n const future = structuredClone(catalog);\n const snapshot = structuredClone(latest);\n snapshot.revision = \"future-test-revision\";\n const policyFile = JSON.parse(snapshot.files[POLICIES]);\n const policy = {\n name: \"future-policy\",\n handler: {\n module: \"$import(@zuplo/runtime)\",\n export: \"FuturePolicy\",\n options: {},\n },\n };\n policyFile.policies.push(policy);\n snapshot.files[POLICIES] = JSON.stringify(policyFile);\n future.snapshots.push(snapshot);\n future.target = snapshot.revision;\n future.steps.push(\n ...catalog.steps\n .filter((s) => s.target === catalog.target)\n .map((s) => ({\n ...s,\n id: `next:${s.id}`,\n target: snapshot.revision,\n })),\n {\n id: \"next:future\",\n target: snapshot.revision,\n kind: \"policy\",\n key: policy.name,\n runtime: \"release\",\n instructions: \"Add the future declaration.\",\n }\n );\n future.assessments.push({\n revision: snapshot.revision,\n classification: \"additions\",\n instructions: \"New declaration only.\",\n });\n const direct = planUpgrade(oldest.files, \"2.0.0\", future);\n assert.deepEqual(direct.blockers, []);\n assert.ok(direct.additions.some((a) => a.key === policy.name));\n const first = planUpgrade(original.files, \"1.0.0\");\n const next = planUpgrade(\n { ...original.files, ...first.after },\n \"2.0.0\",\n future\n );\n assert.deepEqual(next.blockers, []);\n assert.deepEqual(\n next.additions.map((a) => a.key),\n [policy.name]\n );\n });\n it(\"cannot bypass a required manual migration by skipping a release\", () => {\n const future = structuredClone(catalog);\n future.assessments.unshift({\n revision: catalog.snapshots[3].revision,\n classification: \"manual\",\n instructions: \"Required manual authentication migration.\",\n });\n const result = planUpgrade(original.files, \"2.0.0\", future);\n assert.match(result.blockers.join(\"\\n\"), /manual authentication migration/);\n assert.deepEqual(result.after, {});\n });\n\n it(\"checks executor prerequisites for policy-only additions\", () => {\n const input = { ...latest.files };\n const policies = JSON.parse(input[POLICIES]);\n policies.policies = policies.policies.filter(\n (p: any) => p.name !== \"ai-gateway-smart-router-inbound\"\n );\n policies.policies.find((p: any) =>\n p.name.includes(\"configuration-executor\")\n ).handler.export = \"CustomExecutor\";\n input[POLICIES] = JSON.stringify(policies);\n blocked(input, /required policy.*customized/);\n });\n\n it(\"blocks a future release classified as requiring manual changes\", () => {\n const future = structuredClone(catalog);\n future.assessments[0].classification = \"manual\";\n const result = planUpgrade(files(), \"2.0.0\", future);\n assert.ok(result.blockers.length);\n assert.deepEqual(result.after, {});\n });\n});\n"]}
@@ -0,0 +1,19 @@
1
+ import { type UpgradePlan } from "./planner.js";
2
+ export interface ReleaseCheck {
3
+ latest?: string;
4
+ blocker?: string;
5
+ }
6
+ export declare function checkRelease(
7
+ version: string,
8
+ fetcher?: typeof fetch
9
+ ): Promise<ReleaseCheck>;
10
+ export declare function installedRuntime(dir: string): Promise<{
11
+ version: string;
12
+ manifest: string;
13
+ contents: string;
14
+ }>;
15
+ export declare function runtimeBlocker(
16
+ version: string,
17
+ plan: UpgradePlan
18
+ ): string | undefined;
19
+ //# sourceMappingURL=release.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release.d.ts","sourceRoot":"","sources":["../../../src/source/ai-gateway/release.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,YAAY,CAAC,CAkCvB;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA8BlE;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,WAAW,GAChB,MAAM,GAAG,SAAS,CAkBpB"}
@@ -0,0 +1,75 @@
1
+ import { access, readFile, realpath } from "node:fs/promises";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { prerelease, satisfies, valid } from "semver";
5
+ import { z } from "zod";
6
+ export async function checkRelease(version, fetcher = fetch) {
7
+ try {
8
+ const versions = await Promise.all(["@zuplo/cli", "create-zuplo-api", "zuplo"].map(async (name) => {
9
+ const response = await fetcher(`https://registry.npmjs.org/${name}/latest`, { signal: AbortSignal.timeout(5000) });
10
+ if (!response.ok)
11
+ throw new Error(`npm returned HTTP ${response.status}`);
12
+ const manifest = z
13
+ .object({ version: z.string() })
14
+ .parse(await response.json());
15
+ if (!valid(manifest.version) || prerelease(manifest.version))
16
+ throw new Error("npm latest is not a stable release");
17
+ return manifest.version;
18
+ }));
19
+ if (new Set(versions).size !== 1)
20
+ return {
21
+ blocker: "The CLI, scaffolder, and zuplo release pointers are inconsistent. Retry after publishing completes.",
22
+ };
23
+ if (versions[0] !== version)
24
+ return {
25
+ latest: versions[0],
26
+ blocker: `This CLI targets ${version}; published stable is ${versions[0]}. Install zuplo@${versions[0]} before applying.`,
27
+ };
28
+ return { latest: versions[0] };
29
+ }
30
+ catch (error) {
31
+ return {
32
+ blocker: `Published release could not be verified (${error instanceof Error ? error.message : String(error)}). Preview uses the bundled target; retry online before applying.`,
33
+ };
34
+ }
35
+ }
36
+ export async function installedRuntime(dir) {
37
+ const projectRequire = createRequire(path.join(dir, "package.json"));
38
+ const zuplo = projectRequire.resolve("zuplo/package.json");
39
+ const runtimeRequire = createRequire(zuplo);
40
+ for (const base of runtimeRequire.resolve.paths("@zuplo/runtime") ?? []) {
41
+ const candidate = path.join(base, "@zuplo/runtime/package.json");
42
+ let manifest;
43
+ try {
44
+ manifest = await realpath(candidate);
45
+ }
46
+ catch (error) {
47
+ if (error.code === "ENOENT")
48
+ continue;
49
+ throw error;
50
+ }
51
+ const contents = await readFile(manifest, "utf8");
52
+ const pkg = z
53
+ .object({
54
+ name: z.literal("@zuplo/runtime"),
55
+ version: z.string(),
56
+ exports: z.object({ ".": z.object({ import: z.string() }) }),
57
+ })
58
+ .parse(JSON.parse(contents));
59
+ await access(path.resolve(path.dirname(manifest), pkg.exports["."].import));
60
+ return { version: pkg.version, manifest, contents };
61
+ }
62
+ throw new Error("Could not locate the project's installed @zuplo/runtime. Install project dependencies first.");
63
+ }
64
+ export function runtimeBlocker(version, plan) {
65
+ if (!valid(version) ||
66
+ prerelease(version) ||
67
+ !valid(plan.release) ||
68
+ plan.release === "0.0.0")
69
+ return "A published stable project runtime and CLI are required for automatic application.";
70
+ const failed = plan.additions.filter((a) => !satisfies(version, a.step.runtime === "release" ? `>=${plan.release}` : a.step.runtime));
71
+ if (failed.length)
72
+ return `Installed runtime ${version} does not meet ${failed.map((a) => `${a.step.id}: ${a.step.runtime === "release" ? `>=${plan.release}` : a.step.runtime}`).join(", ")}. Upgrade dependencies separately and preview again.`;
73
+ return undefined;
74
+ }
75
+ //# sourceMappingURL=release.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release.js","sourceRoot":"","sources":["../../../src/source/ai-gateway/release.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAe,EACf,UAAwB,KAAK;IAE7B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,CAAC,YAAY,EAAE,kBAAkB,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;YAC7D,MAAM,QAAQ,GAAG,MAAM,OAAO,CAC5B,8BAA8B,IAAI,SAAS,EAC3C,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CACtC,CAAC;YACF,IAAI,CAAC,QAAQ,CAAC,EAAE;gBACd,MAAM,IAAI,KAAK,CAAC,qBAAqB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YAC1D,MAAM,QAAQ,GAAG,CAAC;iBACf,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;iBAC/B,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;YAChC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAC1D,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;YACxD,OAAO,QAAQ,CAAC,OAAO,CAAC;QAC1B,CAAC,CAAC,CACH,CAAC;QACF,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,KAAK,CAAC;YAC9B,OAAO;gBACL,OAAO,EACL,qGAAqG;aACxG,CAAC;QACJ,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,OAAO;YACzB,OAAO;gBACL,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;gBACnB,OAAO,EAAE,oBAAoB,OAAO,yBAAyB,QAAQ,CAAC,CAAC,CAAC,mBAAmB,QAAQ,CAAC,CAAC,CAAC,mBAAmB;aAC1H,CAAC;QACJ,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,4CAA4C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,mEAAmE;SAC/K,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAW;IAEX,MAAM,cAAc,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC;IAErE,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC3D,MAAM,cAAc,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAG5C,KAAK,MAAM,IAAI,IAAI,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,EAAE,EAAE,CAAC;QACxE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,6BAA6B,CAAC,CAAC;QACjE,IAAI,QAAgB,CAAC;QACrB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,SAAS;YACjE,MAAM,KAAK,CAAC;QACd,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,CAAC;aACV,MAAM,CAAC;YACN,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC;YACjC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;YACnB,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;SAC7D,CAAC;aACD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC/B,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAC5E,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACtD,CAAC;IACD,MAAM,IAAI,KAAK,CACb,8FAA8F,CAC/F,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,OAAe,EACf,IAAiB;IAEjB,IACE,CAAC,KAAK,CAAC,OAAO,CAAC;QACf,UAAU,CAAC,OAAO,CAAC;QACnB,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC;QACpB,IAAI,CAAC,OAAO,KAAK,OAAO;QAExB,OAAO,oFAAoF,CAAC;IAC9F,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAClC,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,SAAS,CACR,OAAO,EACP,CAAC,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CACpE,CACJ,CAAC;IACF,IAAI,MAAM,CAAC,MAAM;QACf,OAAO,qBAAqB,OAAO,kBAAkB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,sDAAsD,CAAC;IAClO,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["import { access, readFile, realpath } from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport path from \"node:path\";\nimport { prerelease, satisfies, valid } from \"semver\";\nimport { z } from \"zod\";\nimport { type UpgradePlan } from \"./planner.js\";\n\nexport interface ReleaseCheck {\n latest?: string;\n blocker?: string;\n}\nexport async function checkRelease(\n version: string,\n fetcher: typeof fetch = fetch\n): Promise<ReleaseCheck> {\n try {\n const versions = await Promise.all(\n [\"@zuplo/cli\", \"create-zuplo-api\", \"zuplo\"].map(async (name) => {\n const response = await fetcher(\n `https://registry.npmjs.org/${name}/latest`,\n { signal: AbortSignal.timeout(5000) }\n );\n if (!response.ok)\n throw new Error(`npm returned HTTP ${response.status}`);\n const manifest = z\n .object({ version: z.string() })\n .parse(await response.json());\n if (!valid(manifest.version) || prerelease(manifest.version))\n throw new Error(\"npm latest is not a stable release\");\n return manifest.version;\n })\n );\n if (new Set(versions).size !== 1)\n return {\n blocker:\n \"The CLI, scaffolder, and zuplo release pointers are inconsistent. Retry after publishing completes.\",\n };\n if (versions[0] !== version)\n return {\n latest: versions[0],\n blocker: `This CLI targets ${version}; published stable is ${versions[0]}. Install zuplo@${versions[0]} before applying.`,\n };\n return { latest: versions[0] };\n } catch (error) {\n return {\n blocker: `Published release could not be verified (${error instanceof Error ? error.message : String(error)}). Preview uses the bundled target; retry online before applying.`,\n };\n }\n}\n\nexport async function installedRuntime(\n dir: string\n): Promise<{ version: string; manifest: string; contents: string }> {\n const projectRequire = createRequire(path.join(dir, \"package.json\"));\n // Resolve through the project's zuplo dependency, never through the updater's own dependencies.\n const zuplo = projectRequire.resolve(\"zuplo/package.json\");\n const runtimeRequire = createRequire(zuplo);\n // Runtime exposes an import-only entry point. require.resolve(\"@zuplo/runtime\")\n // rejects that exports map; inspect the package in Node's dependency search order instead.\n for (const base of runtimeRequire.resolve.paths(\"@zuplo/runtime\") ?? []) {\n const candidate = path.join(base, \"@zuplo/runtime/package.json\");\n let manifest: string;\n try {\n manifest = await realpath(candidate);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") continue;\n throw error;\n }\n const contents = await readFile(manifest, \"utf8\");\n const pkg = z\n .object({\n name: z.literal(\"@zuplo/runtime\"),\n version: z.string(),\n exports: z.object({ \".\": z.object({ import: z.string() }) }),\n })\n .parse(JSON.parse(contents));\n await access(path.resolve(path.dirname(manifest), pkg.exports[\".\"].import));\n return { version: pkg.version, manifest, contents };\n }\n throw new Error(\n \"Could not locate the project's installed @zuplo/runtime. Install project dependencies first.\"\n );\n}\n\nexport function runtimeBlocker(\n version: string,\n plan: UpgradePlan\n): string | undefined {\n if (\n !valid(version) ||\n prerelease(version) ||\n !valid(plan.release) ||\n plan.release === \"0.0.0\"\n )\n return \"A published stable project runtime and CLI are required for automatic application.\";\n const failed = plan.additions.filter(\n (a) =>\n !satisfies(\n version,\n a.step.runtime === \"release\" ? `>=${plan.release}` : a.step.runtime\n )\n );\n if (failed.length)\n return `Installed runtime ${version} does not meet ${failed.map((a) => `${a.step.id}: ${a.step.runtime === \"release\" ? `>=${plan.release}` : a.step.runtime}`).join(\", \")}. Upgrade dependencies separately and preview again.`;\n return undefined;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=release.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release.test.d.ts","sourceRoot":"","sources":["../../../src/source/ai-gateway/release.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,62 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import { describe, it } from "node:test";
6
+ import { catalog, planUpgrade } from "./planner.js";
7
+ import { checkRelease, installedRuntime, runtimeBlocker } from "./release.js";
8
+ describe("AI Gateway release safety", () => {
9
+ it("requires matching stable CLI, scaffolder, and zuplo releases", async () => {
10
+ const requests = [];
11
+ const fetcher = async (url) => {
12
+ requests.push(String(url));
13
+ return Response.json({ version: "1.2.3" });
14
+ };
15
+ assert.deepEqual(await checkRelease("1.2.3", fetcher), { latest: "1.2.3" });
16
+ assert.equal(requests.length, 3);
17
+ assert.match((await checkRelease("1.2.2", fetcher)).blocker, /Install zuplo@1.2.3/);
18
+ });
19
+ it("fails closed for offline, invalid, prerelease, and partial publication", async () => {
20
+ assert.match((await checkRelease("1.2.3", async () => {
21
+ throw new Error("offline");
22
+ })).blocker, /could not be verified/);
23
+ for (const version of ["bad", "1.2.3-dev.1"])
24
+ assert.ok((await checkRelease("1.2.3", async () => Response.json({ version })))
25
+ .blocker);
26
+ assert.match((await checkRelease("1.2.3", async (url) => Response.json({
27
+ version: String(url).includes("create-zuplo-api")
28
+ ? "1.2.2"
29
+ : "1.2.3",
30
+ }))).blocker, /inconsistent/);
31
+ });
32
+ it("resolves the project's import-only runtime rather than the updater's runtime", async () => {
33
+ const dir = await mkdtemp(path.join(tmpdir(), "gateway-runtime-resolution-"));
34
+ try {
35
+ await mkdir(path.join(dir, "node_modules/zuplo"), { recursive: true });
36
+ const runtime = path.join(dir, "node_modules/zuplo/node_modules/@zuplo/runtime");
37
+ await mkdir(path.join(runtime, "dist"), { recursive: true });
38
+ await writeFile(path.join(dir, "package.json"), '{"name":"customer"}');
39
+ await writeFile(path.join(dir, "node_modules/zuplo/package.json"), '{"name":"zuplo","version":"1.2.3"}');
40
+ await writeFile(path.join(runtime, "package.json"), JSON.stringify({
41
+ name: "@zuplo/runtime",
42
+ version: "1.2.3",
43
+ exports: { ".": { import: "./dist/index.js" } },
44
+ }));
45
+ await writeFile(path.join(runtime, "dist/index.js"), "throw new Error('must not execute customer runtime');");
46
+ assert.equal((await installedRuntime(dir)).version, "1.2.3");
47
+ await rm(path.join(runtime, "dist/index.js"));
48
+ await assert.rejects(installedRuntime(dir), /ENOENT/);
49
+ }
50
+ finally {
51
+ await rm(dir, { recursive: true, force: true });
52
+ }
53
+ });
54
+ it("requires the project's installed runtime to satisfy each step", () => {
55
+ const plan = planUpgrade(catalog.snapshots[1].files, "1.2.3");
56
+ assert.equal(runtimeBlocker("1.2.3", plan), undefined);
57
+ assert.match(runtimeBlocker("1.2.2", plan), /Upgrade dependencies separately/);
58
+ assert.ok(runtimeBlocker("1.2.4-dev.1", plan));
59
+ assert.ok(runtimeBlocker("0.0.0", { ...plan, release: "0.0.0" }));
60
+ });
61
+ });
62
+ //# sourceMappingURL=release.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release.test.js","sourceRoot":"","sources":["../../../src/source/ai-gateway/release.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport { mkdir, mkdtemp, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport { describe, it } from \"node:test\";\nimport { catalog, planUpgrade } from \"./planner.js\";\nimport { checkRelease, installedRuntime, runtimeBlocker } from \"./release.js\";\n\ndescribe(\"AI Gateway release safety\", () => {\n it(\"requires matching stable CLI, scaffolder, and zuplo releases\", async () => {\n const requests: string[] = [];\n const fetcher: typeof fetch = async (url) => {\n requests.push(String(url));\n return Response.json({ version: \"1.2.3\" });\n };\n assert.deepEqual(await checkRelease(\"1.2.3\", fetcher), { latest: \"1.2.3\" });\n assert.equal(requests.length, 3);\n assert.match(\n (await checkRelease(\"1.2.2\", fetcher)).blocker!,\n /Install zuplo@1.2.3/\n );\n });\n it(\"fails closed for offline, invalid, prerelease, and partial publication\", async () => {\n assert.match(\n (\n await checkRelease(\"1.2.3\", async () => {\n throw new Error(\"offline\");\n })\n ).blocker!,\n /could not be verified/\n );\n for (const version of [\"bad\", \"1.2.3-dev.1\"])\n assert.ok(\n (await checkRelease(\"1.2.3\", async () => Response.json({ version })))\n .blocker\n );\n assert.match(\n (\n await checkRelease(\"1.2.3\", async (url) =>\n Response.json({\n version: String(url).includes(\"create-zuplo-api\")\n ? \"1.2.2\"\n : \"1.2.3\",\n })\n )\n ).blocker!,\n /inconsistent/\n );\n });\n it(\"resolves the project's import-only runtime rather than the updater's runtime\", async () => {\n const dir = await mkdtemp(\n path.join(tmpdir(), \"gateway-runtime-resolution-\")\n );\n try {\n await mkdir(path.join(dir, \"node_modules/zuplo\"), { recursive: true });\n const runtime = path.join(\n dir,\n \"node_modules/zuplo/node_modules/@zuplo/runtime\"\n );\n await mkdir(path.join(runtime, \"dist\"), { recursive: true });\n await writeFile(path.join(dir, \"package.json\"), '{\"name\":\"customer\"}');\n await writeFile(\n path.join(dir, \"node_modules/zuplo/package.json\"),\n '{\"name\":\"zuplo\",\"version\":\"1.2.3\"}'\n );\n await writeFile(\n path.join(runtime, \"package.json\"),\n JSON.stringify({\n name: \"@zuplo/runtime\",\n version: \"1.2.3\",\n exports: { \".\": { import: \"./dist/index.js\" } },\n })\n );\n await writeFile(\n path.join(runtime, \"dist/index.js\"),\n \"throw new Error('must not execute customer runtime');\"\n );\n assert.equal((await installedRuntime(dir)).version, \"1.2.3\");\n await rm(path.join(runtime, \"dist/index.js\"));\n await assert.rejects(installedRuntime(dir), /ENOENT/);\n } finally {\n await rm(dir, { recursive: true, force: true });\n }\n });\n\n it(\"requires the project's installed runtime to satisfy each step\", () => {\n const plan = planUpgrade(catalog.snapshots[1].files, \"1.2.3\");\n assert.equal(runtimeBlocker(\"1.2.3\", plan), undefined);\n assert.match(\n runtimeBlocker(\"1.2.2\", plan)!,\n /Upgrade dependencies separately/\n );\n assert.ok(runtimeBlocker(\"1.2.4-dev.1\", plan));\n assert.ok(runtimeBlocker(\"0.0.0\", { ...plan, release: \"0.0.0\" }));\n });\n});\n"]}