@bleedingdev/modern-js-app-tools 3.5.0-ultramodern.3 → 3.5.0-ultramodern.30

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.
Files changed (32) hide show
  1. package/dist/cjs/builder/generator/getBuilderEnvironments.js +1 -0
  2. package/dist/cjs/index.js +50 -44
  3. package/dist/cjs/plugins/backendFederationBuild.js +347 -0
  4. package/dist/cjs/plugins/deploy/platforms/cloudflare-output-contract.js +91 -0
  5. package/dist/cjs/plugins/deploy/platforms/cloudflare-output-plan.js +93 -0
  6. package/dist/cjs/plugins/deploy/platforms/cloudflare-output-verifier.js +402 -0
  7. package/dist/cjs/plugins/deploy/platforms/cloudflare.js +272 -29
  8. package/dist/cjs/plugins/deploy/platforms/templates/cloudflare-entry.mjs +346 -43
  9. package/dist/esm/builder/generator/getBuilderEnvironments.mjs +1 -0
  10. package/dist/esm/index.mjs +2 -0
  11. package/dist/esm/plugins/backendFederationBuild.mjs +296 -0
  12. package/dist/esm/plugins/deploy/platforms/cloudflare-output-contract.mjs +17 -0
  13. package/dist/esm/plugins/deploy/platforms/cloudflare-output-plan.mjs +45 -0
  14. package/dist/esm/plugins/deploy/platforms/cloudflare-output-verifier.mjs +347 -0
  15. package/dist/esm/plugins/deploy/platforms/cloudflare.mjs +272 -29
  16. package/dist/esm/plugins/deploy/platforms/templates/cloudflare-entry.mjs +346 -43
  17. package/dist/esm-node/builder/generator/getBuilderEnvironments.mjs +1 -0
  18. package/dist/esm-node/index.mjs +2 -0
  19. package/dist/esm-node/plugins/backendFederationBuild.mjs +297 -0
  20. package/dist/esm-node/plugins/deploy/platforms/cloudflare-output-contract.mjs +18 -0
  21. package/dist/esm-node/plugins/deploy/platforms/cloudflare-output-plan.mjs +46 -0
  22. package/dist/esm-node/plugins/deploy/platforms/cloudflare-output-verifier.mjs +348 -0
  23. package/dist/esm-node/plugins/deploy/platforms/cloudflare.mjs +272 -29
  24. package/dist/esm-node/plugins/deploy/platforms/templates/cloudflare-entry.mjs +346 -43
  25. package/dist/types/plugins/backendFederationBuild.d.ts +11 -0
  26. package/dist/types/plugins/deploy/platforms/cloudflare-output-contract.d.ts +13 -0
  27. package/dist/types/plugins/deploy/platforms/cloudflare-output-plan.d.ts +33 -0
  28. package/dist/types/plugins/deploy/platforms/cloudflare-output-verifier.d.ts +32 -0
  29. package/dist/types/types/config/cloudflareDeploy.d.ts +22 -0
  30. package/dist/types/types/config/deploy.d.ts +15 -2
  31. package/lib/types.d.ts +2 -3
  32. package/package.json +17 -11
@@ -1,17 +1,19 @@
1
1
  import node_path from "node:path";
2
2
  import { fs } from "@modern-js/utils";
3
3
  import { readTemplate } from "../utils/index.mjs";
