@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.2 → 3.9.0-ultramodern.20
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 +267 -0
- package/dist/cjs/backend-federation-build/config.js +239 -0
- package/dist/cjs/backend-federation-build/emit.js +154 -0
- package/dist/cjs/backend-federation-build/index.js +53 -0
- package/dist/cjs/build-config/build-environment.js +129 -0
- package/dist/cjs/build-config/precompress/plugin.js +124 -0
- package/dist/cjs/build-config/precompress/types.js +18 -0
- package/dist/cjs/build-config/public.js +43 -0
- package/dist/cjs/build-config/remote-address.js +58 -0
- package/dist/cjs/cloudflare/artifacts.js +8 -19
- package/dist/cjs/cloudflare/constants.js +1 -1
- package/dist/cjs/cloudflare/delivery-unit.js +23 -42
- package/dist/cjs/cloudflare/index.js +10 -2
- package/dist/cjs/cloudflare/worker-manifest.js +13 -4
- package/dist/cjs/cloudflare/wrangler-config.js +35 -7
- package/dist/cjs/cloudflare-builder.js +251 -75
- package/dist/cjs/cloudflare-output-contract.js +69 -3
- package/dist/cjs/cloudflare-output-verifier/identity.js +29 -27
- 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 +14 -6
- package/dist/cjs/config.js +34 -1
- package/dist/cjs/deploy-output/npmAliases.js +251 -0
- package/dist/cjs/deploy-output/plugin.js +97 -0
- package/dist/cjs/deploy-output/public-assets.js +146 -0
- package/dist/cjs/deploy-output/target.js +104 -0
- package/dist/cjs/deploy-output/trace-files.js +90 -0
- package/dist/cjs/native-type-checker.js +193 -0
- package/dist/cjs/policy-defaults.js +101 -0
- package/dist/cjs/release-envelope/framework-output.js +153 -79
- package/dist/cjs/release-envelope/index.js +10 -4
- package/dist/cjs/release-envelope/plugin.js +18 -13
- package/dist/cjs/release-identity.js +5 -6
- package/dist/cjs/runtime-package-resolution.js +118 -0
- package/dist/cjs/templates/cloudflare-entry.002-assets-routes.mjs +1 -1
- package/dist/cjs/templates/cloudflare-entry.004-rendering-css.mjs +5 -1
- package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +98 -10
- 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 +216 -0
- package/dist/esm/backend-federation-build/config.mjs +163 -0
- package/dist/esm/backend-federation-build/emit.mjs +103 -0
- package/dist/esm/backend-federation-build/index.mjs +1 -0
- package/dist/esm/build-config/build-environment.mjs +88 -0
- package/dist/esm/build-config/precompress/plugin.mjs +76 -0
- package/dist/esm/build-config/precompress/types.mjs +0 -0
- package/dist/esm/build-config/public.mjs +1 -0
- package/dist/esm/build-config/remote-address.mjs +20 -0
- package/dist/esm/cloudflare/artifacts.mjs +8 -19
- package/dist/esm/cloudflare/constants.mjs +1 -1
- package/dist/esm/cloudflare/delivery-unit.mjs +17 -36
- package/dist/esm/cloudflare/index.mjs +10 -2
- package/dist/esm/cloudflare/worker-manifest.mjs +13 -4
- package/dist/esm/cloudflare/wrangler-config.mjs +35 -7
- package/dist/esm/cloudflare-builder.mjs +245 -75
- package/dist/esm/cloudflare-output-contract.mjs +59 -2
- package/dist/esm/cloudflare-output-verifier/identity.mjs +27 -25
- 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 +15 -7
- package/dist/esm/config.mjs +3 -0
- package/dist/esm/deploy-output/npmAliases.mjs +199 -0
- package/dist/esm/deploy-output/plugin.mjs +46 -0
- package/dist/esm/deploy-output/public-assets.mjs +82 -0
- package/dist/esm/deploy-output/target.mjs +57 -0
- package/dist/esm/deploy-output/trace-files.mjs +38 -0
- package/dist/esm/native-type-checker.mjs +142 -0
- package/dist/esm/policy-defaults.mjs +45 -0
- package/dist/esm/release-envelope/framework-output.mjs +130 -56
- package/dist/esm/release-envelope/index.mjs +10 -4
- package/dist/esm/release-envelope/plugin.mjs +18 -13
- package/dist/esm/release-identity.mjs +5 -6
- package/dist/esm/runtime-package-resolution.mjs +60 -0
- package/dist/esm/templates/cloudflare-entry.002-assets-routes.mjs +1 -1
- package/dist/esm/templates/cloudflare-entry.004-rendering-css.mjs +5 -1
- package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +98 -10
- 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 +217 -0
- package/dist/esm-node/backend-federation-build/config.mjs +164 -0
- package/dist/esm-node/backend-federation-build/emit.mjs +104 -0
- package/dist/esm-node/backend-federation-build/index.mjs +2 -0
- package/dist/esm-node/build-config/build-environment.mjs +89 -0
- package/dist/esm-node/build-config/precompress/plugin.mjs +77 -0
- package/dist/esm-node/build-config/precompress/types.mjs +1 -0
- package/dist/esm-node/build-config/public.mjs +2 -0
- package/dist/esm-node/build-config/remote-address.mjs +21 -0
- package/dist/esm-node/cloudflare/artifacts.mjs +8 -19
- package/dist/esm-node/cloudflare/constants.mjs +1 -1
- package/dist/esm-node/cloudflare/delivery-unit.mjs +17 -36
- package/dist/esm-node/cloudflare/index.mjs +10 -2
- 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 +245 -75
- package/dist/esm-node/cloudflare-output-contract.mjs +59 -2
- package/dist/esm-node/cloudflare-output-verifier/identity.mjs +27 -25
- 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 +15 -7
- package/dist/esm-node/config.mjs +3 -0
- package/dist/esm-node/deploy-output/npmAliases.mjs +200 -0
- package/dist/esm-node/deploy-output/plugin.mjs +49 -0
- package/dist/esm-node/deploy-output/public-assets.mjs +83 -0
- package/dist/esm-node/deploy-output/target.mjs +58 -0
- package/dist/esm-node/deploy-output/trace-files.mjs +39 -0
- package/dist/esm-node/native-type-checker.mjs +143 -0
- package/dist/esm-node/policy-defaults.mjs +46 -0
- package/dist/esm-node/release-envelope/framework-output.mjs +130 -56
- package/dist/esm-node/release-envelope/index.mjs +10 -4
- package/dist/esm-node/release-envelope/plugin.mjs +18 -13
- package/dist/esm-node/release-identity.mjs +5 -6
- package/dist/esm-node/runtime-package-resolution.mjs +61 -0
- package/dist/esm-node/templates/cloudflare-entry.002-assets-routes.mjs +1 -1
- package/dist/esm-node/templates/cloudflare-entry.004-rendering-css.mjs +5 -1
- package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +98 -10
- 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 +116 -0
- package/dist/types/backend-federation-build/config.d.ts +121 -0
- package/dist/types/backend-federation-build/emit.d.ts +21 -0
- package/dist/types/backend-federation-build/index.d.ts +2 -0
- package/dist/types/build-config/build-environment.d.ts +14 -0
- package/dist/types/build-config/precompress/plugin.d.ts +4 -0
- package/dist/types/build-config/precompress/types.d.ts +19 -0
- package/dist/types/build-config/public.d.ts +2 -0
- package/dist/types/build-config/remote-address.d.ts +3 -0
- package/dist/types/cloudflare/artifacts.d.ts +7 -6
- package/dist/types/cloudflare/constants.d.ts +1 -1
- package/dist/types/cloudflare/delivery-unit.d.ts +3 -3
- package/dist/types/cloudflare/worker-manifest.d.ts +2 -2
- package/dist/types/cloudflare/wrangler-config.d.ts +6 -0
- package/dist/types/cloudflare-builder.d.ts +49 -9
- package/dist/types/cloudflare-output-contract.d.ts +4 -1
- package/dist/types/cloudflare-output-verifier/identity.d.ts +1 -1
- package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +2 -2
- package/dist/types/config.d.ts +29 -1
- package/dist/types/deploy-output/npmAliases.d.ts +14 -0
- package/dist/types/deploy-output/plugin.d.ts +35 -0
- package/dist/types/deploy-output/public-assets.d.ts +51 -0
- package/dist/types/deploy-output/target.d.ts +29 -0
- package/dist/types/deploy-output/trace-files.d.ts +17 -0
- package/dist/types/native-type-checker.d.ts +15 -0
- package/dist/types/policy-defaults.d.ts +46 -0
- package/dist/types/precompress/LICENSE +20 -0
- package/dist/types/precompress/index.d.ts +76 -0
- package/dist/types/release-envelope/framework-output.d.ts +8 -3
- package/dist/types/release-envelope/plugin.d.ts +7 -7
- package/dist/types/runtime-package-resolution.d.ts +28 -0
- package/dist/types/templates/cloudflare-worker-route-data.d.mts +14 -0
- package/package.json +97 -15
- package/src/backend-federation-build/codegen.ts +254 -0
- package/src/backend-federation-build/config.ts +492 -0
- package/src/backend-federation-build/emit.ts +213 -0
- package/src/backend-federation-build/index.ts +2 -0
- package/src/build-config/build-environment.ts +160 -0
- package/src/build-config/precompress/plugin.ts +140 -0
- package/src/build-config/precompress/types.ts +26 -0
- package/src/build-config/public.ts +5 -0
- package/src/build-config/remote-address.ts +33 -0
- package/src/cloudflare/artifacts.ts +22 -43
- package/src/cloudflare/constants.ts +1 -1
- package/src/cloudflare/delivery-unit.ts +42 -53
- package/src/cloudflare/index.ts +24 -11
- package/src/cloudflare/worker-manifest.ts +20 -3
- package/src/cloudflare/wrangler-config.ts +86 -10
- package/src/cloudflare-builder.ts +472 -151
- package/src/cloudflare-output-contract.ts +70 -0
- package/src/cloudflare-output-verifier/identity.ts +5 -1
- 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 +32 -14
- package/src/config.ts +34 -6
- package/src/deploy-output/npmAliases.ts +382 -0
- package/src/deploy-output/plugin.ts +86 -0
- package/src/deploy-output/public-assets.ts +224 -0
- package/src/deploy-output/target.ts +106 -0
- package/src/deploy-output/trace-files.ts +67 -0
- package/src/native-type-checker.ts +183 -0
- package/src/policy-defaults.ts +127 -0
- package/src/release-envelope/framework-output.ts +346 -94
- package/src/release-envelope/index.ts +26 -5
- package/src/release-envelope/plugin.ts +43 -40
- package/src/release-identity.ts +13 -2
- package/src/runtime-package-resolution.ts +115 -0
- package/src/templates/cloudflare-entry.002-assets-routes.mjs +1 -1
- package/src/templates/cloudflare-entry.004-rendering-css.mjs +5 -1
- package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +98 -10
- package/src/templates/cloudflare-entry.006-fetch-handler.mjs +9 -2
- package/src/templates/cloudflare-worker-route-data.mjs +1069 -0
- package/dist/cjs/templates/cloudflare-worker-fs-promises.mjs +0 -11
- package/dist/cjs/templates/cloudflare-worker-loadable-server.mjs +0 -281
- package/dist/cjs/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
- package/dist/cjs/templates/cloudflare-worker-path.mjs +0 -98
- package/dist/esm/templates/cloudflare-worker-fs-promises.mjs +0 -11
- package/dist/esm/templates/cloudflare-worker-loadable-server.mjs +0 -281
- package/dist/esm/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
- package/dist/esm/templates/cloudflare-worker-path.mjs +0 -98
- package/dist/esm-node/templates/cloudflare-worker-fs-promises.mjs +0 -11
- package/dist/esm-node/templates/cloudflare-worker-loadable-server.mjs +0 -281
- package/dist/esm-node/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
- package/dist/esm-node/templates/cloudflare-worker-path.mjs +0 -98
- package/dist/types/templates/cloudflare-worker-fs-promises.d.mts +0 -5
- package/dist/types/templates/cloudflare-worker-loadable-server.d.mts +0 -48
- package/dist/types/templates/cloudflare-worker-mf-ssr-runtime-plugin.d.mts +0 -13
- package/dist/types/templates/cloudflare-worker-path.d.mts +0 -21
- package/src/templates/cloudflare-worker-fs-promises.mjs +0 -11
- package/src/templates/cloudflare-worker-loadable-server.mjs +0 -281
- package/src/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
- package/src/templates/cloudflare-worker-path.mjs +0 -98
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { collectRuntimePackageModuleDirectories, createRuntimePackageResolutionPlugin } from "./runtime-package-resolution.mjs";
|
|
3
|
+
const RENDERER_EXTENSIONS_PACKAGE = '@modern-js/runtime-renderer-extensions';
|
|
4
|
+
const RENDERER_EXTENSIONS_PLUGIN_NAME = 'rendererHead';
|
|
5
|
+
const SERVER_EXTENSIONS_PLUGIN_NAME = '@modern-js/app-tools/server-plugin';
|
|
6
|
+
const POLICY_DEFAULTS_PLUGIN_NAME = '@modern-js/ultramodern-policy-defaults';
|
|
7
|
+
const applyPolicyDefaults = (api, options = {})=>{
|
|
8
|
+
if (false !== options.rendererExtensions) {
|
|
9
|
+
api._internalRuntimePlugins(({ entrypoint, plugins })=>{
|
|
10
|
+
if (!plugins.some((plugin)=>plugin.path === RENDERER_EXTENSIONS_PACKAGE)) plugins.push({
|
|
11
|
+
name: RENDERER_EXTENSIONS_PLUGIN_NAME,
|
|
12
|
+
path: RENDERER_EXTENSIONS_PACKAGE,
|
|
13
|
+
config: {}
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
entrypoint,
|
|
17
|
+
plugins
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
const moduleDirectories = collectRuntimePackageModuleDirectories([
|
|
21
|
+
RENDERER_EXTENSIONS_PACKAGE
|
|
22
|
+
], import.meta.url);
|
|
23
|
+
if (moduleDirectories.length > 0) api.modifyResolvedConfig((config)=>({
|
|
24
|
+
...config,
|
|
25
|
+
builderPlugins: [
|
|
26
|
+
...config.builderPlugins ?? [],
|
|
27
|
+
createRuntimePackageResolutionPlugin(moduleDirectories)
|
|
28
|
+
]
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
if (false !== options.serverExtensions) api._internalServerPlugins(({ plugins })=>{
|
|
32
|
+
if (!plugins.some((plugin)=>plugin.name === SERVER_EXTENSIONS_PLUGIN_NAME)) plugins.push({
|
|
33
|
+
name: SERVER_EXTENSIONS_PLUGIN_NAME
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
plugins
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
const createPolicyDefaultsPlugin = (options = {})=>({
|
|
41
|
+
name: POLICY_DEFAULTS_PLUGIN_NAME,
|
|
42
|
+
setup (api) {
|
|
43
|
+
applyPolicyDefaults(api, options);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
export { POLICY_DEFAULTS_PLUGIN_NAME, RENDERER_EXTENSIONS_PACKAGE, RENDERER_EXTENSIONS_PLUGIN_NAME, SERVER_EXTENSIONS_PLUGIN_NAME, applyPolicyDefaults, createPolicyDefaultsPlugin };
|
|
@@ -2,7 +2,7 @@ import "node:module";
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import promises from "node:fs/promises";
|
|
4
4
|
import node_path from "node:path";
|
|
5
|
-
import { BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_REMOTE_ENTRY_FILE, ULTRAMODERN_BUILD_ARTIFACT_FILE, isUltramodernBuildArtifact } from "@modern-js/
|
|
5
|
+
import { BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_REMOTE_ENTRY_FILE, ULTRAMODERN_BUILD_ARTIFACT_FILE, isUltramodernBuildArtifact } from "@modern-js/backend-federation-contracts";
|
|
6
6
|
import { canonicalSerializeMicroVerticalReleaseEnvelope, createMicroVerticalReleaseEnvelope, verifyMicroVerticalReleaseEnvelope } from "./index.mjs";
|
|
7
7
|
const MICROVERTICAL_RELEASE_ENVELOPE_PATH = 'release/microvertical-release-envelope.json';
|
|
8
8
|
const MICROVERTICAL_RELEASE_IDENTITY_CARRIERS_PATH = 'release/microvertical-release-identity-carriers.json';
|
|
@@ -10,6 +10,21 @@ const MICROVERTICAL_RELEASE_IDENTITY_CARRIERS_KIND = 'ultramodern-release-identi
|
|
|
10
10
|
const MICROVERTICAL_RELEASE_IDENTITY_CARRIERS_SCHEMA_VERSION = 1;
|
|
11
11
|
const COMPILED_MODULE_PATTERN = /\.(?:c|m)?js$/u;
|
|
12
12
|
const EFFECT_BFF_WORKER_PATTERN = /^worker\/__modern_bff_effect\.(?:c|m)?js$/u;
|
|
13
|
+
const EFFECT_BFF_WORKER_SUPPORT_PATTERN = /^worker\/__modern_worker_(?:runtime|shared)_*\.(?:c|m)?js$/u;
|
|
14
|
+
const isEffectBffWorkerArtifact = (logicalPath)=>EFFECT_BFF_WORKER_PATTERN.test(logicalPath) || EFFECT_BFF_WORKER_SUPPORT_PATTERN.test(logicalPath);
|
|
15
|
+
const CRAWLER_POLICY_PATH = 'public/robots.txt';
|
|
16
|
+
const isPublicMetadataPath = (logicalPath)=>'public/_headers' === logicalPath || /^public\/\.well-known\/[^/]+\.json$/u.test(logicalPath);
|
|
17
|
+
const PUBLIC_BUILD_ARTIFACT_PATH = `public/${ULTRAMODERN_BUILD_ARTIFACT_FILE}`;
|
|
18
|
+
const DECLARED_PUBLIC_ASSET_RUNTIME = 'public-asset';
|
|
19
|
+
const resolveDeclaredPublicAssets = (files, declaredPublicAssets, target)=>{
|
|
20
|
+
const staged = new Set(files);
|
|
21
|
+
const declared = new Set();
|
|
22
|
+
for (const logicalPath of declaredPublicAssets){
|
|
23
|
+
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)}.`);
|
|
24
|
+
declared.add(logicalPath);
|
|
25
|
+
}
|
|
26
|
+
return declared;
|
|
27
|
+
};
|
|
13
28
|
const isRecord = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
|
|
14
29
|
const flattenStrings = (value)=>'string' == typeof value ? [
|
|
15
30
|
value
|
|
@@ -22,6 +37,46 @@ const collectNestedStrings = (value, key)=>{
|
|
|
22
37
|
...collectNestedStrings(entryValue, key)
|
|
23
38
|
]);
|
|
24
39
|
};
|
|
40
|
+
const emptyProducerRouteModules = async (distDirectory, files, manifest, manifestLogicalPath)=>{
|
|
41
|
+
if (!isRecord(manifest) || !Array.isArray(manifest.exposes) || 0 !== manifest.exposes.length || !Array.isArray(manifest.remotes) || 0 !== manifest.remotes.length || !isRecord(manifest.metaData) || !isRecord(manifest.metaData.remoteEntry) || '' !== manifest.metaData.remoteEntry.name || '' !== manifest.metaData.remoteEntry.path || 'global' !== manifest.metaData.remoteEntry.type) return [];
|
|
42
|
+
const publicPath = manifest.metaData.publicPath;
|
|
43
|
+
const safeReference = (value)=>'string' == typeof value && value.length > 0 && !/[\\\\%?#\s]/u.test(value) && !value.split('/').some((segment)=>'.' === segment || '..' === segment);
|
|
44
|
+
if (!safeReference(publicPath)) return [];
|
|
45
|
+
const absoluteBase = /^https?:\/\//u.test(publicPath);
|
|
46
|
+
if ('auto' !== publicPath && (!publicPath.endsWith('/') || !absoluteBase && (!publicPath.startsWith('/') || publicPath.startsWith('//')))) return [];
|
|
47
|
+
if (absoluteBase) {
|
|
48
|
+
const base = new URL(publicPath);
|
|
49
|
+
if (base.username || base.password || base.href !== publicPath) return [];
|
|
50
|
+
}
|
|
51
|
+
const directory = node_path.posix.dirname(manifestLogicalPath);
|
|
52
|
+
const routesManifest = await readJson(node_path.join(distDirectory, directory, 'routes-manifest.json'));
|
|
53
|
+
if (!isRecord(routesManifest) || !isRecord(routesManifest.routeAssets)) return [];
|
|
54
|
+
const modules = new Set();
|
|
55
|
+
for (const route of Object.values(routesManifest.routeAssets)){
|
|
56
|
+
if (!isRecord(route) || !Array.isArray(route.assets)) return [];
|
|
57
|
+
for (const reference of route.assets){
|
|
58
|
+
if (!safeReference(reference) || reference.startsWith('//')) return [];
|
|
59
|
+
let relative = reference;
|
|
60
|
+
if (/^https?:\/\//u.test(reference)) {
|
|
61
|
+
if (!absoluteBase || !reference.startsWith(publicPath)) return [];
|
|
62
|
+
relative = reference.slice(publicPath.length);
|
|
63
|
+
} else if (reference.startsWith('/')) {
|
|
64
|
+
const prefix = 'auto' === publicPath ? '/' : absoluteBase ? new URL(publicPath).pathname : publicPath;
|
|
65
|
+
if (!reference.startsWith(prefix)) return [];
|
|
66
|
+
relative = reference.slice(prefix.length);
|
|
67
|
+
}
|
|
68
|
+
if (!/^(?:static|html|public)\/[^:]+$/u.test(relative) || relative.includes('//')) return [];
|
|
69
|
+
const logicalPath = node_path.posix.join(directory, relative);
|
|
70
|
+
if (!files.includes(logicalPath)) return [];
|
|
71
|
+
const assetStat = await promises.lstat(node_path.join(distDirectory, logicalPath));
|
|
72
|
+
if (!assetStat.isFile()) return [];
|
|
73
|
+
if (COMPILED_MODULE_PATTERN.test(logicalPath)) modules.add(logicalPath);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return [
|
|
77
|
+
...modules
|
|
78
|
+
];
|
|
79
|
+
};
|
|
25
80
|
const manifestReferencedClientModules = async (distDirectory, files, manifestLogicalPath = 'mf-manifest.json')=>{
|
|
26
81
|
const manifest = await readJson(node_path.join(distDirectory, manifestLogicalPath));
|
|
27
82
|
const references = [
|
|
@@ -31,9 +86,10 @@ const manifestReferencedClientModules = async (distDirectory, files, manifestLog
|
|
|
31
86
|
...collectNestedStrings(manifest, 'async')
|
|
32
87
|
];
|
|
33
88
|
const normalizedReferences = new Set(references.filter((value)=>'string' == typeof value).map((value)=>value.replace(/^https?:\/\/[^/]+/u, '')).map((value)=>value.replace(/^\/+/u, '')));
|
|
34
|
-
|
|
89
|
+
const modules = files.filter((logicalPath)=>COMPILED_MODULE_PATTERN.test(logicalPath) && [
|
|
35
90
|
...normalizedReferences
|
|
36
91
|
].some((reference)=>reference === logicalPath || reference.endsWith(`/${logicalPath}`) || logicalPath.endsWith(`/${reference}`)));
|
|
92
|
+
return modules.length > 0 ? modules : emptyProducerRouteModules(distDirectory, files, manifest, manifestLogicalPath);
|
|
37
93
|
};
|
|
38
94
|
const routeReferencedSsrModules = async (distDirectory, files, target, routeSpecLogicalPath = 'route.json')=>{
|
|
39
95
|
const routeSpec = await readJson(node_path.join(distDirectory, routeSpecLogicalPath));
|
|
@@ -83,11 +139,14 @@ const writeReleaseEnvelope = async (artifactRoot, envelope)=>{
|
|
|
83
139
|
if (!envelopePath) throw new Error('[ultramodern-release-envelope] could not resolve release envelope path.');
|
|
84
140
|
await promises.writeFile(envelopePath, `${canonicalSerializeMicroVerticalReleaseEnvelope(envelope)}\n`);
|
|
85
141
|
};
|
|
86
|
-
const readBuildArtifact = async (distDirectory)=>{
|
|
142
|
+
const readBuildArtifact = async (distDirectory, requirePromotable = true)=>{
|
|
87
143
|
const artifactPath = node_path.join(distDirectory, ULTRAMODERN_BUILD_ARTIFACT_FILE);
|
|
88
144
|
const artifact = await readJson(artifactPath);
|
|
89
145
|
if (!isUltramodernBuildArtifact(artifact)) throw new Error(`[ultramodern-release-envelope] ${ULTRAMODERN_BUILD_ARTIFACT_FILE} is missing or invalid.`);
|
|
90
|
-
if ('workspace' === artifact.deliveryUnit.sourceRevision)
|
|
146
|
+
if ('workspace' === artifact.deliveryUnit.sourceRevision) {
|
|
147
|
+
if (!requirePromotable) return;
|
|
148
|
+
throw new Error('[ultramodern-release-envelope] sourceRevision "workspace" cannot produce a promotable envelope.');
|
|
149
|
+
}
|
|
91
150
|
return artifact;
|
|
92
151
|
};
|
|
93
152
|
const assertIdentityBlock = (value, identity, location, includeReleaseVersion)=>{
|
|
@@ -156,7 +215,7 @@ const releaseArtifact = (logicalPath, runtime)=>({
|
|
|
156
215
|
const writeReleaseIdentityCarrierMetadata = async (artifactRoot, identity, carrierPaths)=>{
|
|
157
216
|
const surfacesByPath = new Map();
|
|
158
217
|
for (const [surface, logicalPaths] of Object.entries(carrierPaths)){
|
|
159
|
-
if (0 === logicalPaths.length) throw new Error(`[ultramodern-release-envelope] ${surface} has no declared release-identity carrier artifact.`);
|
|
218
|
+
if (0 === logicalPaths.length && 'ssr' !== surface && 'uiClient' !== surface) throw new Error(`[ultramodern-release-envelope] ${surface} has no declared release-identity carrier artifact.`);
|
|
160
219
|
for (const logicalPath of logicalPaths){
|
|
161
220
|
const surfaces = surfacesByPath.get(logicalPath) ?? new Set();
|
|
162
221
|
surfaces.add(surface);
|
|
@@ -196,12 +255,12 @@ const writeReleaseIdentityCarrierMetadata = async (artifactRoot, identity, carri
|
|
|
196
255
|
}, null, 2)}\n`);
|
|
197
256
|
return releaseArtifact(MICROVERTICAL_RELEASE_IDENTITY_CARRIERS_PATH, 'release-identity-metadata');
|
|
198
257
|
};
|
|
199
|
-
const createReleaseArtifactInputs = async (distDirectory, identity, target)=>{
|
|
258
|
+
const createReleaseArtifactInputs = async (distDirectory, identity, target, apiOnly)=>{
|
|
200
259
|
const files = await collectFiles(distDirectory);
|
|
201
|
-
if ('node' === target && files.some(
|
|
260
|
+
if ('node' === target && files.some(isEffectBffWorkerArtifact)) throw new Error('[ultramodern-release-envelope] Node target conflicts with a Cloudflare Effect API/BFF worker artifact; resolve the canonical deploy target instead of emitting a Node envelope for Cloudflare output.');
|
|
202
261
|
const uiClientPaths = files.filter((logicalPath)=>{
|
|
203
262
|
const topLevel = logicalPath.split('/')[0];
|
|
204
|
-
return 'static' === topLevel || 'html' === topLevel || 'public' === topLevel || [
|
|
263
|
+
return 'static' === topLevel || 'html' === topLevel || 'public' === topLevel && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) || [
|
|
205
264
|
'index.html',
|
|
206
265
|
'mf-manifest.json',
|
|
207
266
|
'mf-stats.json',
|
|
@@ -210,16 +269,17 @@ const createReleaseArtifactInputs = async (distDirectory, identity, target)=>{
|
|
|
210
269
|
'loadable-stats.json'
|
|
211
270
|
].includes(logicalPath);
|
|
212
271
|
});
|
|
213
|
-
const ssrPaths = 'node' === target ? files.filter((logicalPath)=>logicalPath.startsWith('bundles/') && COMPILED_MODULE_PATTERN.test(logicalPath)) : files.filter((logicalPath)=>logicalPath.startsWith('worker/') && COMPILED_MODULE_PATTERN.test(logicalPath) && !
|
|
272
|
+
const ssrPaths = 'node' === target ? files.filter((logicalPath)=>logicalPath.startsWith('bundles/') && COMPILED_MODULE_PATTERN.test(logicalPath)) : files.filter((logicalPath)=>logicalPath.startsWith('worker/') && COMPILED_MODULE_PATTERN.test(logicalPath) && !isEffectBffWorkerArtifact(logicalPath));
|
|
214
273
|
const nodeApiEntryPaths = files.filter((logicalPath)=>logicalPath.startsWith('api/') && COMPILED_MODULE_PATTERN.test(logicalPath));
|
|
215
|
-
const apiBackendPaths = 'node' === target ? files.filter((logicalPath)=>(logicalPath.startsWith('api/') || logicalPath.startsWith('shared/')) && COMPILED_MODULE_PATTERN.test(logicalPath)) : files.filter(
|
|
216
|
-
if (
|
|
217
|
-
if (0 ===
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
274
|
+
const apiBackendPaths = 'node' === target ? files.filter((logicalPath)=>(logicalPath.startsWith('api/') || logicalPath.startsWith('shared/')) && COMPILED_MODULE_PATTERN.test(logicalPath)) : files.filter(isEffectBffWorkerArtifact);
|
|
275
|
+
if (apiOnly && (uiClientPaths.length > 0 || ssrPaths.length > 0)) throw new Error('[ultramodern-release-envelope] API-only MicroVertical emitted an undeclared UI/client or SSR surface.');
|
|
276
|
+
if (!apiOnly && 0 === uiClientPaths.length) throw new Error(`[ultramodern-release-envelope] ${target} full-stack MicroVertical has no UI/client artifacts.`);
|
|
277
|
+
if (!apiOnly && 0 === ssrPaths.length) throw new Error(`[ultramodern-release-envelope] ${target} full-stack MicroVertical has no ${'node' === target ? 'Node' : 'workerd'} SSR artifacts.`);
|
|
278
|
+
if ('node' === target && 0 === nodeApiEntryPaths.length || 'cloudflare' === target && !files.some((logicalPath)=>EFFECT_BFF_WORKER_PATTERN.test(logicalPath)) || 0 === apiBackendPaths.length) throw new Error(`[ultramodern-release-envelope] ${target} MicroVertical has no actual ${'node' === target ? 'compiled Node Effect API artifact' : 'Effect API/BFF worker artifact'}.`);
|
|
279
|
+
const clientExecutionPaths = apiOnly ? [] : await manifestReferencedClientModules(distDirectory, files);
|
|
280
|
+
if (!apiOnly && 0 === clientExecutionPaths.length) throw new Error('[ultramodern-release-envelope] UI/client manifest references no compiled execution module.');
|
|
281
|
+
const ssrExecutionPaths = apiOnly ? [] : await routeReferencedSsrModules(distDirectory, files, target);
|
|
282
|
+
if (!apiOnly && 0 === ssrExecutionPaths.length) throw new Error(`[ultramodern-release-envelope] route manifest references no emitted ${'node' === target ? 'Node' : 'Cloudflare'} SSR execution module.`);
|
|
223
283
|
const runtimeByPath = new Map();
|
|
224
284
|
const add = (paths, runtime)=>{
|
|
225
285
|
for (const logicalPath of paths){
|
|
@@ -229,6 +289,8 @@ const createReleaseArtifactInputs = async (distDirectory, identity, target)=>{
|
|
|
229
289
|
}
|
|
230
290
|
};
|
|
231
291
|
add(uiClientPaths, 'browser');
|
|
292
|
+
add(files.filter((logicalPath)=>logicalPath === CRAWLER_POLICY_PATH), 'crawler-policy');
|
|
293
|
+
add(files.filter(isPublicMetadataPath), 'public-metadata');
|
|
232
294
|
add(ssrPaths, 'node' === target ? 'nodejs' : 'workerd');
|
|
233
295
|
add(apiBackendPaths, 'node' === target ? 'nodejs' : 'workerd-effect');
|
|
234
296
|
add([
|
|
@@ -285,17 +347,17 @@ const pathExists = async (filePath)=>{
|
|
|
285
347
|
return false;
|
|
286
348
|
}
|
|
287
349
|
};
|
|
288
|
-
const emitFrameworkMicroVerticalReleaseEnvelope = async ({ apiOnly, distDirectory, target })=>{
|
|
289
|
-
if (apiOnly) return;
|
|
350
|
+
const emitFrameworkMicroVerticalReleaseEnvelope = async ({ apiOnly, distDirectory, requirePromotable = true, target })=>{
|
|
290
351
|
const backendManifestPath = node_path.join(distDirectory, BACKEND_FEDERATION_MANIFEST_FILE);
|
|
291
352
|
const backendContainerPath = node_path.join(distDirectory, BACKEND_FEDERATION_REMOTE_ENTRY_FILE);
|
|
292
353
|
const [hasBackendManifest, hasBackendContainer] = await Promise.all([
|
|
293
354
|
pathExists(backendManifestPath),
|
|
294
355
|
pathExists(backendContainerPath)
|
|
295
356
|
]);
|
|
296
|
-
if (!hasBackendManifest && !hasBackendContainer) return;
|
|
297
|
-
if (!hasBackendManifest || !hasBackendContainer) throw new Error('[ultramodern-release-envelope]
|
|
298
|
-
const buildArtifact = await readBuildArtifact(distDirectory);
|
|
357
|
+
if (!hasBackendManifest && !hasBackendContainer && !apiOnly) return;
|
|
358
|
+
if (!hasBackendManifest || !hasBackendContainer) throw new Error('[ultramodern-release-envelope] MicroVertical backend federation manifest and container must be emitted together.');
|
|
359
|
+
const buildArtifact = await readBuildArtifact(distDirectory, requirePromotable);
|
|
360
|
+
if (!buildArtifact) return;
|
|
299
361
|
const identity = {
|
|
300
362
|
unitId: buildArtifact.deliveryUnit.unitId,
|
|
301
363
|
buildMarker: buildArtifact.deliveryUnit.buildMarker,
|
|
@@ -303,7 +365,7 @@ const emitFrameworkMicroVerticalReleaseEnvelope = async ({ apiOnly, distDirector
|
|
|
303
365
|
releaseVersion: buildArtifact.deliveryUnit.version
|
|
304
366
|
};
|
|
305
367
|
await readBackendManifest(distDirectory, identity);
|
|
306
|
-
const { artifacts, surfaces } = await createReleaseArtifactInputs(distDirectory, identity, target);
|
|
368
|
+
const { artifacts, surfaces } = await createReleaseArtifactInputs(distDirectory, identity, target, apiOnly);
|
|
307
369
|
const envelope = await createMicroVerticalReleaseEnvelope({
|
|
308
370
|
artifactRoot: distDirectory,
|
|
309
371
|
target,
|
|
@@ -334,19 +396,16 @@ const readFrameworkMicroVerticalReleaseEnvelope = async ({ artifactRoot, expecte
|
|
|
334
396
|
});
|
|
335
397
|
};
|
|
336
398
|
const verifyBuildOutputReleaseEnvelope = async (distDirectory, expectedTarget)=>{
|
|
337
|
-
const [hasEnvelope, hasBuildArtifact
|
|
399
|
+
const [hasEnvelope, hasBuildArtifact] = await Promise.all([
|
|
338
400
|
pathExists(node_path.join(distDirectory, MICROVERTICAL_RELEASE_ENVELOPE_PATH)),
|
|
339
|
-
pathExists(node_path.join(distDirectory, ULTRAMODERN_BUILD_ARTIFACT_FILE))
|
|
340
|
-
pathExists(node_path.join(distDirectory, BACKEND_FEDERATION_MANIFEST_FILE)),
|
|
341
|
-
pathExists(node_path.join(distDirectory, BACKEND_FEDERATION_REMOTE_ENTRY_FILE))
|
|
401
|
+
pathExists(node_path.join(distDirectory, ULTRAMODERN_BUILD_ARTIFACT_FILE))
|
|
342
402
|
]);
|
|
343
|
-
const isFullStackMicroVertical = hasBuildArtifact && (hasBackendManifest || hasBackendEntry);
|
|
344
403
|
return readFrameworkMicroVerticalReleaseEnvelope({
|
|
345
404
|
artifactRoot: distDirectory,
|
|
346
405
|
...expectedTarget ? {
|
|
347
406
|
expectedTarget
|
|
348
407
|
} : {},
|
|
349
|
-
required: hasEnvelope ||
|
|
408
|
+
required: hasEnvelope || hasBuildArtifact
|
|
350
409
|
});
|
|
351
410
|
};
|
|
352
411
|
const verifyNodeReleaseEnvelopeStaging = async ({ outputDirectory })=>readFrameworkMicroVerticalReleaseEnvelope({
|
|
@@ -354,8 +413,9 @@ const verifyNodeReleaseEnvelopeStaging = async ({ outputDirectory })=>readFramew
|
|
|
354
413
|
expectedTarget: 'node',
|
|
355
414
|
required: true
|
|
356
415
|
});
|
|
357
|
-
const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity)=>{
|
|
416
|
+
const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly, declaredPublicAssets)=>{
|
|
358
417
|
const files = await collectFiles(outputDirectory);
|
|
418
|
+
const declaredPublicAssetPaths = resolveDeclaredPublicAssets(files, declaredPublicAssets, 'Node');
|
|
359
419
|
for (const requiredPath of [
|
|
360
420
|
'index.js',
|
|
361
421
|
'package.json',
|
|
@@ -368,7 +428,7 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity)=
|
|
|
368
428
|
]);
|
|
369
429
|
const uiClientPaths = files.filter((logicalPath)=>{
|
|
370
430
|
const topLevel = logicalPath.split('/')[0];
|
|
371
|
-
return 'static' === topLevel || 'html' === topLevel || 'public' === topLevel || [
|
|
431
|
+
return 'static' === topLevel || 'html' === topLevel || 'public' === topLevel && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) && !declaredPublicAssetPaths.has(logicalPath) || [
|
|
372
432
|
'index.html',
|
|
373
433
|
'mf-manifest.json',
|
|
374
434
|
'mf-stats.json',
|
|
@@ -379,6 +439,7 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity)=
|
|
|
379
439
|
});
|
|
380
440
|
const ssrPaths = files.filter((logicalPath)=>'index.js' === logicalPath || logicalPath.startsWith('bundles/') && COMPILED_MODULE_PATTERN.test(logicalPath));
|
|
381
441
|
const apiBackendPaths = files.filter((logicalPath)=>(logicalPath.startsWith('api/') || logicalPath.startsWith('shared/')) && COMPILED_MODULE_PATTERN.test(logicalPath));
|
|
442
|
+
if (apiOnly && (uiClientPaths.length > 0 || ssrPaths.some((path)=>'index.js' !== path))) throw new Error('[ultramodern-release-envelope] final API-only Node staging contains an undeclared UI/client or SSR surface.');
|
|
382
443
|
for (const [surface, paths] of [
|
|
383
444
|
[
|
|
384
445
|
'UI/client',
|
|
@@ -386,22 +447,25 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity)=
|
|
|
386
447
|
],
|
|
387
448
|
[
|
|
388
449
|
'Node SSR',
|
|
389
|
-
ssrPaths
|
|
450
|
+
apiOnly ? [] : ssrPaths
|
|
390
451
|
],
|
|
391
452
|
[
|
|
392
453
|
'Node API/backend',
|
|
393
454
|
apiBackendPaths
|
|
394
455
|
]
|
|
395
|
-
])if (0 === paths.length) throw new Error(`[ultramodern-release-envelope] final Node staging has no ${surface} artifacts.`);
|
|
396
|
-
const clientExecutionPaths = await manifestReferencedClientModules(outputDirectory, files);
|
|
397
|
-
const ssrExecutionPaths = await routeReferencedSsrModules(outputDirectory, files, 'node');
|
|
398
|
-
if (0 === clientExecutionPaths.length || 0 === ssrExecutionPaths.length) throw new Error('[ultramodern-release-envelope] final Node manifests must reference emitted UI/client and SSR execution modules.');
|
|
456
|
+
])if (0 === paths.length && (!apiOnly || 'Node API/backend' === surface)) throw new Error(`[ultramodern-release-envelope] final Node staging has no ${surface} artifacts.`);
|
|
457
|
+
const clientExecutionPaths = apiOnly ? [] : await manifestReferencedClientModules(outputDirectory, files);
|
|
458
|
+
const ssrExecutionPaths = apiOnly ? [] : await routeReferencedSsrModules(outputDirectory, files, 'node');
|
|
459
|
+
if (!apiOnly && (0 === clientExecutionPaths.length || 0 === ssrExecutionPaths.length)) throw new Error('[ultramodern-release-envelope] final Node manifests must reference emitted UI/client and SSR execution modules.');
|
|
399
460
|
const runtimeByPath = new Map(files.filter((logicalPath)=>!backendFederationPaths.has(logicalPath)).map((logicalPath)=>[
|
|
400
461
|
logicalPath,
|
|
401
462
|
'nodejs-deployment'
|
|
402
463
|
]));
|
|
403
464
|
for (const logicalPath of uiClientPaths)runtimeByPath.set(logicalPath, 'browser');
|
|
404
|
-
for (const logicalPath of
|
|
465
|
+
for (const logicalPath of declaredPublicAssetPaths)runtimeByPath.set(logicalPath, DECLARED_PUBLIC_ASSET_RUNTIME);
|
|
466
|
+
if (files.includes(CRAWLER_POLICY_PATH)) runtimeByPath.set(CRAWLER_POLICY_PATH, 'crawler-policy');
|
|
467
|
+
for (const logicalPath of files.filter(isPublicMetadataPath))runtimeByPath.set(logicalPath, 'public-metadata');
|
|
468
|
+
for (const logicalPath of apiOnly ? [] : ssrPaths)runtimeByPath.set(logicalPath, 'nodejs');
|
|
405
469
|
for (const logicalPath of apiBackendPaths)runtimeByPath.set(logicalPath, 'nodejs');
|
|
406
470
|
runtimeByPath.set(BACKEND_FEDERATION_MANIFEST_FILE, 'module-federation-manifest');
|
|
407
471
|
runtimeByPath.set(BACKEND_FEDERATION_REMOTE_ENTRY_FILE, 'nodejs');
|
|
@@ -411,7 +475,7 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity)=
|
|
|
411
475
|
BACKEND_FEDERATION_MANIFEST_FILE,
|
|
412
476
|
BACKEND_FEDERATION_REMOTE_ENTRY_FILE
|
|
413
477
|
],
|
|
414
|
-
ssr: [
|
|
478
|
+
ssr: apiOnly ? [] : [
|
|
415
479
|
...new Set([
|
|
416
480
|
...ssrExecutionPaths,
|
|
417
481
|
...ssrPaths.filter((logicalPath)=>'index.js' !== logicalPath)
|
|
@@ -430,7 +494,7 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity)=
|
|
|
430
494
|
uiClient: [
|
|
431
495
|
...uiClientPaths
|
|
432
496
|
].sort((left, right)=>left.localeCompare(right)),
|
|
433
|
-
ssr: [
|
|
497
|
+
ssr: apiOnly ? [] : [
|
|
434
498
|
...ssrPaths
|
|
435
499
|
].sort((left, right)=>left.localeCompare(right)),
|
|
436
500
|
apiBackend: [
|
|
@@ -443,10 +507,10 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity)=
|
|
|
443
507
|
}
|
|
444
508
|
};
|
|
445
509
|
};
|
|
446
|
-
const emitNodeStagedReleaseEnvelope = async ({ distDirectory, outputDirectory })=>{
|
|
510
|
+
const emitNodeStagedReleaseEnvelope = async ({ declaredPublicAssets = [], distDirectory, outputDirectory })=>{
|
|
447
511
|
const source = await verifyBuildOutputReleaseEnvelope(distDirectory, 'node');
|
|
448
512
|
if (!source) return;
|
|
449
|
-
const { artifacts, surfaces } = await createNodeStagedReleaseArtifactInputs(outputDirectory, source.identity);
|
|
513
|
+
const { artifacts, surfaces } = await createNodeStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length, declaredPublicAssets);
|
|
450
514
|
const staged = await createMicroVerticalReleaseEnvelope({
|
|
451
515
|
artifactRoot: outputDirectory,
|
|
452
516
|
target: 'node',
|
|
@@ -464,21 +528,26 @@ const stageCloudflareReleaseEnvelope = async ({ distDirectory, outputDirectory }
|
|
|
464
528
|
if (await pathExists(stagedEnvelopePath)) throw new Error('[ultramodern-release-envelope] Cloudflare staging contains a stale envelope before final output generation.');
|
|
465
529
|
return source;
|
|
466
530
|
};
|
|
467
|
-
const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, identity)=>{
|
|
531
|
+
const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly, declaredPublicAssets)=>{
|
|
468
532
|
const files = await collectFiles(outputDirectory);
|
|
533
|
+
const declaredPublicAssetPaths = resolveDeclaredPublicAssets(files, declaredPublicAssets, 'Cloudflare');
|
|
469
534
|
for (const requiredPath of [
|
|
470
535
|
'server/index.mjs',
|
|
471
536
|
'server/modern-worker-manifest.json',
|
|
472
537
|
'wrangler.json',
|
|
473
538
|
'package.json',
|
|
474
|
-
|
|
539
|
+
...apiOnly ? [] : [
|
|
540
|
+
'worker/package.json'
|
|
541
|
+
]
|
|
475
542
|
])if (!files.includes(requiredPath)) throw new Error(`[ultramodern-release-envelope] final Cloudflare staging is missing ${requiredPath}.`);
|
|
476
543
|
if (files.some((logicalPath)=>'public/release' === logicalPath || logicalPath.startsWith('public/release/'))) throw new Error('[ultramodern-release-envelope] final Cloudflare envelope must remain private and cannot be staged under public/release.');
|
|
477
544
|
const backendManifestPath = `public/${BACKEND_FEDERATION_MANIFEST_FILE}`;
|
|
478
545
|
const backendContainerPath = `public/${BACKEND_FEDERATION_REMOTE_ENTRY_FILE}`;
|
|
479
|
-
const apiBackendPaths = files.filter(
|
|
480
|
-
|
|
481
|
-
const
|
|
546
|
+
const apiBackendPaths = files.filter(isEffectBffWorkerArtifact);
|
|
547
|
+
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.');
|
|
548
|
+
const ssrPaths = files.filter((logicalPath)=>(logicalPath.startsWith('server/') || logicalPath.startsWith('worker/')) && COMPILED_MODULE_PATTERN.test(logicalPath) && !isEffectBffWorkerArtifact(logicalPath));
|
|
549
|
+
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);
|
|
550
|
+
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.');
|
|
482
551
|
for (const [surface, paths] of [
|
|
483
552
|
[
|
|
484
553
|
'UI/client',
|
|
@@ -486,27 +555,32 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
|
|
|
486
555
|
],
|
|
487
556
|
[
|
|
488
557
|
'Cloudflare SSR',
|
|
489
|
-
ssrPaths
|
|
558
|
+
apiOnly ? [] : ssrPaths
|
|
490
559
|
],
|
|
491
560
|
[
|
|
492
561
|
'Cloudflare API/backend',
|
|
493
562
|
apiBackendPaths
|
|
494
563
|
]
|
|
495
|
-
])if (0 === paths.length) throw new Error(`[ultramodern-release-envelope] final Cloudflare staging has no ${surface} artifacts.`);
|
|
564
|
+
])if (0 === paths.length && (!apiOnly || 'Cloudflare API/backend' === surface)) throw new Error(`[ultramodern-release-envelope] final Cloudflare staging has no ${surface} artifacts.`);
|
|
496
565
|
for (const logicalPath of [
|
|
497
566
|
backendManifestPath,
|
|
498
567
|
backendContainerPath
|
|
499
568
|
])if (!files.includes(logicalPath)) throw new Error(`[ultramodern-release-envelope] final Cloudflare staging is missing ${logicalPath}.`);
|
|
500
|
-
const clientExecutionPaths = await manifestReferencedClientModules(outputDirectory, files, 'public/mf-manifest.json');
|
|
501
|
-
const ssrExecutionPaths = await routeReferencedSsrModules(outputDirectory, files, 'cloudflare', 'server/route.json');
|
|
502
|
-
if (0 === clientExecutionPaths.length || 0 === ssrExecutionPaths.length) throw new Error('[ultramodern-release-envelope] final Cloudflare manifests must reference emitted UI/client and SSR execution modules.');
|
|
569
|
+
const clientExecutionPaths = apiOnly ? [] : await manifestReferencedClientModules(outputDirectory, files, 'public/mf-manifest.json');
|
|
570
|
+
const ssrExecutionPaths = apiOnly ? [] : await routeReferencedSsrModules(outputDirectory, files, 'cloudflare', 'server/route.json');
|
|
571
|
+
if (!apiOnly && (0 === clientExecutionPaths.length || 0 === ssrExecutionPaths.length)) throw new Error('[ultramodern-release-envelope] final Cloudflare manifests must reference emitted UI/client and SSR execution modules.');
|
|
503
572
|
const runtimeByPath = new Map();
|
|
504
573
|
for (const logicalPath of files)runtimeByPath.set(logicalPath, 'cloudflare-deployment');
|
|
505
574
|
const add = (paths, runtime)=>{
|
|
506
575
|
for (const logicalPath of paths)runtimeByPath.set(logicalPath, runtime);
|
|
507
576
|
};
|
|
508
577
|
add(uiClientPaths, 'browser');
|
|
509
|
-
add(
|
|
578
|
+
add([
|
|
579
|
+
...declaredPublicAssetPaths
|
|
580
|
+
], DECLARED_PUBLIC_ASSET_RUNTIME);
|
|
581
|
+
if (files.includes(CRAWLER_POLICY_PATH)) runtimeByPath.set(CRAWLER_POLICY_PATH, 'crawler-policy');
|
|
582
|
+
add(files.filter(isPublicMetadataPath), 'public-metadata');
|
|
583
|
+
add(apiOnly ? [] : ssrPaths, 'workerd');
|
|
510
584
|
add(apiBackendPaths, 'workerd-effect');
|
|
511
585
|
add([
|
|
512
586
|
backendManifestPath
|
|
@@ -520,7 +594,7 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
|
|
|
520
594
|
backendManifestPath,
|
|
521
595
|
backendContainerPath
|
|
522
596
|
],
|
|
523
|
-
ssr: [
|
|
597
|
+
ssr: apiOnly ? [] : [
|
|
524
598
|
...new Set([
|
|
525
599
|
...ssrExecutionPaths,
|
|
526
600
|
...ssrPaths.filter((logicalPath)=>logicalPath.startsWith('worker/'))
|
|
@@ -539,7 +613,7 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
|
|
|
539
613
|
uiClient: [
|
|
540
614
|
...uiClientPaths
|
|
541
615
|
].sort((left, right)=>left.localeCompare(right)),
|
|
542
|
-
ssr: [
|
|
616
|
+
ssr: apiOnly ? [] : [
|
|
543
617
|
...ssrPaths
|
|
544
618
|
].sort((left, right)=>left.localeCompare(right)),
|
|
545
619
|
apiBackend: [
|
|
@@ -552,10 +626,10 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
|
|
|
552
626
|
}
|
|
553
627
|
};
|
|
554
628
|
};
|
|
555
|
-
const emitCloudflareStagedReleaseEnvelope = async ({ distDirectory, outputDirectory })=>{
|
|
629
|
+
const emitCloudflareStagedReleaseEnvelope = async ({ declaredPublicAssets = [], distDirectory, outputDirectory })=>{
|
|
556
630
|
const source = await verifyBuildOutputReleaseEnvelope(distDirectory, 'cloudflare');
|
|
557
631
|
if (!source) return;
|
|
558
|
-
const { artifacts, surfaces } = await createCloudflareStagedReleaseArtifactInputs(outputDirectory, source.identity);
|
|
632
|
+
const { artifacts, surfaces } = await createCloudflareStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length, declaredPublicAssets);
|
|
559
633
|
const staged = await createMicroVerticalReleaseEnvelope({
|
|
560
634
|
artifactRoot: outputDirectory,
|
|
561
635
|
target: 'cloudflare',
|
|
@@ -185,7 +185,7 @@ const assertUniqueSortedArtifacts = (artifacts, location)=>{
|
|
|
185
185
|
if (paths.some((logicalPath, index)=>logicalPath !== sorted[index])) throw new Error(`${location} must be sorted by logicalPath.`);
|
|
186
186
|
};
|
|
187
187
|
const assertSurfacePaths = (value, location)=>{
|
|
188
|
-
if (!Array.isArray(value)
|
|
188
|
+
if (!Array.isArray(value)) throw new Error(`${location} must be an array of artifact paths.`);
|
|
189
189
|
const paths = value.map((item, index)=>assertNormalizedLogicalPath(item, `${location}[${index}]`));
|
|
190
190
|
const sorted = [
|
|
191
191
|
...paths
|
|
@@ -207,10 +207,16 @@ const assertSurfaces = (value)=>{
|
|
|
207
207
|
'manifest',
|
|
208
208
|
'container'
|
|
209
209
|
], 'surfaces.backendFederation');
|
|
210
|
+
const uiClient = assertSurfacePaths(surfaces.uiClient, 'surfaces.uiClient');
|
|
211
|
+
const ssr = assertSurfacePaths(surfaces.ssr, 'surfaces.ssr');
|
|
212
|
+
const apiBackend = assertSurfacePaths(surfaces.apiBackend, 'surfaces.apiBackend');
|
|
213
|
+
if (uiClient.length > 0 && 0 === ssr.length) throw new Error('surfaces.ssr must contain at least one artifact path when UI/client is declared.');
|
|
214
|
+
if (ssr.length > 0 && 0 === uiClient.length) throw new Error('surfaces.uiClient must contain at least one artifact path when SSR is declared.');
|
|
215
|
+
if (0 === apiBackend.length) throw new Error('surfaces.apiBackend must contain at least one artifact path.');
|
|
210
216
|
return {
|
|
211
|
-
uiClient
|
|
212
|
-
ssr
|
|
213
|
-
apiBackend
|
|
217
|
+
uiClient,
|
|
218
|
+
ssr,
|
|
219
|
+
apiBackend,
|
|
214
220
|
backendFederation: {
|
|
215
221
|
manifest: assertNormalizedLogicalPath(backendFederation.manifest, 'surfaces.backendFederation.manifest'),
|
|
216
222
|
container: assertNormalizedLogicalPath(backendFederation.container, 'surfaces.backendFederation.container')
|
|
@@ -1,48 +1,53 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import node_path from "node:path";
|
|
3
|
+
import { NODE_PUBLIC_ASSET_SCOPE, normalizeDeclaredPublicAssets, resolveAddedDeclaredPublicAssetPaths } from "../deploy-output/public-assets.mjs";
|
|
4
|
+
import { getDeployingTarget } from "../deploy-output/target.mjs";
|
|
3
5
|
import { emitFrameworkMicroVerticalReleaseEnvelope, emitNodeStagedReleaseEnvelope, verifyBuildOutputReleaseEnvelope, verifyNodeReleaseEnvelopeStaging } from "./framework-output.mjs";
|
|
4
|
-
const
|
|
5
|
-
const { metaName } = api.getAppContext();
|
|
6
|
-
const config = api.getNormalizedConfig();
|
|
7
|
-
if ('modern-js' !== metaName && !config.deploy?.target && !process.env.MODERNJS_DEPLOY) return;
|
|
8
|
-
return resolveDeployTarget(config);
|
|
9
|
-
};
|
|
10
|
-
const createUltramodernReleaseEnvelopePlugin = ({ resolveDeployTarget })=>({
|
|
6
|
+
const createUltramodernReleaseEnvelopePlugin = ()=>({
|
|
11
7
|
name: '@modern-js/ultramodern-release-envelope',
|
|
12
8
|
pre: [
|
|
13
9
|
'@modern-js/backend-federation-build',
|
|
14
|
-
'@modern-js/plugin-bff'
|
|
10
|
+
'@modern-js/plugin-bff',
|
|
11
|
+
'@modern-js/deploy-output-aliases',
|
|
12
|
+
'@modern-js/deploy-output-public-assets'
|
|
15
13
|
],
|
|
16
14
|
post: [
|
|
17
15
|
'@modern-js/plugin-deploy'
|
|
18
16
|
],
|
|
19
17
|
setup (api) {
|
|
20
|
-
const emitBuildEnvelope = async (target)=>{
|
|
18
|
+
const emitBuildEnvelope = async (target, requirePromotable = true)=>{
|
|
21
19
|
const { apiOnly, distDirectory } = api.getAppContext();
|
|
22
20
|
await emitFrameworkMicroVerticalReleaseEnvelope({
|
|
23
21
|
apiOnly,
|
|
24
22
|
distDirectory,
|
|
23
|
+
requirePromotable,
|
|
25
24
|
target
|
|
26
25
|
});
|
|
27
26
|
};
|
|
28
27
|
api.onAfterBuild(async ()=>{
|
|
29
|
-
const configuredTarget =
|
|
28
|
+
const configuredTarget = api.getAppContext().deployTarget?.target;
|
|
30
29
|
if ('node' !== configuredTarget && 'cloudflare' !== configuredTarget) return;
|
|
31
|
-
await emitBuildEnvelope(configuredTarget);
|
|
30
|
+
await emitBuildEnvelope(configuredTarget, false);
|
|
32
31
|
});
|
|
33
32
|
api.onBeforeDeploy(async ()=>{
|
|
34
|
-
const configuredTarget =
|
|
33
|
+
const configuredTarget = getDeployingTarget(api.getAppContext());
|
|
35
34
|
if ('node' !== configuredTarget) return;
|
|
36
35
|
await emitBuildEnvelope(configuredTarget);
|
|
37
36
|
const { distDirectory } = api.getAppContext();
|
|
38
37
|
await verifyBuildOutputReleaseEnvelope(distDirectory, configuredTarget);
|
|
39
38
|
});
|
|
40
39
|
api.onAfterDeploy(async ()=>{
|
|
41
|
-
const configuredTarget =
|
|
40
|
+
const configuredTarget = getDeployingTarget(api.getAppContext());
|
|
42
41
|
if ('node' !== configuredTarget) return;
|
|
43
42
|
const { appDirectory, distDirectory } = api.getAppContext();
|
|
44
43
|
const outputDirectory = node_path.join(appDirectory, '.output');
|
|
45
44
|
const releaseEnvelope = await emitNodeStagedReleaseEnvelope({
|
|
45
|
+
declaredPublicAssets: await resolveAddedDeclaredPublicAssetPaths({
|
|
46
|
+
appDirectory,
|
|
47
|
+
assets: normalizeDeclaredPublicAssets(api.getNormalizedConfig().deploy?.node?.publicAssets, NODE_PUBLIC_ASSET_SCOPE),
|
|
48
|
+
generatedRoot: distDirectory,
|
|
49
|
+
scope: NODE_PUBLIC_ASSET_SCOPE
|
|
50
|
+
}),
|
|
46
51
|
distDirectory,
|
|
47
52
|
outputDirectory
|
|
48
53
|
});
|
|
@@ -3,14 +3,13 @@ import { execFileSync } from "node:child_process";
|
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
const BUILD_MARKER_NAMESPACE = 'ultramodern-delivery-unit-release-build-marker:v1';
|
|
5
5
|
const PROMOTABLE_SOURCE_REVISION_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u;
|
|
6
|
+
const workspaceGitEnv = ()=>Object.fromEntries(Object.entries(process.env).filter(([name])=>!name.toUpperCase().startsWith('GIT_')));
|
|
6
7
|
const gitOutput = (workspaceRoot, args)=>{
|
|
7
8
|
try {
|
|
8
|
-
return execFileSync('git',
|
|
9
|
-
|
|
10
|
-
workspaceRoot,
|
|
11
|
-
...args
|
|
12
|
-
], {
|
|
9
|
+
return execFileSync('git', args, {
|
|
10
|
+
cwd: workspaceRoot,
|
|
13
11
|
encoding: 'utf8',
|
|
12
|
+
env: workspaceGitEnv(),
|
|
14
13
|
stdio: [
|
|
15
14
|
'ignore',
|
|
16
15
|
'pipe',
|
|
@@ -44,7 +43,7 @@ const resolveUltramodernSourceRevision = (workspaceRoot = process.cwd(), configu
|
|
|
44
43
|
]);
|
|
45
44
|
const status = gitOutput(gitRoot, [
|
|
46
45
|
'status',
|
|
47
|
-
'--porcelain
|
|
46
|
+
'--porcelain',
|
|
48
47
|
'--untracked-files=all'
|
|
49
48
|
]);
|
|
50
49
|
if (!revision || void 0 === status || status.length > 0) return 'workspace';
|