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