4
- const WORKER_ENTRY = 'server/index.mjs';
5
- const WORKER_MANIFEST = 'server/modern-worker-manifest.json';
6
- const WRANGLER_CONFIG_FILE = 'wrangler.json';
7
- const OUTPUT_PACKAGE_FILE = 'package.json';
8
- const ASSETS_BINDING = 'ASSETS';
4
+ import { CLOUDFLARE_ASSETS_BINDING, CLOUDFLARE_OUTPUT_PACKAGE_FILE, CLOUDFLARE_PUBLIC_ASSETS_DIRECTORY, CLOUDFLARE_REQUIRED_COMPATIBILITY_FLAGS, CLOUDFLARE_RUNTIME_TYPE, CLOUDFLARE_WORKER_BUNDLE_DIRECTORY, CLOUDFLARE_WORKER_BUNDLE_FORMAT, CLOUDFLARE_WORKER_ENTRY, CLOUDFLARE_WRANGLER_CONFIG_FILE } from "./cloudflare-output-contract.mjs";
5
+ import { createCloudflareOutputPlan } from "./cloudflare-output-plan.mjs";
6
+ import { assertCloudflareOutput } from "./cloudflare-output-verifier.mjs";
7
+ const WORKER_ENTRY = CLOUDFLARE_WORKER_ENTRY;
8
+ const WRANGLER_CONFIG_FILE = CLOUDFLARE_WRANGLER_CONFIG_FILE;
9
+ const OUTPUT_PACKAGE_FILE = CLOUDFLARE_OUTPUT_PACKAGE_FILE;
10
+ const ASSETS_BINDING = CLOUDFLARE_ASSETS_BINDING;
9
11
  const ROUTE_SPEC_FILE = 'route.json';
10
12
  const ROUTE_SPEC_OUTPUT = `server/${ROUTE_SPEC_FILE}`;
11
13
  const LOADABLE_STATS_FILE = 'loadable-stats.json';
12
14
  const ROUTE_MANIFEST_FILE = 'routes-manifest.json';
13
- const PUBLIC_ASSETS_DIRECTORY = 'public';
14
- const WORKER_BUNDLE_DIRECTORY = 'worker';
15
+ const PUBLIC_ASSETS_DIRECTORY = CLOUDFLARE_PUBLIC_ASSETS_DIRECTORY;
16
+ const WORKER_BUNDLE_DIRECTORY = CLOUDFLARE_WORKER_BUNDLE_DIRECTORY;
15
17
  const SERVER_BUNDLE_DIRECTORY = 'bundles';
16
18
  const SERVER_OUTPUT_DIRECTORY = 'server';
