@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.12 → 3.9.0-ultramodern.14
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/backend-federation-build/codegen.js +7 -2
- package/dist/cjs/backend-federation-build/config.js +86 -36
- package/dist/cjs/backend-federation-build/emit.js +17 -13
- package/dist/cjs/cloudflare/delivery-unit.js +16 -26
- package/dist/cjs/cloudflare/index.js +7 -1
- package/dist/cjs/cloudflare/worker-manifest.js +13 -4
- package/dist/cjs/cloudflare/wrangler-config.js +35 -7
- package/dist/cjs/cloudflare-builder.js +247 -16
- package/dist/cjs/cloudflare-output-contract.js +32 -2
- package/dist/cjs/cloudflare-output-verifier/identity.js +26 -24
- package/dist/cjs/cloudflare-output-verifier/index.js +10 -0
- package/dist/cjs/cloudflare-output-verifier/manifest.js +46 -0
- package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +7 -1
- package/dist/cjs/cloudflare-output-verifier/worker-bundles.js +10 -5
- package/dist/cjs/deploy-output/npmAliases.js +28 -3
- package/dist/cjs/release-envelope/framework-output.js +62 -49
- package/dist/cjs/release-envelope/index.js +10 -4
- package/dist/cjs/templates/cloudflare-entry.004-rendering-css.mjs +4 -0
- package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +46 -0
- package/dist/cjs/templates/cloudflare-entry.006-fetch-handler.mjs +9 -2
- package/dist/cjs/templates/cloudflare-worker-route-data.mjs +1069 -0
- package/dist/esm/backend-federation-build/codegen.mjs +7 -2
- package/dist/esm/backend-federation-build/config.mjs +78 -31
- package/dist/esm/backend-federation-build/emit.mjs +18 -14
- package/dist/esm/cloudflare/delivery-unit.mjs +16 -26
- package/dist/esm/cloudflare/index.mjs +7 -1
- package/dist/esm/cloudflare/worker-manifest.mjs +13 -4
- package/dist/esm/cloudflare/wrangler-config.mjs +35 -7
- package/dist/esm/cloudflare-builder.mjs +241 -19
- package/dist/esm/cloudflare-output-contract.mjs +25 -1
- package/dist/esm/cloudflare-output-verifier/identity.mjs +26 -24
- package/dist/esm/cloudflare-output-verifier/index.mjs +10 -0
- package/dist/esm/cloudflare-output-verifier/manifest.mjs +46 -0
- package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +7 -1
- package/dist/esm/cloudflare-output-verifier/worker-bundles.mjs +11 -6
- package/dist/esm/deploy-output/npmAliases.mjs +28 -3
- package/dist/esm/release-envelope/framework-output.mjs +62 -49
- package/dist/esm/release-envelope/index.mjs +10 -4
- package/dist/esm/templates/cloudflare-entry.004-rendering-css.mjs +4 -0
- package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +46 -0
- package/dist/esm/templates/cloudflare-entry.006-fetch-handler.mjs +9 -2
- package/dist/esm/templates/cloudflare-worker-route-data.mjs +1069 -0
- package/dist/esm-node/backend-federation-build/codegen.mjs +7 -2
- package/dist/esm-node/backend-federation-build/config.mjs +78 -31
- package/dist/esm-node/backend-federation-build/emit.mjs +18 -14
- package/dist/esm-node/cloudflare/delivery-unit.mjs +16 -26
- package/dist/esm-node/cloudflare/index.mjs +7 -1
- package/dist/esm-node/cloudflare/worker-manifest.mjs +13 -4
- package/dist/esm-node/cloudflare/wrangler-config.mjs +35 -7
- package/dist/esm-node/cloudflare-builder.mjs +241 -19
- package/dist/esm-node/cloudflare-output-contract.mjs +25 -1
- package/dist/esm-node/cloudflare-output-verifier/identity.mjs +26 -24
- package/dist/esm-node/cloudflare-output-verifier/index.mjs +10 -0
- package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +46 -0
- package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +7 -1
- package/dist/esm-node/cloudflare-output-verifier/worker-bundles.mjs +11 -6
- package/dist/esm-node/deploy-output/npmAliases.mjs +28 -3
- package/dist/esm-node/release-envelope/framework-output.mjs +62 -49
- package/dist/esm-node/release-envelope/index.mjs +10 -4
- package/dist/esm-node/templates/cloudflare-entry.004-rendering-css.mjs +4 -0
- package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +46 -0
- package/dist/esm-node/templates/cloudflare-entry.006-fetch-handler.mjs +9 -2
- package/dist/esm-node/templates/cloudflare-worker-route-data.mjs +1069 -0
- package/dist/types/backend-federation-build/codegen.d.ts +30 -0
- package/dist/types/backend-federation-build/config.d.ts +32 -13
- package/dist/types/cloudflare/delivery-unit.d.ts +1 -1
- package/dist/types/cloudflare/wrangler-config.d.ts +6 -0
- package/dist/types/cloudflare-builder.d.ts +45 -1
- package/dist/types/cloudflare-output-contract.d.ts +3 -1
- package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +2 -2
- package/dist/types/templates/cloudflare-worker-route-data.d.mts +14 -0
- package/package.json +25 -21
- package/src/backend-federation-build/codegen.ts +6 -2
- package/src/backend-federation-build/config.ts +207 -71
- package/src/backend-federation-build/emit.ts +33 -22
- package/src/cloudflare/delivery-unit.ts +39 -33
- package/src/cloudflare/index.ts +20 -6
- package/src/cloudflare/worker-manifest.ts +19 -3
- package/src/cloudflare/wrangler-config.ts +86 -10
- package/src/cloudflare-builder.ts +464 -15
- package/src/cloudflare-output-contract.ts +35 -2
- package/src/cloudflare-output-verifier/identity.ts +4 -0
- package/src/cloudflare-output-verifier/index.ts +20 -0
- package/src/cloudflare-output-verifier/manifest.ts +77 -0
- package/src/cloudflare-output-verifier/mutation-policy.ts +11 -1
- package/src/cloudflare-output-verifier/worker-bundles.ts +27 -11
- package/src/deploy-output/npmAliases.ts +52 -3
- package/src/release-envelope/framework-output.ts +163 -90
- package/src/release-envelope/index.ts +26 -5
- package/src/templates/cloudflare-entry.004-rendering-css.mjs +4 -0
- package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +46 -0
- package/src/templates/cloudflare-entry.006-fetch-handler.mjs +9 -2
- package/src/templates/cloudflare-worker-route-data.mjs +1069 -0
|
@@ -1,17 +1,56 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import node_fs from "node:fs";
|
|
3
|
-
import { createRequire } from "node:module";
|
|
3
|
+
import { createRequire, isBuiltin } from "node:module";
|
|
4
4
|
import node_path from "node:path";
|
|
5
5
|
import { SERVICE_WORKER_ENVIRONMENT_NAME } from "@modern-js/builder";
|
|
6
6
|
import { isProd } from "@modern-js/utils";
|
|
7
7
|
import { provider } from "std-env";
|
|
8
|
-
import { CLOUDFLARE_WORKER_NODE_BUILTINS } from "./cloudflare-output-contract.mjs";
|
|
8
|
+
import { CLOUDFLARE_ROUTE_DATA_HANDLER_EXPORT, CLOUDFLARE_WORKER_NODE_BUILTINS, CLOUDFLARE_WORKER_PLATFORM_MODULES, getCloudflareWorkerRouteDataEntryName } from "./cloudflare-output-contract.mjs";
|
|
9
9
|
import { getTemplatePath } from "./read-template.mjs";
|
|
10
10
|
const moduleRequire = createRequire(import.meta.url);
|
|
11
11
|
const BFF_EFFECT_WORKER_ENTRY_NAME = '__modern_bff_effect';
|
|
12
12
|
const BFF_EFFECT_WORKER_RUNTIME_QUERY = 'modern-bff-runtime';
|
|
13
|
+
const SERVER_LOADER_COMBINED_FILE = 'server-loader-combined.js';
|
|
14
|
+
const ROUTE_SERVER_LOADERS_FILE = 'route-server-loaders.js';
|
|
15
|
+
const WORKER_ROUTE_DATA_FILE = 'cloudflare-worker-route-data.js';
|
|
16
|
+
const WORKER_ROUTE_DATA_TEMPLATE = 'cloudflare-worker-route-data.mjs';
|
|
17
|
+
const DATA_LOADER_CLIENT_TRANSFORM = /plugin-data-loader[\\/](?:.+[\\/])?loader\.[cm]?js$/u;
|
|
13
18
|
const MF_SSR_DATA_FETCH_RUNTIME_PLUGIN = '@module-federation/modern-js-v3/ssr-inject-data-fetch-function-plugin';
|
|
14
19
|
const MF_SSR_DEV_RUNTIME_PLUGIN = '@module-federation/modern-js-v3/ssr-dev-plugin';
|
|
20
|
+
const WORKER_DEPENDENCY_CONDITIONS = [
|
|
21
|
+
[
|
|
22
|
+
'esm',
|
|
23
|
+
'import'
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
'wasm',
|
|
27
|
+
'import'
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
'loaderImport',
|
|
31
|
+
'import'
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
'worker',
|
|
35
|
+
'import'
|
|
36
|
+
],
|
|
37
|
+
[
|
|
38
|
+
'commonjs',
|
|
39
|
+
'require'
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
'amd',
|
|
43
|
+
'require'
|
|
44
|
+
],
|
|
45
|
+
[
|
|
46
|
+
'loader',
|
|
47
|
+
'require'
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
'unknown',
|
|
51
|
+
'require'
|
|
52
|
+
]
|
|
53
|
+
];
|
|
15
54
|
const JS_OR_TS_EXTENSIONS = new Set([
|
|
16
55
|
'.js',
|
|
17
56
|
'.jsx',
|
|
@@ -71,19 +110,154 @@ const resolvePackageFile = (packageName, filePath, paths)=>{
|
|
|
71
110
|
const setAliasIfPresent = (alias, name, value)=>{
|
|
72
111
|
if (value) alias.set(name, value);
|
|
73
112
|
};
|
|
74
|
-
const getCloudflareWorkerNodeExternals = ()=>Object.fromEntries(
|
|
75
|
-
|
|
113
|
+
const getCloudflareWorkerNodeExternals = ()=>Object.fromEntries([
|
|
114
|
+
...CLOUDFLARE_WORKER_NODE_BUILTINS.flatMap((builtin)=>{
|
|
115
|
+
const nodeBuiltin = `node:${builtin}`;
|
|
116
|
+
const external = `module-import ${nodeBuiltin}`;
|
|
117
|
+
return isBuiltin(builtin) ? [
|
|
118
|
+
[
|
|
119
|
+
builtin,
|
|
120
|
+
external
|
|
121
|
+
],
|
|
122
|
+
[
|
|
123
|
+
nodeBuiltin,
|
|
124
|
+
external
|
|
125
|
+
]
|
|
126
|
+
] : [
|
|
127
|
+
[
|
|
128
|
+
nodeBuiltin,
|
|
129
|
+
external
|
|
130
|
+
]
|
|
131
|
+
];
|
|
132
|
+
}),
|
|
133
|
+
...CLOUDFLARE_WORKER_PLATFORM_MODULES.map((platformModule)=>[
|
|
134
|
+
platformModule,
|
|
135
|
+
`module-import ${platformModule}`
|
|
136
|
+
])
|
|
137
|
+
]);
|
|
138
|
+
const getPackageNameFromRequest = (request)=>{
|
|
139
|
+
if (request.startsWith('.') || request.startsWith('/') || request.includes(':') || node_path.isAbsolute(request)) return;
|
|
140
|
+
const segments = request.split('/');
|
|
141
|
+
return request.startsWith('@') ? segments.length > 1 ? `${segments[0]}/${segments[1]}` : void 0 : segments[0];
|
|
142
|
+
};
|
|
143
|
+
const isRedirectedByAliasField = (manifest, aliasFields, request)=>aliasFields.some((field)=>{
|
|
144
|
+
let value = manifest;
|
|
145
|
+
for (const key of 'string' == typeof field ? [
|
|
146
|
+
field
|
|
147
|
+
] : field)value = value && 'object' == typeof value ? value[key] : void 0;
|
|
148
|
+
return 'object' == typeof value && null !== value && Object.hasOwn(value, request);
|
|
149
|
+
});
|
|
150
|
+
const findOwningPackageManifest = (directory, cache)=>{
|
|
151
|
+
if (cache.has(directory)) return cache.get(directory);
|
|
152
|
+
let manifest;
|
|
153
|
+
const packageJsonPath = node_path.join(directory, 'package.json');
|
|
154
|
+
if (node_fs.existsSync(packageJsonPath)) try {
|
|
155
|
+
manifest = JSON.parse(node_fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
156
|
+
} catch {
|
|
157
|
+
manifest = void 0;
|
|
158
|
+
}
|
|
159
|
+
else if (node_path.dirname(directory) !== directory) manifest = findOwningPackageManifest(node_path.dirname(directory), cache);
|
|
160
|
+
cache.set(directory, manifest);
|
|
161
|
+
return manifest;
|
|
162
|
+
};
|
|
163
|
+
const isPackageInstalled = (packageName, directory, moduleDirectories)=>moduleDirectories.some((moduleDirectory)=>{
|
|
164
|
+
if (node_path.isAbsolute(moduleDirectory)) return node_fs.existsSync(node_path.join(moduleDirectory, packageName));
|
|
165
|
+
let current = directory;
|
|
166
|
+
while(true){
|
|
167
|
+
if (node_fs.existsSync(node_path.join(current, moduleDirectory, packageName))) return true;
|
|
168
|
+
const parent = node_path.dirname(current);
|
|
169
|
+
if (parent === current) return false;
|
|
170
|
+
current = parent;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
const createAbsentOptionalDependencyFilter = (isRedirected = ()=>false, moduleDirectories = [
|
|
174
|
+
'node_modules'
|
|
175
|
+
], aliasFields = [])=>{
|
|
176
|
+
const manifests = new Map();
|
|
177
|
+
return (request, context)=>{
|
|
178
|
+
const packageName = getPackageNameFromRequest(request);
|
|
179
|
+
if (!packageName || !context || isRedirected(request)) return false;
|
|
180
|
+
const manifest = findOwningPackageManifest(context, manifests);
|
|
181
|
+
if (!manifest) return false;
|
|
182
|
+
const optional = manifest.peerDependenciesMeta?.[packageName]?.optional === true || Object.hasOwn(manifest.optionalDependencies ?? {}, packageName);
|
|
183
|
+
return optional && !isRedirectedByAliasField(manifest, aliasFields, request) && !isPackageInstalled(packageName, context, moduleDirectories);
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
const getAliasNames = (alias)=>alias ? Array.isArray(alias) ? alias.map((entry)=>entry.onlyModule ? `${entry.name}$` : entry.name) : Object.keys(alias) : [];
|
|
187
|
+
const getExternalMatchers = (externals)=>{
|
|
188
|
+
if (Array.isArray(externals)) return externals.flatMap(getExternalMatchers);
|
|
189
|
+
if ('string' == typeof externals) return [
|
|
190
|
+
(request)=>request === externals
|
|
191
|
+
];
|
|
192
|
+
if (externals instanceof RegExp) return [
|
|
193
|
+
(request)=>{
|
|
194
|
+
externals.lastIndex = 0;
|
|
195
|
+
return externals.test(request);
|
|
196
|
+
}
|
|
197
|
+
];
|
|
198
|
+
if (externals && 'object' == typeof externals) {
|
|
199
|
+
const names = new Set(Object.entries(externals).filter(([, value])=>false !== value).map(([name])=>name));
|
|
76
200
|
return [
|
|
77
|
-
|
|
78
|
-
builtin,
|
|
79
|
-
`module-import ${nodeBuiltin}`
|
|
80
|
-
],
|
|
81
|
-
[
|
|
82
|
-
nodeBuiltin,
|
|
83
|
-
`module-import ${nodeBuiltin}`
|
|
84
|
-
]
|
|
201
|
+
(request)=>names.has(request)
|
|
85
202
|
];
|
|
86
|
-
}
|
|
203
|
+
}
|
|
204
|
+
return [];
|
|
205
|
+
};
|
|
206
|
+
const createRequestRedirectMatcher = (options)=>{
|
|
207
|
+
const aliasNames = [
|
|
208
|
+
...getAliasNames(options.resolve?.alias),
|
|
209
|
+
...getAliasNames(options.resolve?.fallback)
|
|
210
|
+
];
|
|
211
|
+
const externalMatchers = getExternalMatchers(options.externals);
|
|
212
|
+
return (request)=>externalMatchers.some((matches)=>matches(request)) || aliasNames.some((name)=>name.endsWith('$') ? request === name.slice(0, -1) : request === name || request.startsWith(`${name}/`));
|
|
213
|
+
};
|
|
214
|
+
const getFunctionExternals = (externals)=>Array.isArray(externals) ? externals.flatMap(getFunctionExternals) : 'function' == typeof externals ? [
|
|
215
|
+
externals
|
|
216
|
+
] : [];
|
|
217
|
+
const callFunctionExternal = (external, data)=>new Promise((resolve, reject)=>{
|
|
218
|
+
const returned = external(data, (error, result)=>error ? reject(error) : resolve(result));
|
|
219
|
+
if (null !== returned && 'object' == typeof returned && 'function' == typeof returned.then) returned.then(resolve, reject);
|
|
220
|
+
else if (1 === external.length) resolve(returned);
|
|
221
|
+
});
|
|
222
|
+
const claimsByFunctionExternal = async (externals, data)=>{
|
|
223
|
+
for (const external of externals)try {
|
|
224
|
+
const result = await callFunctionExternal(external, data);
|
|
225
|
+
if (null != result && false !== result) return true;
|
|
226
|
+
} catch {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
return false;
|
|
230
|
+
};
|
|
231
|
+
const PLUGIN_NAME = 'AbsentOptionalDependencyPlugin';
|
|
232
|
+
class AbsentOptionalDependencyPlugin {
|
|
233
|
+
apply(compiler) {
|
|
234
|
+
const isAbsent = createAbsentOptionalDependencyFilter(createRequestRedirectMatcher(compiler.options), compiler.options.resolve.modules, compiler.options.resolve.aliasFields);
|
|
235
|
+
const functionExternals = getFunctionExternals(compiler.options.externals);
|
|
236
|
+
compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, (factory)=>{
|
|
237
|
+
factory.hooks.beforeResolve.tapPromise(PLUGIN_NAME, async (data)=>{
|
|
238
|
+
if (!isAbsent(data.request, data.context)) return;
|
|
239
|
+
const claimed = await claimsByFunctionExternal(functionExternals, {
|
|
240
|
+
context: data.context,
|
|
241
|
+
contextInfo: {
|
|
242
|
+
issuer: data.contextInfo.issuer,
|
|
243
|
+
issuerLayer: data.contextInfo.issuerLayer ?? null
|
|
244
|
+
},
|
|
245
|
+
getResolve: (options)=>{
|
|
246
|
+
const resolver = factory.getResolver('normal', options ?? {});
|
|
247
|
+
return (context, request, callback)=>{
|
|
248
|
+
if (callback) return void resolver.resolve({}, context, request, {}, callback);
|
|
249
|
+
return new Promise((resolve, reject)=>{
|
|
250
|
+
resolver.resolve({}, context, request, {}, (error, result)=>error ? reject(error) : resolve(result || void 0));
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
},
|
|
254
|
+
request: data.request
|
|
255
|
+
});
|
|
256
|
+
return claimed ? void 0 : false;
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
87
261
|
const normalizeWorkerOutputName = (name)=>node_path.posix.normalize(name.replace(/\\/gu, '/')).toLowerCase();
|
|
88
262
|
const reserveWorkerChunkName = (baseName, reservedOutputNames)=>{
|
|
89
263
|
let name = baseName;
|
|
@@ -133,6 +307,9 @@ function applyCloudflareWorkerRspackConfig(chain, workerEntryNames) {
|
|
|
133
307
|
chain.optimization.runtimeChunk(config.optimization.runtimeChunk);
|
|
134
308
|
chain.optimization.splitChunks(config.optimization.splitChunks);
|
|
135
309
|
}
|
|
310
|
+
function removeCloudflareWorkerDataLoaderClientTransform(chain) {
|
|
311
|
+
for (const [name, rule] of Object.entries(chain.module.rules.entries() ?? {}))if (rule.uses.values().some((use)=>DATA_LOADER_CLIENT_TRANSFORM.test(String(use.get('loader'))))) chain.module.rules.delete(name);
|
|
312
|
+
}
|
|
136
313
|
function applyCloudflareWorkerMfRuntimeBoundary(chain) {
|
|
137
314
|
const runtimePlugin = getTemplatePath('cloudflare-worker-mf-ssr-runtime-plugin.mjs');
|
|
138
315
|
chain.plugins.delete('plugin-module-federation');
|
|
@@ -204,11 +381,40 @@ const prependBundlerChain = (environment, handler)=>{
|
|
|
204
381
|
}
|
|
205
382
|
};
|
|
206
383
|
};
|
|
384
|
+
const createWorkerRouteDataModule = ()=>[
|
|
385
|
+
`import { routes } from './${ROUTE_SERVER_LOADERS_FILE}';`,
|
|
386
|
+
`import { createRouteDataRequestHandler } from ${JSON.stringify(getTemplatePath(WORKER_ROUTE_DATA_TEMPLATE))};`,
|
|
387
|
+
'',
|
|
388
|
+
`export const ${CLOUDFLARE_ROUTE_DATA_HANDLER_EXPORT} =`,
|
|
389
|
+
' createRouteDataRequestHandler(routes);',
|
|
390
|
+
''
|
|
391
|
+
].join('\n');
|
|
392
|
+
const getWorkerRouteDataEntries = (appContext)=>{
|
|
393
|
+
const { entrypoints, internalDirectory } = appContext;
|
|
394
|
+
if (!entrypoints || !internalDirectory) return {};
|
|
395
|
+
return Object.fromEntries(entrypoints.flatMap(({ entryName })=>{
|
|
396
|
+
const entryDirectory = node_path.join(internalDirectory, entryName);
|
|
397
|
+
if (!node_fs.existsSync(node_path.join(entryDirectory, SERVER_LOADER_COMBINED_FILE)) || !node_fs.existsSync(node_path.join(entryDirectory, ROUTE_SERVER_LOADERS_FILE))) return [];
|
|
398
|
+
const routeDataFile = node_path.join(entryDirectory, WORKER_ROUTE_DATA_FILE);
|
|
399
|
+
node_fs.writeFileSync(routeDataFile, createWorkerRouteDataModule());
|
|
400
|
+
return [
|
|
401
|
+
[
|
|
402
|
+
getCloudflareWorkerRouteDataEntryName(entryName),
|
|
403
|
+
[
|
|
404
|
+
routeDataFile
|
|
405
|
+
]
|
|
406
|
+
]
|
|
407
|
+
];
|
|
408
|
+
}));
|
|
409
|
+
};
|
|
207
410
|
const getWorkerEntries = (environment, normalizedConfig, appContext)=>{
|
|
208
411
|
const configuredEntries = environment.source?.entry;
|
|
209
|
-
if (!configuredEntries) return;
|
|
210
|
-
const entries = rewriteWorkerEntries(configuredEntries);
|
|
211
412
|
const effectApiEntry = getEffectBffEntry(normalizedConfig, appContext);
|
|
413
|
+
if (!configuredEntries && !effectApiEntry) return;
|
|
414
|
+
const entries = configuredEntries ? {
|
|
415
|
+
...rewriteWorkerEntries(configuredEntries),
|
|
416
|
+
...getWorkerRouteDataEntries(appContext)
|
|
417
|
+
} : {};
|
|
212
418
|
return effectApiEntry ? {
|
|
213
419
|
...entries,
|
|
214
420
|
[BFF_EFFECT_WORKER_ENTRY_NAME]: [
|
|
@@ -241,6 +447,7 @@ const createCloudflareBundlerChain = (appContext, workerEntryNames)=>{
|
|
|
241
447
|
];
|
|
242
448
|
return (chain)=>{
|
|
243
449
|
applyCloudflareWorkerRspackConfig(chain, entryNames);
|
|
450
|
+
removeCloudflareWorkerDataLoaderClientTransform(chain);
|
|
244
451
|
chain.output.module(true).library({
|
|
245
452
|
type: 'module'
|
|
246
453
|
}).publicPath('/').chunkFormat('module').chunkLoading('import').workerChunkLoading('import');
|
|
@@ -249,10 +456,15 @@ const createCloudflareBundlerChain = (appContext, workerEntryNames)=>{
|
|
|
249
456
|
'worker',
|
|
250
457
|
'webpack',
|
|
251
458
|
isProd() ? 'production' : 'development',
|
|
252
|
-
'import',
|
|
253
|
-
'require',
|
|
254
459
|
'module'
|
|
255
460
|
])chain.resolve.conditionNames.add(condition);
|
|
461
|
+
for (const [dependencyType, condition] of WORKER_DEPENDENCY_CONDITIONS)chain.resolve.byDependency.set(dependencyType, {
|
|
462
|
+
conditionNames: [
|
|
463
|
+
condition,
|
|
464
|
+
'...'
|
|
465
|
+
]
|
|
466
|
+
});
|
|
467
|
+
chain.plugin('cloudflare-worker-absent-optional-dependencies').use(AbsentOptionalDependencyPlugin);
|
|
256
468
|
applyCloudflareWorkerMfRuntimeBoundary(chain);
|
|
257
469
|
if (tanstackRouterSsrServerFile) {
|
|
258
470
|
chain.resolve.alias.set('@tanstack/router-core/ssr/server$', tanstackRouterSsrServerFile);
|
|
@@ -288,7 +500,14 @@ const createCloudflareBundlerChain = (appContext, workerEntryNames)=>{
|
|
|
288
500
|
};
|
|
289
501
|
function getCloudflareBuilderEnvironments({ appContext, environments, normalizedConfig, resolveDeployProvider = resolveStdEnvDeployProvider }) {
|
|
290
502
|
if (!isCloudflareWorkerDeploy(normalizedConfig, resolveDeployProvider)) return environments;
|
|
291
|
-
const workerEnvironment = environments[SERVICE_WORKER_ENVIRONMENT_NAME]
|
|
503
|
+
const workerEnvironment = environments[SERVICE_WORKER_ENVIRONMENT_NAME] ?? (appContext.apiOnly ? {
|
|
504
|
+
output: {
|
|
505
|
+
target: 'web-worker'
|
|
506
|
+
},
|
|
507
|
+
source: {
|
|
508
|
+
entry: {}
|
|
509
|
+
}
|
|
510
|
+
} : void 0);
|
|
292
511
|
if (!workerEnvironment) return environments;
|
|
293
512
|
const workerEntries = getWorkerEntries(workerEnvironment, normalizedConfig, appContext);
|
|
294
513
|
if (!workerEntries) return environments;
|
|
@@ -304,6 +523,9 @@ function getCloudflareBuilderEnvironments({ appContext, environments, normalized
|
|
|
304
523
|
entry: workerEntries
|
|
305
524
|
}
|
|
306
525
|
}, createCloudflareBundlerChain(appContext, Object.keys(workerEntries)));
|
|
526
|
+
if (appContext.apiOnly) return {
|
|
527
|
+
[SERVICE_WORKER_ENVIRONMENT_NAME]: cloudflareWorkerEnvironment
|
|
528
|
+
};
|
|
307
529
|
return {
|
|
308
530
|
...environments,
|
|
309
531
|
[SERVICE_WORKER_ENVIRONMENT_NAME]: cloudflareWorkerEnvironment
|
|
@@ -325,4 +547,4 @@ const createCloudflareBuilderPlugin = (resolveDeployProvider = resolveStdEnvDepl
|
|
|
325
547
|
}));
|
|
326
548
|
}
|
|
327
549
|
});
|
|
328
|
-
export { applyCloudflareBuilderEnvironments, applyCloudflareWorkerMfRuntimeBoundary, applyCloudflareWorkerRspackConfig, createCloudflareBuilderPlugin, getCloudflareBuilderEnvironments, getCloudflareWorkerRspackConfig };
|
|
550
|
+
export { applyCloudflareBuilderEnvironments, applyCloudflareWorkerMfRuntimeBoundary, applyCloudflareWorkerRspackConfig, claimsByFunctionExternal, createAbsentOptionalDependencyFilter, createCloudflareBuilderPlugin, createRequestRedirectMatcher, getCloudflareBuilderEnvironments, getCloudflareWorkerRspackConfig };
|
|
@@ -11,27 +11,49 @@ const CLOUDFLARE_RUNTIME_TYPE = 'cloudflare-module-worker';
|
|
|
11
11
|
const CLOUDFLARE_OUTPUT_PACKAGE_TYPE = 'module';
|
|
12
12
|
const CLOUDFLARE_WORKER_PACKAGE_TYPE = 'commonjs';
|
|
13
13
|
const CLOUDFLARE_WORKER_BUNDLE_FORMAT = 'commonjs';
|
|
14
|
+
const CLOUDFLARE_ROUTE_DATA_HANDLER_EXPORT = 'handleRouteDataRequest';
|
|
15
|
+
const getCloudflareWorkerRouteDataEntryName = (entryName)=>`${entryName}-server-loaders`;
|
|
14
16
|
const CLOUDFLARE_WORKER_NODE_BUILTINS = [
|
|
17
|
+
'_http_agent',
|
|
18
|
+
'_http_client',
|
|
19
|
+
'_http_common',
|
|
20
|
+
'_http_incoming',
|
|
21
|
+
'_http_outgoing',
|
|
22
|
+
'_http_server',
|
|
23
|
+
'_tls_common',
|
|
24
|
+
'_tls_wrap',
|
|
15
25
|
'assert',
|
|
16
26
|
'assert/strict',
|
|
17
27
|
'async_hooks',
|
|
18
28
|
'buffer',
|
|
19
29
|
'child_process',
|
|
30
|
+
'cluster',
|
|
31
|
+
'console',
|
|
32
|
+
'constants',
|
|
20
33
|
'crypto',
|
|
21
34
|
'dgram',
|
|
35
|
+
'diagnostics_channel',
|
|
22
36
|
'dns',
|
|
37
|
+
'dns/promises',
|
|
23
38
|
'domain',
|
|
24
39
|
'events',
|
|
25
40
|
'fs',
|
|
26
41
|
'fs/promises',
|
|
27
42
|
'http',
|
|
28
43
|
'http2',
|
|
44
|
+
'https',
|
|
29
45
|
'inspector',
|
|
46
|
+
'inspector/promises',
|
|
30
47
|
'module',
|
|
31
48
|
'net',
|
|
32
49
|
'os',
|
|
33
50
|
'path',
|
|
51
|
+
'path/posix',
|
|
52
|
+
'path/win32',
|
|
53
|
+
'perf_hooks',
|
|
34
54
|
'process',
|
|
55
|
+
'punycode',
|
|
56
|
+
'querystring',
|
|
35
57
|
'readline',
|
|
36
58
|
'readline/promises',
|
|
37
59
|
'repl',
|
|
@@ -41,6 +63,8 @@ const CLOUDFLARE_WORKER_NODE_BUILTINS = [
|
|
|
41
63
|
'stream/promises',
|
|
42
64
|
'stream/web',
|
|
43
65
|
'string_decoder',
|
|
66
|
+
'sys',
|
|
67
|
+
'test',
|
|
44
68
|
'timers',
|
|
45
69
|
'timers/promises',
|
|
46
70
|
'tls',
|
|
@@ -62,4 +86,4 @@ const CLOUDFLARE_REQUIRED_COMPATIBILITY_FLAGS = [
|
|
|
62
86
|
'nodejs_compat',
|
|
63
87
|
'global_fetch_strictly_public'
|
|
64
88
|
];
|
|
65
|
-
export { CLOUDFLARE_ASSETS_BINDING, CLOUDFLARE_OUTPUT_PACKAGE_FILE, CLOUDFLARE_OUTPUT_PACKAGE_TYPE, CLOUDFLARE_PUBLIC_ASSETS_DIRECTORY, CLOUDFLARE_REQUIRED_COMPATIBILITY_FLAGS, CLOUDFLARE_RUNTIME_TYPE, CLOUDFLARE_WORKER_BUNDLE_DIRECTORY, CLOUDFLARE_WORKER_BUNDLE_FORMAT, CLOUDFLARE_WORKER_ENTRY, CLOUDFLARE_WORKER_MANIFEST, CLOUDFLARE_WORKER_NODE_BUILTINS, CLOUDFLARE_WORKER_PACKAGE_FILE, CLOUDFLARE_WORKER_PACKAGE_TYPE, CLOUDFLARE_WORKER_PLATFORM_MODULES, CLOUDFLARE_WRANGLER_CONFIG_FILE };
|
|
89
|
+
export { CLOUDFLARE_ASSETS_BINDING, CLOUDFLARE_OUTPUT_PACKAGE_FILE, CLOUDFLARE_OUTPUT_PACKAGE_TYPE, CLOUDFLARE_PUBLIC_ASSETS_DIRECTORY, CLOUDFLARE_REQUIRED_COMPATIBILITY_FLAGS, CLOUDFLARE_ROUTE_DATA_HANDLER_EXPORT, CLOUDFLARE_RUNTIME_TYPE, CLOUDFLARE_WORKER_BUNDLE_DIRECTORY, CLOUDFLARE_WORKER_BUNDLE_FORMAT, CLOUDFLARE_WORKER_ENTRY, CLOUDFLARE_WORKER_MANIFEST, CLOUDFLARE_WORKER_NODE_BUILTINS, CLOUDFLARE_WORKER_PACKAGE_FILE, CLOUDFLARE_WORKER_PACKAGE_TYPE, CLOUDFLARE_WORKER_PLATFORM_MODULES, CLOUDFLARE_WRANGLER_CONFIG_FILE, getCloudflareWorkerRouteDataEntryName };
|
|
@@ -25,30 +25,32 @@ const verifyDeliveryUnitIdentity = (issues, manifest, manifestPath, declared)=>{
|
|
|
25
25
|
]){
|
|
26
26
|
const marker = stamped.surfaces[surface];
|
|
27
27
|
const expected = declared?.surfaces?.[surface];
|
|
28
|
-
if (
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
28
|
+
if (expected || marker) {
|
|
29
|
+
if (declared?.surfaces && !expected) {
|
|
30
|
+
if (marker) addIssue(issues, {
|
|
31
|
+
code: 'delivery-unit-drift',
|
|
32
|
+
message: `Cloudflare worker manifest declares an unexpected ${surface} delivery-unit surface for this topology profile.`,
|
|
33
|
+
path: manifestPath
|
|
34
|
+
});
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (!marker || 'object' != typeof marker) {
|
|
38
|
+
addIssue(issues, {
|
|
39
|
+
code: 'missing-delivery-unit',
|
|
40
|
+
message: `Cloudflare worker manifest is missing the ${surface} delivery-unit surface marker.`,
|
|
41
|
+
path: manifestPath
|
|
42
|
+
});
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
for (const field of DELIVERY_UNIT_IDENTITY_FIELDS){
|
|
46
|
+
const markerValue = deliveryUnitIdentityFieldValue(marker, field);
|
|
47
|
+
const stampedValue = deliveryUnitIdentityFieldValue(stamped, field);
|
|
48
|
+
assertEqual(issues, markerValue, stampedValue, {
|
|
49
|
+
code: 'delivery-unit-drift',
|
|
50
|
+
message: `Cloudflare worker manifest ${surface} surface deliveryUnit.${field} must derive from one delivery-unit record (expected ${stampedValue ?? 'undefined'}, received ${markerValue ?? 'undefined'}).`,
|
|
51
|
+
path: manifestPath
|
|
52
|
+
});
|
|
53
|
+
}
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
};
|
|
@@ -73,6 +73,16 @@ const verifyCloudflareOutput = async (options)=>{
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
if (wrangler) {
|
|
76
|
+
if ('string' != typeof wrangler.name || 0 === wrangler.name.trim().length) addIssue(issues, {
|
|
77
|
+
code: 'invalid-wrangler',
|
|
78
|
+
message: 'wrangler.json name must be a non-empty worker name.',
|
|
79
|
+
path: wranglerPath
|
|
80
|
+
});
|
|
81
|
+
if ('string' != typeof wrangler.compatibility_date || !/^\d{4}-\d{2}-\d{2}$/u.test(wrangler.compatibility_date)) addIssue(issues, {
|
|
82
|
+
code: 'invalid-wrangler',
|
|
83
|
+
message: 'wrangler.json compatibility_date must use YYYY-MM-DD.',
|
|
84
|
+
path: wranglerPath
|
|
85
|
+
});
|
|
76
86
|
assertEqual(issues, wrangler.main, outputPlan.wrangler.main, {
|
|
77
87
|
code: 'invalid-wrangler',
|
|
78
88
|
message: `wrangler.json main must be ${outputPlan.wrangler.main}.`,
|
|
@@ -43,6 +43,52 @@ const verifyManifestShape = (issues, manifest, manifestPath)=>{
|
|
|
43
43
|
message: 'Cloudflare output manifest routeSpec.routes must be array.',
|
|
44
44
|
path: manifestPath
|
|
45
45
|
});
|
|
46
|
+
const security = manifest.security;
|
|
47
|
+
if (security && 'object' == typeof security && 'boolean' == typeof security.enabled) {
|
|
48
|
+
const cors = security.cors;
|
|
49
|
+
if (!cors || 'object' != typeof cors || 'boolean' != typeof cors.assets || !Array.isArray(cors.allowedOrigins) || !Array.isArray(cors.allowedMethods) || !Array.isArray(cors.allowedHeaders)) addIssue(issues, {
|
|
50
|
+
code: 'invalid-manifest',
|
|
51
|
+
message: 'Cloudflare output manifest security.cors is incomplete.',
|
|
52
|
+
path: manifestPath
|
|
53
|
+
});
|
|
54
|
+
if (security.enabled) {
|
|
55
|
+
const headers = security.headers;
|
|
56
|
+
const csp = security.contentSecurityPolicy;
|
|
57
|
+
const noindex = security.noindex;
|
|
58
|
+
if (!headers || 'object' != typeof headers || ![
|
|
59
|
+
'string',
|
|
60
|
+
'boolean'
|
|
61
|
+
].includes(typeof headers.referrerPolicy) || ![
|
|
62
|
+
'string',
|
|
63
|
+
'boolean'
|
|
64
|
+
].includes(typeof headers.contentTypeOptions) || ![
|
|
65
|
+
'string',
|
|
66
|
+
'boolean'
|
|
67
|
+
].includes(typeof headers.permissionsPolicy)) addIssue(issues, {
|
|
68
|
+
code: 'invalid-manifest',
|
|
69
|
+
message: 'Cloudflare output manifest security.headers is incomplete.',
|
|
70
|
+
path: manifestPath
|
|
71
|
+
});
|
|
72
|
+
if (!csp || ![
|
|
73
|
+
'off',
|
|
74
|
+
'report-only',
|
|
75
|
+
'enforce'
|
|
76
|
+
].includes(csp.mode) || !csp.directives || 'object' != typeof csp.directives || Array.isArray(csp.directives) || Object.values(csp.directives).some((value)=>!Array.isArray(value) || value.some((entry)=>'string' != typeof entry))) addIssue(issues, {
|
|
77
|
+
code: 'invalid-manifest',
|
|
78
|
+
message: 'Cloudflare output manifest security.contentSecurityPolicy is invalid.',
|
|
79
|
+
path: manifestPath
|
|
80
|
+
});
|
|
81
|
+
if (!noindex || 'boolean' != typeof noindex.workersDev || 'boolean' != typeof noindex.localhost || !Array.isArray(noindex.previewHostnames)) addIssue(issues, {
|
|
82
|
+
code: 'invalid-manifest',
|
|
83
|
+
message: 'Cloudflare output manifest security.noindex is incomplete.',
|
|
84
|
+
path: manifestPath
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
} else addIssue(issues, {
|
|
88
|
+
code: 'invalid-manifest',
|
|
89
|
+
message: 'Cloudflare output manifest security.enabled must be a boolean.',
|
|
90
|
+
path: manifestPath
|
|
91
|
+
});
|
|
46
92
|
if (manifest.bff?.runtimeFramework === 'effect') assertEqual(issues, manifest.bff.dispatcherExport, BFF_EFFECT_WORKER_DISPATCHER_EXPORT, {
|
|
47
93
|
code: 'invalid-manifest',
|
|
48
94
|
message: `Cloudflare Effect BFF manifest dispatcherExport must be ${BFF_EFFECT_WORKER_DISPATCHER_EXPORT}.`,
|
|
@@ -85,7 +85,13 @@ const verifyCloudflareOutputMutationPolicy = async (options)=>{
|
|
|
85
85
|
const files = (await Promise.all(options.scanRoots.map((scanRoot)=>walkFiles(node_path.resolve(scanRoot))))).flat();
|
|
86
86
|
for (const file of files){
|
|
87
87
|
if (excludedFiles.has(file)) continue;
|
|
88
|
-
|
|
88
|
+
let source;
|
|
89
|
+
try {
|
|
90
|
+
source = await promises.readFile(file, 'utf-8');
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if ('ENOENT' === error.code) continue;
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
89
95
|
for (const forbidden of FORBIDDEN_MUTATION_PATTERNS)if (forbidden.pattern.test(source)) addIssue(issues, {
|
|
90
96
|
code: forbidden.code,
|
|
91
97
|
message: forbidden.message,
|
|
@@ -5,9 +5,9 @@ import { pathToFileURL } from "node:url";
|
|
|
5
5
|
import { isDeepStrictEqual } from "node:util";
|
|
6
6
|
import { parse } from "@babel/parser";
|
|
7
7
|
import traverse from "@babel/traverse";
|
|
8
|
-
import { CLOUDFLARE_WORKER_BUNDLE_DIRECTORY, CLOUDFLARE_WORKER_NODE_BUILTINS, CLOUDFLARE_WORKER_PLATFORM_MODULES } from "../cloudflare-output-contract.mjs";
|
|
8
|
+
import { CLOUDFLARE_ROUTE_DATA_HANDLER_EXPORT, CLOUDFLARE_WORKER_BUNDLE_DIRECTORY, CLOUDFLARE_WORKER_NODE_BUILTINS, CLOUDFLARE_WORKER_PLATFORM_MODULES } from "../cloudflare-output-contract.mjs";
|
|
9
9
|
import { addIssue } from "./issues.mjs";
|
|
10
|
-
const getReferencedRouteWorkers = (manifest)=>Array.isArray(manifest?.routeSpec?.routes) ? manifest.routeSpec.routes.map((route)=>'string' == typeof route?.
|
|
10
|
+
const getReferencedRouteWorkers = (manifest, field)=>Array.isArray(manifest?.routeSpec?.routes) ? manifest.routeSpec.routes.map((route)=>'string' == typeof route?.[field] && route[field].length > 0 ? route[field] : void 0).filter((worker)=>'string' == typeof worker) : [];
|
|
11
11
|
const getEffectBffWorker = (manifest)=>manifest?.bff?.runtimeFramework === 'effect' && 'string' == typeof manifest.bff.worker ? manifest.bff.worker : void 0;
|
|
12
12
|
const getWorkerBundleReferences = (manifest)=>{
|
|
13
13
|
const effectBffWorker = getEffectBffWorker(manifest);
|
|
@@ -19,9 +19,14 @@ const getWorkerBundleReferences = (manifest)=>{
|
|
|
19
19
|
reference: effectBffWorker
|
|
20
20
|
}
|
|
21
21
|
] : [],
|
|
22
|
-
...getReferencedRouteWorkers(manifest).map((reference)=>({
|
|
22
|
+
...getReferencedRouteWorkers(manifest, 'worker').map((reference)=>({
|
|
23
23
|
kind: 'route',
|
|
24
24
|
reference
|
|
25
|
+
})),
|
|
26
|
+
...getReferencedRouteWorkers(manifest, 'routeDataWorker').map((reference)=>({
|
|
27
|
+
dispatcherExport: CLOUDFLARE_ROUTE_DATA_HANDLER_EXPORT,
|
|
28
|
+
kind: 'route-data',
|
|
29
|
+
reference
|
|
25
30
|
}))
|
|
26
31
|
];
|
|
27
32
|
};
|
|
@@ -42,7 +47,7 @@ const resolveWorkerBundleReference = (issues, outputDirectory, reference, manife
|
|
|
42
47
|
path: workerPath
|
|
43
48
|
};
|
|
44
49
|
};
|
|
45
|
-
const missingWorkerBundleMessage = (reference)=>'effect-bff' === reference.kind ? 'Cloudflare Effect BFF manifest points to a missing worker bundle.' : 'Cloudflare route worker manifest points to a missing worker bundle.';
|
|
50
|
+
const missingWorkerBundleMessage = (reference)=>'effect-bff' === reference.kind ? 'Cloudflare Effect BFF manifest points to a missing worker bundle.' : 'route-data' === reference.kind ? 'Cloudflare route data worker manifest points to a missing worker bundle.' : 'Cloudflare route worker manifest points to a missing worker bundle.';
|
|
46
51
|
const isAstNode = (value)=>null !== value && 'object' == typeof value && 'type' in value && 'string' == typeof value.type;
|
|
47
52
|
const getNodeName = (node)=>{
|
|
48
53
|
if (!isAstNode(node)) return;
|
|
@@ -286,9 +291,9 @@ const verifyWorkerModuleClosure = async (issues, entryPath, entrySource, contrac
|
|
|
286
291
|
};
|
|
287
292
|
const verifyWorkerBundleReferences = async (issues, worker, source, contract)=>{
|
|
288
293
|
const entryAnalysis = await verifyWorkerModuleClosure(issues, worker.path, source, contract);
|
|
289
|
-
if ('
|
|
294
|
+
if ('string' == typeof worker.dispatcherExport && !entryAnalysis?.exports.has(worker.dispatcherExport)) addIssue(issues, {
|
|
290
295
|
code: 'invalid-worker-bundle',
|
|
291
|
-
message: 'Cloudflare Effect BFF worker bundle must expose its manifest dispatcherExport.',
|
|
296
|
+
message: 'route-data' === worker.kind ? `Cloudflare route data worker bundle must expose ${worker.dispatcherExport}.` : 'Cloudflare Effect BFF worker bundle must expose its manifest dispatcherExport.',
|
|
292
297
|
path: worker.path
|
|
293
298
|
});
|
|
294
299
|
};
|
|
@@ -5,6 +5,10 @@ const dependencyKeys = [
|
|
|
5
5
|
'dependencies',
|
|
6
6
|
'optionalDependencies'
|
|
7
7
|
];
|
|
8
|
+
const rootDependencyKeys = [
|
|
9
|
+
...dependencyKeys,
|
|
10
|
+
'devDependencies'
|
|
11
|
+
];
|
|
8
12
|
const exactVersionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u;
|
|
9
13
|
async function readPackageJson(filePath) {
|
|
10
14
|
return JSON.parse(await promises.readFile(filePath, 'utf8'));
|
|
@@ -20,14 +24,34 @@ function parseNpmAlias(aliasName, specifier) {
|
|
|
20
24
|
targetVersion: target.slice(versionSeparator + 1)
|
|
21
25
|
};
|
|
22
26
|
}
|
|
23
|
-
function aliasesFromPackageJson(packageJson) {
|
|
27
|
+
function aliasesFromPackageJson(packageJson, keys = dependencyKeys) {
|
|
24
28
|
const aliases = [];
|
|
25
|
-
for (const dependencyKey of
|
|
29
|
+
for (const dependencyKey of keys)for (const [aliasName, specifier] of Object.entries(packageJson[dependencyKey] ?? {})){
|
|
26
30
|
const alias = parseNpmAlias(aliasName, specifier);
|
|
27
31
|
if (alias) aliases.push(alias);
|
|
28
32
|
}
|
|
29
33
|
return aliases;
|
|
30
34
|
}
|
|
35
|
+
async function catalogAliasesFromInstalledPackages(appDirectory, packageJson) {
|
|
36
|
+
const aliases = [];
|
|
37
|
+
for (const dependencyKey of rootDependencyKeys)for (const [aliasName, specifier] of Object.entries(packageJson[dependencyKey] ?? {})){
|
|
38
|
+
if (!specifier.startsWith('catalog:')) continue;
|
|
39
|
+
let installedPackage;
|
|
40
|
+
try {
|
|
41
|
+
installedPackage = await readPackageJson(node_path.join(appDirectory, 'node_modules', aliasName, 'package.json'));
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if ('ENOENT' === error.code) continue;
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
if (!installedPackage.name || !installedPackage.version) throw new Error(`Installed catalog package ${aliasName} has no identity`);
|
|
47
|
+
if (installedPackage.name !== aliasName) aliases.push({
|
|
48
|
+
aliasName,
|
|
49
|
+
targetName: installedPackage.name,
|
|
50
|
+
targetVersion: installedPackage.version
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return aliases;
|
|
54
|
+
}
|
|
31
55
|
async function collectPackageRecords(nodeModulesDirectory) {
|
|
32
56
|
const records = [];
|
|
33
57
|
const visitedNodeModules = new Set();
|
|
@@ -149,7 +173,8 @@ async function preserveNpmAliases({ appDirectory, outputDirectory, implicitAlias
|
|
|
149
173
|
const records = await collectPackageRecords(node_path.join(outputDirectory, 'node_modules'));
|
|
150
174
|
const appPackageJson = await readPackageJson(node_path.join(appDirectory, 'package.json'));
|
|
151
175
|
const rootAliases = [
|
|
152
|
-
...aliasesFromPackageJson(appPackageJson),
|
|
176
|
+
...aliasesFromPackageJson(appPackageJson, rootDependencyKeys),
|
|
177
|
+
...await catalogAliasesFromInstalledPackages(appDirectory, appPackageJson),
|
|
153
178
|
...implicitAliases
|
|
154
179
|
].filter((alias)=>alias.aliasName !== alias.targetName);
|
|
155
180
|
const emittedRootAliases = [];
|