@void/deploy-core 0.26.0 → 0.26.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/README.md +7 -3
- package/dist/index.d.mts +17 -6
- package/dist/index.mjs +52 -49
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# @void/deploy-core
|
|
2
2
|
|
|
3
|
-
Shared deployment contracts for Void runtimes and deployment adapters. Includes manifests, progress events, routing rules, resource plans, and SQL and Durable Object migration metadata.
|
|
3
|
+
Shared deployment contracts for [Void](https://void.cloud) runtimes and deployment adapters. Includes manifests, progress events, routing rules, resource plans, and SQL and Durable Object migration metadata.
|
|
4
4
|
|
|
5
|
-
This package works in Node.js and Cloudflare Workers.
|
|
5
|
+
This package works in Node.js and Cloudflare Workers. To deploy an application, use the `void` CLI.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Documentation
|
|
8
|
+
|
|
9
|
+
See [Deployment](https://void.cloud/guide/deployment) for deploying an app and [Platform Development](https://void.cloud/guide/platform-development) for building a runtime or adapter.
|
|
10
|
+
|
|
11
|
+
Read the [Void documentation](https://void.cloud) online or use the Markdown guides and references in `node_modules/void/docs/`, included with the `void` package.
|
package/dist/index.d.mts
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
export declare const MANIFEST_REDIRECT_RULE_STATUSES: ReadonlySet<number>;
|
|
3
3
|
export declare const MAX_ROUTING_RULES = 2e3;
|
|
4
4
|
export declare const MAX_REVALIDATE_QUERY_ALLOWLIST_ENTRIES: number;
|
|
5
|
+
/**
|
|
6
|
+
* Runtime shape validation for incoming deploy manifests. Downstream code
|
|
7
|
+
* indexes deeply into the manifest (bindings.d1, queues[i].name,
|
|
8
|
+
* websocket.classes, etc.) without re-guarding, so a malformed-but-JSON-valid
|
|
9
|
+
* payload would otherwise crash with a 500 — sometimes after a deployment row
|
|
10
|
+
* or CF resources were created. Everything the deploy pipeline reads must be
|
|
11
|
+
* validated here BEFORE createDeployment runs.
|
|
12
|
+
*/
|
|
5
13
|
export declare function validateManifestShape(parsed: unknown): string | null;
|
|
6
14
|
//#endregion
|
|
7
15
|
//#region src/resource-plan.d.ts
|
|
@@ -175,8 +183,8 @@ export declare function isRecord(value: unknown): value is Record<string, unknow
|
|
|
175
183
|
//#endregion
|
|
176
184
|
//#region src/sql-tokenize.d.ts
|
|
177
185
|
/**
|
|
178
|
-
* Char-by-char SQL tokenizer shared by the
|
|
179
|
-
*
|
|
186
|
+
* Char-by-char SQL tokenizer shared by the migration classifier, the
|
|
187
|
+
* destructive-SQL guard, and the remote migration runner.
|
|
180
188
|
*
|
|
181
189
|
* Walks the input and yields contiguous segments. `sql` runs are
|
|
182
190
|
* emitted as `{ kind: 'sql', start, end }` so callers can `sql.slice`
|
|
@@ -192,12 +200,12 @@ export declare function isRecord(value: unknown): value is Record<string, unknow
|
|
|
192
200
|
* `]` closes the identifier (SQLite semantics;
|
|
193
201
|
* differs from MS SQL)
|
|
194
202
|
*
|
|
195
|
-
* Not handled — intentional,
|
|
203
|
+
* Not handled — intentional, every caller targets SQLite only:
|
|
196
204
|
* - Postgres `$tag$ ... $tag$` dollar-quoting
|
|
197
205
|
* - Postgres `E'...'` extended string literals
|
|
198
206
|
*
|
|
199
|
-
* Adding a new quoting form anywhere should land here so
|
|
200
|
-
*
|
|
207
|
+
* Adding a new quoting form anywhere should land here so every caller
|
|
208
|
+
* stays in lockstep.
|
|
201
209
|
*/
|
|
202
210
|
type SqlSegment = {
|
|
203
211
|
kind: "sql";
|
|
@@ -291,7 +299,10 @@ export type RedirectRule = {
|
|
|
291
299
|
export type AssetConfig = {
|
|
292
300
|
not_found_handling?: "none" | "single-page-application" | "404-page";
|
|
293
301
|
html_handling?: "auto-trailing-slash" | "force-trailing-slash" | "drop-trailing-slash" | "none";
|
|
294
|
-
/**
|
|
302
|
+
/**
|
|
303
|
+
* Native Cloudflare Workers Assets routing. Managed dispatch does not route
|
|
304
|
+
* by it; it reads it only to tell whether the app's worker answered a request.
|
|
305
|
+
*/
|
|
295
306
|
run_worker_first?: boolean | Array<string>;
|
|
296
307
|
};
|
|
297
308
|
export type WebSocketRouteMeta = {
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
//#region src/is-record.ts
|
|
2
|
+
/** Narrow object-shaped input without accepting null or arrays. */
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
1
7
|
//#region src/validate.ts
|
|
2
8
|
const MANIFEST_REDIRECT_RULE_STATUSES = /* @__PURE__ */ new Set([
|
|
3
9
|
200,
|
|
@@ -10,6 +16,8 @@ const MANIFEST_REDIRECT_RULE_STATUSES = /* @__PURE__ */ new Set([
|
|
|
10
16
|
const MAX_ROUTING_RULES = 2e3;
|
|
11
17
|
const MAX_REVALIDATE_QUERY_ALLOWLIST_ENTRIES = MAX_ROUTING_RULES;
|
|
12
18
|
const MAX_PAGES = 5e3;
|
|
19
|
+
const MAX_CRON_EXPRESSIONS = 100;
|
|
20
|
+
const MAX_SCHEDULES = 1e3;
|
|
13
21
|
/**
|
|
14
22
|
* Strict literal-hostname regex used to validate `redirectRules[].host` /
|
|
15
23
|
* `fallbackRules[].host` on the deploy manifest. Mirrors the regex in
|
|
@@ -35,11 +43,19 @@ function validateRuleHost(ruleObject, ruleKind, sourceRef) {
|
|
|
35
43
|
ruleObject.host = normalized;
|
|
36
44
|
return null;
|
|
37
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Runtime shape validation for incoming deploy manifests. Downstream code
|
|
48
|
+
* indexes deeply into the manifest (bindings.d1, queues[i].name,
|
|
49
|
+
* websocket.classes, etc.) without re-guarding, so a malformed-but-JSON-valid
|
|
50
|
+
* payload would otherwise crash with a 500 — sometimes after a deployment row
|
|
51
|
+
* or CF resources were created. Everything the deploy pipeline reads must be
|
|
52
|
+
* validated here BEFORE createDeployment runs.
|
|
53
|
+
*/
|
|
38
54
|
function validateManifestShape(parsed) {
|
|
39
|
-
if (
|
|
55
|
+
if (!isRecord(parsed)) return "invalid manifest shape: expected a JSON object";
|
|
40
56
|
const manifest = parsed;
|
|
41
57
|
if (manifest.emailProtocolVersion !== void 0 && manifest.emailProtocolVersion !== 1) return "invalid manifest shape: emailProtocolVersion must be 1";
|
|
42
|
-
if (
|
|
58
|
+
if (!isRecord(manifest.bindings)) return "invalid manifest shape: bindings must be an object";
|
|
43
59
|
const bindings = manifest.bindings;
|
|
44
60
|
for (const key of [
|
|
45
61
|
"d1",
|
|
@@ -54,7 +70,7 @@ function validateManifestShape(parsed) {
|
|
|
54
70
|
if (manifest.queues !== void 0) {
|
|
55
71
|
if (!Array.isArray(manifest.queues)) return "invalid manifest shape: queues must be an array";
|
|
56
72
|
for (const queue of manifest.queues) {
|
|
57
|
-
if (
|
|
73
|
+
if (!isRecord(queue)) return "invalid manifest shape: queues entries must be objects";
|
|
58
74
|
const queueObject = queue;
|
|
59
75
|
if (typeof queueObject.name !== "string" || typeof queueObject.bindingName !== "string") return "invalid manifest shape: queues entries must have string name and bindingName";
|
|
60
76
|
for (const tuning of [
|
|
@@ -71,21 +87,25 @@ function validateManifestShape(parsed) {
|
|
|
71
87
|
}
|
|
72
88
|
if (manifest.schedules !== void 0) {
|
|
73
89
|
if (!Array.isArray(manifest.schedules)) return "invalid manifest shape: schedules must be an array";
|
|
90
|
+
if (manifest.schedules.length > MAX_SCHEDULES) return `invalid manifest shape: schedules has ${manifest.schedules.length} entries, exceeds the ${MAX_SCHEDULES} per-project limit`;
|
|
91
|
+
const cronExpressions = /* @__PURE__ */ new Set();
|
|
74
92
|
for (const schedule of manifest.schedules) {
|
|
75
|
-
if (
|
|
93
|
+
if (!isRecord(schedule)) return "invalid manifest shape: schedules entries must be objects";
|
|
76
94
|
const scheduleObject = schedule;
|
|
77
95
|
if (typeof scheduleObject.name !== "string" || typeof scheduleObject.cron !== "string") return "invalid manifest shape: schedules entries must have string name and cron";
|
|
96
|
+
cronExpressions.add(scheduleObject.cron);
|
|
78
97
|
}
|
|
98
|
+
if (cronExpressions.size > MAX_CRON_EXPRESSIONS) return `invalid manifest shape: schedules has ${cronExpressions.size} distinct cron expressions, exceeds the ${MAX_CRON_EXPRESSIONS} per-project limit`;
|
|
79
99
|
}
|
|
80
100
|
if (manifest.prerender !== void 0) {
|
|
81
101
|
if (!Array.isArray(manifest.prerender) || !manifest.prerender.every((x) => typeof x === "string")) return "invalid manifest shape: prerender must be an array of strings";
|
|
82
102
|
}
|
|
83
103
|
if (manifest.vars !== void 0) {
|
|
84
|
-
if (
|
|
104
|
+
if (!isRecord(manifest.vars)) return "invalid manifest shape: vars must be an object";
|
|
85
105
|
for (const [k, v] of Object.entries(manifest.vars)) if (typeof v !== "string") return `invalid manifest shape: vars.${k} must be a string`;
|
|
86
106
|
}
|
|
87
107
|
if (manifest.migrations !== void 0) {
|
|
88
|
-
if (
|
|
108
|
+
if (!isRecord(manifest.migrations)) return "invalid manifest shape: migrations must be an object";
|
|
89
109
|
const mig = manifest.migrations;
|
|
90
110
|
if (mig.dialect !== void 0) {
|
|
91
111
|
const allowed = [
|
|
@@ -118,7 +138,7 @@ function validateManifestShape(parsed) {
|
|
|
118
138
|
if (!Array.isArray(manifest.compatibilityFlags) || !manifest.compatibilityFlags.every((x) => typeof x === "string")) return "invalid manifest shape: compatibilityFlags must be an array of strings";
|
|
119
139
|
}
|
|
120
140
|
if (manifest.limits !== void 0) {
|
|
121
|
-
if (
|
|
141
|
+
if (!isRecord(manifest.limits)) return "invalid manifest shape: limits must be an object";
|
|
122
142
|
const limits = manifest.limits;
|
|
123
143
|
if (limits.cpuMs !== void 0) {
|
|
124
144
|
if (typeof limits.cpuMs !== "number" || !Number.isSafeInteger(limits.cpuMs) || limits.cpuMs < 1) return "invalid manifest shape: limits.cpuMs must be a positive integer";
|
|
@@ -134,7 +154,7 @@ function validateManifestShape(parsed) {
|
|
|
134
154
|
if (typeof manifest.type !== "string" || !allowed.includes(manifest.type)) return `invalid manifest shape: type must be one of ${allowed.join(", ")}`;
|
|
135
155
|
}
|
|
136
156
|
if (manifest.assetConfig !== void 0) {
|
|
137
|
-
if (
|
|
157
|
+
if (!isRecord(manifest.assetConfig)) return "invalid manifest shape: assetConfig must be an object";
|
|
138
158
|
const assetConfig = manifest.assetConfig;
|
|
139
159
|
if (assetConfig.not_found_handling !== void 0 && ![
|
|
140
160
|
"none",
|
|
@@ -149,13 +169,13 @@ function validateManifestShape(parsed) {
|
|
|
149
169
|
].includes(assetConfig.html_handling)) return "invalid manifest shape: assetConfig.html_handling must be one of auto-trailing-slash, force-trailing-slash, drop-trailing-slash, none";
|
|
150
170
|
}
|
|
151
171
|
if (manifest.revalidate !== void 0) {
|
|
152
|
-
if (typeof manifest.revalidate === "number") {} else if (
|
|
172
|
+
if (typeof manifest.revalidate === "number") {} else if (!isRecord(manifest.revalidate)) return "invalid manifest shape: revalidate must be a number or object";
|
|
153
173
|
else for (const [pathname, value] of Object.entries(manifest.revalidate)) if (typeof value !== "number") return `invalid manifest shape: revalidate.${pathname} must be a number`;
|
|
154
174
|
}
|
|
155
175
|
if (manifest.headerRules !== void 0) {
|
|
156
176
|
if (!Array.isArray(manifest.headerRules)) return "invalid manifest shape: headerRules must be an array";
|
|
157
177
|
for (const rule of manifest.headerRules) {
|
|
158
|
-
if (
|
|
178
|
+
if (!isRecord(rule)) return "invalid manifest shape: headerRules entries must be objects";
|
|
159
179
|
const ruleObject = rule;
|
|
160
180
|
if (typeof ruleObject.pattern !== "string") return "invalid manifest shape: headerRules entries must have a string pattern";
|
|
161
181
|
if (!Array.isArray(ruleObject.headers)) return "invalid manifest shape: headerRules entries must have a headers array";
|
|
@@ -166,7 +186,7 @@ function validateManifestShape(parsed) {
|
|
|
166
186
|
if (!Array.isArray(manifest.redirectRules)) return "invalid manifest shape: redirectRules must be an array";
|
|
167
187
|
if (manifest.redirectRules.length > 2e3) return `invalid manifest shape: redirectRules has ${manifest.redirectRules.length} entries, exceeds the ${MAX_ROUTING_RULES} per-list limit`;
|
|
168
188
|
for (const rule of manifest.redirectRules) {
|
|
169
|
-
if (
|
|
189
|
+
if (!isRecord(rule)) return "invalid manifest shape: redirectRules entries must be objects";
|
|
170
190
|
const ruleObject = rule;
|
|
171
191
|
if (typeof ruleObject.source !== "string" || typeof ruleObject.destination !== "string" || typeof ruleObject.status !== "number") return "invalid manifest shape: redirectRules entries must have string source, string destination, and numeric status";
|
|
172
192
|
const sourceRef = JSON.stringify(ruleObject.source);
|
|
@@ -181,7 +201,7 @@ function validateManifestShape(parsed) {
|
|
|
181
201
|
}
|
|
182
202
|
}
|
|
183
203
|
if (manifest.revalidateQueryAllowlist !== void 0) {
|
|
184
|
-
if (
|
|
204
|
+
if (!isRecord(manifest.revalidateQueryAllowlist)) return "invalid manifest shape: revalidateQueryAllowlist must be an object";
|
|
185
205
|
const entries = Object.entries(manifest.revalidateQueryAllowlist);
|
|
186
206
|
if (entries.length > 2e3) return `invalid manifest shape: revalidateQueryAllowlist has ${entries.length} entries, exceeds the ${MAX_REVALIDATE_QUERY_ALLOWLIST_ENTRIES} entry limit`;
|
|
187
207
|
for (const [pattern, value] of entries) {
|
|
@@ -194,21 +214,21 @@ function validateManifestShape(parsed) {
|
|
|
194
214
|
if (!Array.isArray(manifest.pages)) return "invalid manifest shape: pages must be an array";
|
|
195
215
|
if (manifest.pages.length > MAX_PAGES) return `invalid manifest shape: pages has ${manifest.pages.length} entries, exceeds the ${MAX_PAGES} entry limit`;
|
|
196
216
|
for (const entry of manifest.pages) {
|
|
197
|
-
if (
|
|
217
|
+
if (!isRecord(entry)) return "invalid manifest shape: each pages entry must be an object";
|
|
198
218
|
const e = entry;
|
|
199
219
|
if (typeof e.pattern !== "string" || e.pattern === "") return "invalid manifest shape: pages entry pattern must be a string";
|
|
200
220
|
if (e.revalidate !== void 0 && typeof e.revalidate !== "number") return "invalid manifest shape: pages entry revalidate must be a number";
|
|
201
221
|
}
|
|
202
222
|
}
|
|
203
223
|
if (manifest.pathRevalidate !== void 0) {
|
|
204
|
-
if (
|
|
224
|
+
if (!isRecord(manifest.pathRevalidate)) return "invalid manifest shape: pathRevalidate must be an object";
|
|
205
225
|
for (const [pathname, value] of Object.entries(manifest.pathRevalidate)) if (typeof value !== "number") return `invalid manifest shape: pathRevalidate.${pathname} must be a number`;
|
|
206
226
|
}
|
|
207
227
|
if (manifest.fallbackRules !== void 0) {
|
|
208
228
|
if (!Array.isArray(manifest.fallbackRules)) return "invalid manifest shape: fallbackRules must be an array";
|
|
209
229
|
if (manifest.fallbackRules.length > 2e3) return `invalid manifest shape: fallbackRules has ${manifest.fallbackRules.length} entries, exceeds the ${MAX_ROUTING_RULES} per-list limit`;
|
|
210
230
|
for (const rule of manifest.fallbackRules) {
|
|
211
|
-
if (
|
|
231
|
+
if (!isRecord(rule)) return "invalid manifest shape: fallbackRules entries must be objects";
|
|
212
232
|
const ruleObject = rule;
|
|
213
233
|
if (typeof ruleObject.source !== "string" || typeof ruleObject.destination !== "string" || typeof ruleObject.status !== "number") return "invalid manifest shape: fallbackRules entries must have string source, string destination, and numeric status";
|
|
214
234
|
const sourceRef = JSON.stringify(ruleObject.source);
|
|
@@ -222,11 +242,11 @@ function validateManifestShape(parsed) {
|
|
|
222
242
|
}
|
|
223
243
|
}
|
|
224
244
|
if (manifest.websocket !== void 0) {
|
|
225
|
-
if (
|
|
245
|
+
if (!isRecord(manifest.websocket)) return "invalid manifest shape: websocket must be an object";
|
|
226
246
|
const websocket = manifest.websocket;
|
|
227
247
|
if (!Array.isArray(websocket.routes)) return "invalid manifest shape: websocket.routes must be an array";
|
|
228
248
|
for (const route of websocket.routes) {
|
|
229
|
-
if (
|
|
249
|
+
if (!isRecord(route)) return "invalid manifest shape: websocket.routes entries must be objects";
|
|
230
250
|
const routeObject = route;
|
|
231
251
|
if (typeof routeObject.className !== "string") return "invalid manifest shape: websocket.routes entries must have a string className";
|
|
232
252
|
if (routeObject.bindingName !== void 0 && typeof routeObject.bindingName !== "string") return "invalid manifest shape: websocket.routes entry bindingName must be a string";
|
|
@@ -235,7 +255,7 @@ function validateManifestShape(parsed) {
|
|
|
235
255
|
if (websocket.migrations !== void 0) {
|
|
236
256
|
if (!Array.isArray(websocket.migrations)) return "invalid manifest shape: websocket.migrations must be an array";
|
|
237
257
|
for (const migration of websocket.migrations) {
|
|
238
|
-
if (
|
|
258
|
+
if (!isRecord(migration)) return "invalid manifest shape: websocket.migrations entries must be objects";
|
|
239
259
|
const migrationObject = migration;
|
|
240
260
|
if (typeof migrationObject.tag !== "string" || migrationObject.tag.length === 0) return "invalid manifest shape: websocket.migrations entries must have a non-empty string tag";
|
|
241
261
|
for (const field of [
|
|
@@ -251,7 +271,7 @@ function validateManifestShape(parsed) {
|
|
|
251
271
|
}
|
|
252
272
|
if (manifest.sandbox !== void 0) {
|
|
253
273
|
if (manifest.type === "static" || manifest.type === "spa") return "sandbox is not supported for static/spa deployments";
|
|
254
|
-
if (
|
|
274
|
+
if (!isRecord(manifest.sandbox)) return "invalid manifest shape: sandbox must be an object";
|
|
255
275
|
const sandbox = manifest.sandbox;
|
|
256
276
|
for (const key of [
|
|
257
277
|
"bindingName",
|
|
@@ -265,7 +285,7 @@ function validateManifestShape(parsed) {
|
|
|
265
285
|
if (sandbox.migrations !== void 0) {
|
|
266
286
|
if (!Array.isArray(sandbox.migrations)) return "invalid manifest shape: sandbox.migrations must be an array";
|
|
267
287
|
for (const migration of sandbox.migrations) {
|
|
268
|
-
if (
|
|
288
|
+
if (!isRecord(migration)) return "invalid manifest shape: sandbox.migrations entries must be objects";
|
|
269
289
|
const migrationObject = migration;
|
|
270
290
|
if (typeof migrationObject.tag !== "string") return "invalid manifest shape: sandbox.migrations entries must have a string tag";
|
|
271
291
|
if (migrationObject.newSqliteClasses !== void 0 && (!Array.isArray(migrationObject.newSqliteClasses) || !migrationObject.newSqliteClasses.every((value) => typeof value === "string"))) return "invalid manifest shape: sandbox.migrations newSqliteClasses must be an array of strings";
|
|
@@ -274,7 +294,7 @@ function validateManifestShape(parsed) {
|
|
|
274
294
|
}
|
|
275
295
|
}
|
|
276
296
|
if (manifest.live !== void 0) {
|
|
277
|
-
if (
|
|
297
|
+
if (!isRecord(manifest.live)) return "invalid manifest shape: live must be an object";
|
|
278
298
|
const live = manifest.live;
|
|
279
299
|
for (const key of ["bindingName", "className"]) if (typeof live[key] !== "string" || live[key].length === 0) return `invalid manifest shape: live.${key} must be a non-empty string`;
|
|
280
300
|
if (live.storage !== "sqlite") return "invalid manifest shape: live.storage must be \"sqlite\"";
|
|
@@ -282,7 +302,7 @@ function validateManifestShape(parsed) {
|
|
|
282
302
|
if (live.migrations !== void 0) {
|
|
283
303
|
if (!Array.isArray(live.migrations)) return "invalid manifest shape: live.migrations must be an array";
|
|
284
304
|
for (const migration of live.migrations) {
|
|
285
|
-
if (
|
|
305
|
+
if (!isRecord(migration)) return "invalid manifest shape: live.migrations entries must be objects";
|
|
286
306
|
const migrationObject = migration;
|
|
287
307
|
if (typeof migrationObject.tag !== "string") return "invalid manifest shape: live.migrations entries must have a string tag";
|
|
288
308
|
if (migrationObject.newSqliteClasses !== void 0 && (!Array.isArray(migrationObject.newSqliteClasses) || !migrationObject.newSqliteClasses.every((value) => typeof value === "string"))) return "invalid manifest shape: live.migrations newSqliteClasses must be an array of strings";
|
|
@@ -292,8 +312,7 @@ function validateManifestShape(parsed) {
|
|
|
292
312
|
return null;
|
|
293
313
|
}
|
|
294
314
|
function isLocalImageReference(image) {
|
|
295
|
-
|
|
296
|
-
return image.startsWith(".") || image.startsWith("/") || lowerImage === "dockerfile" || lowerImage.includes("dockerfile") || lowerImage.endsWith("/dockerfile") || lowerImage.endsWith("\\dockerfile");
|
|
315
|
+
return image.startsWith(".") || image.startsWith("/") || image.toLowerCase().includes("dockerfile");
|
|
297
316
|
}
|
|
298
317
|
//#endregion
|
|
299
318
|
//#region src/resource-plan.ts
|
|
@@ -407,12 +426,6 @@ function canonicalJson(value) {
|
|
|
407
426
|
throw new TypeError(`Unsupported canonical JSON value: ${typeof value}`);
|
|
408
427
|
}
|
|
409
428
|
//#endregion
|
|
410
|
-
//#region src/is-record.ts
|
|
411
|
-
/** Narrow object-shaped input without accepting null or arrays. */
|
|
412
|
-
function isRecord(value) {
|
|
413
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
414
|
-
}
|
|
415
|
-
//#endregion
|
|
416
429
|
//#region src/sql-tokenize.ts
|
|
417
430
|
function* tokenizeSql(sql) {
|
|
418
431
|
const sqlLength = sql.length;
|
|
@@ -519,38 +532,28 @@ const UNSPLIT_MULTI_STATEMENT_PRAGMA = "-- void:allow-unsplit-multi-statement";
|
|
|
519
532
|
function canonicalSql(sql) {
|
|
520
533
|
return (sql.charCodeAt(0) === 65279 ? sql.slice(1) : sql).replace(/\r\n?/g, "\n");
|
|
521
534
|
}
|
|
522
|
-
function isStandalonePragmaLine(line, pragma) {
|
|
523
|
-
return line.trim() === pragma;
|
|
524
|
-
}
|
|
525
535
|
function hasStandalonePragma(sql, pragma) {
|
|
526
|
-
return canonicalSql(sql).split("\n").some((line) =>
|
|
536
|
+
return canonicalSql(sql).split("\n").some((line) => line.trim() === pragma);
|
|
527
537
|
}
|
|
528
|
-
|
|
529
|
-
|
|
538
|
+
/** Yield `--> statement-breakpoint` line comments outside literals. */
|
|
539
|
+
function* breakpointComments(normalized) {
|
|
540
|
+
for (const segment of tokenizeSql(normalized)) if (segment.kind === "line-comment" && normalized.slice(segment.start, segment.end).trim() === "--> statement-breakpoint") yield segment;
|
|
530
541
|
}
|
|
531
542
|
function splitBreakpointFramedSql(sql) {
|
|
532
543
|
const normalized = canonicalSql(sql);
|
|
533
544
|
const statements = [];
|
|
534
545
|
let cursor = 0;
|
|
535
|
-
for (const segment of
|
|
536
|
-
|
|
537
|
-
if (
|
|
538
|
-
|
|
539
|
-
if (statement) statements.push(statement);
|
|
540
|
-
cursor = segment.end;
|
|
541
|
-
}
|
|
546
|
+
for (const segment of breakpointComments(normalized)) {
|
|
547
|
+
const statement = normalized.slice(cursor, segment.start).trim();
|
|
548
|
+
if (statement) statements.push(statement);
|
|
549
|
+
cursor = segment.end;
|
|
542
550
|
}
|
|
543
551
|
const statement = normalized.slice(cursor).trim();
|
|
544
552
|
if (statement) statements.push(statement);
|
|
545
553
|
return statements;
|
|
546
554
|
}
|
|
547
555
|
function hasStatementBreakpointOutsideLiterals(sql) {
|
|
548
|
-
|
|
549
|
-
for (const segment of tokenizeSql(normalized)) {
|
|
550
|
-
if (segment.kind !== "line-comment") continue;
|
|
551
|
-
if (isBreakpointComment(normalized.slice(segment.start, segment.end))) return true;
|
|
552
|
-
}
|
|
553
|
-
return false;
|
|
556
|
+
return !breakpointComments(canonicalSql(sql)).next().done;
|
|
554
557
|
}
|
|
555
558
|
function scanStatements(sql) {
|
|
556
559
|
const normalized = canonicalSql(sql);
|
package/package.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@void/deploy-core",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.1",
|
|
4
|
+
"description": "Shared deployment contracts, manifests, and resource plans for Void.",
|
|
5
|
+
"homepage": "https://void.cloud",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/voidzero-dev/void/issues"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
4
10
|
"repository": {
|
|
5
11
|
"type": "git",
|
|
6
12
|
"url": "git+https://github.com/voidzero-dev/void.git",
|
|
@@ -21,7 +27,7 @@
|
|
|
21
27
|
},
|
|
22
28
|
"devDependencies": {
|
|
23
29
|
"typescript": "7.0.2",
|
|
24
|
-
"vite-plus": "1.
|
|
30
|
+
"vite-plus": "1.1.0"
|
|
25
31
|
},
|
|
26
32
|
"scripts": {
|
|
27
33
|
"build": "vp pack",
|