17
19
  const DEFAULT_SERVER_ONLY_PUBLIC_ASSET_EXCLUDES = [
@@ -22,10 +24,7 @@ const BFF_EFFECT_WORKER_ENTRY = `${WORKER_BUNDLE_DIRECTORY}/__modern_bff_effect.
22
24
  const EFFECT_BFF_CLOUDFLARE_IMPORT_GUIDANCE = 'Ensure the Effect API entry exists at api/index.ts or bff.effect.entry, and import Cloudflare edge handlers from @modern-js/plugin-bff/effect-edge instead of lambda/Hono server helpers.';
23
25
  const DEFAULT_COMPATIBILITY_DATE = '2026-06-02';
24
26
  const COMPATIBILITY_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
25
- const REQUIRED_COMPATIBILITY_FLAGS = [
26
- 'nodejs_compat',
27
- 'global_fetch_strictly_public'
28
- ];
27
+ const REQUIRED_COMPATIBILITY_FLAGS = CLOUDFLARE_REQUIRED_COMPATIBILITY_FLAGS;
29
28
  const RESERVED_ARTIFACT_DESTINATION_FILES = new Set([
30
29
  WRANGLER_CONFIG_FILE,
31
30
  OUTPUT_PACKAGE_FILE
@@ -109,6 +108,102 @@ const DEFAULT_CSP_DIRECTIVES = {
109
108
  'blob:'
110
109
  ]
111
110
  };
111
+ const I18N_SERVER_PLUGIN_NAME = 'plugin-i18n/server';
112
+ const DEFAULT_I18NEXT_DETECTION_OPTIONS = {
113
+ order: [
114
+ 'querystring',
115
+ 'cookie',
116
+ 'localStorage',
117
+ 'header',
118
+ 'navigator',
119
+ 'htmlTag',
120
+ 'path',
121
+ 'subdomain'
122
+ ],
123
+ lookupQuerystring: 'lng',
124
+ lookupCookie: 'i18next',
125
+ lookupHeader: 'accept-language'
126
+ };
127
+ const SERVER_SIDE_I18N_DETECTORS = new Set([
128
+ 'querystring',
129
+ 'cookie',
130
+ 'header'
131
+ ]);
132
+ const isRecord = (value)=>null !== value && 'object' == typeof value && !Array.isArray(value);
133
+ const toStringArray = (value)=>Array.isArray(value) ? value.filter((entry)=>'string' == typeof entry) : [];
134
+ const getI18nServerPlugin = (appContext)=>appContext.serverPlugins?.find((plugin)=>plugin.name.includes(I18N_SERVER_PLUGIN_NAME));
135
+ const removeLocaleDetectionByEntry = (config)=>{
136
+ const { localeDetectionByEntry: _localeDetectionByEntry, ...rest } = config;
137
+ return rest;
138
+ };
139
+ const getEntryLocaleDetection = (entryName, localeDetection)=>{
140
+ const byEntry = isRecord(localeDetection.localeDetectionByEntry) ? localeDetection.localeDetectionByEntry : void 0;
141
+ const entryConfig = byEntry?.[entryName];
142
+ const globalConfig = removeLocaleDetectionByEntry(localeDetection);
143
+ return isRecord(entryConfig) ? {
144
+ ...globalConfig,
145
+ ...entryConfig
146
+ } : globalConfig;
147
+ };
148
+ const normalizeI18nDetection = (detection)=>{
149
+ const configured = isRecord(detection) ? detection : {};
150
+ const rawOrder = Array.isArray(configured.order) ? configured.order : DEFAULT_I18NEXT_DETECTION_OPTIONS.order;
151
+ const order = rawOrder.filter((entry)=>'string' == typeof entry && SERVER_SIDE_I18N_DETECTORS.has(entry));
152
+ return {
153
+ order: order.length > 0 ? order : [
154
+ 'querystring',
155
+ 'cookie',
156
+ 'header'
157
+ ],
158
+ lookupQuerystring: 'string' == typeof configured.lookupQuerystring && configured.lookupQuerystring ? configured.lookupQuerystring : DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupQuerystring,
159
+ lookupCookie: 'string' == typeof configured.lookupCookie && configured.lookupCookie ? configured.lookupCookie : DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupCookie,
160
+ lookupHeader: 'string' == typeof configured.lookupHeader && configured.lookupHeader ? configured.lookupHeader : DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupHeader
161
+ };
162
+ };
163
+ const normalizeLocalisedUrls = (localisedUrls)=>{
164
+ if (!isRecord(localisedUrls) || 0 === Object.keys(localisedUrls).length) return;
165
+ const normalized = {};
166
+ for (const [canonicalPath, languageMap] of Object.entries(localisedUrls)){
167
+ if (!isRecord(languageMap)) continue;
168
+ const normalizedLanguageMap = Object.fromEntries(Object.entries(languageMap).filter((entry)=>'string' == typeof entry[1] && entry[1].length > 0));
169
+ if (Object.keys(normalizedLanguageMap).length > 0) normalized[canonicalPath] = normalizedLanguageMap;
170
+ }
171
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
172
+ };
173
+ const normalizeWorkerI18nEntry = (entryName, localeDetection, staticRoutePrefixes)=>{
174
+ const entryConfig = getEntryLocaleDetection(entryName, localeDetection);
175
+ const languages = toStringArray(entryConfig.languages);
176
+ if (true !== entryConfig.localePathRedirect || 0 === languages.length) return;
177
+ const fallbackLanguage = 'string' == typeof entryConfig.fallbackLanguage && entryConfig.fallbackLanguage ? entryConfig.fallbackLanguage : 'en';
178
+ const ignoreRedirectRoutes = Array.isArray(entryConfig.ignoreRedirectRoutes) ? toStringArray(entryConfig.ignoreRedirectRoutes) : [];
179
+ const localisedUrls = normalizeLocalisedUrls(entryConfig.localisedUrls);
180
+ return {
181
+ i18nextDetector: false !== entryConfig.i18nextDetector,
182
+ languages,
183
+ fallbackLanguage,
184
+ detection: normalizeI18nDetection(entryConfig.detection),
185
+ ignoreRedirectRoutes,
186
+ staticRoutePrefixes,
187
+ ...localisedUrls ? {
188
+ localisedUrls
189
+ } : {}
190
+ };
191
+ };
192
+ const createI18nWorkerManifest = (routeSpec, appContext)=>{
193
+ const i18nServerPlugin = getI18nServerPlugin(appContext);
194
+ const localeDetection = i18nServerPlugin?.options?.localeDetection;
195
+ if (!isRecord(localeDetection)) return;
196
+ const staticRoutePrefixes = toStringArray(i18nServerPlugin?.options?.staticRoutePrefixes);
197
+ const entries = {};
198
+ for (const route of routeSpec.routes){
199
+ if ('string' != typeof route.entryName || entries[route.entryName]) continue;
200
+ const entry = normalizeWorkerI18nEntry(route.entryName, localeDetection, staticRoutePrefixes);
201
+ if (entry) entries[route.entryName] = entry;
202
+ }
203
+ return Object.keys(entries).length > 0 ? {
204
+ entries
205
+ } : void 0;
206
+ };
112
207
  const getCompatibilityDate = (modernConfig)=>{
113
208
  const configuredDate = modernConfig.deploy?.worker?.compatibilityDate?.trim();
114
209
  const compatibilityDate = configuredDate || DEFAULT_COMPATIBILITY_DATE;
@@ -123,11 +218,12 @@ const getConfiguredWorkerName = (appDirectory, modernConfig)=>{
123
218
  const configuredName = modernConfig.deploy?.worker?.name?.trim();
124
219
  return configuredName || getWorkerName(appDirectory);
125
220
  };
126
- const normalizeRelativePath = (value, label, scope = 'app output')=>{
221
+ const normalizeRelativePath = (value, label, scope = 'app output', options = {})=>{
127
222
  if ('string' != typeof value) throw new Error(`${label} must be a relative path inside the ${scope}.`);
128
223
  const normalized = value.trim().replace(/\\/g, '/').replace(/^\.\/+/u, '').replace(/\/+$/u, '');
129
224
  const segments = normalized.split('/');
130
- if (!normalized || node_path.isAbsolute(normalized) || '.' === normalized || segments.includes('..')) throw new Error(`${label} must be a relative path inside the ${scope}.`);
225
+ const isRootDestination = '.' === normalized;
226
+ if (!normalized || node_path.isAbsolute(normalized) || !options.allowRoot && isRootDestination || segments.includes('..')) throw new Error(`${label} must be a relative path inside the ${scope}.`);
131
227
  return normalized;
132
228
  };
133
229
  const normalizeDirectiveValues = (value)=>{
@@ -282,9 +378,48 @@ const createWranglerD1Databases = (modernConfig, configuredWranglerD1)=>{
282
378
  if (!Array.isArray(d1Databases)) throw new Error('deploy.worker.d1Databases must be an array.');
283
379
  return d1Databases.map(normalizeD1Database);
284
380
  };
381
+ const normalizeServiceBinding = (service, index)=>{
382
+ const binding = assertNonEmptyString(service.binding, `deploy.worker.services[${index}].binding`);
383
+ const serviceName = assertNonEmptyString(service.service, `deploy.worker.services[${index}].service`);
384
+ const prefix = void 0 === service.prefix ? void 0 : assertNonEmptyString(service.prefix, `deploy.worker.services[${index}].prefix`);
385
+ return {
386
+ binding,
387
+ service: serviceName,
388
+ ...void 0 === prefix ? {} : {
389
+ prefix
390
+ }
391
+ };
392
+ };
393
+ const createWorkerServiceBindings = (modernConfig, configuredWranglerServices)=>{
394
+ const services = modernConfig.deploy?.worker?.services;
395
+ if (void 0 === services) return configuredWranglerServices;
396
+ if (void 0 !== configuredWranglerServices) throw new Error('Use deploy.worker.services or deploy.worker.wrangler.services, not both.');
397
+ if (!Array.isArray(services)) throw new Error('deploy.worker.services must be an array.');
398
+ return services.map(normalizeServiceBinding);
399
+ };
400
+ const createWranglerServices = (serviceBindings)=>{
401
+ if (!Array.isArray(serviceBindings)) return serviceBindings;
402
+ if (!serviceBindings.every(isJsonRecord)) return serviceBindings;
403
+ return serviceBindings.map((service)=>{
404
+ const { prefix, ...wranglerService } = service;
405
+ return wranglerService;
406
+ });
407
+ };
408
+ const createWorkerManifestServiceBindings = (serviceBindings)=>{
409
+ if (!Array.isArray(serviceBindings)) return;
410
+ const dispatchBindings = serviceBindings.filter((service)=>isJsonRecord(service) && 'string' == typeof service.binding && 'string' == typeof service.service && 'string' == typeof service.prefix).map((service)=>({
411
+ binding: service.binding,
412
+ service: service.service,
413
+ prefix: service.prefix,
414
+ interface: 'fetch'
415
+ }));
416
+ return dispatchBindings.length > 0 ? dispatchBindings : void 0;
417
+ };
285
418
  const createWranglerConfig = (appDirectory, modernConfig)=>{
286
419
  const wrangler = getConfiguredWrangler(modernConfig);
287
420
  const d1Databases = createWranglerD1Databases(modernConfig, wrangler.d1_databases);
421
+ const serviceBindings = createWorkerServiceBindings(modernConfig, wrangler.services);
422
+ const wranglerServices = createWranglerServices(serviceBindings);
288
423
  return {
289
424
  $schema: 'node_modules/wrangler/config-schema.json',
290
425
  name: getConfiguredWorkerName(appDirectory, modernConfig),
@@ -295,6 +430,9 @@ const createWranglerConfig = (appDirectory, modernConfig)=>{
295
430
  assets: createWranglerAssetsConfig(wrangler.assets),
296
431
  ...void 0 === d1Databases ? {} : {
297
432
  d1_databases: d1Databases
433
+ },
434
+ ...void 0 === wranglerServices ? {} : {
435
+ services: wranglerServices
298
436
  }
299
437
  };
300
438
  };
@@ -311,6 +449,18 @@ const normalizeCloudflareArtifact = (artifact, index)=>{
311
449
  };
312
450
  };
313
451
  const getCloudflareArtifacts = (modernConfig)=>(modernConfig.deploy?.worker?.artifacts ?? []).map(normalizeCloudflareArtifact);
452
+ const normalizeCloudflarePublicAsset = (asset, index)=>{
453
+ const from = normalizeRelativePath(asset.from, `deploy.worker.publicAssets[${index}].from`, 'app root');
454
+ const to = normalizeRelativePath(asset.to, `deploy.worker.publicAssets[${index}].to`, 'Cloudflare public output', {
455
+ allowRoot: true
456
+ });
457
+ return {
458
+ from,
459
+ to,
460
+ index
461
+ };
462
+ };
463
+ const getCloudflarePublicAssets = (modernConfig)=>(modernConfig.deploy?.worker?.publicAssets ?? []).map(normalizeCloudflarePublicAsset);
314
464
  const copyCloudflareArtifacts = async (appDirectory, outputDirectory, artifacts)=>{
315
465
  for (const artifact of artifacts){
316
466
  const sourcePath = node_path.join(appDirectory, artifact.from);
@@ -318,6 +468,13 @@ const copyCloudflareArtifacts = async (appDirectory, outputDirectory, artifacts)
318
468
  await fs.copy(sourcePath, node_path.join(outputDirectory, artifact.to));
319
469
  }
320
470
  };
471
+ const copyCloudflarePublicAssets = async (appDirectory, publicDirectory, publicAssets)=>{
472
+ for (const asset of publicAssets){
473
+ const sourcePath = node_path.join(appDirectory, asset.from);
474
+ if (!await fs.pathExists(sourcePath)) throw new Error(`deploy.worker.publicAssets[${asset.index}].from does not exist: ${asset.from}`);
475
+ await fs.copy(sourcePath, node_path.join(publicDirectory, asset.to));
476
+ }
477
+ };
321
478
  const copyCloudflareD1Migrations = async (appDirectory, outputDirectory, modernConfig)=>{
322
479
  for (const [index, database] of (modernConfig.deploy?.worker?.d1Databases ?? []).entries()){
323
480
  if (!database.migrationsDir) continue;
@@ -338,8 +495,79 @@ const readRouteSpec = async (outputDirectory)=>{
338
495
  routes: Array.isArray(routeSpec.routes) ? routeSpec.routes : []
339
496
  };
340
497
  };
498
+ const COMPACT_CONFIG_PATH = '.modernjs/ultramodern.json';
499
+ const ULTRAMODERN_BUILD_MODULE = 'shared/ultramodern-build.ts';
500
+ const nonEmptyString = (value)=>'string' == typeof value && value.length > 0 ? value : void 0;
501
+ const toDeliveryUnitIdentity = (value)=>{
502
+ if (!isRecord(value)) return;
503
+ const unitId = nonEmptyString(value.unitId);
504
+ const buildMarker = nonEmptyString(value.buildMarker);
505
+ const sourceRevision = nonEmptyString(value.sourceRevision);
506
+ if (!unitId || !buildMarker || !sourceRevision) return;
507
+ return {
508
+ unitId,
509
+ buildMarker,
510
+ sourceRevision
511
+ };
512
+ };
513
+ const findWorkspaceRoot = async (appDirectory)=>{
514
+ let current = node_path.resolve(appDirectory);
515
+ for(;;){
516
+ if (await fs.pathExists(node_path.join(current, COMPACT_CONFIG_PATH))) return current;
517
+ const parent = node_path.dirname(current);
518
+ if (parent === current) return;
519
+ current = parent;
520
+ }
521
+ };
522
+ const resolveTopologyDeliveryUnit = async (appDirectory)=>{
523
+ const workspaceRoot = await findWorkspaceRoot(appDirectory);
524
+ if (!workspaceRoot) return;
525
+ let compactConfig;
526
+ try {
527
+ compactConfig = await fs.readJSON(node_path.join(workspaceRoot, COMPACT_CONFIG_PATH));
528
+ } catch {
529
+ return;
530
+ }
531
+ if (!isRecord(compactConfig)) return;
532
+ const topology = isRecord(compactConfig.topology) ? compactConfig.topology : void 0;
533
+ const apps = Array.isArray(topology?.apps) ? topology.apps : [];
534
+ const resolvedAppDirectory = node_path.resolve(appDirectory);
535
+ for (const app of apps){
536
+ if (!isRecord(app)) continue;
537
+ const appPath = nonEmptyString(app.path);
538
+ if (appPath && node_path.resolve(workspaceRoot, appPath.replace(/^\.\/+/u, '')) === resolvedAppDirectory) return toDeliveryUnitIdentity(app.deliveryUnit);
539
+ }
540
+ return toDeliveryUnitIdentity(compactConfig.deliveryUnit);
541
+ };
542
+ const resolveWorkerDeliveryUnitStamp = async (appDirectory)=>{
543
+ const buildModulePath = node_path.join(appDirectory, ULTRAMODERN_BUILD_MODULE);
544
+ if (!await fs.pathExists(buildModulePath)) return;
545
+ const source = await fs.readFile(buildModulePath, 'utf8');
546
+ const buildMarker = source.match(/\bbuild:\s*['"]([^'"]+)['"]/u)?.[1];
547
+ const unitId = source.match(/\bunitId:\s*['"]([^'"]+)['"]/u)?.[1];
548
+ const sourceRevision = source.match(/\bsourceRevision:\s*['"]([^'"]+)['"]/u)?.[1];
549
+ if (!buildMarker || !unitId || !sourceRevision) return;
550
+ const identity = {
551
+ unitId,
552
+ buildMarker,
553
+ sourceRevision
554
+ };
555
+ return {
556
+ ...identity,
557
+ surfaces: {
558
+ ui: {
559
+ ...identity,
560
+ surface: 'ui'
561
+ },
562
+ api: {
563
+ ...identity,
564
+ surface: 'api'
565
+ }
566
+ }
567
+ };
568
+ };
341
569
  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}`);
