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