@astrale-os/adapter-cloudflare 0.5.0-beta.15 → 0.5.0-beta.151
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 +46 -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 +38 -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 +9 -16
- package/dist/build/index.js +8 -16
- 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 +2 -0
- package/dist/build/worker/codegen/entry.js +28 -0
- package/dist/build/worker/codegen/service.d.ts +21 -0
- package/dist/build/worker/codegen/service.js +54 -0
- package/dist/build/worker/execution.prepare.d.ts +20 -0
- package/dist/build/worker/execution.prepare.js +121 -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 +1 -0
- package/dist/build/worker/specifier.js +5 -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 +48 -0
- package/dist/worker/entry.js +159 -0
- package/dist/worker/fetch.d.ts +0 -1
- package/dist/worker/fetch.js +0 -1
- package/dist/worker/frontend.d.ts +4 -0
- package/dist/worker/frontend.js +75 -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 -218
- 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 +35 -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 -332
- package/src/worker/tsconfig.json +0 -10
|
@@ -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,38 @@
|
|
|
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
|
+
export async function frontendFiles(input) {
|
|
6
|
+
const frontend = input.frontend;
|
|
7
|
+
if (frontend === undefined)
|
|
8
|
+
return { files: [], addressed: [] };
|
|
9
|
+
if (frontend.source.kind === 'external') {
|
|
10
|
+
return { files: frontendBuild.files(frontend, []), addressed: [] };
|
|
11
|
+
}
|
|
12
|
+
await rm(input.output, { recursive: true, force: true });
|
|
13
|
+
const built = await buildViteFrontend({
|
|
14
|
+
projectDir: input.projectDir,
|
|
15
|
+
output: input.output,
|
|
16
|
+
...(frontend.source.config === undefined ? {} : { config: frontend.source.config }),
|
|
17
|
+
recordSource: input.recordSource,
|
|
18
|
+
signal: input.signal,
|
|
19
|
+
});
|
|
20
|
+
const files = frontendBuild.files(frontend, await readFiles(input.output));
|
|
21
|
+
// A name the bundler reported but that never reached disk is not a served path; keeping only the
|
|
22
|
+
// intersection means the evidence describes exactly what this deployment will answer with.
|
|
23
|
+
const served = new Set(files.map((file) => file.path));
|
|
24
|
+
return { files, addressed: built.addressed.filter((path) => served.has(path)) };
|
|
25
|
+
}
|
|
26
|
+
async function readFiles(root, current = '') {
|
|
27
|
+
const entries = await readdir(join(root, current), { withFileTypes: true });
|
|
28
|
+
const files = [];
|
|
29
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
30
|
+
const path = current === '' ? entry.name : `${current}/${entry.name}`;
|
|
31
|
+
if (entry.isDirectory())
|
|
32
|
+
files.push(...(await readFiles(root, path)));
|
|
33
|
+
else if (entry.isFile()) {
|
|
34
|
+
files.push({ path, content: new Uint8Array(await readFile(join(root, path))) });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return files;
|
|
38
|
+
}
|
|
@@ -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,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, 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 { artifact, materialize, prepareWorkerArtifact } from './artifact.js';
|
|
2
|
+
export { loadWorkerArtifact } from './worker/index.js';
|
|
3
|
+
export type { WorkerArtifact } 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';
|
package/dist/build/index.js
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* adapter API (`@astrale-os/adapter-cloudflare`) — it's the seam two adapters
|
|
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 { artifact, materialize, prepareWorkerArtifact } from './artifact.js';
|
|
2
|
+
export { loadWorkerArtifact } from './worker/index.js';
|
|
3
|
+
export { resolveRouter, workerName } from './configuration.js';
|
|
4
|
+
export { frontendFiles } from './frontend/files.js';
|
|
5
|
+
export { verifyWorkerFrontend } from './frontend/readiness.js';
|
|
6
|
+
export { qualifyWorkerBundle } from './qualification.js';
|
|
7
|
+
export { assertSingletonRuntimeRoots } from './roots.js';
|
|
8
|
+
export { runWranglerBundle, runWranglerDeploy, runWranglerDeploymentStatus, runWranglerTypes, } from '../wrangler/index.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
export async function materialize(root, files) {
|
|
4
|
+
await rm(root, { recursive: true, force: true });
|
|
5
|
+
for (const file of files) {
|
|
6
|
+
const path = join(root, file.path);
|
|
7
|
+
await mkdir(dirname(path), { recursive: true });
|
|
8
|
+
await writeFile(path, file.bytes());
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
/** Decode Wrangler's emitted esbuild graph at the provider-tooling boundary. */
|
|
3
|
+
export async function readWranglerMetafile(path) {
|
|
4
|
+
const source = await readFile(path, 'utf8');
|
|
5
|
+
const value = JSON.parse(source);
|
|
6
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
7
|
+
throw new TypeError('Wrangler metafile is not an object.');
|
|
8
|
+
}
|
|
9
|
+
const inputs = Reflect.get(value, 'inputs');
|
|
10
|
+
if (inputs === null || typeof inputs !== 'object' || Array.isArray(inputs)) {
|
|
11
|
+
throw new TypeError('Wrangler metafile has no input graph.');
|
|
12
|
+
}
|
|
13
|
+
return Object.freeze({ inputs: inputs });
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { assertRuntimeClosure } from './closure.js';
|
|
2
|
+
import { assertSingletonRuntimeRoots } from './roots.js';
|
|
3
|
+
/** Qualify Wrangler's emitted module, not generated source or an inferred graph. */
|
|
4
|
+
export async function qualifyWorkerBundle(evidence) {
|
|
5
|
+
await assertRuntimeClosure(evidence.metafilePath);
|
|
6
|
+
await assertSingletonRuntimeRoots(evidence.metafilePath, evidence.inputRoot);
|
|
7
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { realpath } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { readWranglerMetafile } from './metafile.js';
|
|
4
|
+
const RUNTIME_PACKAGES = [
|
|
5
|
+
'@astrale-os/adapter-cloudflare',
|
|
6
|
+
'@astrale-os/sdk',
|
|
7
|
+
'@astrale-os/kernel-core',
|
|
8
|
+
'@astrale-os/kernel-dsl',
|
|
9
|
+
'@astrale-os/kernel-ports',
|
|
10
|
+
'@astrale-os/kernel-protocol',
|
|
11
|
+
'@astrale-os/kernel-server',
|
|
12
|
+
'@astrale-os/kernel-client',
|
|
13
|
+
];
|
|
14
|
+
/** Prove that Wrangler emitted at most one physical root per runtime package. */
|
|
15
|
+
export async function assertSingletonRuntimeRoots(metafilePath, inputRoot) {
|
|
16
|
+
const metafile = await readWranglerMetafile(metafilePath);
|
|
17
|
+
for (const name of RUNTIME_PACKAGES) {
|
|
18
|
+
const roots = new Set();
|
|
19
|
+
for (const input of Object.keys(metafile.inputs)) {
|
|
20
|
+
const root = packageRoot(input, name);
|
|
21
|
+
if (root !== undefined)
|
|
22
|
+
roots.add(await realpath(resolve(inputRoot, root)));
|
|
23
|
+
}
|
|
24
|
+
if (roots.size > 1) {
|
|
25
|
+
throw new Error(`Cloudflare Worker contains duplicate physical ${name} roots: ${[...roots].join(', ')}.`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function packageRoot(input, name) {
|
|
30
|
+
const normalized = input.replaceAll('\\', '/');
|
|
31
|
+
const marker = `node_modules/${name}/`;
|
|
32
|
+
const index = normalized.lastIndexOf(marker);
|
|
33
|
+
return index < 0 ? undefined : normalized.slice(0, index + marker.length - 1);
|
|
34
|
+
}
|