@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
|
@@ -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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
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 =
|
|
14
|
-
const WORKER_BUNDLE_DIRECTORY =
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
445
|
-
const
|
|
446
|
-
const
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
};
|