342
- const createWorkerManifest = async (outputDirectory, modernConfig)=>{
570
+ const createWorkerManifest = async (outputDirectory, modernConfig, appContext, deliveryUnitStamp)=>{
343
571
  const routeSpec = await readRouteSpec(outputDirectory);
344
572
  const routes = await Promise.all(routeSpec.routes.map(async (route)=>{
345
573
  const worker = 'string' == typeof route.worker ? route.worker : void 0;
@@ -356,11 +584,12 @@ const createWorkerManifest = async (outputDirectory, modernConfig)=>{
356
584
  const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
357
585
  const isEffectApi = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
358
586
  const effectApiWorkerExists = await fs.pathExists(node_path.join(outputDirectory, BFF_EFFECT_WORKER_ENTRY));
587
+ const serviceBindings = createWorkerManifestServiceBindings(createWorkerServiceBindings(modernConfig, void 0));
359
588
  if (isEffectApi && primaryBffPrefix && !effectApiWorkerExists) throw createMissingEffectBffWorkerError(outputDirectory, BFF_EFFECT_WORKER_ENTRY);
360
589
  return {
361
590
  version: 1,
362
591
  runtime: {
363
- type: 'cloudflare-module-worker',
592
+ type: CLOUDFLARE_RUNTIME_TYPE,
364
593
  entry: WORKER_ENTRY,
365
594
  fetchExport: true,
366
595
  nodeListen: false
@@ -376,7 +605,7 @@ const createWorkerManifest = async (outputDirectory, modernConfig)=>{
376
605
  },
377
606
  workerBundles: {
378
607
  directory: WORKER_BUNDLE_DIRECTORY,
379
- format: 'commonjs',
608
+ format: CLOUDFLARE_WORKER_BUNDLE_FORMAT,
380
609
  importableFromModuleWorker: true,
381
610
  requestHandlerExport: 'requestHandler'
382
611
  },
@@ -385,11 +614,18 @@ const createWorkerManifest = async (outputDirectory, modernConfig)=>{
385
614
  routeManifest: ROUTE_MANIFEST_FILE
386
615
  },
387
616
  security: createCloudflareWorkerSecurityPolicy(modernConfig),
617
+ ...deliveryUnitStamp ? {
618
+ deliveryUnit: deliveryUnitStamp
619
+ } : {},
620
+ i18n: createI18nWorkerManifest(routeSpec, appContext),
388
621
  bff: isEffectApi && primaryBffPrefix && effectApiWorkerExists ? {
389
622
  runtimeFramework: 'effect',
390
623
  prefix: primaryBffPrefix,
391
624
  worker: BFF_EFFECT_WORKER_ENTRY
392
- } : void 0
625
+ } : void 0,
626
+ ...void 0 === serviceBindings ? {} : {
627
+ serviceBindings
628
+ }
393
629
  };
394
630
  };
395
631
  const createWorkerModuleLoaders = (manifest)=>{
@@ -441,11 +677,12 @@ const shouldCopyToWorkerBundle = (src, workerBundleDirectory)=>{
441
677
  const createCloudflarePreset = ({ appContext, modernConfig })=>{
442
678
  const { appDirectory, distDirectory } = appContext;
443
679
  const outputDirectory = node_path.join(appDirectory, '.output');
444
- const publicDirectory = node_path.join(outputDirectory, PUBLIC_ASSETS_DIRECTORY);
445
- const workerEntryPath = node_path.join(outputDirectory, WORKER_ENTRY);
446
- const workerManifestPath = node_path.join(outputDirectory, WORKER_MANIFEST);
680
+ const outputPlan = createCloudflareOutputPlan(outputDirectory);
681
+ const publicDirectory = outputPlan.paths.publicAssets;
682
+ const workerEntryPath = outputPlan.paths.workerEntry;
683
+ const workerManifestPath = outputPlan.paths.workerManifest;
447
684
  const routeSpecOutputPath = node_path.join(outputDirectory, ROUTE_SPEC_OUTPUT);
448
- const wranglerConfigPath = node_path.join(outputDirectory, WRANGLER_CONFIG_FILE);
685
+ const wranglerConfigPath = outputPlan.paths.wranglerConfig;
449
686
  const cloudflareArtifacts = getCloudflareArtifacts(modernConfig);
450
687
  const publicAssetExcludes = getPublicAssetExcludes(appDirectory, modernConfig);
451
688
  return {
@@ -461,31 +698,37 @@ const createCloudflarePreset = ({ appContext, modernConfig })=>{
461
698
  const routeSpecSourcePath = node_path.join(distDirectory, ROUTE_SPEC_FILE);
462
699
  if (await fs.pathExists(routeSpecSourcePath)) await fs.copy(routeSpecSourcePath, routeSpecOutputPath);
463
700
  const workerBundleSourceDirectory = node_path.join(distDirectory, WORKER_BUNDLE_DIRECTORY);
464
- const workerBundleOutputDirectory = node_path.join(outputDirectory, WORKER_BUNDLE_DIRECTORY);
701
+ const workerBundleOutputDirectory = outputPlan.paths.workerBundle;
465
702
  if (await fs.pathExists(workerBundleSourceDirectory)) {
466
703
  await fs.copy(workerBundleSourceDirectory, workerBundleOutputDirectory, {
467
704
  filter: (src)=>shouldCopyToWorkerBundle(src, workerBundleSourceDirectory)
468
705
  });
469
- await fs.writeJSON(node_path.join(workerBundleOutputDirectory, 'package.json'), {
470
- type: 'commonjs'
471
- });
706
+ await fs.writeJSON(outputPlan.paths.workerPackage, outputPlan.packages.worker);
472
707
  }
473
708
  await copyCloudflareArtifacts(appDirectory, outputDirectory, cloudflareArtifacts);
709
+ await copyCloudflarePublicAssets(appDirectory, publicDirectory, getCloudflarePublicAssets(modernConfig));
474
710
  await copyCloudflareD1Migrations(appDirectory, outputDirectory, modernConfig);
475
711
  await fs.writeJSON(wranglerConfigPath, createWranglerConfig(appDirectory, modernConfig), {
476
712
  spaces: 2
477
713
  });
478
- await fs.writeJSON(workerManifestPath, await createWorkerManifest(outputDirectory, modernConfig), {
714
+ const deliveryUnitStamp = await resolveWorkerDeliveryUnitStamp(appDirectory);
715
+ await fs.writeJSON(workerManifestPath, await createWorkerManifest(outputDirectory, modernConfig, appContext, deliveryUnitStamp), {
479
716
  spaces: 2
480
717
  });
481
- await fs.writeJSON(node_path.join(outputDirectory, OUTPUT_PACKAGE_FILE), {
482
- type: 'module'
483
- });
718
+ await fs.writeJSON(outputPlan.paths.outputPackage, outputPlan.packages.output);
484
719
  },
485
720
  async genEntry () {
486
721
  const template = await readTemplate('cloudflare-entry.mjs');
487
722
  const manifest = await fs.readJSON(workerManifestPath);
488
723
  await fs.writeFile(workerEntryPath, template.replace('p_workerManifest', JSON.stringify(manifest, null, 2)).replace('p_workerModuleLoaders', createWorkerModuleLoaders(manifest)));
724
+ const topologyDeliveryUnit = await resolveTopologyDeliveryUnit(appDirectory);
725
+ await assertCloudflareOutput({
726
+ outputDirectory,
727
+ importWorker: false,
728
+ ...topologyDeliveryUnit ? {
729
+ deliveryUnit: topologyDeliveryUnit
730
+ } : {}
731
+ });
489
732
  }
490
733
  };
491
734
  };