@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.15 → 3.9.0-ultramodern.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/cjs/cloudflare/artifacts.js +8 -19
  2. package/dist/cjs/cloudflare/index.js +3 -1
  3. package/dist/cjs/deploy-output/plugin.js +22 -2
  4. package/dist/cjs/deploy-output/public-assets.js +146 -0
  5. package/dist/cjs/release-envelope/framework-output.js +24 -8
  6. package/dist/cjs/release-envelope/plugin.js +9 -1
  7. package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +52 -10
  8. package/dist/esm/cloudflare/artifacts.mjs +8 -19
  9. package/dist/esm/cloudflare/index.mjs +3 -1
  10. package/dist/esm/deploy-output/plugin.mjs +18 -1
  11. package/dist/esm/deploy-output/public-assets.mjs +82 -0
  12. package/dist/esm/release-envelope/framework-output.mjs +24 -8
  13. package/dist/esm/release-envelope/plugin.mjs +9 -1
  14. package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +52 -10
  15. package/dist/esm-node/cloudflare/artifacts.mjs +8 -19
  16. package/dist/esm-node/cloudflare/index.mjs +3 -1
  17. package/dist/esm-node/deploy-output/plugin.mjs +18 -1
  18. package/dist/esm-node/deploy-output/public-assets.mjs +83 -0
  19. package/dist/esm-node/release-envelope/framework-output.mjs +24 -8
  20. package/dist/esm-node/release-envelope/plugin.mjs +9 -1
  21. package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +52 -10
  22. package/dist/types/cloudflare/artifacts.d.ts +7 -6
  23. package/dist/types/config.d.ts +24 -0
  24. package/dist/types/deploy-output/plugin.d.ts +26 -0
  25. package/dist/types/deploy-output/public-assets.d.ts +51 -0
  26. package/dist/types/release-envelope/framework-output.d.ts +6 -2
  27. package/dist/types/release-envelope/plugin.d.ts +4 -0
  28. package/package.json +14 -14
  29. package/src/cloudflare/artifacts.ts +22 -43
  30. package/src/cloudflare/index.ts +4 -2
  31. package/src/config.ts +26 -0
  32. package/src/deploy-output/plugin.ts +54 -0
  33. package/src/deploy-output/public-assets.ts +224 -0
  34. package/src/release-envelope/framework-output.ts +49 -1
  35. package/src/release-envelope/plugin.ts +22 -1
  36. package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +52 -10
