@vinext/cloudflare 0.2.1 → 1.0.0-beta.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/dist/cache/kv-data-adapter.d.ts +2 -1
- package/dist/cache/kv-data-adapter.js +2 -1
- package/dist/cache/kv-data-adapter.runtime.js +1 -1
- package/dist/cdn-warm.d.ts +37 -0
- package/dist/cdn-warm.js +183 -0
- package/dist/cli.js +7 -1
- package/dist/deploy-config.d.ts +9 -1
- package/dist/deploy-config.js +21 -2
- package/dist/deploy-help.js +16 -0
- package/dist/deploy.d.ts +46 -7
- package/dist/deploy.js +339 -37
- package/dist/prerender-kv-populate.d.ts +25 -0
- package/dist/prerender-kv-populate.js +120 -0
- package/dist/tpr.d.ts +11 -3
- package/dist/tpr.js +146 -8
- package/dist/version-deploy.d.ts +43 -0
- package/dist/version-deploy.js +223 -0
- package/dist/workers-dev-url.d.ts +4 -0
- package/dist/workers-dev-url.js +42 -0
- package/package.json +12 -3
package/dist/tpr.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ENTRY_PREFIX } from "./cache/kv-data-adapter.runtime.js";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import fs from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import { VINEXT_REVALIDATE_HEADER } from "vinext/internal/server/headers";
|
|
@@ -33,13 +33,24 @@ import { buildAppPageCacheTags } from "vinext/internal/server/app-page-cache";
|
|
|
33
33
|
* Parse wrangler config (JSONC or TOML) to extract the fields TPR needs:
|
|
34
34
|
* account_id, VINEXT_KV_CACHE KV namespace ID, and custom domain.
|
|
35
35
|
*/
|
|
36
|
-
function parseWranglerConfig(root) {
|
|
36
|
+
function parseWranglerConfig(root, configPath) {
|
|
37
|
+
if (configPath) {
|
|
38
|
+
const filepath = path.resolve(root, configPath);
|
|
39
|
+
if (!fs.existsSync(filepath)) return null;
|
|
40
|
+
const content = fs.readFileSync(filepath, "utf-8");
|
|
41
|
+
if (filepath.endsWith(".toml")) return extractFromTOML(content);
|
|
42
|
+
try {
|
|
43
|
+
return extractFromJSON(JSON.parse(stripJsonCommentsAndTrailingCommas(content)));
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
37
48
|
for (const filename of ["wrangler.jsonc", "wrangler.json"]) {
|
|
38
49
|
const filepath = path.join(root, filename);
|
|
39
50
|
if (fs.existsSync(filepath)) {
|
|
40
51
|
const content = fs.readFileSync(filepath, "utf-8");
|
|
41
52
|
try {
|
|
42
|
-
return extractFromJSON(JSON.parse(
|
|
53
|
+
return extractFromJSON(JSON.parse(stripJsonCommentsAndTrailingCommas(content)));
|
|
43
54
|
} catch {
|
|
44
55
|
continue;
|
|
45
56
|
}
|
|
@@ -50,10 +61,10 @@ function parseWranglerConfig(root) {
|
|
|
50
61
|
return null;
|
|
51
62
|
}
|
|
52
63
|
/**
|
|
53
|
-
* Strip single-line (//)
|
|
54
|
-
* preserving strings that contain
|
|
64
|
+
* Strip single-line (//), multi-line comments, and trailing commas from JSONC
|
|
65
|
+
* while preserving strings that contain comment-like text or commas.
|
|
55
66
|
*/
|
|
56
|
-
function
|
|
67
|
+
function stripJsonCommentsAndTrailingCommas(str) {
|
|
57
68
|
let result = "";
|
|
58
69
|
let inString = false;
|
|
59
70
|
let inSingleLine = false;
|
|
@@ -106,12 +117,41 @@ function stripJsonComments(str) {
|
|
|
106
117
|
i++;
|
|
107
118
|
continue;
|
|
108
119
|
}
|
|
120
|
+
if (!inString && ch === "," && isJsonTrailingComma(str, i + 1)) continue;
|
|
109
121
|
result += ch;
|
|
110
122
|
}
|
|
111
123
|
return result;
|
|
112
124
|
}
|
|
125
|
+
function isJsonTrailingComma(str, start) {
|
|
126
|
+
for (let i = start; i < str.length; i++) {
|
|
127
|
+
const ch = str[i];
|
|
128
|
+
const next = str[i + 1];
|
|
129
|
+
if (ch === void 0) return false;
|
|
130
|
+
if (/\s/.test(ch)) continue;
|
|
131
|
+
if (ch === "/" && next === "/") {
|
|
132
|
+
i += 2;
|
|
133
|
+
while (i < str.length && str[i] !== "\n") i++;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (ch === "/" && next === "*") {
|
|
137
|
+
i += 2;
|
|
138
|
+
while (i < str.length) {
|
|
139
|
+
if (str[i] === "*" && str[i + 1] === "/") {
|
|
140
|
+
i++;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
i++;
|
|
144
|
+
}
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
return ch === "}" || ch === "]";
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
113
151
|
function extractFromJSON(config) {
|
|
114
152
|
const result = {};
|
|
153
|
+
if (typeof config.name === "string" && config.name.length > 0) result.name = config.name;
|
|
154
|
+
if (typeof config.legacy_env === "boolean") result.legacyEnv = config.legacy_env;
|
|
115
155
|
if (typeof config.account_id === "string") result.accountId = config.account_id;
|
|
116
156
|
if (Array.isArray(config.kv_namespaces)) {
|
|
117
157
|
const vinextKV = config.kv_namespaces.find((ns) => ns && typeof ns === "object" && (ns.binding === "VINEXT_KV_CACHE" || ns.binding === "VINEXT_CACHE"));
|
|
@@ -119,6 +159,25 @@ function extractFromJSON(config) {
|
|
|
119
159
|
}
|
|
120
160
|
const domain = extractDomainFromRoutes(config.routes) ?? extractDomainFromCustomDomains(config);
|
|
121
161
|
if (domain) result.customDomain = domain;
|
|
162
|
+
const env = extractEnvConfigs(config.env);
|
|
163
|
+
if (env) result.env = env;
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
function extractEnvConfigs(envs) {
|
|
167
|
+
if (!envs || typeof envs !== "object" || Array.isArray(envs)) return void 0;
|
|
168
|
+
const result = {};
|
|
169
|
+
for (const [envName, rawConfig] of Object.entries(envs)) {
|
|
170
|
+
if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) continue;
|
|
171
|
+
const envConfig = extractEnvironmentConfig(rawConfig);
|
|
172
|
+
if (envConfig.name || envConfig.customDomain) result[envName] = envConfig;
|
|
173
|
+
}
|
|
174
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
175
|
+
}
|
|
176
|
+
function extractEnvironmentConfig(config) {
|
|
177
|
+
const result = {};
|
|
178
|
+
if (typeof config.name === "string" && config.name.length > 0) result.name = config.name;
|
|
179
|
+
const domain = extractDomainFromRoutes(config.routes) ?? extractDomainFromCustomDomains(config);
|
|
180
|
+
if (domain) result.customDomain = domain;
|
|
122
181
|
return result;
|
|
123
182
|
}
|
|
124
183
|
function extractDomainFromRoutes(routes) {
|
|
@@ -152,6 +211,10 @@ function cleanDomain(raw) {
|
|
|
152
211
|
*/
|
|
153
212
|
function extractFromTOML(content) {
|
|
154
213
|
const result = {};
|
|
214
|
+
const nameMatch = content.match(/^name\s*=\s*"([^"]+)"/m);
|
|
215
|
+
if (nameMatch) result.name = nameMatch[1];
|
|
216
|
+
const legacyEnvMatch = content.match(/^legacy_env\s*=\s*(true|false)\s*$/m);
|
|
217
|
+
if (legacyEnvMatch) result.legacyEnv = legacyEnvMatch[1] === "true";
|
|
155
218
|
const accountMatch = content.match(/^account_id\s*=\s*"([^"]+)"/m);
|
|
156
219
|
if (accountMatch) result.accountId = accountMatch[1];
|
|
157
220
|
const kvBlocks = content.split(/\[\[kv_namespaces\]\]/);
|
|
@@ -179,8 +242,83 @@ function extractFromTOML(content) {
|
|
|
179
242
|
}
|
|
180
243
|
}
|
|
181
244
|
}
|
|
245
|
+
const env = extractEnvConfigsFromTOML(content);
|
|
246
|
+
if (env) result.env = env;
|
|
182
247
|
return result;
|
|
183
248
|
}
|
|
249
|
+
function extractEnvConfigsFromTOML(content) {
|
|
250
|
+
const result = {};
|
|
251
|
+
for (const section of getTomlSections(content)) {
|
|
252
|
+
const envName = section.header.match(/^env\.([^.]+)$/)?.[1];
|
|
253
|
+
if (envName) {
|
|
254
|
+
const envConfig = result[envName] ?? {};
|
|
255
|
+
const nameMatch = section.body.match(/^name\s*=\s*"([^"]+)"/m);
|
|
256
|
+
if (nameMatch) envConfig.name = nameMatch[1];
|
|
257
|
+
const domain = extractTomlScalarRouteDomain(section.body) ?? extractTomlRoutesArrayDomain(section.body);
|
|
258
|
+
if (domain) envConfig.customDomain = domain;
|
|
259
|
+
if (envConfig.name || envConfig.customDomain) result[envName] = envConfig;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const routesEnvName = section.header.match(/^env\.([^.]+)\.routes$/)?.[1];
|
|
263
|
+
if (routesEnvName) {
|
|
264
|
+
const envConfig = result[routesEnvName] ?? {};
|
|
265
|
+
const domain = extractTomlRouteBlockDomain(section.body);
|
|
266
|
+
if (domain) envConfig.customDomain = domain;
|
|
267
|
+
if (envConfig.name || envConfig.customDomain) result[routesEnvName] = envConfig;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
271
|
+
}
|
|
272
|
+
function getTomlSections(content) {
|
|
273
|
+
const sections = [];
|
|
274
|
+
let currentHeader = null;
|
|
275
|
+
let currentBody = [];
|
|
276
|
+
for (const line of content.split("\n")) {
|
|
277
|
+
const header = parseTomlSectionHeader(line);
|
|
278
|
+
if (header) {
|
|
279
|
+
if (currentHeader) sections.push({
|
|
280
|
+
header: currentHeader,
|
|
281
|
+
body: currentBody.join("\n")
|
|
282
|
+
});
|
|
283
|
+
currentHeader = header;
|
|
284
|
+
currentBody = [];
|
|
285
|
+
} else if (currentHeader) currentBody.push(line);
|
|
286
|
+
}
|
|
287
|
+
if (currentHeader) sections.push({
|
|
288
|
+
header: currentHeader,
|
|
289
|
+
body: currentBody.join("\n")
|
|
290
|
+
});
|
|
291
|
+
return sections;
|
|
292
|
+
}
|
|
293
|
+
function parseTomlSectionHeader(line) {
|
|
294
|
+
const trimmed = line.trim();
|
|
295
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return null;
|
|
296
|
+
const isArrayHeader = trimmed.startsWith("[[") && trimmed.endsWith("]]");
|
|
297
|
+
const start = isArrayHeader ? 2 : 1;
|
|
298
|
+
const end = isArrayHeader ? trimmed.length - 2 : trimmed.length - 1;
|
|
299
|
+
const header = trimmed.slice(start, end).trim();
|
|
300
|
+
return header.length > 0 ? header : null;
|
|
301
|
+
}
|
|
302
|
+
function extractTomlScalarRouteDomain(section) {
|
|
303
|
+
const routeMatch = section.match(/^route\s*=\s*"([^"]+)"/m);
|
|
304
|
+
if (!routeMatch) return null;
|
|
305
|
+
const domain = cleanDomain(routeMatch[1]);
|
|
306
|
+
return domain && !domain.includes("workers.dev") ? domain : null;
|
|
307
|
+
}
|
|
308
|
+
function extractTomlRoutesArrayDomain(section) {
|
|
309
|
+
const routesMatch = section.match(/^routes\s*=\s*\[([\s\S]*?)\]/m);
|
|
310
|
+
if (!routesMatch) return null;
|
|
311
|
+
const patternMatch = (routesMatch[1] ?? "").match(/(?:pattern\s*=\s*)?"([^"]+)"/);
|
|
312
|
+
if (!patternMatch) return null;
|
|
313
|
+
const domain = cleanDomain(patternMatch[1]);
|
|
314
|
+
return domain && !domain.includes("workers.dev") ? domain : null;
|
|
315
|
+
}
|
|
316
|
+
function extractTomlRouteBlockDomain(section) {
|
|
317
|
+
const patternMatch = section.match(/^(?:pattern|zone_name)\s*=\s*"([^"]+)"/m);
|
|
318
|
+
if (!patternMatch) return null;
|
|
319
|
+
const domain = cleanDomain(patternMatch[1]);
|
|
320
|
+
return domain && !domain.includes("workers.dev") ? domain : null;
|
|
321
|
+
}
|
|
184
322
|
/**
|
|
185
323
|
* Generate zone lookup candidates from shortest (2-part) to longest.
|
|
186
324
|
* Tries the most common case first (e.g., "example.com") and progressively
|
|
@@ -507,7 +645,7 @@ const DEFAULT_REVALIDATE_SECONDS = 3600;
|
|
|
507
645
|
*/
|
|
508
646
|
async function runTPR(options) {
|
|
509
647
|
const startTime = Date.now();
|
|
510
|
-
const { root, coverage, limit, window: windowHours } = options;
|
|
648
|
+
const { root, config, coverage, limit, window: windowHours } = options;
|
|
511
649
|
const skip = (reason) => ({
|
|
512
650
|
totalPaths: 0,
|
|
513
651
|
prerenderedCount: 0,
|
|
@@ -517,7 +655,7 @@ async function runTPR(options) {
|
|
|
517
655
|
});
|
|
518
656
|
const apiToken = process.env.CLOUDFLARE_API_TOKEN;
|
|
519
657
|
if (!apiToken) return skip("no CLOUDFLARE_API_TOKEN set");
|
|
520
|
-
const wranglerConfig = parseWranglerConfig(root);
|
|
658
|
+
const wranglerConfig = parseWranglerConfig(root, config);
|
|
521
659
|
if (!wranglerConfig) return skip("could not parse wrangler config");
|
|
522
660
|
if (!wranglerConfig.customDomain) return skip("no custom domain — zone analytics unavailable");
|
|
523
661
|
if (!wranglerConfig.kvNamespaceId) return skip("no VINEXT_KV_CACHE KV namespace configured");
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { parseWorkersDevUrl } from "./workers-dev-url.js";
|
|
2
|
+
import { DeployOptions } from "./deploy.js";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
//#region src/version-deploy.d.ts
|
|
6
|
+
type WranglerVersionUploadResult = {
|
|
7
|
+
versionId: string;
|
|
8
|
+
previewUrl: string | null;
|
|
9
|
+
output: string;
|
|
10
|
+
};
|
|
11
|
+
type WranglerVersionDeployResult = {
|
|
12
|
+
deployedUrl: string | null;
|
|
13
|
+
output: string;
|
|
14
|
+
};
|
|
15
|
+
type WranglerVersionTraffic = {
|
|
16
|
+
versionId: string;
|
|
17
|
+
percentage: number;
|
|
18
|
+
};
|
|
19
|
+
type WranglerDeploymentStatus = {
|
|
20
|
+
versions: WranglerVersionTraffic[];
|
|
21
|
+
output: string;
|
|
22
|
+
};
|
|
23
|
+
type WranglerVersionArgs = {
|
|
24
|
+
args: string[];
|
|
25
|
+
env: string | undefined;
|
|
26
|
+
};
|
|
27
|
+
declare function parseVersionId(output: string): string | null;
|
|
28
|
+
declare function parseWranglerVersionUploadOutput(output: string): WranglerVersionUploadResult;
|
|
29
|
+
declare function buildWranglerVersionUploadArgs(options: Pick<DeployOptions, "preview" | "env" | "name" | "config"> & {
|
|
30
|
+
previewAlias?: string;
|
|
31
|
+
}): WranglerVersionArgs;
|
|
32
|
+
declare function buildWranglerVersionDeployArgs(versionTraffic: readonly WranglerVersionTraffic[], options: Pick<DeployOptions, "preview" | "env" | "name" | "config">): WranglerVersionArgs;
|
|
33
|
+
declare function buildWranglerDeploymentsStatusArgs(options: Pick<DeployOptions, "preview" | "env" | "name" | "config">): WranglerVersionArgs;
|
|
34
|
+
declare function buildWranglerTriggersDeployArgs(options: Pick<DeployOptions, "preview" | "env" | "name" | "config">): WranglerVersionArgs;
|
|
35
|
+
declare function parseWranglerDeploymentStatusOutput(output: string): WranglerDeploymentStatus;
|
|
36
|
+
declare function runWranglerVersionUpload(root: string, options: Pick<DeployOptions, "preview" | "env" | "name" | "config"> & {
|
|
37
|
+
previewAlias?: string;
|
|
38
|
+
}, execute?: typeof execFileSync): WranglerVersionUploadResult;
|
|
39
|
+
declare function runWranglerVersionDeploy(root: string, versionTraffic: readonly WranglerVersionTraffic[], options: Pick<DeployOptions, "preview" | "env" | "name" | "config">, phase?: "stage" | "promote-warmed" | "promote-uploaded", execute?: typeof execFileSync): WranglerVersionDeployResult;
|
|
40
|
+
declare function runWranglerDeploymentStatus(root: string, options: Pick<DeployOptions, "preview" | "env" | "name" | "config">, execute?: typeof execFileSync): WranglerDeploymentStatus;
|
|
41
|
+
declare function runWranglerTriggersDeploy(root: string, options: Pick<DeployOptions, "preview" | "env" | "name" | "config">, execute?: typeof execFileSync): WranglerVersionDeployResult;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { WranglerDeploymentStatus, WranglerVersionDeployResult, WranglerVersionTraffic, WranglerVersionUploadResult, buildWranglerDeploymentsStatusArgs, buildWranglerTriggersDeployArgs, buildWranglerVersionDeployArgs, buildWranglerVersionUploadArgs, parseVersionId, parseWorkersDevUrl, parseWranglerDeploymentStatusOutput, parseWranglerVersionUploadOutput, runWranglerDeploymentStatus, runWranglerTriggersDeploy, runWranglerVersionDeploy, runWranglerVersionUpload };
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { parseWorkersDevUrl } from "./workers-dev-url.js";
|
|
2
|
+
import { buildNodeCliInvocation, resolveWranglerBin, validateWranglerEnvName } from "./deploy.js";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
//#region src/version-deploy.ts
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function parseJsonObject(output) {
|
|
9
|
+
const trimmed = output.trim();
|
|
10
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null;
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(trimmed);
|
|
13
|
+
return isRecord(parsed) || Array.isArray(parsed) ? parsed : null;
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function findStringInRecord(value, keys) {
|
|
19
|
+
if (!isRecord(value)) return null;
|
|
20
|
+
for (const key of keys) {
|
|
21
|
+
const field = value[key];
|
|
22
|
+
if (typeof field === "string" && field.length > 0) return field;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function findVersionIdInUploadJson(parsed) {
|
|
27
|
+
if (!isRecord(parsed)) return null;
|
|
28
|
+
return findStringInRecord(parsed, [
|
|
29
|
+
"version_id",
|
|
30
|
+
"versionId",
|
|
31
|
+
"id"
|
|
32
|
+
]) ?? findStringInRecord(parsed.version, [
|
|
33
|
+
"version_id",
|
|
34
|
+
"versionId",
|
|
35
|
+
"id"
|
|
36
|
+
]) ?? findStringInRecord(parsed.result, [
|
|
37
|
+
"version_id",
|
|
38
|
+
"versionId",
|
|
39
|
+
"id"
|
|
40
|
+
]);
|
|
41
|
+
}
|
|
42
|
+
function findPreviewUrlInUploadJson(parsed) {
|
|
43
|
+
if (!isRecord(parsed)) return null;
|
|
44
|
+
return findStringInRecord(parsed, [
|
|
45
|
+
"preview_url",
|
|
46
|
+
"previewUrl",
|
|
47
|
+
"url"
|
|
48
|
+
]) ?? findStringInRecord(parsed.version, [
|
|
49
|
+
"preview_url",
|
|
50
|
+
"previewUrl",
|
|
51
|
+
"url"
|
|
52
|
+
]) ?? findStringInRecord(parsed.result, [
|
|
53
|
+
"preview_url",
|
|
54
|
+
"previewUrl",
|
|
55
|
+
"url"
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
function parseVersionId(output) {
|
|
59
|
+
return output.match(/\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b/)?.[0] ?? null;
|
|
60
|
+
}
|
|
61
|
+
function parseWranglerVersionUploadOutput(output) {
|
|
62
|
+
const parsed = parseJsonObject(output);
|
|
63
|
+
const versionId = findVersionIdInUploadJson(parsed) ?? parseVersionId(output);
|
|
64
|
+
const previewUrl = findPreviewUrlInUploadJson(parsed) ?? parseWorkersDevUrl(output);
|
|
65
|
+
if (!versionId) throw new Error("Could not detect Worker version ID from `wrangler versions upload` output.");
|
|
66
|
+
return {
|
|
67
|
+
versionId,
|
|
68
|
+
previewUrl,
|
|
69
|
+
output
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function buildWranglerVersionUploadArgs(options) {
|
|
73
|
+
const args = ["versions", "upload"];
|
|
74
|
+
const env = options.env || (options.preview ? "preview" : void 0);
|
|
75
|
+
if (options.config) args.push("--config", options.config);
|
|
76
|
+
if (options.name) args.push("--name", options.name);
|
|
77
|
+
if (env) args.push("--env", validateWranglerEnvName(env));
|
|
78
|
+
if (options.previewAlias) args.push("--preview-alias", options.previewAlias);
|
|
79
|
+
return {
|
|
80
|
+
args,
|
|
81
|
+
env
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function buildWranglerVersionDeployArgs(versionTraffic, options) {
|
|
85
|
+
const args = [
|
|
86
|
+
"versions",
|
|
87
|
+
"deploy",
|
|
88
|
+
...versionTraffic.map(({ versionId, percentage }) => `${versionId}@${percentage}%`),
|
|
89
|
+
"--yes"
|
|
90
|
+
];
|
|
91
|
+
const env = options.env || (options.preview ? "preview" : void 0);
|
|
92
|
+
if (options.config) args.push("--config", options.config);
|
|
93
|
+
if (options.name) args.push("--name", options.name);
|
|
94
|
+
if (env) args.push("--env", validateWranglerEnvName(env));
|
|
95
|
+
return {
|
|
96
|
+
args,
|
|
97
|
+
env
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function buildWranglerDeploymentsStatusArgs(options) {
|
|
101
|
+
const args = [
|
|
102
|
+
"deployments",
|
|
103
|
+
"status",
|
|
104
|
+
"--json"
|
|
105
|
+
];
|
|
106
|
+
const env = options.env || (options.preview ? "preview" : void 0);
|
|
107
|
+
if (options.config) args.push("--config", options.config);
|
|
108
|
+
if (options.name) args.push("--name", options.name);
|
|
109
|
+
if (env) args.push("--env", validateWranglerEnvName(env));
|
|
110
|
+
return {
|
|
111
|
+
args,
|
|
112
|
+
env
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function buildWranglerTriggersDeployArgs(options) {
|
|
116
|
+
const args = ["triggers", "deploy"];
|
|
117
|
+
const env = options.env || (options.preview ? "preview" : void 0);
|
|
118
|
+
if (options.config) args.push("--config", options.config);
|
|
119
|
+
if (options.name) args.push("--name", options.name);
|
|
120
|
+
if (env) args.push("--env", validateWranglerEnvName(env));
|
|
121
|
+
return {
|
|
122
|
+
args,
|
|
123
|
+
env
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function runWranglerCommand(root, args, execute = execFileSync) {
|
|
127
|
+
const invocation = buildNodeCliInvocation(resolveWranglerBin(root), args);
|
|
128
|
+
const execOpts = {
|
|
129
|
+
cwd: root,
|
|
130
|
+
stdio: "pipe",
|
|
131
|
+
encoding: "utf-8",
|
|
132
|
+
shell: false
|
|
133
|
+
};
|
|
134
|
+
const output = execute(invocation.file, invocation.args, execOpts);
|
|
135
|
+
if (output.trim()) for (const line of output.trim().split("\n")) console.log(` ${line}`);
|
|
136
|
+
return output;
|
|
137
|
+
}
|
|
138
|
+
function errorText(error) {
|
|
139
|
+
if (!error || typeof error !== "object") return String(error);
|
|
140
|
+
const parts = error instanceof Error ? [error.message] : [];
|
|
141
|
+
const record = error;
|
|
142
|
+
for (const field of [
|
|
143
|
+
"stdout",
|
|
144
|
+
"stderr",
|
|
145
|
+
"output"
|
|
146
|
+
]) {
|
|
147
|
+
const value = record[field];
|
|
148
|
+
if (Array.isArray(value)) parts.push(...value.filter((entry) => typeof entry === "string" || Buffer.isBuffer(entry)).map((entry) => entry.toString()));
|
|
149
|
+
else if (typeof value === "string" || Buffer.isBuffer(value)) parts.push(value.toString());
|
|
150
|
+
}
|
|
151
|
+
return parts.join("\n");
|
|
152
|
+
}
|
|
153
|
+
function isMissingWorkerVersionUploadError(error) {
|
|
154
|
+
return /cannot upload a new version of a Worker that does not yet exist/i.test(errorText(error));
|
|
155
|
+
}
|
|
156
|
+
function withInitialDeployRequiredMessage() {
|
|
157
|
+
return /* @__PURE__ */ new Error("CDN pre-warm needs an existing Cloudflare Worker before it can upload a new Worker version. Run `vinext-cloudflare deploy` once without `--experimental-warm-cdn-cache` to create the Worker, then rerun your pre-warm deploy.");
|
|
158
|
+
}
|
|
159
|
+
function parseDeploymentVersions(value) {
|
|
160
|
+
const deployment = Array.isArray(value) ? value.at(-1) : value;
|
|
161
|
+
if (!isRecord(deployment) || !Array.isArray(deployment.versions)) return [];
|
|
162
|
+
const versions = [];
|
|
163
|
+
for (const version of deployment.versions) {
|
|
164
|
+
if (!isRecord(version)) continue;
|
|
165
|
+
const versionId = version.version_id;
|
|
166
|
+
const percentage = version.percentage;
|
|
167
|
+
if (typeof versionId !== "string" || typeof percentage !== "number") continue;
|
|
168
|
+
versions.push({
|
|
169
|
+
versionId,
|
|
170
|
+
percentage
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return versions;
|
|
174
|
+
}
|
|
175
|
+
function parseWranglerDeploymentStatusOutput(output) {
|
|
176
|
+
const parsed = parseJsonObject(output);
|
|
177
|
+
if (!parsed) throw new Error("Could not parse `wrangler deployments status --json` output.");
|
|
178
|
+
return {
|
|
179
|
+
versions: parseDeploymentVersions(parsed),
|
|
180
|
+
output
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function runWranglerVersionUpload(root, options, execute = execFileSync) {
|
|
184
|
+
const { args, env } = buildWranglerVersionUploadArgs(options);
|
|
185
|
+
if (env) console.log(`\n Uploading Worker version for env: ${env}...`);
|
|
186
|
+
else console.log("\n Uploading Worker version for production...");
|
|
187
|
+
try {
|
|
188
|
+
return parseWranglerVersionUploadOutput(runWranglerCommand(root, args, execute));
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (isMissingWorkerVersionUploadError(error)) throw withInitialDeployRequiredMessage();
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function runWranglerVersionDeploy(root, versionTraffic, options, phase = "promote-uploaded", execute = execFileSync) {
|
|
195
|
+
const { args, env } = buildWranglerVersionDeployArgs(versionTraffic, options);
|
|
196
|
+
const target = env ? `env: ${env}` : "production";
|
|
197
|
+
if (phase === "stage") console.log(`\n Staging uploaded Worker version at 0% for CDN warmup in ${target}...`);
|
|
198
|
+
else if (phase === "promote-warmed") console.log(`\n Promoting warmed Worker version to ${target}...`);
|
|
199
|
+
else console.log(`\n Promoting uploaded Worker version to ${target}...`);
|
|
200
|
+
const output = runWranglerCommand(root, args, execute);
|
|
201
|
+
return {
|
|
202
|
+
deployedUrl: parseWorkersDevUrl(output),
|
|
203
|
+
output
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function runWranglerDeploymentStatus(root, options, execute = execFileSync) {
|
|
207
|
+
const { args, env } = buildWranglerDeploymentsStatusArgs(options);
|
|
208
|
+
if (env) console.log(`\n Reading current Worker deployment for env: ${env}...`);
|
|
209
|
+
else console.log("\n Reading current Worker deployment...");
|
|
210
|
+
return parseWranglerDeploymentStatusOutput(runWranglerCommand(root, args, execute));
|
|
211
|
+
}
|
|
212
|
+
function runWranglerTriggersDeploy(root, options, execute = execFileSync) {
|
|
213
|
+
const { args, env } = buildWranglerTriggersDeployArgs(options);
|
|
214
|
+
if (env) console.log(`\n Applying Worker triggers for env: ${env}...`);
|
|
215
|
+
else console.log("\n Applying Worker triggers...");
|
|
216
|
+
const output = runWranglerCommand(root, args, execute);
|
|
217
|
+
return {
|
|
218
|
+
deployedUrl: parseWorkersDevUrl(output),
|
|
219
|
+
output
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
//#endregion
|
|
223
|
+
export { buildWranglerDeploymentsStatusArgs, buildWranglerTriggersDeployArgs, buildWranglerVersionDeployArgs, buildWranglerVersionUploadArgs, parseVersionId, parseWorkersDevUrl, parseWranglerDeploymentStatusOutput, parseWranglerVersionUploadOutput, runWranglerDeploymentStatus, runWranglerTriggersDeploy, runWranglerVersionDeploy, runWranglerVersionUpload };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region src/workers-dev-url.ts
|
|
2
|
+
function parseWorkersDevUrl(output) {
|
|
3
|
+
for (const rawToken of splitWhitespace(output)) {
|
|
4
|
+
const candidate = trimUrlPunctuation(rawToken);
|
|
5
|
+
if (!candidate.startsWith("https://")) continue;
|
|
6
|
+
try {
|
|
7
|
+
const url = new URL(candidate);
|
|
8
|
+
if (url.protocol !== "https:") continue;
|
|
9
|
+
if (url.hostname !== "workers.dev" && !url.hostname.endsWith(".workers.dev")) continue;
|
|
10
|
+
if (url.pathname === "/" && url.search === "" && url.hash === "") return url.origin;
|
|
11
|
+
return url.toString();
|
|
12
|
+
} catch {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
function splitWhitespace(value) {
|
|
19
|
+
const tokens = [];
|
|
20
|
+
let tokenStart = null;
|
|
21
|
+
for (let i = 0; i < value.length; i++) {
|
|
22
|
+
const char = value[i];
|
|
23
|
+
if (char === void 0) continue;
|
|
24
|
+
if (char.trim() === "") {
|
|
25
|
+
if (tokenStart !== null) {
|
|
26
|
+
tokens.push(value.slice(tokenStart, i));
|
|
27
|
+
tokenStart = null;
|
|
28
|
+
}
|
|
29
|
+
} else if (tokenStart === null) tokenStart = i;
|
|
30
|
+
}
|
|
31
|
+
if (tokenStart !== null) tokens.push(value.slice(tokenStart));
|
|
32
|
+
return tokens;
|
|
33
|
+
}
|
|
34
|
+
function trimUrlPunctuation(value) {
|
|
35
|
+
let start = 0;
|
|
36
|
+
let end = value.length;
|
|
37
|
+
while (start < end && "\"'(<".includes(value[start])) start++;
|
|
38
|
+
while (end > start && "\"')>.,;".includes(value[end - 1])) end--;
|
|
39
|
+
return value.slice(start, end);
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { parseWorkersDevUrl };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vinext/cloudflare",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
4
|
"description": "Cloudflare adapters for vinext (KV data cache and Images-backed image optimization).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,6 +33,14 @@
|
|
|
33
33
|
"types": "./dist/tpr.d.ts",
|
|
34
34
|
"import": "./dist/tpr.js"
|
|
35
35
|
},
|
|
36
|
+
"./internal/cdn-warm": {
|
|
37
|
+
"types": "./dist/cdn-warm.d.ts",
|
|
38
|
+
"import": "./dist/cdn-warm.js"
|
|
39
|
+
},
|
|
40
|
+
"./internal/version-deploy": {
|
|
41
|
+
"types": "./dist/version-deploy.d.ts",
|
|
42
|
+
"import": "./dist/version-deploy.js"
|
|
43
|
+
},
|
|
36
44
|
"./cache/*": {
|
|
37
45
|
"types": "./dist/cache/*.d.ts",
|
|
38
46
|
"import": "./dist/cache/*.js"
|
|
@@ -44,8 +52,9 @@
|
|
|
44
52
|
},
|
|
45
53
|
"devDependencies": {
|
|
46
54
|
"@types/node": "^25.9.2",
|
|
47
|
-
"vite
|
|
48
|
-
"
|
|
55
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.2.2",
|
|
56
|
+
"vite-plus": "0.2.2",
|
|
57
|
+
"vinext": "1.0.0-beta.1"
|
|
49
58
|
},
|
|
50
59
|
"peerDependencies": {
|
|
51
60
|
"vinext": ">=0.0.0"
|