@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.22 → 3.9.0-ultramodern.23
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 +23 -0
- package/dist/cjs/cloudflare/worker-manifest.js +8 -6
- package/dist/cjs/cloudflare/wrangler-config.js +175 -6
- package/dist/cjs/cloudflare-output-contract.js +2 -1
- package/dist/esm/cloudflare/worker-manifest.mjs +9 -7
- package/dist/esm/cloudflare/wrangler-config.mjs +168 -5
- package/dist/esm/cloudflare-output-contract.mjs +2 -1
- package/dist/esm-node/cloudflare/worker-manifest.mjs +9 -7
- package/dist/esm-node/cloudflare/wrangler-config.mjs +168 -5
- package/dist/esm-node/cloudflare-output-contract.mjs +2 -1
- package/dist/types/cloudflare/worker-manifest.d.ts +7 -2
- package/dist/types/cloudflare/wrangler-config.d.ts +52 -5
- package/dist/types/cloudflare-output-contract.d.ts +1 -1
- package/dist/types/config.d.ts +69 -12
- package/package.json +15 -15
- package/src/cloudflare/worker-manifest.ts +19 -6
- package/src/cloudflare/wrangler-config.ts +305 -5
- package/src/cloudflare-output-contract.ts +3 -0
- package/src/config.ts +71 -12
package/README.md
CHANGED
|
@@ -3,3 +3,26 @@
|
|
|
3
3
|
Fork-owned UltraModern.js app tooling for Cloudflare Worker delivery, output verification, and release-envelope identity enforcement.
|
|
4
4
|
|
|
5
5
|
The package is composed into upstream app-tools through existing deploy/plugin extension seams. It must not depend on `@modern-js/app-tools`.
|
|
6
|
+
|
|
7
|
+
## Private origins and module-scope bindings
|
|
8
|
+
|
|
9
|
+
Private origins behind a Cloudflare Tunnel are first-class on
|
|
10
|
+
`deploy.worker.vpcServices` (`{ binding, serviceId, prefix? }`). Modern.js
|
|
11
|
+
writes them to `wrangler.json` as `vpc_services` (top level and every
|
|
12
|
+
`wrangler.env.<name>`, because Wrangler bindings are not inherited), and a
|
|
13
|
+
binding with `prefix`
|
|
14
|
+
is dispatched with `env[binding].fetch(request)` like a prefixed
|
|
15
|
+
`deploy.worker.services` entry. A VPC binding name may not reuse any other
|
|
16
|
+
Worker binding: any `binding` field or array-entry `name` in the Wrangler
|
|
17
|
+
config (`ASSETS`, services, D1, KV, Hyperdrive, queue producers, Durable
|
|
18
|
+
Objects, `send_email`, rate limits, …), including each `wrangler.env.<name>`'s
|
|
19
|
+
own bindings, or a `vars` key. A VPC prefix must be a decoded path starting with `/` that URL parsing keeps unchanged (no query, fragment,
|
|
20
|
+
`.` or `..` segments), and may not overlap, even as a percent-encoded alias, the
|
|
21
|
+
Effect BFF prefix or any other routed prefix (the worker decodes the request
|
|
22
|
+
path, dispatches the BFF first, matches path segments and takes the first
|
|
23
|
+
match),
|
|
24
|
+
and the typed option excludes a raw `wrangler.vpc_services`.
|
|
25
|
+
|
|
26
|
+
Code that needs a binding outside the request handler, such as a Hyperdrive
|
|
27
|
+
connection string or a VPC fetcher, imports `env` from `cloudflare:workers`,
|
|
28
|
+
which worker bundles externalize like `cloudflare:sockets`.
|
|
@@ -126,13 +126,15 @@ const createWorkerManifest = async (outputDirectory, modernConfig, appContext, d
|
|
|
126
126
|
} : {}
|
|
127
127
|
};
|
|
128
128
|
}));
|
|
129
|
-
const bffPrefix = modernConfig.bff?.prefix;
|
|
130
|
-
const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
|
|
131
129
|
const isEffectApi = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
|
|
130
|
+
const effectBffPrefix = (0, external_wrangler_config_js_namespaceObject.getWorkerEffectBffPrefix)(modernConfig);
|
|
132
131
|
const effectApiWorkerExists = await utils_namespaceObject.fs.pathExists(external_node_path_default().join(outputDirectory, external_constants_js_namespaceObject.BFF_EFFECT_WORKER_ENTRY));
|
|
133
|
-
const
|
|
132
|
+
const typedServiceBindings = (0, external_wrangler_config_js_namespaceObject.createWorkerServiceBindings)(modernConfig, void 0);
|
|
133
|
+
const serviceBindings = (0, external_wrangler_config_js_namespaceObject.createWorkerManifestServiceBindings)(typedServiceBindings, (0, external_wrangler_config_js_namespaceObject.createWorkerVpcServiceBindings)(modernConfig, void 0, typedServiceBindings, new Set([
|
|
134
|
+
external_constants_js_namespaceObject.ASSETS_BINDING
|
|
135
|
+
])));
|
|
134
136
|
const moduleFederation = await createModuleFederationWorkerManifest(outputDirectory);
|
|
135
|
-
if (
|
|
137
|
+
if (void 0 !== effectBffPrefix && !effectApiWorkerExists) throw createMissingEffectBffWorkerError(outputDirectory, external_constants_js_namespaceObject.BFF_EFFECT_WORKER_ENTRY);
|
|
136
138
|
const effectBffManifest = isEffectApi ? createEffectBffWorkerManifest(modernConfig) : void 0;
|
|
137
139
|
return {
|
|
138
140
|
version: 1,
|
|
@@ -169,10 +171,10 @@ const createWorkerManifest = async (outputDirectory, modernConfig, appContext, d
|
|
|
169
171
|
deliveryUnit: deliveryUnitStamp
|
|
170
172
|
} : {},
|
|
171
173
|
i18n: (0, external_i18n_worker_js_namespaceObject.createI18nWorkerManifest)(routeSpec, appContext),
|
|
172
|
-
bff:
|
|
174
|
+
bff: void 0 !== effectBffPrefix && effectApiWorkerExists ? {
|
|
173
175
|
dispatcherExport: external_constants_js_namespaceObject.BFF_EFFECT_WORKER_DISPATCHER_EXPORT,
|
|
174
176
|
runtimeFramework: 'effect',
|
|
175
|
-
prefix:
|
|
177
|
+
prefix: effectBffPrefix,
|
|
176
178
|
worker: external_constants_js_namespaceObject.BFF_EFFECT_WORKER_ENTRY,
|
|
177
179
|
effect: effectBffManifest
|
|
178
180
|
} : void 0,
|
|
@@ -39,7 +39,9 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
39
39
|
__webpack_require__.d(__webpack_exports__, {
|
|
40
40
|
createWorkerManifestServiceBindings: ()=>createWorkerManifestServiceBindings,
|
|
41
41
|
createWorkerServiceBindings: ()=>createWorkerServiceBindings,
|
|
42
|
-
|
|
42
|
+
createWorkerVpcServiceBindings: ()=>createWorkerVpcServiceBindings,
|
|
43
|
+
createWranglerConfig: ()=>createWranglerConfig,
|
|
44
|
+
getWorkerEffectBffPrefix: ()=>getWorkerEffectBffPrefix
|
|
43
45
|
});
|
|
44
46
|
const external_node_path_namespaceObject = require("node:path");
|
|
45
47
|
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
|
|
@@ -171,6 +173,129 @@ const createWorkerServiceBindings = (modernConfig, configuredWranglerServices)=>
|
|
|
171
173
|
if (!Array.isArray(services)) throw new Error('deploy.worker.services must be an array.');
|
|
172
174
|
return services.map(normalizeServiceBinding);
|
|
173
175
|
};
|
|
176
|
+
const normalizeVpcService = (vpcService, index)=>{
|
|
177
|
+
const binding = assertNonEmptyString(vpcService.binding, `deploy.worker.vpcServices[${index}].binding`);
|
|
178
|
+
const serviceId = assertNonEmptyString(vpcService.serviceId, `deploy.worker.vpcServices[${index}].serviceId`);
|
|
179
|
+
const prefix = void 0 === vpcService.prefix ? void 0 : assertNonEmptyString(vpcService.prefix, `deploy.worker.vpcServices[${index}].prefix`);
|
|
180
|
+
return {
|
|
181
|
+
binding,
|
|
182
|
+
serviceId,
|
|
183
|
+
...void 0 === prefix ? {} : {
|
|
184
|
+
prefix
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
const decodeRoutePrefix = (prefix)=>{
|
|
189
|
+
let decoded = prefix;
|
|
190
|
+
for(let round = 0; round < 4; round += 1){
|
|
191
|
+
let next;
|
|
192
|
+
try {
|
|
193
|
+
next = decodeURIComponent(decoded);
|
|
194
|
+
} catch {
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
if (next === decoded) break;
|
|
198
|
+
decoded = next;
|
|
199
|
+
}
|
|
200
|
+
return decoded;
|
|
201
|
+
};
|
|
202
|
+
const normalizeRoutePrefix = (prefix)=>{
|
|
203
|
+
const decoded = decodeRoutePrefix(prefix);
|
|
204
|
+
return '/' === decoded ? decoded : decoded.replace(/\/+$/u, '');
|
|
205
|
+
};
|
|
206
|
+
const isCanonicalRoutePrefix = (prefix)=>prefix.startsWith('/') && !prefix.includes('\\') && decodeRoutePrefix(prefix) === prefix && new URL(prefix, 'https://route.invalid').pathname === prefix;
|
|
207
|
+
const collectWorkerBindingNames = (config)=>{
|
|
208
|
+
const names = new Set();
|
|
209
|
+
const visit = (value, inArray)=>{
|
|
210
|
+
if (Array.isArray(value)) {
|
|
211
|
+
for (const item of value)visit(item, true);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (!(0, external_utils_js_namespaceObject.isJsonRecord)(value)) return;
|
|
215
|
+
for (const field of inArray ? [
|
|
216
|
+
'binding',
|
|
217
|
+
'name'
|
|
218
|
+
] : [
|
|
219
|
+
'binding'
|
|
220
|
+
]){
|
|
221
|
+
const name = value[field];
|
|
222
|
+
if ('string' == typeof name) names.add(name.trim());
|
|
223
|
+
}
|
|
224
|
+
for (const child of Object.values(value))visit(child, false);
|
|
225
|
+
};
|
|
226
|
+
for (const [key, value] of Object.entries(config))if ('env' !== key && 'services' !== key && 'vpc_services' !== key) {
|
|
227
|
+
if ('vars' === key && (0, external_utils_js_namespaceObject.isJsonRecord)(value)) {
|
|
228
|
+
for (const name of Object.keys(value))names.add(name);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
visit(value, false);
|
|
232
|
+
}
|
|
233
|
+
return names;
|
|
234
|
+
};
|
|
235
|
+
const prefixesOverlap = (left, right)=>{
|
|
236
|
+
const [first, second] = [
|
|
237
|
+
normalizeRoutePrefix(left),
|
|
238
|
+
normalizeRoutePrefix(right)
|
|
239
|
+
];
|
|
240
|
+
return '/' === first || '/' === second || first === second || first.startsWith(`${second}/`) || second.startsWith(`${first}/`);
|
|
241
|
+
};
|
|
242
|
+
const getWorkerEffectBffPrefix = (modernConfig)=>{
|
|
243
|
+
const bffPrefix = modernConfig.bff?.prefix;
|
|
244
|
+
const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
|
|
245
|
+
const isEffectApi = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
|
|
246
|
+
return isEffectApi && primaryBffPrefix ? primaryBffPrefix : void 0;
|
|
247
|
+
};
|
|
248
|
+
const effectiveServiceBindings = (serviceBindings)=>{
|
|
249
|
+
const bindings = Array.isArray(serviceBindings) ? serviceBindings : [];
|
|
250
|
+
return bindings.flatMap((service)=>(0, external_utils_js_namespaceObject.isJsonRecord)(service) && 'string' == typeof service.binding ? [
|
|
251
|
+
{
|
|
252
|
+
binding: service.binding.trim(),
|
|
253
|
+
prefix: 'string' == typeof service.prefix ? service.prefix : void 0
|
|
254
|
+
}
|
|
255
|
+
] : []);
|
|
256
|
+
};
|
|
257
|
+
const createWorkerVpcServiceBindings = (modernConfig, configuredWranglerVpcServices, serviceBindings, workerBindingNames)=>{
|
|
258
|
+
const vpcServices = modernConfig.deploy?.worker?.vpcServices;
|
|
259
|
+
if (void 0 === vpcServices) return;
|
|
260
|
+
if (void 0 !== configuredWranglerVpcServices) throw new Error('Use deploy.worker.vpcServices or deploy.worker.wrangler.vpc_services, not both.');
|
|
261
|
+
if (!Array.isArray(vpcServices)) throw new Error('deploy.worker.vpcServices must be an array.');
|
|
262
|
+
const services = effectiveServiceBindings(serviceBindings);
|
|
263
|
+
const serviceBindingNames = new Set(services.map(({ binding })=>binding));
|
|
264
|
+
const effectBffPrefix = getWorkerEffectBffPrefix(modernConfig);
|
|
265
|
+
const routes = [
|
|
266
|
+
...void 0 === effectBffPrefix ? [] : [
|
|
267
|
+
{
|
|
268
|
+
owner: 'the Effect BFF',
|
|
269
|
+
prefix: effectBffPrefix
|
|
270
|
+
}
|
|
271
|
+
],
|
|
272
|
+
...services.flatMap(({ binding, prefix })=>void 0 === prefix ? [] : [
|
|
273
|
+
{
|
|
274
|
+
owner: `binding "${binding}"`,
|
|
275
|
+
prefix
|
|
276
|
+
}
|
|
277
|
+
])
|
|
278
|
+
];
|
|
279
|
+
const vpcBindingNames = new Set();
|
|
280
|
+
return vpcServices.map((vpcService, index)=>{
|
|
281
|
+
const normalized = normalizeVpcService(vpcService, index);
|
|
282
|
+
if (serviceBindingNames.has(normalized.binding)) throw new Error(`deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is already declared by deploy.worker.services.`);
|
|
283
|
+
if (workerBindingNames.has(normalized.binding)) throw new Error(`deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is already a binding of this Worker.`);
|
|
284
|
+
if (vpcBindingNames.has(normalized.binding)) throw new Error(`deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is declared more than once.`);
|
|
285
|
+
vpcBindingNames.add(normalized.binding);
|
|
286
|
+
if (void 0 !== normalized.prefix) {
|
|
287
|
+
const { prefix } = normalized;
|
|
288
|
+
if (!isCanonicalRoutePrefix(prefix)) throw new Error(`deploy.worker.vpcServices[${index}].prefix "${prefix}" must be a decoded URL path that starts with "/" (no query, fragment, "." or ".." segments).`);
|
|
289
|
+
const shadow = routes.find((route)=>prefixesOverlap(route.prefix, prefix));
|
|
290
|
+
if (void 0 !== shadow) throw new Error(`deploy.worker.vpcServices[${index}].prefix "${prefix}" overlaps the prefix "${shadow.prefix}" of ${shadow.owner}; the worker dispatcher would route one of them ambiguously.`);
|
|
291
|
+
routes.push({
|
|
292
|
+
owner: `binding "${normalized.binding}"`,
|
|
293
|
+
prefix
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return normalized;
|
|
297
|
+
});
|
|
298
|
+
};
|
|
174
299
|
const createWranglerServices = (serviceBindings)=>{
|
|
175
300
|
if (!Array.isArray(serviceBindings)) return serviceBindings;
|
|
176
301
|
if (!serviceBindings.every(external_utils_js_namespaceObject.isJsonRecord)) return serviceBindings;
|
|
@@ -179,8 +304,16 @@ const createWranglerServices = (serviceBindings)=>{
|
|
|
179
304
|
return wranglerService;
|
|
180
305
|
});
|
|
181
306
|
};
|
|
182
|
-
const createWorkerManifestServiceBindings = (serviceBindings)=>{
|
|
183
|
-
|
|
307
|
+
const createWorkerManifestServiceBindings = (serviceBindings, vpcServiceBindings)=>{
|
|
308
|
+
const vpcManifestBindings = (vpcServiceBindings ?? []).flatMap(({ binding, prefix, serviceId })=>void 0 === prefix ? [] : [
|
|
309
|
+
{
|
|
310
|
+
binding,
|
|
311
|
+
interface: 'fetch',
|
|
312
|
+
prefix,
|
|
313
|
+
vpcServiceId: serviceId
|
|
314
|
+
}
|
|
315
|
+
]);
|
|
316
|
+
if (!Array.isArray(serviceBindings)) return vpcManifestBindings.length > 0 ? vpcManifestBindings : void 0;
|
|
184
317
|
const bindings = serviceBindings;
|
|
185
318
|
const manifestBindings = bindings.filter((service)=>(0, external_utils_js_namespaceObject.isJsonRecord)(service) && 'string' == typeof service.binding && 'string' == typeof service.service && ('string' == typeof service.prefix || Array.isArray(service.fragments) && service.fragments.length > 0)).map((service)=>({
|
|
186
319
|
binding: service.binding,
|
|
@@ -193,7 +326,11 @@ const createWorkerManifestServiceBindings = (serviceBindings)=>{
|
|
|
193
326
|
fragments: service.fragments
|
|
194
327
|
}
|
|
195
328
|
}));
|
|
196
|
-
|
|
329
|
+
const allBindings = [
|
|
330
|
+
...manifestBindings,
|
|
331
|
+
...vpcManifestBindings
|
|
332
|
+
];
|
|
333
|
+
return allBindings.length > 0 ? allBindings : void 0;
|
|
197
334
|
};
|
|
198
335
|
const createWranglerConfig = (appDirectory, modernConfig)=>{
|
|
199
336
|
const wrangler = getConfiguredWrangler(modernConfig);
|
|
@@ -201,7 +338,7 @@ const createWranglerConfig = (appDirectory, modernConfig)=>{
|
|
|
201
338
|
const d1Databases = createWranglerD1Databases(modernConfig, wrangler.d1_databases);
|
|
202
339
|
const serviceBindings = createWorkerServiceBindings(modernConfig, wrangler.services);
|
|
203
340
|
const wranglerServices = createWranglerServices(serviceBindings);
|
|
204
|
-
|
|
341
|
+
const workerConfig = {
|
|
205
342
|
$schema: 'node_modules/wrangler/config-schema.json',
|
|
206
343
|
name: getConfiguredWorkerName(appDirectory, modernConfig),
|
|
207
344
|
...wrangler,
|
|
@@ -219,14 +356,46 @@ const createWranglerConfig = (appDirectory, modernConfig)=>{
|
|
|
219
356
|
services: wranglerServices
|
|
220
357
|
}
|
|
221
358
|
};
|
|
359
|
+
const vpcServiceBindings = createWorkerVpcServiceBindings(modernConfig, wrangler.vpc_services, serviceBindings, collectWorkerBindingNames(workerConfig));
|
|
360
|
+
if (void 0 === vpcServiceBindings) return workerConfig;
|
|
361
|
+
const vpcServicesConfig = vpcServiceBindings.map(({ binding, serviceId })=>({
|
|
362
|
+
binding,
|
|
363
|
+
service_id: serviceId
|
|
364
|
+
}));
|
|
365
|
+
return {
|
|
366
|
+
...workerConfig,
|
|
367
|
+
...void 0 === environments ? {} : {
|
|
368
|
+
env: Object.fromEntries(Object.entries(environments).map(([name, environment])=>{
|
|
369
|
+
if (void 0 !== environment.vpc_services) throw new Error(`Use deploy.worker.vpcServices or deploy.worker.wrangler.env.${name}.vpc_services, not both.`);
|
|
370
|
+
const environmentBindingNames = new Set([
|
|
371
|
+
...collectWorkerBindingNames(environment),
|
|
372
|
+
...effectiveServiceBindings(environment.services).map(({ binding })=>binding)
|
|
373
|
+
]);
|
|
374
|
+
const collision = vpcServiceBindings.find(({ binding })=>environmentBindingNames.has(binding));
|
|
375
|
+
if (void 0 !== collision) throw new Error(`deploy.worker.vpcServices binding "${collision.binding}" is already a binding of deploy.worker.wrangler.env.${name}.`);
|
|
376
|
+
return [
|
|
377
|
+
name,
|
|
378
|
+
{
|
|
379
|
+
...environment,
|
|
380
|
+
vpc_services: vpcServicesConfig
|
|
381
|
+
}
|
|
382
|
+
];
|
|
383
|
+
}))
|
|
384
|
+
},
|
|
385
|
+
vpc_services: vpcServicesConfig
|
|
386
|
+
};
|
|
222
387
|
};
|
|
223
388
|
exports.createWorkerManifestServiceBindings = __webpack_exports__.createWorkerManifestServiceBindings;
|
|
224
389
|
exports.createWorkerServiceBindings = __webpack_exports__.createWorkerServiceBindings;
|
|
390
|
+
exports.createWorkerVpcServiceBindings = __webpack_exports__.createWorkerVpcServiceBindings;
|
|
225
391
|
exports.createWranglerConfig = __webpack_exports__.createWranglerConfig;
|
|
392
|
+
exports.getWorkerEffectBffPrefix = __webpack_exports__.getWorkerEffectBffPrefix;
|
|
226
393
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
227
394
|
"createWorkerManifestServiceBindings",
|
|
228
395
|
"createWorkerServiceBindings",
|
|
229
|
-
"
|
|
396
|
+
"createWorkerVpcServiceBindings",
|
|
397
|
+
"createWranglerConfig",
|
|
398
|
+
"getWorkerEffectBffPrefix"
|
|
230
399
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
231
400
|
Object.defineProperty(exports, '__esModule', {
|
|
232
401
|
value: true
|
|
@@ -108,7 +108,8 @@ const CLOUDFLARE_WORKER_NODE_BUILTINS = [
|
|
|
108
108
|
'zlib'
|
|
109
109
|
];
|
|
110
110
|
const CLOUDFLARE_WORKER_PLATFORM_MODULES = [
|
|
111
|
-
'cloudflare:sockets'
|
|
111
|
+
'cloudflare:sockets',
|
|
112
|
+
'cloudflare:workers'
|
|
112
113
|
];
|
|
113
114
|
const CLOUDFLARE_REQUIRED_COMPATIBILITY_FLAGS = [
|
|
114
115
|
'nodejs_compat',
|
|
@@ -6,7 +6,7 @@ import { ASSETS_BINDING, BFF_EFFECT_WORKER_DISPATCHER_EXPORT, BFF_EFFECT_WORKER_
|
|
|
6
6
|
import { createI18nWorkerManifest } from "./i18n-worker.mjs";
|
|
7
7
|
import { createCloudflareWorkerSecurityPolicy } from "./security-policies.mjs";
|
|
8
8
|
import { isRecord, normalizeRelativePath } from "./utils.mjs";
|
|
9
|
-
import { createWorkerManifestServiceBindings, createWorkerServiceBindings } from "./wrangler-config.mjs";
|
|
9
|
+
import { createWorkerManifestServiceBindings, createWorkerServiceBindings, createWorkerVpcServiceBindings, getWorkerEffectBffPrefix } from "./wrangler-config.mjs";
|
|
10
10
|
const createMissingEffectBffWorkerError = (outputDirectory, worker)=>new Error(`Cloudflare Effect API runtime is configured, but the BFF worker bundle is missing: ${node_path.join(outputDirectory, worker)}. ${EFFECT_BFF_CLOUDFLARE_IMPORT_GUIDANCE}`);
|
|
11
11
|
const createEffectBffWorkerManifest = (modernConfig)=>{
|
|
12
12
|
const bff = modernConfig.bff;
|
|
@@ -80,13 +80,15 @@ const createWorkerManifest = async (outputDirectory, modernConfig, appContext, d
|
|
|
80
80
|
} : {}
|
|
81
81
|
};
|
|
82
82
|
}));
|
|
83
|
-
const bffPrefix = modernConfig.bff?.prefix;
|
|
84
|
-
const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
|
|
85
83
|
const isEffectApi = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
|
|
84
|
+
const effectBffPrefix = getWorkerEffectBffPrefix(modernConfig);
|
|
86
85
|
const effectApiWorkerExists = await fs.pathExists(node_path.join(outputDirectory, BFF_EFFECT_WORKER_ENTRY));
|
|
87
|
-
const
|
|
86
|
+
const typedServiceBindings = createWorkerServiceBindings(modernConfig, void 0);
|
|
87
|
+
const serviceBindings = createWorkerManifestServiceBindings(typedServiceBindings, createWorkerVpcServiceBindings(modernConfig, void 0, typedServiceBindings, new Set([
|
|
88
|
+
ASSETS_BINDING
|
|
89
|
+
])));
|
|
88
90
|
const moduleFederation = await createModuleFederationWorkerManifest(outputDirectory);
|
|
89
|
-
if (
|
|
91
|
+
if (void 0 !== effectBffPrefix && !effectApiWorkerExists) throw createMissingEffectBffWorkerError(outputDirectory, BFF_EFFECT_WORKER_ENTRY);
|
|
90
92
|
const effectBffManifest = isEffectApi ? createEffectBffWorkerManifest(modernConfig) : void 0;
|
|
91
93
|
return {
|
|
92
94
|
version: 1,
|
|
@@ -123,10 +125,10 @@ const createWorkerManifest = async (outputDirectory, modernConfig, appContext, d
|
|
|
123
125
|
deliveryUnit: deliveryUnitStamp
|
|
124
126
|
} : {},
|
|
125
127
|
i18n: createI18nWorkerManifest(routeSpec, appContext),
|
|
126
|
-
bff:
|
|
128
|
+
bff: void 0 !== effectBffPrefix && effectApiWorkerExists ? {
|
|
127
129
|
dispatcherExport: BFF_EFFECT_WORKER_DISPATCHER_EXPORT,
|
|
128
130
|
runtimeFramework: 'effect',
|
|
129
|
-
prefix:
|
|
131
|
+
prefix: effectBffPrefix,
|
|
130
132
|
worker: BFF_EFFECT_WORKER_ENTRY,
|
|
131
133
|
effect: effectBffManifest
|
|
132
134
|
} : void 0,
|
|
@@ -127,6 +127,129 @@ const createWorkerServiceBindings = (modernConfig, configuredWranglerServices)=>
|
|
|
127
127
|
if (!Array.isArray(services)) throw new Error('deploy.worker.services must be an array.');
|
|
128
128
|
return services.map(normalizeServiceBinding);
|
|
129
129
|
};
|
|
130
|
+
const normalizeVpcService = (vpcService, index)=>{
|
|
131
|
+
const binding = assertNonEmptyString(vpcService.binding, `deploy.worker.vpcServices[${index}].binding`);
|
|
132
|
+
const serviceId = assertNonEmptyString(vpcService.serviceId, `deploy.worker.vpcServices[${index}].serviceId`);
|
|
133
|
+
const prefix = void 0 === vpcService.prefix ? void 0 : assertNonEmptyString(vpcService.prefix, `deploy.worker.vpcServices[${index}].prefix`);
|
|
134
|
+
return {
|
|
135
|
+
binding,
|
|
136
|
+
serviceId,
|
|
137
|
+
...void 0 === prefix ? {} : {
|
|
138
|
+
prefix
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
const decodeRoutePrefix = (prefix)=>{
|
|
143
|
+
let decoded = prefix;
|
|
144
|
+
for(let round = 0; round < 4; round += 1){
|
|
145
|
+
let next;
|
|
146
|
+
try {
|
|
147
|
+
next = decodeURIComponent(decoded);
|
|
148
|
+
} catch {
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
if (next === decoded) break;
|
|
152
|
+
decoded = next;
|
|
153
|
+
}
|
|
154
|
+
return decoded;
|
|
155
|
+
};
|
|
156
|
+
const normalizeRoutePrefix = (prefix)=>{
|
|
157
|
+
const decoded = decodeRoutePrefix(prefix);
|
|
158
|
+
return '/' === decoded ? decoded : decoded.replace(/\/+$/u, '');
|
|
159
|
+
};
|
|
160
|
+
const isCanonicalRoutePrefix = (prefix)=>prefix.startsWith('/') && !prefix.includes('\\') && decodeRoutePrefix(prefix) === prefix && new URL(prefix, 'https://route.invalid').pathname === prefix;
|
|
161
|
+
const collectWorkerBindingNames = (config)=>{
|
|
162
|
+
const names = new Set();
|
|
163
|
+
const visit = (value, inArray)=>{
|
|
164
|
+
if (Array.isArray(value)) {
|
|
165
|
+
for (const item of value)visit(item, true);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (!isJsonRecord(value)) return;
|
|
169
|
+
for (const field of inArray ? [
|
|
170
|
+
'binding',
|
|
171
|
+
'name'
|
|
172
|
+
] : [
|
|
173
|
+
'binding'
|
|
174
|
+
]){
|
|
175
|
+
const name = value[field];
|
|
176
|
+
if ('string' == typeof name) names.add(name.trim());
|
|
177
|
+
}
|
|
178
|
+
for (const child of Object.values(value))visit(child, false);
|
|
179
|
+
};
|
|
180
|
+
for (const [key, value] of Object.entries(config))if ('env' !== key && 'services' !== key && 'vpc_services' !== key) {
|
|
181
|
+
if ('vars' === key && isJsonRecord(value)) {
|
|
182
|
+
for (const name of Object.keys(value))names.add(name);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
visit(value, false);
|
|
186
|
+
}
|
|
187
|
+
return names;
|
|
188
|
+
};
|
|
189
|
+
const prefixesOverlap = (left, right)=>{
|
|
190
|
+
const [first, second] = [
|
|
191
|
+
normalizeRoutePrefix(left),
|
|
192
|
+
normalizeRoutePrefix(right)
|
|
193
|
+
];
|
|
194
|
+
return '/' === first || '/' === second || first === second || first.startsWith(`${second}/`) || second.startsWith(`${first}/`);
|
|
195
|
+
};
|
|
196
|
+
const getWorkerEffectBffPrefix = (modernConfig)=>{
|
|
197
|
+
const bffPrefix = modernConfig.bff?.prefix;
|
|
198
|
+
const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
|
|
199
|
+
const isEffectApi = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
|
|
200
|
+
return isEffectApi && primaryBffPrefix ? primaryBffPrefix : void 0;
|
|
201
|
+
};
|
|
202
|
+
const effectiveServiceBindings = (serviceBindings)=>{
|
|
203
|
+
const bindings = Array.isArray(serviceBindings) ? serviceBindings : [];
|
|
204
|
+
return bindings.flatMap((service)=>isJsonRecord(service) && 'string' == typeof service.binding ? [
|
|
205
|
+
{
|
|
206
|
+
binding: service.binding.trim(),
|
|
207
|
+
prefix: 'string' == typeof service.prefix ? service.prefix : void 0
|
|
208
|
+
}
|
|
209
|
+
] : []);
|
|
210
|
+
};
|
|
211
|
+
const createWorkerVpcServiceBindings = (modernConfig, configuredWranglerVpcServices, serviceBindings, workerBindingNames)=>{
|
|
212
|
+
const vpcServices = modernConfig.deploy?.worker?.vpcServices;
|
|
213
|
+
if (void 0 === vpcServices) return;
|
|
214
|
+
if (void 0 !== configuredWranglerVpcServices) throw new Error('Use deploy.worker.vpcServices or deploy.worker.wrangler.vpc_services, not both.');
|
|
215
|
+
if (!Array.isArray(vpcServices)) throw new Error('deploy.worker.vpcServices must be an array.');
|
|
216
|
+
const services = effectiveServiceBindings(serviceBindings);
|
|
217
|
+
const serviceBindingNames = new Set(services.map(({ binding })=>binding));
|
|
218
|
+
const effectBffPrefix = getWorkerEffectBffPrefix(modernConfig);
|
|
219
|
+
const routes = [
|
|
220
|
+
...void 0 === effectBffPrefix ? [] : [
|
|
221
|
+
{
|
|
222
|
+
owner: 'the Effect BFF',
|
|
223
|
+
prefix: effectBffPrefix
|
|
224
|
+
}
|
|
225
|
+
],
|
|
226
|
+
...services.flatMap(({ binding, prefix })=>void 0 === prefix ? [] : [
|
|
227
|
+
{
|
|
228
|
+
owner: `binding "${binding}"`,
|
|
229
|
+
prefix
|
|
230
|
+
}
|
|
231
|
+
])
|
|
232
|
+
];
|
|
233
|
+
const vpcBindingNames = new Set();
|
|
234
|
+
return vpcServices.map((vpcService, index)=>{
|
|
235
|
+
const normalized = normalizeVpcService(vpcService, index);
|
|
236
|
+
if (serviceBindingNames.has(normalized.binding)) throw new Error(`deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is already declared by deploy.worker.services.`);
|
|
237
|
+
if (workerBindingNames.has(normalized.binding)) throw new Error(`deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is already a binding of this Worker.`);
|
|
238
|
+
if (vpcBindingNames.has(normalized.binding)) throw new Error(`deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is declared more than once.`);
|
|
239
|
+
vpcBindingNames.add(normalized.binding);
|
|
240
|
+
if (void 0 !== normalized.prefix) {
|
|
241
|
+
const { prefix } = normalized;
|
|
242
|
+
if (!isCanonicalRoutePrefix(prefix)) throw new Error(`deploy.worker.vpcServices[${index}].prefix "${prefix}" must be a decoded URL path that starts with "/" (no query, fragment, "." or ".." segments).`);
|
|
243
|
+
const shadow = routes.find((route)=>prefixesOverlap(route.prefix, prefix));
|
|
244
|
+
if (void 0 !== shadow) throw new Error(`deploy.worker.vpcServices[${index}].prefix "${prefix}" overlaps the prefix "${shadow.prefix}" of ${shadow.owner}; the worker dispatcher would route one of them ambiguously.`);
|
|
245
|
+
routes.push({
|
|
246
|
+
owner: `binding "${normalized.binding}"`,
|
|
247
|
+
prefix
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return normalized;
|
|
251
|
+
});
|
|
252
|
+
};
|
|
130
253
|
const createWranglerServices = (serviceBindings)=>{
|
|
131
254
|
if (!Array.isArray(serviceBindings)) return serviceBindings;
|
|
132
255
|
if (!serviceBindings.every(isJsonRecord)) return serviceBindings;
|
|
@@ -135,8 +258,16 @@ const createWranglerServices = (serviceBindings)=>{
|
|
|
135
258
|
return wranglerService;
|
|
136
259
|
});
|
|
137
260
|
};
|
|
138
|
-
const createWorkerManifestServiceBindings = (serviceBindings)=>{
|
|
139
|
-
|
|
261
|
+
const createWorkerManifestServiceBindings = (serviceBindings, vpcServiceBindings)=>{
|
|
262
|
+
const vpcManifestBindings = (vpcServiceBindings ?? []).flatMap(({ binding, prefix, serviceId })=>void 0 === prefix ? [] : [
|
|
263
|
+
{
|
|
264
|
+
binding,
|
|
265
|
+
interface: 'fetch',
|
|
266
|
+
prefix,
|
|
267
|
+
vpcServiceId: serviceId
|
|
268
|
+
}
|
|
269
|
+
]);
|
|
270
|
+
if (!Array.isArray(serviceBindings)) return vpcManifestBindings.length > 0 ? vpcManifestBindings : void 0;
|
|
140
271
|
const bindings = serviceBindings;
|
|
141
272
|
const manifestBindings = bindings.filter((service)=>isJsonRecord(service) && 'string' == typeof service.binding && 'string' == typeof service.service && ('string' == typeof service.prefix || Array.isArray(service.fragments) && service.fragments.length > 0)).map((service)=>({
|
|
142
273
|
binding: service.binding,
|
|
@@ -149,7 +280,11 @@ const createWorkerManifestServiceBindings = (serviceBindings)=>{
|
|
|
149
280
|
fragments: service.fragments
|
|
150
281
|
}
|
|
151
282
|
}));
|
|
152
|
-
|
|
283
|
+
const allBindings = [
|
|
284
|
+
...manifestBindings,
|
|
285
|
+
...vpcManifestBindings
|
|
286
|
+
];
|
|
287
|
+
return allBindings.length > 0 ? allBindings : void 0;
|
|
153
288
|
};
|
|
154
289
|
const createWranglerConfig = (appDirectory, modernConfig)=>{
|
|
155
290
|
const wrangler = getConfiguredWrangler(modernConfig);
|
|
@@ -157,7 +292,7 @@ const createWranglerConfig = (appDirectory, modernConfig)=>{
|
|
|
157
292
|
const d1Databases = createWranglerD1Databases(modernConfig, wrangler.d1_databases);
|
|
158
293
|
const serviceBindings = createWorkerServiceBindings(modernConfig, wrangler.services);
|
|
159
294
|
const wranglerServices = createWranglerServices(serviceBindings);
|
|
160
|
-
|
|
295
|
+
const workerConfig = {
|
|
161
296
|
$schema: 'node_modules/wrangler/config-schema.json',
|
|
162
297
|
name: getConfiguredWorkerName(appDirectory, modernConfig),
|
|
163
298
|
...wrangler,
|
|
@@ -175,5 +310,33 @@ const createWranglerConfig = (appDirectory, modernConfig)=>{
|
|
|
175
310
|
services: wranglerServices
|
|
176
311
|
}
|
|
177
312
|
};
|
|
313
|
+
const vpcServiceBindings = createWorkerVpcServiceBindings(modernConfig, wrangler.vpc_services, serviceBindings, collectWorkerBindingNames(workerConfig));
|
|
314
|
+
if (void 0 === vpcServiceBindings) return workerConfig;
|
|
315
|
+
const vpcServicesConfig = vpcServiceBindings.map(({ binding, serviceId })=>({
|
|
316
|
+
binding,
|
|
317
|
+
service_id: serviceId
|
|
318
|
+
}));
|
|
319
|
+
return {
|
|
320
|
+
...workerConfig,
|
|
321
|
+
...void 0 === environments ? {} : {
|
|
322
|
+
env: Object.fromEntries(Object.entries(environments).map(([name, environment])=>{
|
|
323
|
+
if (void 0 !== environment.vpc_services) throw new Error(`Use deploy.worker.vpcServices or deploy.worker.wrangler.env.${name}.vpc_services, not both.`);
|
|
324
|
+
const environmentBindingNames = new Set([
|
|
325
|
+
...collectWorkerBindingNames(environment),
|
|
326
|
+
...effectiveServiceBindings(environment.services).map(({ binding })=>binding)
|
|
327
|
+
]);
|
|
328
|
+
const collision = vpcServiceBindings.find(({ binding })=>environmentBindingNames.has(binding));
|
|
329
|
+
if (void 0 !== collision) throw new Error(`deploy.worker.vpcServices binding "${collision.binding}" is already a binding of deploy.worker.wrangler.env.${name}.`);
|
|
330
|
+
return [
|
|
331
|
+
name,
|
|
332
|
+
{
|
|
333
|
+
...environment,
|
|
334
|
+
vpc_services: vpcServicesConfig
|
|
335
|
+
}
|
|
336
|
+
];
|
|
337
|
+
}))
|
|
338
|
+
},
|
|
339
|
+
vpc_services: vpcServicesConfig
|
|
340
|
+
};
|
|
178
341
|
};
|
|
179
|
-
export { createWorkerManifestServiceBindings, createWorkerServiceBindings, createWranglerConfig };
|
|
342
|
+
export { createWorkerManifestServiceBindings, createWorkerServiceBindings, createWorkerVpcServiceBindings, createWranglerConfig, getWorkerEffectBffPrefix };
|
|
@@ -7,7 +7,7 @@ import { ASSETS_BINDING, BFF_EFFECT_WORKER_DISPATCHER_EXPORT, BFF_EFFECT_WORKER_
|
|
|
7
7
|
import { createI18nWorkerManifest } from "./i18n-worker.mjs";
|
|
8
8
|
import { createCloudflareWorkerSecurityPolicy } from "./security-policies.mjs";
|
|
9
9
|
import { isRecord, normalizeRelativePath } from "./utils.mjs";
|
|
10
|
-
import { createWorkerManifestServiceBindings, createWorkerServiceBindings } from "./wrangler-config.mjs";
|
|
10
|
+
import { createWorkerManifestServiceBindings, createWorkerServiceBindings, createWorkerVpcServiceBindings, getWorkerEffectBffPrefix } from "./wrangler-config.mjs";
|
|
11
11
|
const createMissingEffectBffWorkerError = (outputDirectory, worker)=>new Error(`Cloudflare Effect API runtime is configured, but the BFF worker bundle is missing: ${node_path.join(outputDirectory, worker)}. ${EFFECT_BFF_CLOUDFLARE_IMPORT_GUIDANCE}`);
|
|
12
12
|
const createEffectBffWorkerManifest = (modernConfig)=>{
|
|
13
13
|
const bff = modernConfig.bff;
|
|
@@ -81,13 +81,15 @@ const createWorkerManifest = async (outputDirectory, modernConfig, appContext, d
|
|
|
81
81
|
} : {}
|
|
82
82
|
};
|
|
83
83
|
}));
|
|
84
|
-
const bffPrefix = modernConfig.bff?.prefix;
|
|
85
|
-
const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
|
|
86
84
|
const isEffectApi = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
|
|
85
|
+
const effectBffPrefix = getWorkerEffectBffPrefix(modernConfig);
|
|
87
86
|
const effectApiWorkerExists = await fs.pathExists(node_path.join(outputDirectory, BFF_EFFECT_WORKER_ENTRY));
|
|
88
|
-
const
|
|
87
|
+
const typedServiceBindings = createWorkerServiceBindings(modernConfig, void 0);
|
|
88
|
+
const serviceBindings = createWorkerManifestServiceBindings(typedServiceBindings, createWorkerVpcServiceBindings(modernConfig, void 0, typedServiceBindings, new Set([
|
|
89
|
+
ASSETS_BINDING
|
|
90
|
+
])));
|
|
89
91
|
const moduleFederation = await createModuleFederationWorkerManifest(outputDirectory);
|
|
90
|
-
if (
|
|
92
|
+
if (void 0 !== effectBffPrefix && !effectApiWorkerExists) throw createMissingEffectBffWorkerError(outputDirectory, BFF_EFFECT_WORKER_ENTRY);
|
|
91
93
|
const effectBffManifest = isEffectApi ? createEffectBffWorkerManifest(modernConfig) : void 0;
|
|
92
94
|
return {
|
|
93
95
|
version: 1,
|
|
@@ -124,10 +126,10 @@ const createWorkerManifest = async (outputDirectory, modernConfig, appContext, d
|
|
|
124
126
|
deliveryUnit: deliveryUnitStamp
|
|
125
127
|
} : {},
|
|
126
128
|
i18n: createI18nWorkerManifest(routeSpec, appContext),
|
|
127
|
-
bff:
|
|
129
|
+
bff: void 0 !== effectBffPrefix && effectApiWorkerExists ? {
|
|
128
130
|
dispatcherExport: BFF_EFFECT_WORKER_DISPATCHER_EXPORT,
|
|
129
131
|
runtimeFramework: 'effect',
|
|
130
|
-
prefix:
|
|
132
|
+
prefix: effectBffPrefix,
|
|
131
133
|
worker: BFF_EFFECT_WORKER_ENTRY,
|
|
132
134
|
effect: effectBffManifest
|
|
133
135
|
} : void 0,
|