@@ -47,6 +47,7 @@ __webpack_require__.d(__webpack_exports__, {
47
47
  const external_node_path_namespaceObject = require("node:path");
48
48
  var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
49
49
  const utils_namespaceObject = require("@modern-js/utils");
50
+ const public_assets_js_namespaceObject = require("../deploy-output/public-assets.js");
50
51
  const external_constants_js_namespaceObject = require("./constants.js");
51
52
  const external_utils_js_namespaceObject = require("./utils.js");
52
53
  const normalizeCloudflareArtifact = (artifact, index)=>{
@@ -62,18 +63,7 @@ const normalizeCloudflareArtifact = (artifact, index)=>{
62
63
  };
63
64
  };
64
65
  const getCloudflareArtifacts = (modernConfig)=>(modernConfig.deploy?.worker?.artifacts ?? []).map(normalizeCloudflareArtifact);
65
- const normalizeCloudflarePublicAsset = (asset, index)=>{
66
- const from = (0, external_utils_js_namespaceObject.normalizeRelativePath)(asset.from, `deploy.worker.publicAssets[${index}].from`, 'app root');
67
- const to = (0, external_utils_js_namespaceObject.normalizeRelativePath)(asset.to, `deploy.worker.publicAssets[${index}].to`, 'Cloudflare public output', {
68
- allowRoot: true
69
- });
70
- return {
71
- from,
72
- to,
73
- index
74
- };
75
- };
76
- const getCloudflarePublicAssets = (modernConfig)=>(modernConfig.deploy?.worker?.publicAssets ?? []).map(normalizeCloudflarePublicAsset);
66
+ const getCloudflarePublicAssets = (modernConfig)=>(0, public_assets_js_namespaceObject.normalizeDeclaredPublicAssets)(modernConfig.deploy?.worker?.publicAssets, public_assets_js_namespaceObject.CLOUDFLARE_PUBLIC_ASSET_SCOPE);
77
67
  const copyCloudflareArtifacts = async (appDirectory, outputDirectory, artifacts)=>{
78
68
  for (const artifact of artifacts){
79
69
  const sourcePath = external_node_path_default().join(appDirectory, artifact.from);
@@ -81,13 +71,12 @@ const copyCloudflareArtifacts = async (appDirectory, outputDirectory, artifacts)
81
71
  await utils_namespaceObject.fs.copy(sourcePath, external_node_path_default().join(outputDirectory, artifact.to));
82
72
  }
83
73
  };
84
- const copyCloudflarePublicAssets = async (appDirectory, publicDirectory, publicAssets)=>{
85
- for (const asset of publicAssets){
86
- const sourcePath = external_node_path_default().join(appDirectory, asset.from);
87
- if (!await utils_namespaceObject.fs.pathExists(sourcePath)) throw new Error(`deploy.worker.publicAssets[${asset.index}].from does not exist: ${asset.from}`);
88
- await utils_namespaceObject.fs.copy(sourcePath, external_node_path_default().join(publicDirectory, asset.to));
89
- }
90
- };
74
+ const copyCloudflarePublicAssets = async (appDirectory, outputDirectory, publicAssets)=>(0, public_assets_js_namespaceObject.stageDeclaredPublicAssets)({
75
+ appDirectory,
76
+ outputDirectory,
77
+ assets: publicAssets,
78
+ scope: public_assets_js_namespaceObject.CLOUDFLARE_PUBLIC_ASSET_SCOPE
79
+ });
91
80
  const copyCloudflareD1Migrations = async (appDirectory, outputDirectory, modernConfig)=>{
92
81
  for (const [index, database] of (modernConfig.deploy?.worker?.d1Databases ?? []).entries()){
93
82
  if (!database.migrationsDir) continue;
@@ -74,6 +74,7 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
74
74
  const publicAssetExcludes = (0, external_worker_manifest_js_namespaceObject.getPublicAssetExcludes)(appDirectory, modernConfig);
75
75
  const releaseEnvelopeEnabled = api.isPluginExists('@modern-js/ultramodern-release-envelope');
76
76
  let hasReleaseEnvelope = false;
77
+ let declaredPublicAssets = [];
77
78
  return {
78
79
  async prepare () {
79
80
  await utils_namespaceObject.fs.remove(outputDirectory);
@@ -116,7 +117,7 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
116
117
  await utils_namespaceObject.fs.writeJSON(outputPlan.paths.workerPackage, outputPlan.packages.worker);
117
118
  }
118
119
  await (0, external_artifacts_js_namespaceObject.copyCloudflareArtifacts)(appDirectory, outputDirectory, cloudflareArtifacts);
119
- await (0, external_artifacts_js_namespaceObject.copyCloudflarePublicAssets)(appDirectory, publicDirectory, (0, external_artifacts_js_namespaceObject.getCloudflarePublicAssets)(modernConfig));
120
+ declaredPublicAssets = await (0, external_artifacts_js_namespaceObject.copyCloudflarePublicAssets)(appDirectory, outputDirectory, (0, external_artifacts_js_namespaceObject.getCloudflarePublicAssets)(modernConfig));
120
121
  await (0, external_artifacts_js_namespaceObject.copyCloudflareD1Migrations)(appDirectory, outputDirectory, modernConfig);
121
122
  await utils_namespaceObject.fs.writeJSON(wranglerConfigPath, (0, external_wrangler_config_js_namespaceObject.createWranglerConfig)(appDirectory, modernConfig), {
122
123
  spaces: 2
@@ -145,6 +146,7 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
145
146
  });
146
147
  if (hasReleaseEnvelope) {
147
148
  await (0, framework_output_js_namespaceObject.emitCloudflareStagedReleaseEnvelope)({
149
+ declaredPublicAssets,
148
150
  distDirectory,
149
151
  outputDirectory
150
152
  });
@@ -37,12 +37,14 @@ var __webpack_require__ = {};
37
37
  var __webpack_exports__ = {};
38
38
  __webpack_require__.r(__webpack_exports__);
39
39
  __webpack_require__.d(__webpack_exports__, {
40
- createDeployOutputAliasesPlugin: ()=>createDeployOutputAliasesPlugin
40
+ createDeployOutputAliasesPlugin: ()=>createDeployOutputAliasesPlugin,
41
+ createDeployOutputPublicAssetsPlugin: ()=>createDeployOutputPublicAssetsPlugin
41
42
  });
42
43
  const external_node_module_namespaceObject = require("node:module");
43
44
  const external_node_path_namespaceObject = require("node:path");
44
45
  var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
45
46
  const external_npmAliases_js_namespaceObject = require("./npmAliases.js");
47
+ const external_public_assets_js_namespaceObject = require("./public-assets.js");
46
48
  const external_target_js_namespaceObject = require("./target.js");
47
49
  const createDeployOutputAliasesPlugin = ({ resolveDeployTarget = external_target_js_namespaceObject.resolveDeployTarget } = {})=>({
48
50
  name: '@modern-js/deploy-output-aliases',
@@ -67,9 +69,27 @@ const createDeployOutputAliasesPlugin = ({ resolveDeployTarget = external_target
67
69
  });
68
70
  }
69
71
  });
72
+ const createDeployOutputPublicAssetsPlugin = ({ resolveDeployTarget = external_target_js_namespaceObject.resolveDeployTarget } = {})=>({
73
+ name: '@modern-js/deploy-output-public-assets',
74
+ setup (api) {
75
+ api.onAfterDeploy(async ()=>{
76
+ const { appDirectory, metaName } = api.getAppContext();
77
+ const config = api.getNormalizedConfig();
78
+ if ('modern-js' !== metaName && !config.deploy?.target && !process.env.MODERNJS_DEPLOY || 'node' !== resolveDeployTarget(config)) return;
79
+ await (0, external_public_assets_js_namespaceObject.stageDeclaredPublicAssets)({
80
+ appDirectory,
81
+ outputDirectory: external_node_path_default().join(appDirectory, '.output'),
82
+ assets: (0, external_public_assets_js_namespaceObject.normalizeDeclaredPublicAssets)(config.deploy?.node?.publicAssets, external_public_assets_js_namespaceObject.NODE_PUBLIC_ASSET_SCOPE),
83
+ scope: external_public_assets_js_namespaceObject.NODE_PUBLIC_ASSET_SCOPE
84
+ });
85
+ });
86
+ }
87
+ });
70
88
  exports.createDeployOutputAliasesPlugin = __webpack_exports__.createDeployOutputAliasesPlugin;
89
+ exports.createDeployOutputPublicAssetsPlugin = __webpack_exports__.createDeployOutputPublicAssetsPlugin;
71
90
  for(var __rspack_i in __webpack_exports__)if (-1 === [
72
- "createDeployOutputAliasesPlugin"
91
+ "createDeployOutputAliasesPlugin",
92
+ "createDeployOutputPublicAssetsPlugin"
73
93
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
74
94
  Object.defineProperty(exports, '__esModule', {
75
95
  value: true
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.n = (module)=>{
5
+ var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
6
+ __webpack_require__.d(getter, {
7
+ a: getter
8
+ });
9
+ return getter;
10
+ };
11
+ })();
12
+ (()=>{
13
+ __webpack_require__.d = (exports1, getters, values)=>{
14
+ var define = (defs, kind)=>{
15
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
16
+ enumerable: true,
17
+ [kind]: defs[key]
18
+ });
19
+ };
20
+ define(getters, "get");
21
+ define(values, "value");
22
+ };
23
+ })();
24
+ (()=>{
25
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
26
+ })();
27
+ (()=>{
28
+ __webpack_require__.r = (exports1)=>{
29
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
30
+ value: 'Module'
31
+ });
32
+ Object.defineProperty(exports1, '__esModule', {
33
+ value: true
34
+ });
35
+ };
36
+ })();
37
+ var __webpack_exports__ = {};
38
+ __webpack_require__.r(__webpack_exports__);
39
+ __webpack_require__.d(__webpack_exports__, {
40
+ CLOUDFLARE_PUBLIC_ASSET_SCOPE: ()=>CLOUDFLARE_PUBLIC_ASSET_SCOPE,
41
+ DECLARED_PUBLIC_ASSET_DIRECTORY: ()=>DECLARED_PUBLIC_ASSET_DIRECTORY,
42
+ NODE_PUBLIC_ASSET_SCOPE: ()=>NODE_PUBLIC_ASSET_SCOPE,
43
+ normalizeDeclaredPublicAssets: ()=>normalizeDeclaredPublicAssets,
44
+ resolveAddedDeclaredPublicAssetPaths: ()=>resolveAddedDeclaredPublicAssetPaths,
45
+ stageDeclaredPublicAssets: ()=>stageDeclaredPublicAssets
46
+ });
47
+ const promises_namespaceObject = require("node:fs/promises");
48
+ var promises_default = /*#__PURE__*/ __webpack_require__.n(promises_namespaceObject);
49
+ const external_node_path_namespaceObject = require("node:path");
50
+ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
51
+ const utils_js_namespaceObject = require("../cloudflare/utils.js");
52
+ const DECLARED_PUBLIC_ASSET_DIRECTORY = 'public';
53
+ const CLOUDFLARE_PUBLIC_ASSET_SCOPE = {
54
+ label: 'deploy.worker.publicAssets',
55
+ output: 'Cloudflare public output'
56
+ };
57
+ const NODE_PUBLIC_ASSET_SCOPE = {
58
+ label: 'deploy.node.publicAssets',
59
+ output: 'Node public output'
60
+ };
61
+ const normalizeDeclaredPublicAssets = (assets, scope)=>(assets ?? []).map((asset, index)=>({
62
+ from: (0, utils_js_namespaceObject.normalizeRelativePath)(asset?.from, `${scope.label}[${index}].from`, 'app root'),
63
+ to: (0, utils_js_namespaceObject.normalizeRelativePath)(asset?.to, `${scope.label}[${index}].to`, scope.output, {
64
+ allowRoot: true
65
+ }),
66
+ index
67
+ }));
68
+ const isMissingPathError = (error)=>error instanceof Error && 'code' in error && 'ENOENT' === error.code;
69
+ const pathExists = async (filePath)=>{
70
+ try {
71
+ await promises_default().lstat(filePath);
72
+ return true;
73
+ } catch (error) {
74
+ if (isMissingPathError(error)) return false;
75
+ throw error;
76
+ }
77
+ };
78
+ const listDeclaredPublicAssetFiles = async (appDirectory, assets, scope)=>{
79
+ const files = [];
80
+ const owners = new Map();
81
+ const add = (index, logicalPath)=>{
82
+ const owner = owners.get(logicalPath);
83
+ if (void 0 !== owner) throw new Error(`${scope.label}[${index}] and ${scope.label}[${owner}] both stage ${JSON.stringify(logicalPath)}.`);
84
+ owners.set(logicalPath, index);
85
+ files.push({
86
+ index,
87
+ logicalPath
88
+ });
89
+ };
90
+ const visit = async (asset, sourcePath, destination)=>{
91
+ const stat = await promises_default().lstat(sourcePath);
92
+ if (stat.isSymbolicLink()) throw new Error(`${scope.label}[${asset.index}].from must not contain symbolic links: ${external_node_path_default().relative(appDirectory, sourcePath).replace(/\\/gu, '/')}`);
93
+ if (stat.isFile()) {
94
+ if (destination === DECLARED_PUBLIC_ASSET_DIRECTORY) throw new Error(`${scope.label}[${asset.index}].to must name a file when ${scope.label}[${asset.index}].from is a file.`);
95
+ add(asset.index, destination);
96
+ return;
97
+ }
98
+ if (!stat.isDirectory()) throw new Error(`${scope.label}[${asset.index}].from must contain only files and directories: ${external_node_path_default().relative(appDirectory, sourcePath).replace(/\\/gu, '/')}`);
99
+ const entries = (await promises_default().readdir(sourcePath)).sort();
100
+ for (const entry of entries)await visit(asset, external_node_path_default().join(sourcePath, entry), external_node_path_default().posix.join(destination, entry));
101
+ };
102
+ for (const asset of assets){
103
+ const sourcePath = external_node_path_default().join(appDirectory, asset.from);
104
+ if (!await pathExists(sourcePath)) throw new Error(`${scope.label}[${asset.index}].from does not exist: ${asset.from}`);
105
+ await visit(asset, sourcePath, external_node_path_default().posix.join(DECLARED_PUBLIC_ASSET_DIRECTORY, asset.to));
106
+ }
107
+ return files;
108
+ };
109
+ const resolveAddedDeclaredPublicAssetPaths = async ({ appDirectory, assets, generatedRoot, scope })=>{
110
+ const added = [];
111
+ for (const file of (await listDeclaredPublicAssetFiles(appDirectory, assets, scope)))if (!await pathExists(external_node_path_default().join(generatedRoot, file.logicalPath))) added.push(file.logicalPath);
112
+ return added.sort();
113
+ };
114
+ const stageDeclaredPublicAssets = async ({ appDirectory, outputDirectory, assets, scope })=>{
115
+ const files = await listDeclaredPublicAssetFiles(appDirectory, assets, scope);
116
+ const added = [];
117
+ for (const file of files){
118
+ const asset = assets.find((candidate)=>candidate.index === file.index);
119
+ const relativePath = external_node_path_default().posix.relative(external_node_path_default().posix.join(DECLARED_PUBLIC_ASSET_DIRECTORY, asset.to), file.logicalPath);
120
+ const destination = external_node_path_default().join(outputDirectory, file.logicalPath);
121
+ const replacesGeneratedFile = await pathExists(destination);
122
+ await promises_default().mkdir(external_node_path_default().dirname(destination), {
123
+ recursive: true
124
+ });
125
+ await promises_default().copyFile(external_node_path_default().join(appDirectory, asset.from, relativePath), destination);
126
+ if (!replacesGeneratedFile) added.push(file.logicalPath);
127
+ }
128
+ return added.sort();
129
+ };
130
+ exports.CLOUDFLARE_PUBLIC_ASSET_SCOPE = __webpack_exports__.CLOUDFLARE_PUBLIC_ASSET_SCOPE;
131
+ exports.DECLARED_PUBLIC_ASSET_DIRECTORY = __webpack_exports__.DECLARED_PUBLIC_ASSET_DIRECTORY;
132
+ exports.NODE_PUBLIC_ASSET_SCOPE = __webpack_exports__.NODE_PUBLIC_ASSET_SCOPE;
133
+ exports.normalizeDeclaredPublicAssets = __webpack_exports__.normalizeDeclaredPublicAssets;
134
+ exports.resolveAddedDeclaredPublicAssetPaths = __webpack_exports__.resolveAddedDeclaredPublicAssetPaths;
135
+ exports.stageDeclaredPublicAssets = __webpack_exports__.stageDeclaredPublicAssets;
136
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
137
+ "CLOUDFLARE_PUBLIC_ASSET_SCOPE",
138
+ "DECLARED_PUBLIC_ASSET_DIRECTORY",
139
+ "NODE_PUBLIC_ASSET_SCOPE",
140
+ "normalizeDeclaredPublicAssets",
141
+ "resolveAddedDeclaredPublicAssetPaths",
142
+ "stageDeclaredPublicAssets"
143
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
144
+ Object.defineProperty(exports, '__esModule', {
145
+ value: true
146
+ });
@@ -66,6 +66,16 @@ const isEffectBffWorkerArtifact = (logicalPath)=>EFFECT_BFF_WORKER_PATTERN.test(
66
66
  const CRAWLER_POLICY_PATH = 'public/robots.txt';
67
67
  const isPublicMetadataPath = (logicalPath)=>'public/_headers' === logicalPath || /^public\/\.well-known\/[^/]+\.json$/u.test(logicalPath);
68
68
  const PUBLIC_BUILD_ARTIFACT_PATH = `public/${backend_federation_contracts_namespaceObject.ULTRAMODERN_BUILD_ARTIFACT_FILE}`;
69
+ const DECLARED_PUBLIC_ASSET_RUNTIME = 'public-asset';
70
+ const resolveDeclaredPublicAssets = (files, declaredPublicAssets, target)=>{
71
+ const staged = new Set(files);
72
+ const declared = new Set();
73
+ for (const logicalPath of declaredPublicAssets){
74
+ if (!logicalPath.startsWith('public/') || !staged.has(logicalPath)) throw new Error(`[ultramodern-release-envelope] final ${target} staging has no declared public asset file ${JSON.stringify(logicalPath)}.`);
75
+ declared.add(logicalPath);
76
+ }
77
+ return declared;
78
+ };
69
79
  const isRecord = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
70
80
  const flattenStrings = (value)=>'string' == typeof value ? [
71
81
  value
@@ -454,8 +464,9 @@ const verifyNodeReleaseEnvelopeStaging = async ({ outputDirectory })=>readFramew
454
464
  expectedTarget: 'node',
455
465
  required: true
456
466
  });
457
- const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly)=>{
467
+ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly, declaredPublicAssets)=>{
458
468
  const files = await collectFiles(outputDirectory);
469
+ const declaredPublicAssetPaths = resolveDeclaredPublicAssets(files, declaredPublicAssets, 'Node');
459
470
  for (const requiredPath of [
460
471
  'index.js',
461
472
  'package.json',
@@ -468,7 +479,7 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity,
468
479
  ]);
469
480
  const uiClientPaths = files.filter((logicalPath)=>{
470
481
  const topLevel = logicalPath.split('/')[0];
471
- return 'static' === topLevel || 'html' === topLevel || 'public' === topLevel && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) || [
482
+ return 'static' === topLevel || 'html' === topLevel || 'public' === topLevel && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) && !declaredPublicAssetPaths.has(logicalPath) || [
472
483
  'index.html',
473
484
  'mf-manifest.json',
474
485
  'mf-stats.json',
@@ -502,6 +513,7 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity,
502
513
  'nodejs-deployment'
503
514
  ]));
504
515
  for (const logicalPath of uiClientPaths)runtimeByPath.set(logicalPath, 'browser');
516
+ for (const logicalPath of declaredPublicAssetPaths)runtimeByPath.set(logicalPath, DECLARED_PUBLIC_ASSET_RUNTIME);
505
517
  if (files.includes(CRAWLER_POLICY_PATH)) runtimeByPath.set(CRAWLER_POLICY_PATH, 'crawler-policy');
506
518
  for (const logicalPath of files.filter(isPublicMetadataPath))runtimeByPath.set(logicalPath, 'public-metadata');
507
519
  for (const logicalPath of apiOnly ? [] : ssrPaths)runtimeByPath.set(logicalPath, 'nodejs');
@@ -546,10 +558,10 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity,
546
558
  }
547
559
  };
