@astrale-os/adapter-cloudflare 0.5.0-beta.16 → 0.5.0-beta.161
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 +93 -2
- package/dist/adapter.d.ts +14 -0
- package/dist/adapter.js +82 -0
- package/dist/build/artifact.d.ts +7 -0
- package/dist/build/artifact.js +170 -0
- package/dist/build/bundle/admit.d.ts +1 -0
- package/dist/build/bundle/admit.js +9 -0
- package/dist/build/closure.d.ts +2 -0
- package/dist/build/closure.js +22 -0
- package/dist/build/codegen/execution.d.ts +3 -0
- package/dist/build/codegen/execution.js +2 -0
- package/dist/build/codegen/merge.d.ts +0 -1
- package/dist/build/codegen/merge.js +0 -1
- package/dist/build/codegen/wrangler.d.ts +12 -3
- package/dist/build/codegen/wrangler.js +26 -18
- package/dist/build/configuration/addressing.d.ts +2 -0
- package/dist/build/configuration/addressing.js +17 -0
- package/dist/build/configuration/invocation-timeout.d.ts +2 -0
- package/dist/build/configuration/invocation-timeout.js +9 -0
- package/dist/build/configuration/request-limit.d.ts +2 -0
- package/dist/build/configuration/request-limit.js +9 -0
- package/dist/build/configuration/router.d.ts +7 -0
- package/dist/build/configuration/router.js +11 -0
- package/dist/build/configuration/worker-name.d.ts +2 -0
- package/dist/build/configuration/worker-name.js +18 -0
- package/dist/build/configuration.d.ts +5 -0
- package/dist/build/configuration.js +5 -0
- package/dist/build/direct.d.ts +4 -0
- package/dist/build/direct.js +90 -0
- package/dist/build/frontend/files.d.ts +19 -0
- package/dist/build/frontend/files.js +99 -0
- package/dist/build/frontend/readiness.d.ts +10 -0
- package/dist/build/frontend/readiness.js +21 -0
- package/dist/build/frontend/revision.d.ts +5 -0
- package/dist/build/frontend/revision.js +14 -0
- package/dist/build/frontend/vite.d.ts +14 -0
- package/dist/build/frontend/vite.js +42 -0
- package/dist/build/frontend/vite.process.d.ts +7 -0
- package/dist/build/frontend/vite.process.js +65 -0
- package/dist/build/frontend/vite.runner.d.ts +1 -0
- package/dist/build/frontend/vite.runner.js +155 -0
- package/dist/build/index.d.ts +10 -16
- package/dist/build/index.js +9 -16
- package/dist/build/material.d.ts +10 -0
- package/dist/build/material.js +25 -0
- package/dist/build/materialize.d.ts +4 -0
- package/dist/build/materialize.js +10 -0
- package/dist/build/metafile.d.ts +5 -0
- package/dist/build/metafile.js +14 -0
- package/dist/build/qualification.d.ts +5 -0
- package/dist/build/qualification.js +7 -0
- package/dist/build/roots.d.ts +2 -0
- package/dist/build/roots.js +34 -0
- package/dist/build/worker/artifact.d.ts +40 -0
- package/dist/build/worker/artifact.js +1 -0
- package/dist/build/worker/artifact.load.d.ts +3 -0
- package/dist/build/worker/artifact.load.js +308 -0
- package/dist/build/worker/artifact.manifest.d.ts +48 -0
- package/dist/build/worker/artifact.manifest.js +87 -0
- package/dist/build/worker/artifact.paths.d.ts +4 -0
- package/dist/build/worker/artifact.paths.js +25 -0
- package/dist/build/worker/codegen/entry.d.ts +11 -0
- package/dist/build/worker/codegen/entry.js +68 -0
- package/dist/build/worker/codegen/literal.d.ts +28 -0
- package/dist/build/worker/codegen/literal.js +136 -0
- package/dist/build/worker/codegen/service.d.ts +21 -0
- package/dist/build/worker/codegen/service.js +63 -0
- package/dist/build/worker/execution.prepare.d.ts +20 -0
- package/dist/build/worker/execution.prepare.js +134 -0
- package/dist/build/worker/index.d.ts +2 -0
- package/dist/build/worker/index.js +1 -0
- package/dist/build/worker/modules.d.ts +11 -0
- package/dist/build/worker/modules.js +26 -0
- package/dist/build/worker/runtime.d.ts +10 -0
- package/dist/build/worker/runtime.js +35 -0
- package/dist/build/worker/specifier.d.ts +3 -0
- package/dist/build/worker/specifier.js +10 -0
- package/dist/cloudflare.d.ts +1 -45
- package/dist/cloudflare.js +1 -202
- package/dist/deploy/deploy.d.ts +17 -0
- package/dist/deploy/deploy.js +229 -0
- package/dist/deploy/index.d.ts +1 -0
- package/dist/deploy/index.js +1 -0
- package/dist/deploy/output.d.ts +2 -0
- package/dist/deploy/output.js +10 -0
- package/dist/deploy/readiness.d.ts +5 -0
- package/dist/deploy/readiness.js +21 -0
- package/dist/identity/binding.d.ts +2 -0
- package/dist/identity/binding.js +2 -0
- package/dist/identity/index.d.ts +3 -0
- package/dist/identity/index.js +3 -0
- package/dist/identity/material.d.ts +7 -0
- package/dist/identity/material.js +42 -0
- package/dist/identity/secret.d.ts +3 -0
- package/dist/identity/secret.js +7 -0
- package/dist/identity/source.d.ts +26 -0
- package/dist/identity/source.js +127 -0
- package/dist/index.d.ts +10 -9
- package/dist/index.js +10 -9
- package/dist/params.d.ts +43 -36
- package/dist/params.js +0 -1
- package/dist/release/deployment.d.ts +44 -0
- package/dist/release/deployment.js +141 -0
- package/dist/release/index.d.ts +2 -0
- package/dist/release/index.js +2 -0
- package/dist/release/staged-worker-name.d.ts +3 -0
- package/dist/release/staged-worker-name.js +10 -0
- package/dist/worker/entry.d.ts +31 -0
- package/dist/worker/entry.js +105 -0
- package/dist/worker/fetch.d.ts +0 -1
- package/dist/worker/fetch.js +0 -1
- package/dist/worker/front.d.ts +49 -0
- package/dist/worker/front.js +75 -0
- package/dist/worker/frontend.d.ts +4 -0
- package/dist/worker/frontend.js +83 -0
- package/dist/worker/identity.d.ts +7 -0
- package/dist/worker/identity.js +23 -0
- package/dist/worker/index.d.ts +2 -61
- package/dist/worker/index.js +2 -221
- package/dist/wrangler/bundle.d.ts +10 -0
- package/dist/wrangler/bundle.js +28 -0
- package/dist/wrangler/deploy.d.ts +24 -0
- package/dist/wrangler/deploy.js +44 -0
- package/dist/wrangler/index.d.ts +5 -0
- package/dist/wrangler/index.js +5 -0
- package/dist/wrangler/process.d.ts +15 -0
- package/dist/wrangler/process.js +102 -0
- package/dist/wrangler/rollout.d.ts +27 -0
- package/dist/wrangler/rollout.js +75 -0
- package/dist/wrangler/secrets.d.ts +2 -0
- package/dist/wrangler/secrets.js +24 -0
- package/dist/wrangler/status.d.ts +18 -0
- package/dist/wrangler/status.js +75 -0
- package/dist/wrangler/triggers.d.ts +5 -0
- package/dist/wrangler/triggers.js +12 -0
- package/dist/wrangler/type-generation.d.ts +6 -0
- package/dist/wrangler/type-generation.js +19 -0
- package/dist/wrangler/versions.d.ts +13 -0
- package/dist/wrangler/versions.js +38 -0
- package/package.json +39 -19
- package/template/astrale.config.ts +30 -31
- package/dist/build/codegen/identity.d.ts +0 -10
- package/dist/build/codegen/identity.d.ts.map +0 -1
- package/dist/build/codegen/identity.js +0 -32
- package/dist/build/codegen/identity.js.map +0 -1
- package/dist/build/codegen/merge.d.ts.map +0 -1
- package/dist/build/codegen/merge.js.map +0 -1
- package/dist/build/codegen/worker.d.ts +0 -12
- package/dist/build/codegen/worker.d.ts.map +0 -1
- package/dist/build/codegen/worker.js +0 -40
- package/dist/build/codegen/worker.js.map +0 -1
- package/dist/build/codegen/wrangler.d.ts.map +0 -1
- package/dist/build/codegen/wrangler.js.map +0 -1
- package/dist/build/frontend.d.ts +0 -27
- package/dist/build/frontend.d.ts.map +0 -1
- package/dist/build/frontend.js +0 -252
- package/dist/build/frontend.js.map +0 -1
- package/dist/build/index.d.ts.map +0 -1
- package/dist/build/index.js.map +0 -1
- package/dist/build/parse-output.d.ts +0 -27
- package/dist/build/parse-output.d.ts.map +0 -1
- package/dist/build/parse-output.js +0 -58
- package/dist/build/parse-output.js.map +0 -1
- package/dist/build/prepare.d.ts +0 -18
- package/dist/build/prepare.d.ts.map +0 -1
- package/dist/build/prepare.js +0 -138
- package/dist/build/prepare.js.map +0 -1
- package/dist/build/worker.d.ts +0 -15
- package/dist/build/worker.d.ts.map +0 -1
- package/dist/build/worker.js +0 -21
- package/dist/build/worker.js.map +0 -1
- package/dist/build/wrangler-cli.d.ts +0 -88
- package/dist/build/wrangler-cli.d.ts.map +0 -1
- package/dist/build/wrangler-cli.js +0 -283
- package/dist/build/wrangler-cli.js.map +0 -1
- package/dist/cloudflare.d.ts.map +0 -1
- package/dist/cloudflare.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/params.d.ts.map +0 -1
- package/dist/params.js.map +0 -1
- package/dist/worker/fetch.d.ts.map +0 -1
- package/dist/worker/fetch.js.map +0 -1
- package/dist/worker/index.d.ts.map +0 -1
- package/dist/worker/index.js.map +0 -1
- package/src/build/codegen/identity.ts +0 -40
- package/src/build/codegen/merge.ts +0 -92
- package/src/build/codegen/worker.ts +0 -53
- package/src/build/codegen/wrangler.ts +0 -128
- package/src/build/frontend.ts +0 -298
- package/src/build/index.ts +0 -29
- package/src/build/parse-output.ts +0 -60
- package/src/build/prepare.ts +0 -189
- package/src/build/worker.ts +0 -37
- package/src/build/wrangler-cli.ts +0 -372
- package/src/cloudflare.ts +0 -275
- package/src/index.ts +0 -21
- package/src/params.ts +0 -92
- package/src/worker/fetch.ts +0 -46
- package/src/worker/index.ts +0 -335
- package/src/worker/tsconfig.json +0 -10
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
const WORKER_NAME = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u;
|
|
3
|
+
export function workerName(parameters, origin, environment = '') {
|
|
4
|
+
if (parameters.workerName !== undefined) {
|
|
5
|
+
if (!WORKER_NAME.test(parameters.workerName) || parameters.workerName.length > 63) {
|
|
6
|
+
throw new TypeError('Cloudflare workerName is invalid.');
|
|
7
|
+
}
|
|
8
|
+
return parameters.workerName;
|
|
9
|
+
}
|
|
10
|
+
const readable = origin
|
|
11
|
+
.toLowerCase()
|
|
12
|
+
.replace(/[^a-z0-9]+/gu, '-')
|
|
13
|
+
.slice(0, 12)
|
|
14
|
+
.replace(/^-+|-+$/gu, '') || 'domain';
|
|
15
|
+
const hexadecimal = createHash('sha256').update(`${origin}\0${environment}`).digest('hex');
|
|
16
|
+
const identity = BigInt(`0x${hexadecimal}`).toString(36).padStart(50, '0');
|
|
17
|
+
return `${readable}-${identity}`;
|
|
18
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { deploymentAddressing } from './configuration/addressing.js';
|
|
2
|
+
export { workerInvocationTimeout } from './configuration/invocation-timeout.js';
|
|
3
|
+
export { workerRequestLimit } from './configuration/request-limit.js';
|
|
4
|
+
export { resolveRouter } from './configuration/router.js';
|
|
5
|
+
export { workerName } from './configuration/worker-name.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { deploymentAddressing } from './configuration/addressing.js';
|
|
2
|
+
export { workerInvocationTimeout } from './configuration/invocation-timeout.js';
|
|
3
|
+
export { workerRequestLimit } from './configuration/request-limit.js';
|
|
4
|
+
export { resolveRouter } from './configuration/router.js';
|
|
5
|
+
export { workerName } from './configuration/worker-name.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PreparedArtifact } from '@astrale-os/sdk/deployment/adapter';
|
|
2
|
+
import type { CloudflareParams } from '../params.js';
|
|
3
|
+
import type { WorkerArtifact } from './worker/index.js';
|
|
4
|
+
export declare function admitDirectPlacement(artifact: PreparedArtifact, worker: WorkerArtifact, parameters: CloudflareParams, environment?: string): void;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { generateWranglerConfig } from './codegen/wrangler.js';
|
|
3
|
+
import { resolveRouter, workerName } from './configuration.js';
|
|
4
|
+
import { workerModules } from './worker/modules.js';
|
|
5
|
+
export function admitDirectPlacement(artifact, worker, parameters, environment = '') {
|
|
6
|
+
const name = workerName(parameters, worker.origin, environment);
|
|
7
|
+
const release = config(artifact, 'wrangler.jsonc');
|
|
8
|
+
const router = resolveRouter(parameters.router);
|
|
9
|
+
const frontend = artifact.build.frontend;
|
|
10
|
+
const hasViteFrontend = frontend?.source.kind === 'vite';
|
|
11
|
+
equal(worker.assets !== undefined, hasViteFrontend);
|
|
12
|
+
if (worker.assets !== undefined) {
|
|
13
|
+
equal(worker.assets.notFound, 'none');
|
|
14
|
+
equal(worker.assets.runWorkerFirst, true);
|
|
15
|
+
}
|
|
16
|
+
equal(release, generated({
|
|
17
|
+
workerName: name,
|
|
18
|
+
main: `./${worker.release.entrypoint}`,
|
|
19
|
+
...(parameters.route === undefined ? {} : { route: parameters.route }),
|
|
20
|
+
vars: worker.bindings.vars,
|
|
21
|
+
secretBindings: worker.bindings.requiredSecrets,
|
|
22
|
+
modules: directModules(worker.release),
|
|
23
|
+
...(hasViteFrontend
|
|
24
|
+
? {
|
|
25
|
+
frontend: {
|
|
26
|
+
directory: './frontend',
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
: {}),
|
|
30
|
+
...(router === undefined
|
|
31
|
+
? {}
|
|
32
|
+
: { router: { binding: router.binding, service: router.service } }),
|
|
33
|
+
...(parameters.wrangler === undefined ? {} : { wrangler: parameters.wrangler }),
|
|
34
|
+
selfBinding: false,
|
|
35
|
+
}));
|
|
36
|
+
equal(release.compatibility_date, worker.runtime.compatibilityDate);
|
|
37
|
+
equalStringCollection(release.compatibility_flags, worker.runtime.compatibilityFlags);
|
|
38
|
+
equal(release.services ?? [], worker.bindings.services.map(serviceBinding));
|
|
39
|
+
equal(record(release.assets).html_handling, worker.assets?.htmlHandling);
|
|
40
|
+
}
|
|
41
|
+
function directModules(worker) {
|
|
42
|
+
try {
|
|
43
|
+
return workerModules(worker);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
mismatch();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function generated(input) {
|
|
50
|
+
return JSON.parse(generateWranglerConfig(input));
|
|
51
|
+
}
|
|
52
|
+
function config(artifact, path) {
|
|
53
|
+
const file = artifact.files.find((candidate) => candidate.path === path);
|
|
54
|
+
if (file === undefined)
|
|
55
|
+
throw new TypeError(`Direct Cloudflare artifact is missing ${path}.`);
|
|
56
|
+
try {
|
|
57
|
+
const value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(file.bytes()));
|
|
58
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
59
|
+
throw new Error();
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
catch (cause) {
|
|
63
|
+
throw new TypeError(`Direct Cloudflare artifact ${path} is invalid.`, { cause });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function serviceBinding(binding) {
|
|
67
|
+
return { binding: binding.name, service: binding.service };
|
|
68
|
+
}
|
|
69
|
+
function record(input) {
|
|
70
|
+
return input !== null && typeof input === 'object' && !Array.isArray(input)
|
|
71
|
+
? input
|
|
72
|
+
: {};
|
|
73
|
+
}
|
|
74
|
+
function equal(actual, expected) {
|
|
75
|
+
if (!isDeepStrictEqual(actual, expected)) {
|
|
76
|
+
throw new TypeError('Direct Cloudflare placement differs from its admitted Worker artifact.', {
|
|
77
|
+
cause: { actual, expected },
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function equalStringCollection(actual, expected) {
|
|
82
|
+
if (!Array.isArray(actual) ||
|
|
83
|
+
actual.some((value) => typeof value !== 'string') ||
|
|
84
|
+
!isDeepStrictEqual([...actual].sort(), [...expected].sort())) {
|
|
85
|
+
mismatch();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function mismatch() {
|
|
89
|
+
throw new TypeError('Direct Cloudflare placement differs from its admitted Worker artifact.');
|
|
90
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Build } from '@astrale-os/sdk/deployment/build';
|
|
2
|
+
import { frontend as frontendBuild } from '@astrale-os/sdk/deployment/build';
|
|
3
|
+
/** The served frontend, and which of its files a browser may keep without revalidating. */
|
|
4
|
+
export interface FrontendOutput {
|
|
5
|
+
readonly files: readonly frontendBuild.FrontendFile[];
|
|
6
|
+
/**
|
|
7
|
+
* Served paths the bundler content-addressed, so their bytes can never change under a given name.
|
|
8
|
+
* A frontend Astrale does not build leaves this empty, which is what keeps the absence of evidence
|
|
9
|
+
* indistinguishable from evidence of mutability.
|
|
10
|
+
*/
|
|
11
|
+
readonly addressed: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function frontendFiles(input: {
|
|
14
|
+
readonly frontend: Build['frontend'];
|
|
15
|
+
readonly projectDir: string;
|
|
16
|
+
readonly output: string;
|
|
17
|
+
readonly recordSource?: (path: string) => void;
|
|
18
|
+
readonly signal: AbortSignal;
|
|
19
|
+
}): Promise<FrontendOutput>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { frontend as frontendBuild } from '@astrale-os/sdk/deployment/build';
|
|
2
|
+
import { readFile, readdir, rm } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { buildViteFrontend } from './vite.js';
|
|
5
|
+
/**
|
|
6
|
+
* Root names that Cloudflare Workers Static Assets reads as configuration rather than serving.
|
|
7
|
+
*
|
|
8
|
+
* Wrangler withholds them from a direct upload and applies them, while the managed upload through
|
|
9
|
+
* Services serves them as ordinary public files, so the same output would behave differently per
|
|
10
|
+
* adapter. The Worker runs first on every request and owns frontend response headers and routing for
|
|
11
|
+
* both, which leaves these files nothing to configure.
|
|
12
|
+
*/
|
|
13
|
+
const RESERVED_ROOT_NAMES = new Set([
|
|
14
|
+
'_headers',
|
|
15
|
+
'_redirects',
|
|
16
|
+
'.assetsignore',
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* Name of a Cloudflare Pages worker script, the server-side code of a Pages project.
|
|
20
|
+
*
|
|
21
|
+
* Wrangler refuses to upload a root asset under this name, since that would publish the code, unless
|
|
22
|
+
* an `.assetsignore` exists, and the reserved names above already exclude one. A direct deployment
|
|
23
|
+
* would therefore fail at upload, while the managed upload through Services would publish the code.
|
|
24
|
+
* Neither adapter runs a Pages worker: the Worker is the only server-side code for both.
|
|
25
|
+
*/
|
|
26
|
+
const PAGES_WORKER_NAME = '_worker.js';
|
|
27
|
+
export async function frontendFiles(input) {
|
|
28
|
+
const frontend = input.frontend;
|
|
29
|
+
if (frontend === undefined)
|
|
30
|
+
return { files: [], addressed: [] };
|
|
31
|
+
if (frontend.source.kind === 'external') {
|
|
32
|
+
return { files: frontendBuild.files(frontend, []), addressed: [] };
|
|
33
|
+
}
|
|
34
|
+
await rm(input.output, { recursive: true, force: true });
|
|
35
|
+
const built = await buildViteFrontend({
|
|
36
|
+
projectDir: input.projectDir,
|
|
37
|
+
output: input.output,
|
|
38
|
+
...(frontend.source.config === undefined ? {} : { config: frontend.source.config }),
|
|
39
|
+
recordSource: input.recordSource,
|
|
40
|
+
signal: input.signal,
|
|
41
|
+
});
|
|
42
|
+
const files = frontendBuild.files(frontend, await readFiles(input.output));
|
|
43
|
+
refuseReservedNames(files);
|
|
44
|
+
refusePagesWorker(files);
|
|
45
|
+
// A name the bundler reported but that never reached disk is not a served path; keeping only the
|
|
46
|
+
// intersection means the evidence describes exactly what this deployment will answer with.
|
|
47
|
+
const served = new Set(files.map((file) => file.path));
|
|
48
|
+
return { files, addressed: built.addressed.filter((path) => served.has(path)) };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Refuse output that Cloudflare would read as asset configuration instead of serving it.
|
|
52
|
+
*
|
|
53
|
+
* Only the root is reserved: a nested `docs/_headers` stays an ordinary file. The match ignores case
|
|
54
|
+
* and covers directories, because Wrangler's upload filter withholds any root entry under these names
|
|
55
|
+
* regardless of case, and a case-insensitive filesystem hands it `_Headers` when it reads `_headers`.
|
|
56
|
+
*/
|
|
57
|
+
function refuseReservedNames(files) {
|
|
58
|
+
const reserved = new Set(files
|
|
59
|
+
.map((file) => file.path.split('/')[0])
|
|
60
|
+
.filter((name) => RESERVED_ROOT_NAMES.has(name.toLowerCase())));
|
|
61
|
+
if (reserved.size === 0)
|
|
62
|
+
return;
|
|
63
|
+
const names = [...reserved].join(', ');
|
|
64
|
+
throw new TypeError(`Frontend output contains reserved Cloudflare asset configuration name(s) at its root: ${names}. ` +
|
|
65
|
+
'A direct cloudflare() deployment would apply them as Workers Static Assets configuration, while a managed astrale() deployment would serve them as public files. ' +
|
|
66
|
+
'The Worker owns frontend response headers and routing for both adapters: remove them from the Vite public directory, or stop the build from emitting them.');
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Refuse output that Wrangler would decline to upload as a Cloudflare Pages worker script.
|
|
70
|
+
*
|
|
71
|
+
* Wrangler tests the relative path with a plain `startsWith`, not through its case-insensitive upload
|
|
72
|
+
* filter, so the match here is a case-sensitive prefix of the root name too. It covers a `_worker.js`
|
|
73
|
+
* file or directory and siblings such as `_worker.js.map`, which Wrangler refuses alike, while both
|
|
74
|
+
* adapters serve `_Worker.js` or a nested `docs/_worker.js` as an ordinary file.
|
|
75
|
+
*/
|
|
76
|
+
function refusePagesWorker(files) {
|
|
77
|
+
const workers = new Set(files
|
|
78
|
+
.map((file) => file.path.split('/')[0])
|
|
79
|
+
.filter((name) => name.startsWith(PAGES_WORKER_NAME)));
|
|
80
|
+
if (workers.size === 0)
|
|
81
|
+
return;
|
|
82
|
+
const names = [...workers].join(', ');
|
|
83
|
+
throw new TypeError(`Frontend output contains Cloudflare Pages worker name(s) at its root: ${names}. ` +
|
|
84
|
+
`A direct cloudflare() deployment would fail because Wrangler refuses to upload a root name starting with ${PAGES_WORKER_NAME} as possible server-side code, while a managed astrale() deployment would serve them as public files. ` +
|
|
85
|
+
'The Worker is the only server-side code for both adapters: remove or rename them in the Vite public directory, or stop the build from emitting them.');
|
|
86
|
+
}
|
|
87
|
+
async function readFiles(root, current = '') {
|
|
88
|
+
const entries = await readdir(join(root, current), { withFileTypes: true });
|
|
89
|
+
const files = [];
|
|
90
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
91
|
+
const path = current === '' ? entry.name : `${current}/${entry.name}`;
|
|
92
|
+
if (entry.isDirectory())
|
|
93
|
+
files.push(...(await readFiles(root, path)));
|
|
94
|
+
else if (entry.isFile()) {
|
|
95
|
+
files.push({ path, content: new Uint8Array(await readFile(join(root, path))) });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return files;
|
|
99
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Release } from '@astrale-os/sdk/deployment/release';
|
|
2
|
+
import type { verifyReadiness } from '@astrale-os/sdk/deployment/verify';
|
|
3
|
+
import type { WorkerArtifact } from '../worker/artifact.js';
|
|
4
|
+
type ReadinessFetch = NonNullable<NonNullable<Parameters<typeof verifyReadiness>[1]>['fetch']>;
|
|
5
|
+
/** Publication equality alone cannot prove a frontend-only deployment has propagated. */
|
|
6
|
+
export declare function verifyWorkerFrontend(release: Release, worker: Pick<WorkerArtifact, 'assets'>, options?: {
|
|
7
|
+
readonly fetch?: ReadinessFetch;
|
|
8
|
+
readonly signal?: AbortSignal;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { frontendRevision } from './revision.js';
|
|
2
|
+
/** Publication equality alone cannot prove a frontend-only deployment has propagated. */
|
|
3
|
+
export async function verifyWorkerFrontend(release, worker, options = {}) {
|
|
4
|
+
const frontend = release.build.frontend;
|
|
5
|
+
if (frontend === undefined || frontend.source.kind === 'external')
|
|
6
|
+
return;
|
|
7
|
+
if (worker.assets === undefined)
|
|
8
|
+
throw new Error('Prepared Worker frontend assets are missing.');
|
|
9
|
+
const expected = frontendRevision(worker.assets.files);
|
|
10
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
11
|
+
for (const route of frontend.routes) {
|
|
12
|
+
const response = await fetcher(new URL(route.path, release.addressing.publicUrl).href, {
|
|
13
|
+
redirect: 'error',
|
|
14
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
15
|
+
});
|
|
16
|
+
const matches = response.status === 200 && response.headers.get('X-Astrale-Frontend') === expected;
|
|
17
|
+
await response.body?.cancel();
|
|
18
|
+
if (!matches)
|
|
19
|
+
throw new Error(`Frontend ${route.path} is not serving the prepared browser build yet.`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/** Bind the generated Worker to its exact browser files, independently of Schema revision. */
|
|
3
|
+
export function frontendRevision(files) {
|
|
4
|
+
const hash = createHash('sha256');
|
|
5
|
+
const ordered = files
|
|
6
|
+
.map((file) => ({ path: file.path.replace(/^\//u, ''), bytes: file.bytes }))
|
|
7
|
+
.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
8
|
+
for (const file of ordered) {
|
|
9
|
+
const bytes = file.bytes();
|
|
10
|
+
hash.update(JSON.stringify([file.path, bytes.byteLength]));
|
|
11
|
+
hash.update(bytes);
|
|
12
|
+
}
|
|
13
|
+
return `sha256:${hash.digest('hex')}`;
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a Vite frontend in an isolated process and report its authored inputs, along with the
|
|
3
|
+
* outputs the bundler content-addressed. Those are the files a browser may keep forever, and only
|
|
4
|
+
* the build that named them can say which they are.
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildViteFrontend(input: {
|
|
7
|
+
readonly projectDir: string;
|
|
8
|
+
readonly output: string;
|
|
9
|
+
readonly config?: string;
|
|
10
|
+
readonly recordSource?: (path: string) => void;
|
|
11
|
+
readonly signal: AbortSignal;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
readonly addressed: readonly string[];
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { runViteProcess } from './vite.process.js';
|
|
6
|
+
/**
|
|
7
|
+
* Build a Vite frontend in an isolated process and report its authored inputs, along with the
|
|
8
|
+
* outputs the bundler content-addressed. Those are the files a browser may keep forever, and only
|
|
9
|
+
* the build that named them can say which they are.
|
|
10
|
+
*/
|
|
11
|
+
export async function buildViteFrontend(input) {
|
|
12
|
+
const parent = dirname(input.output);
|
|
13
|
+
await mkdir(parent, { recursive: true });
|
|
14
|
+
const scratch = await mkdtemp(join(parent, 'vite-run-'));
|
|
15
|
+
const evidence = join(scratch, 'sources.json');
|
|
16
|
+
const addressed = join(scratch, 'addressed.json');
|
|
17
|
+
try {
|
|
18
|
+
await runViteProcess({
|
|
19
|
+
cwd: input.projectDir,
|
|
20
|
+
arguments: [runnerPath(), input.output, evidence, input.config ?? '', addressed],
|
|
21
|
+
signal: input.signal,
|
|
22
|
+
});
|
|
23
|
+
const sources = JSON.parse(await readFile(evidence, 'utf8'));
|
|
24
|
+
if (!Array.isArray(sources) || sources.some((path) => typeof path !== 'string')) {
|
|
25
|
+
throw new TypeError('Vite preparation source evidence is invalid.');
|
|
26
|
+
}
|
|
27
|
+
for (const path of sources)
|
|
28
|
+
input.recordSource?.(path);
|
|
29
|
+
const named = JSON.parse(await readFile(addressed, 'utf8'));
|
|
30
|
+
if (!Array.isArray(named) || named.some((path) => typeof path !== 'string')) {
|
|
31
|
+
throw new TypeError('Vite preparation output evidence is invalid.');
|
|
32
|
+
}
|
|
33
|
+
return { addressed: Object.freeze([...named]) };
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
await rm(scratch, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function runnerPath() {
|
|
40
|
+
const built = fileURLToPath(new URL('./vite.runner.js', import.meta.url));
|
|
41
|
+
return existsSync(built) ? built : fileURLToPath(new URL('./vite.runner.ts', import.meta.url));
|
|
42
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
const MAXIMUM_OUTPUT_BYTES = 1024 * 1024;
|
|
3
|
+
const TERMINATION_GRACE_MS = 1_000;
|
|
4
|
+
const VITE_TIMEOUT_MS = 300_000;
|
|
5
|
+
/** Run one bounded, cancellable Vite subprocess. */
|
|
6
|
+
export function runViteProcess(input) {
|
|
7
|
+
if (input.signal.aborted)
|
|
8
|
+
return Promise.reject(abortReason(input.signal));
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const child = spawn(process.execPath, input.arguments, {
|
|
11
|
+
cwd: input.cwd,
|
|
12
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
13
|
+
windowsHide: true,
|
|
14
|
+
});
|
|
15
|
+
let output = '';
|
|
16
|
+
let outputBytes = 0;
|
|
17
|
+
let terminating;
|
|
18
|
+
let escalation;
|
|
19
|
+
const timeout = setTimeout(() => terminate(new Error('Vite process timed out.')), input.timeoutMs ?? VITE_TIMEOUT_MS);
|
|
20
|
+
const abort = () => terminate(abortReason(input.signal));
|
|
21
|
+
input.signal.addEventListener('abort', abort, { once: true });
|
|
22
|
+
const cleanup = () => {
|
|
23
|
+
clearTimeout(timeout);
|
|
24
|
+
if (escalation !== undefined)
|
|
25
|
+
clearTimeout(escalation);
|
|
26
|
+
input.signal.removeEventListener('abort', abort);
|
|
27
|
+
};
|
|
28
|
+
function terminate(cause) {
|
|
29
|
+
if (terminating !== undefined)
|
|
30
|
+
return;
|
|
31
|
+
terminating = cause;
|
|
32
|
+
child.kill('SIGTERM');
|
|
33
|
+
escalation = setTimeout(() => {
|
|
34
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
35
|
+
child.kill('SIGKILL');
|
|
36
|
+
}, TERMINATION_GRACE_MS);
|
|
37
|
+
}
|
|
38
|
+
const collect = (buffer) => {
|
|
39
|
+
outputBytes += buffer.byteLength;
|
|
40
|
+
if (outputBytes > MAXIMUM_OUTPUT_BYTES) {
|
|
41
|
+
terminate(new Error('Vite process exceeded its output limit.'));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
output += buffer.toString();
|
|
45
|
+
};
|
|
46
|
+
child.stdout.on('data', collect);
|
|
47
|
+
child.stderr.on('data', collect);
|
|
48
|
+
child.once('error', (cause) => {
|
|
49
|
+
cleanup();
|
|
50
|
+
reject(terminating ?? cause);
|
|
51
|
+
});
|
|
52
|
+
child.once('close', (code) => {
|
|
53
|
+
cleanup();
|
|
54
|
+
if (terminating !== undefined)
|
|
55
|
+
reject(terminating);
|
|
56
|
+
else if (code === 0)
|
|
57
|
+
resolve();
|
|
58
|
+
else
|
|
59
|
+
reject(new Error(`Vite failed (code ${code ?? 1}):\n${output.slice(-1500)}`));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function abortReason(signal) {
|
|
64
|
+
return signal.reason instanceof Error ? signal.reason : new Error('Vite process cancelled.');
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { readdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
+
const CONFIG_FILES = Object.freeze([
|
|
6
|
+
'vite.config.js',
|
|
7
|
+
'vite.config.mjs',
|
|
8
|
+
'vite.config.ts',
|
|
9
|
+
'vite.config.cjs',
|
|
10
|
+
'vite.config.mts',
|
|
11
|
+
'vite.config.cts',
|
|
12
|
+
]);
|
|
13
|
+
await main();
|
|
14
|
+
async function main() {
|
|
15
|
+
const [output, evidence, configured, addressed] = process.argv.slice(2);
|
|
16
|
+
if (output === undefined || evidence === undefined) {
|
|
17
|
+
throw new TypeError('Vite preparation runner arguments are invalid.');
|
|
18
|
+
}
|
|
19
|
+
const projectDir = process.cwd();
|
|
20
|
+
const vite = await projectVite(projectDir);
|
|
21
|
+
const sources = new Set();
|
|
22
|
+
const named = new Set();
|
|
23
|
+
let publicDir = false;
|
|
24
|
+
let environment = {
|
|
25
|
+
directory: false,
|
|
26
|
+
mode: 'production',
|
|
27
|
+
};
|
|
28
|
+
const config = configured === '' || configured === undefined ? undefined : configured;
|
|
29
|
+
await vite.build({
|
|
30
|
+
logLevel: 'silent',
|
|
31
|
+
...(config === undefined
|
|
32
|
+
? {}
|
|
33
|
+
: { configFile: isAbsolute(config) ? config : resolve(projectDir, config) }),
|
|
34
|
+
build: { outDir: output, emptyOutDir: true },
|
|
35
|
+
plugins: [
|
|
36
|
+
{
|
|
37
|
+
name: 'astrale:preparation-sources',
|
|
38
|
+
configResolved(value) {
|
|
39
|
+
publicDir = value.publicDir;
|
|
40
|
+
environment = { directory: value.envDir, mode: value.mode };
|
|
41
|
+
for (const path of value.configFileDependencies)
|
|
42
|
+
sources.add(path);
|
|
43
|
+
},
|
|
44
|
+
transform(_source, id) {
|
|
45
|
+
const path = physicalModulePath(id);
|
|
46
|
+
if (path !== undefined)
|
|
47
|
+
sources.add(path);
|
|
48
|
+
},
|
|
49
|
+
// The bundler is the only party that knows which outputs it content-addressed: a served path
|
|
50
|
+
// is safe to retain exactly when its name changes with its bytes. Reading that here, from
|
|
51
|
+
// the build that produced them, is what keeps the answer a fact rather than a guess about
|
|
52
|
+
// directory layout or file naming.
|
|
53
|
+
generateBundle(options, bundle) {
|
|
54
|
+
for (const [fileName, entry] of Object.entries(bundle)) {
|
|
55
|
+
if (addressedName(options, entry))
|
|
56
|
+
named.add(fileName);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
for (const path of configCandidates(projectDir, config))
|
|
63
|
+
sources.add(path);
|
|
64
|
+
for (const path of environmentCandidates(environment))
|
|
65
|
+
sources.add(path);
|
|
66
|
+
if (publicDir !== false)
|
|
67
|
+
await collectTree(publicDir, sources);
|
|
68
|
+
await writeFile(evidence, JSON.stringify([...sources]));
|
|
69
|
+
if (addressed !== undefined)
|
|
70
|
+
await writeFile(addressed, JSON.stringify([...named].sort()));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Whether this output's name carries a content hash.
|
|
74
|
+
*
|
|
75
|
+
* Two facts are required, and each is necessary. The output must carry a `name`, which the bundler
|
|
76
|
+
* sets only for the outputs it named itself: a plugin emitting an explicit `fileName` bypasses the
|
|
77
|
+
* template entirely, and such a file can sit anywhere, including beside the bundle's own output. And
|
|
78
|
+
* the template that was applied must embed `[hash]`, since a project is free to strip it.
|
|
79
|
+
*
|
|
80
|
+
* Anything else leaves the answer unknown, and unknown must read as "not content-addressed": losing
|
|
81
|
+
* the optimization costs a revalidation, while claiming it wrongly serves a stale file for as long
|
|
82
|
+
* as the header says, with no way to recall it.
|
|
83
|
+
*/
|
|
84
|
+
function addressedName(options, entry) {
|
|
85
|
+
if (typeof entry.name !== 'string')
|
|
86
|
+
return false;
|
|
87
|
+
const template = entry.type === 'asset'
|
|
88
|
+
? options.assetFileNames
|
|
89
|
+
: entry.isEntry === true
|
|
90
|
+
? options.entryFileNames
|
|
91
|
+
: options.chunkFileNames;
|
|
92
|
+
return typeof template === 'string' && template.includes('[hash]');
|
|
93
|
+
}
|
|
94
|
+
function environmentCandidates(input) {
|
|
95
|
+
const directory = input.directory;
|
|
96
|
+
if (directory === false)
|
|
97
|
+
return [];
|
|
98
|
+
return ['.env', '.env.local', `.env.${input.mode}`, `.env.${input.mode}.local`].map((name) => join(directory, name));
|
|
99
|
+
}
|
|
100
|
+
function physicalModulePath(id) {
|
|
101
|
+
if (id.startsWith('\0'))
|
|
102
|
+
return undefined;
|
|
103
|
+
const path = id.replace(/[?#].*$/u, '');
|
|
104
|
+
if (path.length === 0)
|
|
105
|
+
return undefined;
|
|
106
|
+
if (path.startsWith('file:')) {
|
|
107
|
+
try {
|
|
108
|
+
return fileURLToPath(path);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return isAbsolute(path) ? path : undefined;
|
|
115
|
+
}
|
|
116
|
+
async function projectVite(projectDir) {
|
|
117
|
+
try {
|
|
118
|
+
const manifest = createRequire(join(projectDir, 'package.json')).resolve('vite/package.json');
|
|
119
|
+
const entry = createRequire(manifest).resolve('vite');
|
|
120
|
+
return (await import(pathToFileURL(entry).href));
|
|
121
|
+
}
|
|
122
|
+
catch (cause) {
|
|
123
|
+
throw new TypeError('Vite frontend requires a project-local Vite package.', { cause });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function configCandidates(projectDir, configured) {
|
|
127
|
+
if (configured !== undefined) {
|
|
128
|
+
return [isAbsolute(configured) ? configured : resolve(projectDir, configured)];
|
|
129
|
+
}
|
|
130
|
+
return CONFIG_FILES.map((name) => join(projectDir, name));
|
|
131
|
+
}
|
|
132
|
+
async function collectTree(path, output) {
|
|
133
|
+
output.add(path);
|
|
134
|
+
let entries;
|
|
135
|
+
try {
|
|
136
|
+
entries = await readdir(path, { withFileTypes: true });
|
|
137
|
+
}
|
|
138
|
+
catch (cause) {
|
|
139
|
+
if (errorCode(cause) === 'ENOENT')
|
|
140
|
+
return;
|
|
141
|
+
throw cause;
|
|
142
|
+
}
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
const child = join(path, entry.name);
|
|
145
|
+
if (entry.isDirectory())
|
|
146
|
+
await collectTree(child, output);
|
|
147
|
+
else if (entry.isFile())
|
|
148
|
+
output.add(child);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function errorCode(input) {
|
|
152
|
+
return input !== null && typeof input === 'object' && 'code' in input
|
|
153
|
+
? String(Reflect.get(input, 'code'))
|
|
154
|
+
: undefined;
|
|
155
|
+
}
|
package/dist/build/index.d.ts
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*/
|
|
12
|
-
export { buildFrontend, FRONTEND_STAGE_DIR, startFrontendDev, type FrontendDevHandle, type ViteBuild, type ViteBuildInput, } from './frontend.js';
|
|
13
|
-
export { logTo, prepare } from './prepare.js';
|
|
14
|
-
export { buildCloudflareWorker, type CloudflareWorkerBuild } from './worker.js';
|
|
15
|
-
export { bulkPutSecrets, runWranglerBundle, runWranglerDeploy, runWranglerDev, runWranglerTypes, } from './wrangler-cli.js';
|
|
16
|
-
//# sourceMappingURL=index.d.ts.map
|
|
1
|
+
export { generateBuildMaterialModule } from './material.js';
|
|
2
|
+
export { artifact, materialize, prepareWorkerArtifact } from './artifact.js';
|
|
3
|
+
export { loadWorkerArtifact } from './worker/index.js';
|
|
4
|
+
export type { WorkerArtifact } from './worker/index.js';
|
|
5
|
+
export { resolveRouter, workerName } from './configuration.js';
|
|
6
|
+
export { frontendFiles } from './frontend/files.js';
|
|
7
|
+
export { verifyWorkerFrontend } from './frontend/readiness.js';
|
|
8
|
+
export { qualifyWorkerBundle } from './qualification.js';
|
|
9
|
+
export { assertSingletonRuntimeRoots } from './roots.js';
|
|
10
|
+
export { runWranglerBundle, runWranglerDeploy, runWranglerDeploymentStatus, runWranglerTypes, } from '../wrangler/index.js';
|
package/dist/build/index.js
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* build on so the codegen/bundling/dev logic lives in exactly one place.
|
|
11
|
-
*/
|
|
12
|
-
export { buildFrontend, FRONTEND_STAGE_DIR, startFrontendDev, } from './frontend.js';
|
|
13
|
-
export { logTo, prepare } from './prepare.js';
|
|
14
|
-
export { buildCloudflareWorker } from './worker.js';
|
|
15
|
-
export { bulkPutSecrets, runWranglerBundle, runWranglerDeploy, runWranglerDev, runWranglerTypes, } from './wrangler-cli.js';
|
|
16
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
export { generateBuildMaterialModule } from './material.js';
|
|
2
|
+
export { artifact, materialize, prepareWorkerArtifact } from './artifact.js';
|
|
3
|
+
export { loadWorkerArtifact } from './worker/index.js';
|
|
4
|
+
export { resolveRouter, workerName } from './configuration.js';
|
|
5
|
+
export { frontendFiles } from './frontend/files.js';
|
|
6
|
+
export { verifyWorkerFrontend } from './frontend/readiness.js';
|
|
7
|
+
export { qualifyWorkerBundle } from './qualification.js';
|
|
8
|
+
export { assertSingletonRuntimeRoots } from './roots.js';
|
|
9
|
+
export { runWranglerBundle, runWranglerDeploy, runWranglerDeploymentStatus, runWranglerTypes, } from '../wrangler/index.js';
|