@statewalker/cellb 0.6.0 → 0.8.0
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 +42 -7
- package/dist/bindings/html-rewriter.d.ts +7 -0
- package/dist/bindings/html-rewriter.js +4 -11
- package/dist/bindings/html-rewriter.js.map +1 -1
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js.map +1 -1
- package/dist/bundle.d.ts +2 -0
- package/dist/bundle.js +5 -0
- package/dist/bundle.js.map +1 -0
- package/dist/core/object-host.d.ts +7 -0
- package/dist/core/object-host.js +40 -7
- package/dist/core/object-host.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/node-compat/browser-registry.d.ts +1 -0
- package/dist/node-compat/browser-registry.js +5 -0
- package/dist/node-compat/browser-registry.js.map +1 -1
- package/dist/node-compat/util-guard.d.ts +2 -0
- package/dist/node-compat/util-guard.js +14 -0
- package/dist/node-compat/util-guard.js.map +1 -0
- package/dist/project/browser-bundle.d.ts +3 -0
- package/dist/project/browser-bundle.js +2 -0
- package/dist/project/browser-bundle.js.map +1 -1
- package/dist/project/browser-host.js +3 -2
- package/dist/project/browser-host.js.map +1 -1
- package/dist/project/browser-load.d.ts +16 -4
- package/dist/project/browser-load.js +46 -9
- package/dist/project/browser-load.js.map +1 -1
- package/dist/project/browser-shards.d.ts +2 -23
- package/dist/project/browser-shards.js +2 -8
- package/dist/project/browser-shards.js.map +1 -1
- package/dist/project/browser-tab-protocol.d.ts +30 -0
- package/dist/project/browser-tab-protocol.js +9 -0
- package/dist/project/browser-tab-protocol.js.map +1 -0
- package/dist/project/browser-tabs.js +1 -1
- package/dist/project/browser-tabs.js.map +1 -1
- package/dist/project/build.js +3 -2
- package/dist/project/build.js.map +1 -1
- package/dist/project/bundle-compiler.d.ts +31 -0
- package/dist/project/bundle-compiler.js +73 -0
- package/dist/project/bundle-compiler.js.map +1 -0
- package/dist/project/module-rules.d.ts +26 -0
- package/dist/project/module-rules.js +131 -0
- package/dist/project/module-rules.js.map +1 -0
- package/dist/project/node-load.js +2 -1
- package/dist/project/node-load.js.map +1 -1
- package/dist/project/node-project.d.ts +3 -0
- package/dist/project/node-project.js +7 -3
- package/dist/project/node-project.js.map +1 -1
- package/dist/project/site-compiler.d.ts +67 -0
- package/dist/project/site-compiler.js +134 -0
- package/dist/project/site-compiler.js.map +1 -0
- package/dist/project/source-project.d.ts +46 -0
- package/dist/project/source-project.js +45 -0
- package/dist/project/source-project.js.map +1 -0
- package/dist/project/wrangler.d.ts +31 -3
- package/dist/project/wrangler.js +52 -23
- package/dist/project/wrangler.js.map +1 -1
- package/dist/site.d.ts +2 -0
- package/dist/site.js +4 -3
- package/dist/site.js.map +1 -1
- package/dist/tabs.d.ts +1 -0
- package/dist/tabs.js +8 -0
- package/dist/tabs.js.map +1 -0
- package/package.json +15 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { HostedSiteBuilder } from "@statewalker/webrun-site-host";
|
|
2
|
+
import { linkOverPort, serveHandlerOverPort } from "../transport/port.js";
|
|
3
|
+
import { createProjectSite } from "./site.js";
|
|
4
|
+
/** The default host: a ServiceWorker registered from this realm. */
|
|
5
|
+
function serviceWorkerHost(serviceWorkerUrl) {
|
|
6
|
+
return async (handler) => {
|
|
7
|
+
if (!globalThis.navigator?.serviceWorker)
|
|
8
|
+
throw new Error("siteCompiler: this realm cannot register a ServiceWorker (a Dedicated Worker cannot): " +
|
|
9
|
+
"pass `host: siteHostOverPort(port)` and call `hostSitesForWorker(port)` in the page");
|
|
10
|
+
const builder = new HostedSiteBuilder().setHandler(handler);
|
|
11
|
+
if (serviceWorkerUrl)
|
|
12
|
+
builder.setServiceWorkerUrl(serviceWorkerUrl);
|
|
13
|
+
const site = await builder.build();
|
|
14
|
+
return { baseUrl: site.baseUrl, stop: () => site.stop() };
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A compiler for `loadProject(files, { compile })` that serves the project's modules from an
|
|
19
|
+
* in-browser HTTP server (`createProjectSite`): `main` and Worker Loader code are imported from
|
|
20
|
+
* it, so a change can rebuild one module. The site stops when the project closes.
|
|
21
|
+
*/
|
|
22
|
+
export function siteCompiler(options = {}) {
|
|
23
|
+
const host = options.host ?? serviceWorkerHost(options.serviceWorkerUrl);
|
|
24
|
+
return {
|
|
25
|
+
kind: "site",
|
|
26
|
+
async compile({ files, nodeCompat }) {
|
|
27
|
+
let nodeModules;
|
|
28
|
+
if (nodeCompat) {
|
|
29
|
+
// The served `node:*` proxies take their export names from the registry entries, which
|
|
30
|
+
// the browser host binds in this realm; the npm `util` polyfill reads `process` when it
|
|
31
|
+
// is imported.
|
|
32
|
+
(await import("../node-compat/install.js")).installNodeGlobals();
|
|
33
|
+
nodeModules = (await import("../node-compat/browser-registry.js")).nodeRegistryEntries;
|
|
34
|
+
}
|
|
35
|
+
const handler = createProjectSite({
|
|
36
|
+
projectFiles: files,
|
|
37
|
+
nodeCompat,
|
|
38
|
+
nodeModules,
|
|
39
|
+
lowerAwait: options.lowerAwait,
|
|
40
|
+
});
|
|
41
|
+
const site = await host(handler);
|
|
42
|
+
const baseUrl = site.baseUrl.endsWith("/") ? site.baseUrl : `${site.baseUrl}/`;
|
|
43
|
+
return { code: { site: baseUrl }, close: () => site.stop() };
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const HOST_SITE = "cellb:host-site";
|
|
48
|
+
const STOP_SITE = "cellb:stop-site";
|
|
49
|
+
/** The next message on `port`, or `{ error }` after `timeoutMs` (a page that never answers). */
|
|
50
|
+
const once = (port, timeoutMs) => new Promise((resolve) => {
|
|
51
|
+
const timer = setTimeout(() => resolve({ error: `no answer in ${timeoutMs} ms (does the page run hostSitesForWorker?)` }), timeoutMs);
|
|
52
|
+
port.onmessage = (e) => {
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
resolve(e.data);
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Worker side: hosts each site through the page at the other end of `control`, which runs
|
|
59
|
+
* `hostSitesForWorker`. The handler stays in this worker; the page's ServiceWorker forwards each
|
|
60
|
+
* request to it.
|
|
61
|
+
*/
|
|
62
|
+
export function siteHostOverPort(control,
|
|
63
|
+
/**
|
|
64
|
+
* How long the page may take to host the site (default 30 s; a ServiceWorker's first install
|
|
65
|
+
* can be slow), and to confirm it stopped one (default 2 s; the worker stops serving anyway).
|
|
66
|
+
*/
|
|
67
|
+
{ timeoutMs = 30_000, stopTimeoutMs = 2_000, } = {}) {
|
|
68
|
+
return async (handler) => {
|
|
69
|
+
const link = new MessageChannel();
|
|
70
|
+
const reply = new MessageChannel();
|
|
71
|
+
const stopServing = await serveHandlerOverPort(link.port1, handler);
|
|
72
|
+
const answered = once(reply.port1, timeoutMs);
|
|
73
|
+
const request = { type: HOST_SITE, link: link.port2, reply: reply.port2 };
|
|
74
|
+
control.postMessage(request, [link.port2, reply.port2]);
|
|
75
|
+
const { baseUrl, error } = await answered;
|
|
76
|
+
if (!baseUrl) {
|
|
77
|
+
// A page that hosts the site after all finds this stop waiting for it, and takes the site
|
|
78
|
+
// down (its port stays open for that).
|
|
79
|
+
reply.port1.onmessage = null;
|
|
80
|
+
reply.port1.postMessage({ type: STOP_SITE });
|
|
81
|
+
await stopServing();
|
|
82
|
+
throw new Error(`the page did not host the site: ${error}`);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
baseUrl,
|
|
86
|
+
stop: async () => {
|
|
87
|
+
// A page that does not answer stops nothing: the worker stops serving all the same.
|
|
88
|
+
const stopped = once(reply.port1, stopTimeoutMs);
|
|
89
|
+
reply.port1.postMessage({ type: STOP_SITE });
|
|
90
|
+
await stopped;
|
|
91
|
+
reply.port1.close();
|
|
92
|
+
await stopServing();
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Page side: hosts in this page's ServiceWorker the sites a worker asks for over `control`
|
|
99
|
+
* (`siteHostOverPort`). Returns a function that stops listening; the sites stop when the worker
|
|
100
|
+
* stops them.
|
|
101
|
+
*/
|
|
102
|
+
export function hostSitesForWorker(control, options = {}) {
|
|
103
|
+
const listener = async (e) => {
|
|
104
|
+
const req = e.data;
|
|
105
|
+
if (req?.type !== HOST_SITE)
|
|
106
|
+
return;
|
|
107
|
+
try {
|
|
108
|
+
// The worker may give up waiting (`timeoutMs`) and ask to stop before the site is up: the
|
|
109
|
+
// stop then waits on `reply` until the handler below reads it.
|
|
110
|
+
const link = await linkOverPort(req.link);
|
|
111
|
+
const builder = new HostedSiteBuilder().setHandler(link.send);
|
|
112
|
+
if (options.serviceWorkerUrl)
|
|
113
|
+
builder.setServiceWorkerUrl(options.serviceWorkerUrl);
|
|
114
|
+
const site = await builder.build();
|
|
115
|
+
req.reply.onmessage = async (m) => {
|
|
116
|
+
if (m.data?.type !== STOP_SITE)
|
|
117
|
+
return;
|
|
118
|
+
await site.stop().catch(() => { });
|
|
119
|
+
await link.close().catch(() => { });
|
|
120
|
+
req.reply.postMessage({ stopped: true });
|
|
121
|
+
req.reply.close();
|
|
122
|
+
};
|
|
123
|
+
req.reply.postMessage({ baseUrl: site.baseUrl });
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
req.reply.postMessage({ error: String(err?.message ?? err) });
|
|
127
|
+
req.reply.close();
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
control.addEventListener("message", listener);
|
|
131
|
+
control.start?.();
|
|
132
|
+
return () => control.removeEventListener("message", listener);
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=site-compiler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-compiler.js","sourceRoot":"","sources":["../../src/project/site-compiler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `siteCompiler`: a project's sources served by an in-browser HTTP server (`createProjectSite`,\n * hosted in a ServiceWorker by webrun-site-host), for `loadProject(files, { compile })`. Modules\n * are compiled one by one as the browser imports them; nothing is bundled.\n *\n * `loadProject` runs in a Dedicated Worker, which cannot register a ServiceWorker: the site is\n * hosted by a page. `siteHostOverPort` (worker) and `hostSitesForWorker` (page) do that over a\n * MessagePort: the worker serves the site's handler, the page mounts it in its ServiceWorker.\n */\nimport type { SiteHandler } from \"@statewalker/webrun-site-builder\";\nimport { HostedSiteBuilder } from \"@statewalker/webrun-site-host\";\nimport { linkOverPort, serveHandlerOverPort } from \"../transport/port.js\";\nimport { createProjectSite } from \"./site.js\";\nimport type { ProjectCompiler } from \"./source-project.js\";\n\n/** A site, hosted: its base URL (ending in \"/\") and how to take it down. */\nexport interface HostedProjectSite {\n baseUrl: string;\n stop(): Promise<void>;\n}\n\n/** Hosts a site handler where this realm's code can `import()` from it. */\nexport type SiteHost = (handler: SiteHandler) => Promise<HostedProjectSite>;\n\nexport interface SiteCompilerOptions {\n /**\n * Where the site is hosted. Default: a ServiceWorker this realm registers (`HostedSiteBuilder`),\n * which a page can do and a Dedicated Worker cannot; in a worker, pass `siteHostOverPort(port)`\n * and call `hostSitesForWorker(port)` in the page.\n */\n host?: SiteHost;\n /** The ServiceWorker script for the default host (default: `/sw-worker.js`). */\n serviceWorkerUrl?: string;\n /** `createProjectSite`'s `lowerAwait` (default: with nodejs_compat, unless AsyncContext). */\n lowerAwait?: boolean;\n}\n\n/** The default host: a ServiceWorker registered from this realm. */\nfunction serviceWorkerHost(serviceWorkerUrl?: string): SiteHost {\n return async (handler) => {\n if (!(globalThis as { navigator?: { serviceWorker?: unknown } }).navigator?.serviceWorker)\n throw new Error(\n \"siteCompiler: this realm cannot register a ServiceWorker (a Dedicated Worker cannot): \" +\n \"pass `host: siteHostOverPort(port)` and call `hostSitesForWorker(port)` in the page\",\n );\n const builder = new HostedSiteBuilder().setHandler(handler);\n if (serviceWorkerUrl) builder.setServiceWorkerUrl(serviceWorkerUrl);\n const site = await builder.build();\n return { baseUrl: site.baseUrl, stop: () => site.stop() };\n };\n}\n\n/**\n * A compiler for `loadProject(files, { compile })` that serves the project's modules from an\n * in-browser HTTP server (`createProjectSite`): `main` and Worker Loader code are imported from\n * it, so a change can rebuild one module. The site stops when the project closes.\n */\nexport function siteCompiler(options: SiteCompilerOptions = {}): ProjectCompiler {\n const host = options.host ?? serviceWorkerHost(options.serviceWorkerUrl);\n return {\n kind: \"site\",\n async compile({ files, nodeCompat }) {\n let nodeModules: Record<string, unknown> | undefined;\n if (nodeCompat) {\n // The served `node:*` proxies take their export names from the registry entries, which\n // the browser host binds in this realm; the npm `util` polyfill reads `process` when it\n // is imported.\n (await import(\"../node-compat/install.js\")).installNodeGlobals();\n nodeModules = (await import(\"../node-compat/browser-registry.js\")).nodeRegistryEntries;\n }\n const handler = createProjectSite({\n projectFiles: files,\n nodeCompat,\n nodeModules,\n lowerAwait: options.lowerAwait,\n });\n const site = await host(handler);\n const baseUrl = site.baseUrl.endsWith(\"/\") ? site.baseUrl : `${site.baseUrl}/`;\n return { code: { site: baseUrl }, close: () => site.stop() };\n },\n };\n}\n\nconst HOST_SITE = \"cellb:host-site\";\nconst STOP_SITE = \"cellb:stop-site\";\n\n/**\n * Where a worker and its page talk about sites: a MessagePort, the `Worker` (page side) or the\n * worker's own scope (`self`). Other messages on it are left alone.\n */\nexport interface SiteControl {\n postMessage(message: unknown, transfer: Transferable[]): void;\n addEventListener(type: \"message\", listener: (e: MessageEvent) => void): void;\n removeEventListener(type: \"message\", listener: (e: MessageEvent) => void): void;\n}\n\n/** What a worker asks its page for, on the control port. */\ninterface HostSiteRequest {\n type: typeof HOST_SITE;\n /** The worker serves the site's handler on this port's other end. */\n link: MessagePort;\n /** The page answers here, and the worker asks to stop here. */\n reply: MessagePort;\n}\n\n/** The next message on `port`, or `{ error }` after `timeoutMs` (a page that never answers). */\nconst once = (port: MessagePort, timeoutMs: number) =>\n new Promise<{ baseUrl?: string; error?: string }>((resolve) => {\n const timer = setTimeout(\n () =>\n resolve({ error: `no answer in ${timeoutMs} ms (does the page run hostSitesForWorker?)` }),\n timeoutMs,\n );\n port.onmessage = (e) => {\n clearTimeout(timer);\n resolve(e.data);\n };\n });\n\n/**\n * Worker side: hosts each site through the page at the other end of `control`, which runs\n * `hostSitesForWorker`. The handler stays in this worker; the page's ServiceWorker forwards each\n * request to it.\n */\nexport function siteHostOverPort(\n control: SiteControl,\n /**\n * How long the page may take to host the site (default 30 s; a ServiceWorker's first install\n * can be slow), and to confirm it stopped one (default 2 s; the worker stops serving anyway).\n */\n {\n timeoutMs = 30_000,\n stopTimeoutMs = 2_000,\n }: { timeoutMs?: number; stopTimeoutMs?: number } = {},\n): SiteHost {\n return async (handler) => {\n const link = new MessageChannel();\n const reply = new MessageChannel();\n const stopServing = await serveHandlerOverPort(link.port1, handler);\n const answered = once(reply.port1, timeoutMs);\n const request: HostSiteRequest = { type: HOST_SITE, link: link.port2, reply: reply.port2 };\n control.postMessage(request, [link.port2, reply.port2]);\n const { baseUrl, error } = await answered;\n if (!baseUrl) {\n // A page that hosts the site after all finds this stop waiting for it, and takes the site\n // down (its port stays open for that).\n reply.port1.onmessage = null;\n reply.port1.postMessage({ type: STOP_SITE });\n await stopServing();\n throw new Error(`the page did not host the site: ${error}`);\n }\n return {\n baseUrl,\n stop: async () => {\n // A page that does not answer stops nothing: the worker stops serving all the same.\n const stopped = once(reply.port1, stopTimeoutMs);\n reply.port1.postMessage({ type: STOP_SITE });\n await stopped;\n reply.port1.close();\n await stopServing();\n },\n };\n };\n}\n\n/**\n * Page side: hosts in this page's ServiceWorker the sites a worker asks for over `control`\n * (`siteHostOverPort`). Returns a function that stops listening; the sites stop when the worker\n * stops them.\n */\nexport function hostSitesForWorker(\n control: SiteControl,\n options: { serviceWorkerUrl?: string } = {},\n): () => void {\n const listener = async (e: MessageEvent) => {\n const req = e.data as HostSiteRequest | undefined;\n if (req?.type !== HOST_SITE) return;\n try {\n // The worker may give up waiting (`timeoutMs`) and ask to stop before the site is up: the\n // stop then waits on `reply` until the handler below reads it.\n const link = await linkOverPort(req.link);\n const builder = new HostedSiteBuilder().setHandler(link.send);\n if (options.serviceWorkerUrl) builder.setServiceWorkerUrl(options.serviceWorkerUrl);\n const site = await builder.build();\n req.reply.onmessage = async (m) => {\n if ((m.data as { type?: string })?.type !== STOP_SITE) return;\n await site.stop().catch(() => {});\n await link.close().catch(() => {});\n req.reply.postMessage({ stopped: true });\n req.reply.close();\n };\n req.reply.postMessage({ baseUrl: site.baseUrl });\n } catch (err) {\n req.reply.postMessage({ error: String((err as Error)?.message ?? err) });\n req.reply.close();\n }\n };\n control.addEventListener(\"message\", listener);\n (control as { start?: () => void }).start?.();\n return () => control.removeEventListener(\"message\", listener);\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A project's sources (`wrangler.jsonc`, `main`, `package.json`, assets) in a FilesApi, and the
|
|
3
|
+
* compilers that turn them into code `browserProjectRuntime` runs (`loadProject`'s `compile`).
|
|
4
|
+
*
|
|
5
|
+
* Platform-neutral and free of compilers: a compiler is made by the entry point that owns it
|
|
6
|
+
* (`bundleCompiler` in `@statewalker/cellb/bundle`, `siteCompiler` in `@statewalker/cellb/site`),
|
|
7
|
+
* so a plain `loadProject` ships neither esbuild-wasm nor webrun-modules.
|
|
8
|
+
*/
|
|
9
|
+
import type { FilesApi } from "@statewalker/webrun-files";
|
|
10
|
+
import type { AssetFiles } from "../bindings/assets.js";
|
|
11
|
+
import { hasNodeCompat, WRANGLER_FILES, type WranglerConfig } from "./wrangler.js";
|
|
12
|
+
export { hasNodeCompat, WRANGLER_FILES };
|
|
13
|
+
/** A source project read from a FilesApi: what a compiler needs besides the files. */
|
|
14
|
+
export interface SourceProject {
|
|
15
|
+
config: WranglerConfig;
|
|
16
|
+
/** The files of `assets.directory` ("/index.html" → bytes), or undefined without assets. */
|
|
17
|
+
assets?: AssetFiles;
|
|
18
|
+
}
|
|
19
|
+
/** What a compiler gives `browserProjectRuntime`: the project's code, and what to stop after. */
|
|
20
|
+
export interface CompiledProject {
|
|
21
|
+
/** `browserProjectRuntime`'s `code`. */
|
|
22
|
+
code: {
|
|
23
|
+
bundle: string | null;
|
|
24
|
+
} | {
|
|
25
|
+
site: string;
|
|
26
|
+
};
|
|
27
|
+
/** Releases what the compiler holds (a site's ServiceWorker registration); after `close()`. */
|
|
28
|
+
close?(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/** Turns a source project into code for `browserProjectRuntime` (`loadProject`'s `compile`). */
|
|
31
|
+
export interface ProjectCompiler {
|
|
32
|
+
/** A name for messages ("bundle", "site"). */
|
|
33
|
+
readonly kind: string;
|
|
34
|
+
compile(project: {
|
|
35
|
+
/** The project's files, its configuration at the root. */
|
|
36
|
+
files: FilesApi;
|
|
37
|
+
config: WranglerConfig;
|
|
38
|
+
/** Whether the project has `nodejs_compat` (or a `nodejs_compat_*` flag). */
|
|
39
|
+
nodeCompat: boolean;
|
|
40
|
+
}): Promise<CompiledProject>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Reads a source folder at the root of `files`: its `wrangler.jsonc` (or `wrangler.json`) and the
|
|
44
|
+
* files of its `assets.directory`. Undefined when there is no configuration.
|
|
45
|
+
*/
|
|
46
|
+
export declare function readSourceProject(files: FilesApi): Promise<SourceProject | undefined>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { filesApiReader, readJson } from "./built-project.js";
|
|
2
|
+
import { hasNodeCompat, WRANGLER_FILES } from "./wrangler.js";
|
|
3
|
+
export { hasNodeCompat, WRANGLER_FILES };
|
|
4
|
+
/**
|
|
5
|
+
* Reads a source folder at the root of `files`: its `wrangler.jsonc` (or `wrangler.json`) and the
|
|
6
|
+
* files of its `assets.directory`. Undefined when there is no configuration.
|
|
7
|
+
*/
|
|
8
|
+
export async function readSourceProject(files) {
|
|
9
|
+
const reader = filesApiReader(files);
|
|
10
|
+
let config;
|
|
11
|
+
for (const name of WRANGLER_FILES) {
|
|
12
|
+
config = await readJson(reader, name);
|
|
13
|
+
if (config)
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
if (!config)
|
|
17
|
+
return undefined;
|
|
18
|
+
if (!config.main && !config.assets)
|
|
19
|
+
throw new Error("wrangler config has neither `main` nor `assets`");
|
|
20
|
+
if (!config.assets?.directory)
|
|
21
|
+
return { config };
|
|
22
|
+
const dir = folderPath(config.assets.directory);
|
|
23
|
+
const paths = await reader.list(dir || "/");
|
|
24
|
+
const assets = new Map();
|
|
25
|
+
await Promise.all(paths.map(async (path) => {
|
|
26
|
+
const bytes = await reader.bytes(`${dir}${path}`);
|
|
27
|
+
if (bytes)
|
|
28
|
+
assets.set(path, bytes);
|
|
29
|
+
}));
|
|
30
|
+
return { config, assets };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A folder of the project as a path from its root, "" for the root itself: "./public/" →
|
|
34
|
+
* "/public", "." → "", "a/../b" → "/b" (as `path.resolve` takes `assets.directory` on Node).
|
|
35
|
+
*/
|
|
36
|
+
function folderPath(dir) {
|
|
37
|
+
const parts = [];
|
|
38
|
+
for (const part of dir.split("/"))
|
|
39
|
+
if (part === "..")
|
|
40
|
+
parts.pop();
|
|
41
|
+
else if (part && part !== ".")
|
|
42
|
+
parts.push(part);
|
|
43
|
+
return parts.map((p) => `/${p}`).join("");
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=source-project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-project.js","sourceRoot":"","sources":["../../src/project/source-project.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,cAAc,EAAuB,MAAM,eAAe,CAAC;AAEnF,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC;AA8BzC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAAe;IACrD,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACrC,IAAI,MAAkC,CAAC;IACvC,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,MAAM,GAAG,MAAM,QAAQ,CAAiB,MAAM,EAAE,IAAI,CAAC,CAAC;QACtD,IAAI,MAAM;YAAE,MAAM;IACpB,CAAC;IACD,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM;QAChC,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACrE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IACjD,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,MAAO,MAAM,CAAC,IAAwC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;IACjF,MAAM,MAAM,GAAe,IAAI,GAAG,EAAE,CAAC;IACrC,MAAM,OAAO,CAAC,GAAG,CACf,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACvB,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,IAAI,EAAE,CAAC,CAAC;QAClD,IAAI,KAAK;YAAE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC,CAAC,CACH,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU,CAAC,GAAW;IAC7B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC;QAC/B,IAAI,IAAI,KAAK,IAAI;YAAE,KAAK,CAAC,GAAG,EAAE,CAAC;aAC1B,IAAI,IAAI,IAAI,IAAI,KAAK,GAAG;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["/**\n * A project's sources (`wrangler.jsonc`, `main`, `package.json`, assets) in a FilesApi, and the\n * compilers that turn them into code `browserProjectRuntime` runs (`loadProject`'s `compile`).\n *\n * Platform-neutral and free of compilers: a compiler is made by the entry point that owns it\n * (`bundleCompiler` in `@statewalker/cellb/bundle`, `siteCompiler` in `@statewalker/cellb/site`),\n * so a plain `loadProject` ships neither esbuild-wasm nor webrun-modules.\n */\nimport type { FilesApi } from \"@statewalker/webrun-files\";\nimport type { AssetFiles } from \"../bindings/assets.js\";\nimport { filesApiReader, readJson } from \"./built-project.js\";\nimport { hasNodeCompat, WRANGLER_FILES, type WranglerConfig } from \"./wrangler.js\";\n\nexport { hasNodeCompat, WRANGLER_FILES };\n\n/** A source project read from a FilesApi: what a compiler needs besides the files. */\nexport interface SourceProject {\n config: WranglerConfig;\n /** The files of `assets.directory` (\"/index.html\" → bytes), or undefined without assets. */\n assets?: AssetFiles;\n}\n\n/** What a compiler gives `browserProjectRuntime`: the project's code, and what to stop after. */\nexport interface CompiledProject {\n /** `browserProjectRuntime`'s `code`. */\n code: { bundle: string | null } | { site: string };\n /** Releases what the compiler holds (a site's ServiceWorker registration); after `close()`. */\n close?(): Promise<void>;\n}\n\n/** Turns a source project into code for `browserProjectRuntime` (`loadProject`'s `compile`). */\nexport interface ProjectCompiler {\n /** A name for messages (\"bundle\", \"site\"). */\n readonly kind: string;\n compile(project: {\n /** The project's files, its configuration at the root. */\n files: FilesApi;\n config: WranglerConfig;\n /** Whether the project has `nodejs_compat` (or a `nodejs_compat_*` flag). */\n nodeCompat: boolean;\n }): Promise<CompiledProject>;\n}\n\n/**\n * Reads a source folder at the root of `files`: its `wrangler.jsonc` (or `wrangler.json`) and the\n * files of its `assets.directory`. Undefined when there is no configuration.\n */\nexport async function readSourceProject(files: FilesApi): Promise<SourceProject | undefined> {\n const reader = filesApiReader(files);\n let config: WranglerConfig | undefined;\n for (const name of WRANGLER_FILES) {\n config = await readJson<WranglerConfig>(reader, name);\n if (config) break;\n }\n if (!config) return undefined;\n if (!config.main && !config.assets)\n throw new Error(\"wrangler config has neither `main` nor `assets`\");\n if (!config.assets?.directory) return { config };\n const dir = folderPath(config.assets.directory);\n const paths = await (reader.list as NonNullable<typeof reader.list>)(dir || \"/\");\n const assets: AssetFiles = new Map();\n await Promise.all(\n paths.map(async (path) => {\n const bytes = await reader.bytes(`${dir}${path}`);\n if (bytes) assets.set(path, bytes);\n }),\n );\n return { config, assets };\n}\n\n/**\n * A folder of the project as a path from its root, \"\" for the root itself: \"./public/\" →\n * \"/public\", \".\" → \"\", \"a/../b\" → \"/b\" (as `path.resolve` takes `assets.directory` on Node).\n */\nfunction folderPath(dir: string): string {\n const parts: string[] = [];\n for (const part of dir.split(\"/\"))\n if (part === \"..\") parts.pop();\n else if (part && part !== \".\") parts.push(part);\n return parts.map((p) => `/${p}`).join(\"\");\n}\n"]}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { FilesApi } from "@statewalker/webrun-files";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import type { AssetFiles, AssetsConfig } from "../bindings/assets.js";
|
|
3
|
+
import type { QueueConsumerConfig } from "../bindings/celld-queues.js";
|
|
4
|
+
import type { ModuleLoader } from "../bindings/worker-loader.js";
|
|
5
5
|
import { type WorkersAiOptions } from "../bindings/workers-ai.js";
|
|
6
6
|
import { type CellRuntime, type CellRuntimeOptions, type WorkerModule } from "../core/runtime.js";
|
|
7
|
+
import type { ModuleRule } from "./module-rules.js";
|
|
7
8
|
/** The subset of wrangler.json(c) that celld deploys (and cellb reads). */
|
|
8
9
|
export interface WranglerConfig {
|
|
9
10
|
name?: string;
|
|
@@ -67,6 +68,8 @@ export interface WranglerConfig {
|
|
|
67
68
|
ai?: {
|
|
68
69
|
binding: string;
|
|
69
70
|
};
|
|
71
|
+
/** Module rules: how imports of non-JS files load (`CompiledWasm`, `Data`, `Text`). */
|
|
72
|
+
rules?: ModuleRule[];
|
|
70
73
|
/** Containers (celld runs them with Docker or Podman): not supported, reported. */
|
|
71
74
|
containers?: {
|
|
72
75
|
class_name: string;
|
|
@@ -90,7 +93,28 @@ export interface ProjectHost extends Pick<CellRuntimeOptions, "openSql" | "stora
|
|
|
90
93
|
files: FilesApi;
|
|
91
94
|
/** The OpenAI-compatible endpoint behind the `ai` binding; without it the binding is unsupported. */
|
|
92
95
|
ai?: WorkersAiOptions;
|
|
96
|
+
/** The binding implementations the config needs: `await loadProjectBindings(config)`. */
|
|
97
|
+
bindings?: ProjectBindings;
|
|
93
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* The implementations of the bindings a project declares, loaded on demand: a project with
|
|
101
|
+
* Durable Objects only loads none of them (Miniflare's D1 simulator, celld's KV, queue and
|
|
102
|
+
* workflow code, R2, assets, cron and Worker Loaders), and a browser bundle keeps them in chunks
|
|
103
|
+
* of their own.
|
|
104
|
+
*/
|
|
105
|
+
export interface ProjectBindings {
|
|
106
|
+
kv?: typeof import("../bindings/celld-kv.js");
|
|
107
|
+
d1?: typeof import("../miniflare-compat/vendored.js");
|
|
108
|
+
queues?: typeof import("../bindings/celld-queues.js");
|
|
109
|
+
workflows?: typeof import("../bindings/celld-workflows.js");
|
|
110
|
+
r2?: typeof import("../bindings/r2.js");
|
|
111
|
+
r2Celld?: typeof import("../bindings/r2-celld.js");
|
|
112
|
+
cron?: typeof import("../bindings/cron.js");
|
|
113
|
+
assets?: typeof import("../bindings/assets.js");
|
|
114
|
+
workerLoader?: typeof import("../bindings/worker-loader.js");
|
|
115
|
+
}
|
|
116
|
+
/** Loads the binding implementations `config` declares, for `createProjectRuntime`'s host. */
|
|
117
|
+
export declare function loadProjectBindings(config: WranglerConfig): Promise<ProjectBindings>;
|
|
94
118
|
export interface ProjectRuntime {
|
|
95
119
|
runtime: CellRuntime;
|
|
96
120
|
/** Configuration entries cellb does not support yet (reported, not silently ignored). */
|
|
@@ -120,6 +144,10 @@ export declare function withResourceIds(config: WranglerConfig): WranglerConfig;
|
|
|
120
144
|
* `nodejs_compat_do_not_populate_process_env` is set. Frameworks read their settings there
|
|
121
145
|
* (EmDash's `EMDASH_SITE_URL`, for one).
|
|
122
146
|
*/
|
|
147
|
+
/** Whether `config` asks for nodejs_compat (the flag, or a `nodejs_compat_*` one). */
|
|
148
|
+
export declare function hasNodeCompat(config: WranglerConfig): boolean;
|
|
149
|
+
/** The configuration files of a project folder, in the order celld reads them. */
|
|
150
|
+
export declare const WRANGLER_FILES: string[];
|
|
123
151
|
export declare function populatesProcessEnv(config: WranglerConfig): boolean;
|
|
124
152
|
/** The text `vars` of a project, as `process.env` holds them. */
|
|
125
153
|
export declare function processEnvOf(config: WranglerConfig): Record<string, string>;
|
package/dist/project/wrangler.js
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
|
-
import { createAssets } from "../bindings/assets.js";
|
|
2
|
-
import { celldKv } from "../bindings/celld-kv.js";
|
|
3
|
-
import { celldQueues } from "../bindings/celld-queues.js";
|
|
4
|
-
import { celldWorkflows } from "../bindings/celld-workflows.js";
|
|
5
|
-
import { startCronTriggers } from "../bindings/cron.js";
|
|
6
|
-
import { r2Bucket, r2BucketOn } from "../bindings/r2.js";
|
|
7
|
-
import { celldR2Backend } from "../bindings/r2-celld.js";
|
|
8
|
-
import { workerLoader } from "../bindings/worker-loader.js";
|
|
9
1
|
import { workersAi } from "../bindings/workers-ai.js";
|
|
10
2
|
import { entrypointStub } from "../core/handles.js";
|
|
11
3
|
import { createCellRuntime, } from "../core/runtime.js";
|
|
12
4
|
import { splitSqlStatements } from "../core/sql-split.js";
|
|
13
5
|
import { filesApiBlobService } from "../miniflare-compat/blob-service.js";
|
|
14
6
|
import { simulatorFetcher } from "../miniflare-compat/simulator-fetcher.js";
|
|
15
|
-
import { D1DatabaseObject, makeD1Binding } from "../miniflare-compat/vendored.js";
|
|
16
7
|
import { systemClock } from "../ports.js";
|
|
8
|
+
/** Loads the binding implementations `config` declares, for `createProjectRuntime`'s host. */
|
|
9
|
+
export async function loadProjectBindings(config) {
|
|
10
|
+
const when = (cond, load) => cond ? load() : Promise.resolve(undefined);
|
|
11
|
+
const [kv, d1, queues, workflows, r2, r2Celld, cron, assets, workerLoader] = await Promise.all([
|
|
12
|
+
when(config.kv_namespaces?.length, () => import("../bindings/celld-kv.js")),
|
|
13
|
+
when(config.d1_databases?.length, () => import("../miniflare-compat/vendored.js")),
|
|
14
|
+
when(config.queues?.producers?.length || config.queues?.consumers?.length, () => import("../bindings/celld-queues.js")),
|
|
15
|
+
when(config.workflows?.length, () => import("../bindings/celld-workflows.js")),
|
|
16
|
+
when(config.r2_buckets?.length, () => import("../bindings/r2.js")),
|
|
17
|
+
when(config.r2_buckets?.length, () => import("../bindings/r2-celld.js")),
|
|
18
|
+
when(config.triggers?.crons?.length, () => import("../bindings/cron.js")),
|
|
19
|
+
when(config.assets, () => import("../bindings/assets.js")),
|
|
20
|
+
when(config.worker_loaders?.length, () => import("../bindings/worker-loader.js")),
|
|
21
|
+
]);
|
|
22
|
+
return { kv, d1, queues, workflows, r2, r2Celld, cron, assets, workerLoader };
|
|
23
|
+
}
|
|
24
|
+
/** The loaded implementation of a binding the config declares, or an error saying how to load it. */
|
|
25
|
+
function binding(host, key, what) {
|
|
26
|
+
const m = host.bindings?.[key];
|
|
27
|
+
if (!m)
|
|
28
|
+
throw new Error(`${what}: the host has no implementation loaded; pass \`bindings: await loadProjectBindings(config)\``);
|
|
29
|
+
return m;
|
|
30
|
+
}
|
|
17
31
|
/**
|
|
18
32
|
* What `env` of `cloudflare:workers` holds while the Worker's module is evaluated, before the
|
|
19
33
|
* runtime exists: the vars, and the bindings that need no runtime (`ai`; Flue's entry reads
|
|
@@ -47,6 +61,10 @@ export function createProjectRuntime(config, module, host) {
|
|
|
47
61
|
for (const r of m.renamed_classes ?? [])
|
|
48
62
|
unsupported.push(`migrations.renamed_classes (${r.from} → ${r.to})`);
|
|
49
63
|
}
|
|
64
|
+
// wrangler's module rule types: the bundles apply CompiledWasm, Data and Text (module-rules.ts)
|
|
65
|
+
for (const [i, r] of (config.rules ?? []).entries())
|
|
66
|
+
if (!["ESModule", "CommonJS", "CompiledWasm", "Data", "Text"].includes(r.type))
|
|
67
|
+
unsupported.push(`rules[${i}].type "${r.type}"`);
|
|
50
68
|
for (const c of config.containers ?? [])
|
|
51
69
|
unsupported.push(`containers[${c.class_name}] (cellb runs no containers)`);
|
|
52
70
|
for (const b of config.durable_objects?.bindings ?? []) {
|
|
@@ -57,12 +75,12 @@ export function createProjectRuntime(config, module, host) {
|
|
|
57
75
|
const worker = { ...module };
|
|
58
76
|
let kv;
|
|
59
77
|
if (config.kv_namespaces?.length) {
|
|
60
|
-
kv = celldKv(host.files);
|
|
78
|
+
kv = binding(host, "kv", "kv_namespaces").celldKv(host.files);
|
|
61
79
|
worker.__KvNamespace = kv.KvNamespaceCell;
|
|
62
80
|
durableObjects[KV_OBJECTS] = { className: "__KvNamespace" };
|
|
63
81
|
}
|
|
64
82
|
if (config.d1_databases?.length) {
|
|
65
|
-
worker.__D1Database = D1DatabaseObject;
|
|
83
|
+
worker.__D1Database = binding(host, "d1", "d1_databases").D1DatabaseObject;
|
|
66
84
|
durableObjects[D1_OBJECTS] = { className: "__D1Database" };
|
|
67
85
|
}
|
|
68
86
|
// Queues: every queue is a cell of a runtime-supplied class (as on celld).
|
|
@@ -72,7 +90,7 @@ export function createProjectRuntime(config, module, host) {
|
|
|
72
90
|
let queueDispatcher;
|
|
73
91
|
let queues;
|
|
74
92
|
if (config.queues?.producers?.length || consumers.size) {
|
|
75
|
-
queues = celldQueues({
|
|
93
|
+
queues = binding(host, "queues", "queues").celldQueues({
|
|
76
94
|
namespace: () => runtime.env[QUEUE_OBJECTS],
|
|
77
95
|
consumers: [...consumers.values()],
|
|
78
96
|
compatibilityDate: config.compatibility_date,
|
|
@@ -85,7 +103,7 @@ export function createProjectRuntime(config, module, host) {
|
|
|
85
103
|
// celld's own workflow code (vendor/celld/workflows.js): its instance cell and binding.
|
|
86
104
|
let workflows;
|
|
87
105
|
if (config.workflows?.length) {
|
|
88
|
-
workflows = celldWorkflows({
|
|
106
|
+
workflows = binding(host, "workflows", "workflows").celldWorkflows({
|
|
89
107
|
script: config.name ?? "worker",
|
|
90
108
|
workflows: Object.fromEntries(config.workflows.map((w) => [w.name, w.class_name])),
|
|
91
109
|
exports: () => module,
|
|
@@ -170,12 +188,14 @@ export function createProjectRuntime(config, module, host) {
|
|
|
170
188
|
};
|
|
171
189
|
}
|
|
172
190
|
for (const l of config.worker_loaders ?? []) {
|
|
173
|
-
if (!host.loadModules)
|
|
191
|
+
if (!host.loadModules) {
|
|
174
192
|
unsupported.push(`worker_loaders[${l.binding}] (the host provides no module loader)`);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const { workerLoader } = binding(host, "workerLoader", "worker_loaders");
|
|
196
|
+
env[l.binding] = host.isolatedModules
|
|
197
|
+
? workerLoader(host.isolatedModules) // facets of loaded code run in the isolate as well
|
|
198
|
+
: workerLoader(host.loadModules);
|
|
179
199
|
}
|
|
180
200
|
// Cron Triggers (validated at load time like `celld deploy` does).
|
|
181
201
|
const crons = config.triggers?.crons ?? [];
|
|
@@ -185,7 +205,7 @@ export function createProjectRuntime(config, module, host) {
|
|
|
185
205
|
unsupported.push("triggers.crons without a scheduled() handler");
|
|
186
206
|
else {
|
|
187
207
|
const db = host.openSql("__cellb/cron");
|
|
188
|
-
const stop = startCronTriggers(crons, (c, x) => runtime.runEvent(() => scheduled.call(module.default, c, env, x)), host.clock ?? systemClock, cronState(db));
|
|
208
|
+
const stop = binding(host, "cron", "triggers.crons").startCronTriggers(crons, (c, x) => runtime.runEvent(() => scheduled.call(module.default, c, env, x)), host.clock ?? systemClock, cronState(db));
|
|
189
209
|
const close = runtime.close.bind(runtime);
|
|
190
210
|
runtime.close = async () => {
|
|
191
211
|
stop();
|
|
@@ -199,7 +219,7 @@ export function createProjectRuntime(config, module, host) {
|
|
|
199
219
|
const hasWorker = typeof module.default?.fetch === "function";
|
|
200
220
|
if (host.assetFiles?.has("/.assetsignore"))
|
|
201
221
|
throw new Error(`${config.assets.directory ?? "assets"}/.assetsignore is not supported by celld; remove it`);
|
|
202
|
-
const assets = createAssets(host.assetFiles ?? new Map(), config.assets, hasWorker);
|
|
222
|
+
const assets = binding(host, "assets", "assets").createAssets(host.assetFiles ?? new Map(), config.assets, hasWorker);
|
|
203
223
|
if (config.assets.binding)
|
|
204
224
|
env[config.assets.binding] = assets.binding;
|
|
205
225
|
const workerFetch = runtime.fetch.bind(runtime);
|
|
@@ -221,12 +241,15 @@ export function createProjectRuntime(config, module, host) {
|
|
|
221
241
|
}
|
|
222
242
|
// R2 objects live directly in the host's store, as celld keeps them in the fleet bucket.
|
|
223
243
|
// With a celld fleet bucket, R2 objects live in it as celld keeps them; else in the FilesApi.
|
|
224
|
-
for (const r2 of config.r2_buckets ?? [])
|
|
244
|
+
for (const r2 of config.r2_buckets ?? []) {
|
|
245
|
+
const { r2Bucket, r2BucketOn } = binding(host, "r2", "r2_buckets");
|
|
246
|
+
const { celldR2Backend } = binding(host, "r2Celld", "r2_buckets");
|
|
225
247
|
env[r2.binding] = host.storage?.objects
|
|
226
248
|
? r2BucketOn(celldR2Backend(host.storage.objects, r2.bucket_name), r2.binding)
|
|
227
249
|
: r2Bucket(host.files, `/r2/${r2.bucket_name}`, r2.binding);
|
|
250
|
+
}
|
|
228
251
|
for (const d1 of config.d1_databases ?? [])
|
|
229
|
-
env[d1.binding] = celldExec(makeD1Binding({
|
|
252
|
+
env[d1.binding] = celldExec(binding(host, "d1", "d1_databases").makeD1Binding({
|
|
230
253
|
fetcher: simulatorFetcher(env[D1_OBJECTS], d1.database_id),
|
|
231
254
|
}));
|
|
232
255
|
for (const name of [...sqliteClasses])
|
|
@@ -355,9 +378,15 @@ export function withResourceIds(config) {
|
|
|
355
378
|
* `nodejs_compat_do_not_populate_process_env` is set. Frameworks read their settings there
|
|
356
379
|
* (EmDash's `EMDASH_SITE_URL`, for one).
|
|
357
380
|
*/
|
|
381
|
+
/** Whether `config` asks for nodejs_compat (the flag, or a `nodejs_compat_*` one). */
|
|
382
|
+
export function hasNodeCompat(config) {
|
|
383
|
+
return (config.compatibility_flags ?? []).some((f) => f.startsWith("nodejs_compat"));
|
|
384
|
+
}
|
|
385
|
+
/** The configuration files of a project folder, in the order celld reads them. */
|
|
386
|
+
export const WRANGLER_FILES = ["wrangler.jsonc", "wrangler.json"];
|
|
358
387
|
export function populatesProcessEnv(config) {
|
|
359
388
|
const flags = config.compatibility_flags ?? [];
|
|
360
|
-
if (!
|
|
389
|
+
if (!hasNodeCompat(config))
|
|
361
390
|
return false;
|
|
362
391
|
if (flags.includes("nodejs_compat_do_not_populate_process_env"))
|
|
363
392
|
return false;
|