548
560
  };
549
- const emitNodeStagedReleaseEnvelope = async ({ distDirectory, outputDirectory })=>{
561
+ const emitNodeStagedReleaseEnvelope = async ({ declaredPublicAssets = [], distDirectory, outputDirectory })=>{
550
562
  const source = await verifyBuildOutputReleaseEnvelope(distDirectory, 'node');
551
563
  if (!source) return;
552
- const { artifacts, surfaces } = await createNodeStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length);
564
+ const { artifacts, surfaces } = await createNodeStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length, declaredPublicAssets);
553
565
  const staged = await (0, external_index_js_namespaceObject.createMicroVerticalReleaseEnvelope)({
554
566
  artifactRoot: outputDirectory,
555
567
  target: 'node',
@@ -567,8 +579,9 @@ const stageCloudflareReleaseEnvelope = async ({ distDirectory, outputDirectory }
567
579
  if (await pathExists(stagedEnvelopePath)) throw new Error('[ultramodern-release-envelope] Cloudflare staging contains a stale envelope before final output generation.');
568
580
  return source;
569
581
  };
570
- const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly)=>{
582
+ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly, declaredPublicAssets)=>{
571
583
  const files = await collectFiles(outputDirectory);
584
+ const declaredPublicAssetPaths = resolveDeclaredPublicAssets(files, declaredPublicAssets, 'Cloudflare');
572
585
  for (const requiredPath of [
573
586
  'server/index.mjs',
574
587
  'server/modern-worker-manifest.json',
@@ -584,7 +597,7 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
584
597
  const apiBackendPaths = files.filter(isEffectBffWorkerArtifact);
585
598
  if (!files.some((logicalPath)=>EFFECT_BFF_WORKER_PATTERN.test(logicalPath))) throw new Error('[ultramodern-release-envelope] final Cloudflare staging has no actual Effect API/BFF worker artifact.');
586
599
  const ssrPaths = files.filter((logicalPath)=>(logicalPath.startsWith('server/') || logicalPath.startsWith('worker/')) && COMPILED_MODULE_PATTERN.test(logicalPath) && !isEffectBffWorkerArtifact(logicalPath));
587
- const uiClientPaths = files.filter((logicalPath)=>logicalPath.startsWith('public/') && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) && logicalPath !== PUBLIC_BUILD_ARTIFACT_PATH && logicalPath !== backendManifestPath && logicalPath !== backendContainerPath);
600
+ const uiClientPaths = files.filter((logicalPath)=>logicalPath.startsWith('public/') && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) && !declaredPublicAssetPaths.has(logicalPath) && logicalPath !== PUBLIC_BUILD_ARTIFACT_PATH && logicalPath !== backendManifestPath && logicalPath !== backendContainerPath);
588
601
  if (apiOnly && (uiClientPaths.length > 0 || ssrPaths.some((path)=>'server/index.mjs' !== path))) throw new Error('[ultramodern-release-envelope] final API-only Cloudflare staging contains an undeclared UI/client or SSR surface.');
589
602
  for (const [surface, paths] of [
590
603
  [
@@ -613,6 +626,9 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
613
626
  for (const logicalPath of paths)runtimeByPath.set(logicalPath, runtime);
614
627
  };
615
628
  add(uiClientPaths, 'browser');
629
+ add([
630
+ ...declaredPublicAssetPaths
631
+ ], DECLARED_PUBLIC_ASSET_RUNTIME);
616
632
  if (files.includes(CRAWLER_POLICY_PATH)) runtimeByPath.set(CRAWLER_POLICY_PATH, 'crawler-policy');
617
633
  add(files.filter(isPublicMetadataPath), 'public-metadata');
618
634
  add(apiOnly ? [] : ssrPaths, 'workerd');
@@ -661,10 +677,10 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
661
677
  }
662
678
  };
663
679
  };
664
- const emitCloudflareStagedReleaseEnvelope = async ({ distDirectory, outputDirectory })=>{
680
+ const emitCloudflareStagedReleaseEnvelope = async ({ declaredPublicAssets = [], distDirectory, outputDirectory })=>{
665
681
  const source = await verifyBuildOutputReleaseEnvelope(distDirectory, 'cloudflare');
666
682
  if (!source) return;
667
- const { artifacts, surfaces } = await createCloudflareStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length);
683
+ const { artifacts, surfaces } = await createCloudflareStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length, declaredPublicAssets);
668
684
  const staged = await (0, external_index_js_namespaceObject.createMicroVerticalReleaseEnvelope)({
669
685
  artifactRoot: outputDirectory,
670
686
  target: 'cloudflare',
@@ -41,6 +41,7 @@ __webpack_require__.d(__webpack_exports__, {
41
41
  });
42
42
  const external_node_path_namespaceObject = require("node:path");
43
43
  var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
44
+ const public_assets_js_namespaceObject = require("../deploy-output/public-assets.js");
44
45
  const external_framework_output_js_namespaceObject = require("./framework-output.js");
45
46
  const resolveActiveDeployTarget = (api, resolveDeployTarget)=>{
46
47
  const { metaName } = api.getAppContext();
@@ -53,7 +54,8 @@ const createUltramodernReleaseEnvelopePlugin = ({ resolveDeployTarget })=>({
53
54
  pre: [
54
55
  '@modern-js/backend-federation-build',
55
56
  '@modern-js/plugin-bff',
56
- '@modern-js/deploy-output-aliases'
57
+ '@modern-js/deploy-output-aliases',
58
+ '@modern-js/deploy-output-public-assets'
57
59
  ],
58
60
  post: [
59
61
  '@modern-js/plugin-deploy'
@@ -86,6 +88,12 @@ const createUltramodernReleaseEnvelopePlugin = ({ resolveDeployTarget })=>({
86
88
  const { appDirectory, distDirectory } = api.getAppContext();
87
89
  const outputDirectory = external_node_path_default().join(appDirectory, '.output');
88
90
  const releaseEnvelope = await (0, external_framework_output_js_namespaceObject.emitNodeStagedReleaseEnvelope)({
91
+ declaredPublicAssets: await (0, public_assets_js_namespaceObject.resolveAddedDeclaredPublicAssetPaths)({
92
+ appDirectory,
93
+ assets: (0, public_assets_js_namespaceObject.normalizeDeclaredPublicAssets)(api.getNormalizedConfig().deploy?.node?.publicAssets, public_assets_js_namespaceObject.NODE_PUBLIC_ASSET_SCOPE),
94
+ generatedRoot: distDirectory,
95
+ scope: public_assets_js_namespaceObject.NODE_PUBLIC_ASSET_SCOPE
96
+ }),
89
97
  distDirectory,
90
98
  outputDirectory
91
99
  });
@@ -160,14 +160,46 @@ async function dispatchRouteWorker(route, request, env, ctx) {
160
160
  );
161
161
  }
162
162
 
163
- function matchesPrefix(pathname, prefix) {
163
+ // Worker Static Assets decode percent-encoded paths, so an encoded spelling
164
+ // of a mounted prefix (`/%70refix/...`, `/prefix%2F...`) would otherwise skip
165
+ // the BFF or service binding that owns the prefix and reach ASSETS directly.
166
+ // Resolve such spellings to the canonical pathname the owner sees; a
167
+ // backslash or undecodable spelling becomes a path the owner rejects.
168
+ function resolvePrefixPathname(pathname, prefix) {
164
169
  if (!prefix || prefix === '/') {
165
- return true;
170
+ return pathname;
166
171
  }
167
172
 
168
173
  const normalized = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
174
+ let candidate = pathname;
175
+
176
+ for (let attempt = 0; attempt < 4; attempt += 1) {
177
+ if (candidate === normalized || candidate.startsWith(`${normalized}/`)) {
178
+ return candidate;
179
+ }
180
+ if (candidate.startsWith(`${normalized}\\`)) {
181
+ return `${normalized}/__invalid_encoded_path__`;
182
+ }
183
+
184
+ let decoded;
185
+ try {
186
+ decoded = decodeURIComponent(candidate);
187
+ } catch {
188
+ return candidate.toLowerCase().startsWith(normalized.toLowerCase())
189
+ ? `${normalized}/__invalid_encoded_path__`
190
+ : null;
191
+ }
192
+ if (decoded === candidate) {
193
+ return null;
194
+ }
195
+ candidate = decoded;
196
+ }
169
197
 
170
- return pathname === normalized || pathname.startsWith(`${normalized}/`);
198
+ return null;
199
+ }
200
+
201
+ function matchesPrefix(pathname, prefix) {
202
+ return resolvePrefixPathname(pathname, prefix) !== null;
171
203
  }
172
204
 
173
205
  function createRequestForMountedPrefix(request, prefix) {
@@ -177,12 +209,13 @@ function createRequestForMountedPrefix(request, prefix) {
177
209
 
178
210
  const url = new URL(request.url);
179
211
  const normalized = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
212
+ const matchedPathname = resolvePrefixPathname(url.pathname, normalized);
180
213
 
181
- if (!matchesPrefix(url.pathname, normalized)) {
214
+ if (matchedPathname === null) {
182
215
  return request;
183
216
  }
184
217
 
185
- const nextPath = url.pathname.slice(normalized.length) || '/';
218
+ const nextPath = matchedPathname.slice(normalized.length) || '/';
186
219
  url.pathname = nextPath.startsWith('/') ? nextPath : `/${nextPath}`;
187
220
 
188
221
  return new Request(url, request);
@@ -309,12 +342,21 @@ function getEffectBffDispatcher(bff, runtime) {
309
342
  async function dispatchBffRequest(request, env) {
310
343
  const bff = MODERN_WORKER_MANIFEST.bff;
311
344
 
312
- if (
313
- !bff?.worker ||
314
- !matchesPrefix(new URL(request.url).pathname, bff.prefix)
315
- ) {
345
+ const requestUrl = new URL(request.url);
346
+ const matchedPathname = resolvePrefixPathname(
347
+ requestUrl.pathname,
348
+ bff?.prefix,
349
+ );
350
+ if (!bff?.worker || matchedPathname === null) {
316
351
  return null;
317
352
  }
353
+ const canonicalRequest =
354
+ matchedPathname === requestUrl.pathname
355
+ ? request
356
+ : new Request(
357
+ Object.assign(requestUrl, { pathname: matchedPathname }),
358
+ request,
359
+ );
318
360
  if (bff.runtimeFramework !== 'effect') {
319
361
  return createEffectBffDispatcherErrorResponse(
320
362
  bff,
@@ -347,7 +389,7 @@ async function dispatchBffRequest(request, env) {
347
389
  return createEffectBffDispatcherErrorResponse(bff, error);
348
390
  }
349
391
 
350
- return effectDispatcher.dispatch(request, { env });
392
+ return effectDispatcher.dispatch(canonicalRequest, { env });
351
393
  }
352
394
 
353
395
  const directHandler =
@@ -1,5 +1,6 @@
1
1
  import node_path from "node:path";
2
2
  import { fs } from "@modern-js/utils";
3
+ import { CLOUDFLARE_PUBLIC_ASSET_SCOPE, normalizeDeclaredPublicAssets, stageDeclaredPublicAssets } from "../deploy-output/public-assets.mjs";
3
4
  import { RESERVED_ARTIFACT_DESTINATION_DIRECTORIES, RESERVED_ARTIFACT_DESTINATION_FILES, ROUTE_SPEC_OUTPUT } from "./constants.mjs";
4
5
  import { normalizeRelativePath } from "./utils.mjs";
5
6
  const normalizeCloudflareArtifact = (artifact, index)=>{
@@ -15,18 +16,7 @@ const normalizeCloudflareArtifact = (artifact, index)=>{
15
16
  };
16
17
  };
17
18
  const getCloudflareArtifacts = (modernConfig)=>(modernConfig.deploy?.worker?.artifacts ?? []).map(normalizeCloudflareArtifact);
18
- const normalizeCloudflarePublicAsset = (asset, index)=>{
19
- const from = normalizeRelativePath(asset.from, `deploy.worker.publicAssets[${index}].from`, 'app root');
20
- const to = normalizeRelativePath(asset.to, `deploy.worker.publicAssets[${index}].to`, 'Cloudflare public output', {
21
- allowRoot: true
22
- });
23
- return {
24
- from,
25
- to,
26
- index
27
- };
28
- };
29
- const getCloudflarePublicAssets = (modernConfig)=>(modernConfig.deploy?.worker?.publicAssets ?? []).map(normalizeCloudflarePublicAsset);
19
+ const getCloudflarePublicAssets = (modernConfig)=>normalizeDeclaredPublicAssets(modernConfig.deploy?.worker?.publicAssets, CLOUDFLARE_PUBLIC_ASSET_SCOPE);
30
20
  const copyCloudflareArtifacts = async (appDirectory, outputDirectory, artifacts)=>{
31
21
  for (const artifact of artifacts){
32
22
  const sourcePath = node_path.join(appDirectory, artifact.from);
@@ -34,13 +24,12 @@ const copyCloudflareArtifacts = async (appDirectory, outputDirectory, artifacts)
34
24
  await fs.copy(sourcePath, node_path.join(outputDirectory, artifact.to));
35
25
  }
36
26
  };
37
- const copyCloudflarePublicAssets = async (appDirectory, publicDirectory, publicAssets)=>{
38
- for (const asset of publicAssets){
39
- const sourcePath = node_path.join(appDirectory, asset.from);
40
- if (!await fs.pathExists(sourcePath)) throw new Error(`deploy.worker.publicAssets[${asset.index}].from does not exist: ${asset.from}`);
41
- await fs.copy(sourcePath, node_path.join(publicDirectory, asset.to));
42
- }
43
- };
27
+ const copyCloudflarePublicAssets = async (appDirectory, outputDirectory, publicAssets)=>stageDeclaredPublicAssets({
28
+ appDirectory,
29
+ outputDirectory,
30
+ assets: publicAssets,
31
+ scope: CLOUDFLARE_PUBLIC_ASSET_SCOPE
32
+ });
44
33
  const copyCloudflareD1Migrations = async (appDirectory, outputDirectory, modernConfig)=>{
45
34
  for (const [index, database] of (modernConfig.deploy?.worker?.d1Databases ?? []).entries()){
46
35
  if (!database.migrationsDir) continue;
@@ -32,6 +32,7 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
32
32
  const publicAssetExcludes = getPublicAssetExcludes(appDirectory, modernConfig);
33
33
  const releaseEnvelopeEnabled = api.isPluginExists('@modern-js/ultramodern-release-envelope');
34
34
  let hasReleaseEnvelope = false;
35
+ let declaredPublicAssets = [];
35
36
  return {
36
37
  async prepare () {
37
38
  await fs.remove(outputDirectory);
@@ -74,7 +75,7 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
74
75
  await fs.writeJSON(outputPlan.paths.workerPackage, outputPlan.packages.worker);
75
76
  }
76
77
  await copyCloudflareArtifacts(appDirectory, outputDirectory, cloudflareArtifacts);
77
- await copyCloudflarePublicAssets(appDirectory, publicDirectory, getCloudflarePublicAssets(modernConfig));
78
+ declaredPublicAssets = await copyCloudflarePublicAssets(appDirectory, outputDirectory, getCloudflarePublicAssets(modernConfig));
78
79
  await copyCloudflareD1Migrations(appDirectory, outputDirectory, modernConfig);
79
80
  await fs.writeJSON(wranglerConfigPath, createWranglerConfig(appDirectory, modernConfig), {
80
81
  spaces: 2
@@ -103,6 +104,7 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
103
104
  });
104
105
  if (hasReleaseEnvelope) {
105
106
  await emitCloudflareStagedReleaseEnvelope({
107
+ declaredPublicAssets,
106
108
  distDirectory,
107
109
  outputDirectory
108
110
  });
@@ -1,6 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import node_path from "node:path";
3
3
  import { preserveNpmAliases, readPackageIdentity } from "./npmAliases.mjs";
4
+ import { NODE_PUBLIC_ASSET_SCOPE, normalizeDeclaredPublicAssets, stageDeclaredPublicAssets } from "./public-assets.mjs";
4
5
  import { resolveDeployTarget as external_target_mjs_resolveDeployTarget } from "./target.mjs";
5
6
  const createDeployOutputAliasesPlugin = ({ resolveDeployTarget = external_target_mjs_resolveDeployTarget } = {})=>({
6
7
  name: '@modern-js/deploy-output-aliases',
@@ -25,4 +26,20 @@ const createDeployOutputAliasesPlugin = ({ resolveDeployTarget = external_target
25
26
  });
26
27
  }
27
28
  });
28
- export { createDeployOutputAliasesPlugin };
29
+ const createDeployOutputPublicAssetsPlugin = ({ resolveDeployTarget = external_target_mjs_resolveDeployTarget } = {})=>({
30
+ name: '@modern-js/deploy-output-public-assets',
31
+ setup (api) {
32
+ api.onAfterDeploy(async ()=>{
33
+ const { appDirectory, metaName } = api.getAppContext();
34
+ const config = api.getNormalizedConfig();
35
+ if ('modern-js' !== metaName && !config.deploy?.target && !process.env.MODERNJS_DEPLOY || 'node' !== resolveDeployTarget(config)) return;
36
+ await stageDeclaredPublicAssets({
37
+ appDirectory,
38
+ outputDirectory: node_path.join(appDirectory, '.output'),
39
+ assets: normalizeDeclaredPublicAssets(config.deploy?.node?.publicAssets, NODE_PUBLIC_ASSET_SCOPE),
40
+ scope: NODE_PUBLIC_ASSET_SCOPE
41
+ });
42
+ });
43
+ }
44
+ });
45
+ export { createDeployOutputAliasesPlugin, createDeployOutputPublicAssetsPlugin };