@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.
- package/dist/cjs/builder/generator/getBuilderEnvironments.js +1 -0
- package/dist/cjs/index.js +50 -44
- package/dist/cjs/plugins/backendFederationBuild.js +347 -0
- package/dist/cjs/plugins/deploy/platforms/cloudflare-output-contract.js +91 -0
- package/dist/cjs/plugins/deploy/platforms/cloudflare-output-plan.js +93 -0
- package/dist/cjs/plugins/deploy/platforms/cloudflare-output-verifier.js +402 -0
- package/dist/cjs/plugins/deploy/platforms/cloudflare.js +272 -29
- package/dist/cjs/plugins/deploy/platforms/templates/cloudflare-entry.mjs +346 -43
- package/dist/esm/builder/generator/getBuilderEnvironments.mjs +1 -0
- package/dist/esm/index.mjs +2 -0
- package/dist/esm/plugins/backendFederationBuild.mjs +296 -0
- package/dist/esm/plugins/deploy/platforms/cloudflare-output-contract.mjs +17 -0
- package/dist/esm/plugins/deploy/platforms/cloudflare-output-plan.mjs +45 -0
- package/dist/esm/plugins/deploy/platforms/cloudflare-output-verifier.mjs +347 -0
- package/dist/esm/plugins/deploy/platforms/cloudflare.mjs +272 -29
- package/dist/esm/plugins/deploy/platforms/templates/cloudflare-entry.mjs +346 -43
- package/dist/esm-node/builder/generator/getBuilderEnvironments.mjs +1 -0
- package/dist/esm-node/index.mjs +2 -0
- package/dist/esm-node/plugins/backendFederationBuild.mjs +297 -0
- package/dist/esm-node/plugins/deploy/platforms/cloudflare-output-contract.mjs +18 -0
- package/dist/esm-node/plugins/deploy/platforms/cloudflare-output-plan.mjs +46 -0
- package/dist/esm-node/plugins/deploy/platforms/cloudflare-output-verifier.mjs +348 -0
- package/dist/esm-node/plugins/deploy/platforms/cloudflare.mjs +272 -29
- package/dist/esm-node/plugins/deploy/platforms/templates/cloudflare-entry.mjs +346 -43
- package/dist/types/plugins/backendFederationBuild.d.ts +11 -0
- package/dist/types/plugins/deploy/platforms/cloudflare-output-contract.d.ts +13 -0
- package/dist/types/plugins/deploy/platforms/cloudflare-output-plan.d.ts +33 -0
- package/dist/types/plugins/deploy/platforms/cloudflare-output-verifier.d.ts +32 -0
- package/dist/types/types/config/cloudflareDeploy.d.ts +22 -0
- package/dist/types/types/config/deploy.d.ts +15 -2
- package/lib/types.d.ts +2 -3
- 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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
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 =
|
|
15
|
-
const WORKER_BUNDLE_DIRECTORY =
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
446
|
-
const
|
|
447
|
-
const
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
};
|