@bleedingdev/modern-js-create 3.5.0-ultramodern.3 → 3.5.0-ultramodern.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +21 -17
  2. package/dist/cjs/ultramodern-tooling/commands.cjs +772 -75
  3. package/dist/cjs/ultramodern-tooling/config.cjs +149 -8
  4. package/dist/cjs/ultramodern-workspace/add-vertical.cjs +25 -0
  5. package/dist/cjs/ultramodern-workspace/api.cjs +44 -0
  6. package/dist/cjs/ultramodern-workspace/app-files.cjs +8 -11
  7. package/dist/cjs/ultramodern-workspace/backend-federation.cjs +282 -0
  8. package/dist/cjs/ultramodern-workspace/contracts.cjs +24 -8
  9. package/dist/cjs/ultramodern-workspace/delivery-unit-sync.cjs +157 -0
  10. package/dist/cjs/ultramodern-workspace/delivery-unit.cjs +88 -0
  11. package/dist/cjs/ultramodern-workspace/descriptors.cjs +24 -0
  12. package/dist/cjs/ultramodern-workspace/module-federation.cjs +161 -50
  13. package/dist/cjs/ultramodern-workspace/package-json.cjs +26 -43
  14. package/dist/cjs/ultramodern-workspace/pnpm-workspace-policy-plan.cjs +166 -0
  15. package/dist/cjs/ultramodern-workspace/tooling-command-catalog.cjs +153 -0
  16. package/dist/cjs/ultramodern-workspace/versions.cjs +8 -3
  17. package/dist/cjs/ultramodern-workspace/workspace-script-plan.cjs +175 -0
  18. package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +104 -63
  19. package/dist/cjs/ultramodern-workspace/workspace-validation-contract.cjs +97 -0
  20. package/dist/cjs/ultramodern-workspace/write-workspace.cjs +11 -3
  21. package/dist/cjs/ultramodern-workspace/zerops.cjs +100 -0
  22. package/dist/esm/ultramodern-tooling/commands.js +775 -78
  23. package/dist/esm/ultramodern-tooling/config.js +144 -9
  24. package/dist/esm/ultramodern-workspace/add-vertical.js +26 -1
  25. package/dist/esm/ultramodern-workspace/api.js +43 -2
  26. package/dist/esm/ultramodern-workspace/app-files.js +8 -11
  27. package/dist/esm/ultramodern-workspace/backend-federation.js +217 -0
  28. package/dist/esm/ultramodern-workspace/contracts.js +24 -8
  29. package/dist/esm/ultramodern-workspace/delivery-unit-sync.js +108 -0
  30. package/dist/esm/ultramodern-workspace/delivery-unit.js +31 -0
  31. package/dist/esm/ultramodern-workspace/descriptors.js +13 -1
  32. package/dist/esm/ultramodern-workspace/module-federation.js +160 -42
  33. package/dist/esm/ultramodern-workspace/package-json.js +16 -40
  34. package/dist/esm/ultramodern-workspace/pnpm-workspace-policy-plan.js +113 -0
  35. package/dist/esm/ultramodern-workspace/tooling-command-catalog.js +100 -0
  36. package/dist/esm/ultramodern-workspace/versions.js +6 -4
  37. package/dist/esm/ultramodern-workspace/workspace-script-plan.js +110 -0
  38. package/dist/esm/ultramodern-workspace/workspace-scripts.js +88 -65
  39. package/dist/esm/ultramodern-workspace/workspace-validation-contract.js +59 -0
  40. package/dist/esm/ultramodern-workspace/write-workspace.js +14 -6
  41. package/dist/esm/ultramodern-workspace/zerops.js +62 -0
  42. package/dist/esm-node/ultramodern-tooling/commands.js +775 -78
  43. package/dist/esm-node/ultramodern-tooling/config.js +144 -9
  44. package/dist/esm-node/ultramodern-workspace/add-vertical.js +26 -1
  45. package/dist/esm-node/ultramodern-workspace/api.js +43 -2
  46. package/dist/esm-node/ultramodern-workspace/app-files.js +8 -11
  47. package/dist/esm-node/ultramodern-workspace/backend-federation.js +218 -0
  48. package/dist/esm-node/ultramodern-workspace/contracts.js +24 -8
  49. package/dist/esm-node/ultramodern-workspace/delivery-unit-sync.js +109 -0
  50. package/dist/esm-node/ultramodern-workspace/delivery-unit.js +32 -0
  51. package/dist/esm-node/ultramodern-workspace/descriptors.js +13 -1
  52. package/dist/esm-node/ultramodern-workspace/module-federation.js +160 -42
  53. package/dist/esm-node/ultramodern-workspace/package-json.js +16 -40
  54. package/dist/esm-node/ultramodern-workspace/pnpm-workspace-policy-plan.js +114 -0
  55. package/dist/esm-node/ultramodern-workspace/tooling-command-catalog.js +101 -0
  56. package/dist/esm-node/ultramodern-workspace/versions.js +6 -4
  57. package/dist/esm-node/ultramodern-workspace/workspace-script-plan.js +111 -0
  58. package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +88 -65
  59. package/dist/esm-node/ultramodern-workspace/workspace-validation-contract.js +60 -0
  60. package/dist/esm-node/ultramodern-workspace/write-workspace.js +14 -6
  61. package/dist/esm-node/ultramodern-workspace/zerops.js +63 -0
  62. package/dist/types/ultramodern-tooling/config.d.ts +6 -0
  63. package/dist/types/ultramodern-workspace/api.d.ts +1 -0
  64. package/dist/types/ultramodern-workspace/backend-federation.d.ts +11 -0
  65. package/dist/types/ultramodern-workspace/contracts.d.ts +1 -1
  66. package/dist/types/ultramodern-workspace/delivery-unit-sync.d.ts +6 -0
  67. package/dist/types/ultramodern-workspace/delivery-unit.d.ts +27 -0
  68. package/dist/types/ultramodern-workspace/descriptors.d.ts +4 -0
  69. package/dist/types/ultramodern-workspace/module-federation.d.ts +4 -9
  70. package/dist/types/ultramodern-workspace/package-json.d.ts +0 -1
  71. package/dist/types/ultramodern-workspace/pnpm-workspace-policy-plan.d.ts +39 -0
  72. package/dist/types/ultramodern-workspace/tooling-command-catalog.d.ts +18 -0
  73. package/dist/types/ultramodern-workspace/versions.d.ts +5 -3
  74. package/dist/types/ultramodern-workspace/workspace-script-plan.d.ts +72 -0
  75. package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +13 -0
  76. package/dist/types/ultramodern-workspace/workspace-validation-contract.d.ts +42 -0
  77. package/dist/types/ultramodern-workspace/zerops.d.ts +2 -0
  78. package/package.json +3 -3
  79. package/template-workspace/.gitignore.handlebars +4 -0
  80. package/template-workspace/README.md.handlebars +18 -15
  81. package/template-workspace/patches/@module-federation__bridge-react@2.6.0.patch +140 -0
  82. package/template-workspace/patches/@module-federation__modern-js-v3@2.6.0.patch +57 -0
  83. package/template-workspace/patches/drizzle-orm-ts7-strict-declarations.patch +452 -0
  84. package/template-workspace/patches/effect-schema-error-type-id.patch +18 -0
  85. package/template-workspace/pnpm-workspace.yaml.handlebars +8 -0
  86. package/template-workspace/scripts/bootstrap-agent-skills.mjs +11 -3
  87. package/templates/workspace-scripts/check-ultramodern-api-boundaries.mts +40 -2
  88. package/templates/workspace-scripts/generate-node-backend-federation.mjs +270 -0
  89. package/templates/workspace-scripts/materialize-zerops-runtime.mjs +448 -0
  90. package/templates/workspace-scripts/proof-cloudflare-version.mjs +237 -9
  91. package/templates/workspace-scripts/proof-node-backend-federation.mjs +729 -0
  92. package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +188 -2
  93. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +434 -18
@@ -0,0 +1,108 @@
1
+ import node_fs from "node:fs";
2
+ import node_path from "node:path";
3
+ import { readUltramodernConfig, workspaceAppsFromToolingConfig } from "../ultramodern-tooling/config.js";
4
+ import { createBackendFederationContract } from "./backend-federation.js";
5
+ import { createDeliveryUnitRecord, deliveryUnitContractBlock } from "./delivery-unit.js";
6
+ import { ULTRAMODERN_CONFIG_PATH } from "./descriptors.js";
7
+ import { createUltramodernBuildModule } from "./module-federation.js";
8
+ const REFERENCE_TOPOLOGY_PATH = 'topology/reference-topology.json';
9
+ function readOption(args, name) {
10
+ const prefix = `${name}=`;
11
+ const inline = args.find((arg)=>arg.startsWith(prefix));
12
+ if (inline) {
13
+ const value = inline.slice(prefix.length);
14
+ if (!value) throw new Error(`${name} needs a value.`);
15
+ return value;
16
+ }
17
+ const index = args.indexOf(name);
18
+ if (-1 === index) return;
19
+ const value = args[index + 1];
20
+ if (!value || value.startsWith('--')) throw new Error(`${name} needs a value.`);
21
+ return value;
22
+ }
23
+ function isPlainObject(value) {
24
+ return 'object' == typeof value && null !== value && !Array.isArray(value);
25
+ }
26
+ function writeTextIfChanged(absolutePath, content) {
27
+ if (node_fs.existsSync(absolutePath) && node_fs.readFileSync(absolutePath, 'utf-8') === content) return false;
28
+ node_fs.mkdirSync(node_path.dirname(absolutePath), {
29
+ recursive: true
30
+ });
31
+ node_fs.writeFileSync(absolutePath, content, 'utf-8');
32
+ return true;
33
+ }
34
+ function writeJsonIfChanged(absolutePath, value) {
35
+ return writeTextIfChanged(absolutePath, `${JSON.stringify(value, null, 2)}\n`);
36
+ }
37
+ function stampDeliveryUnitIdentity(entry, scope, app) {
38
+ const block = deliveryUnitContractBlock(createDeliveryUnitRecord(scope, app));
39
+ entry.deliveryUnit = block;
40
+ if (isPlainObject(entry.backendFederation)) {
41
+ entry.backendFederation.deliveryUnit = block;
42
+ if (!isPlainObject(entry.backendFederation.versionBoundary)) entry.backendFederation.versionBoundary = {};
43
+ entry.backendFederation.versionBoundary.identityRoot = 'deliveryUnit';
44
+ } else entry.backendFederation = createBackendFederationContract(scope, app);
45
+ }
46
+ function runSyncDeliveryUnit(args, context) {
47
+ if (args.includes('--help') || args.includes('-h')) {
48
+ process.stdout.write(`Usage:
49
+ modern-js-create ultramodern sync-delivery-unit [--workspace <dir>]
50
+
51
+ Backfills the per-vertical delivery-unit identity blocks required by
52
+ \`ultramodern validate\` onto an existing generated workspace, in place and
53
+ through framework tooling only. For each full-stack vertical it writes the
54
+ delivery-unit block into .modernjs/ultramodern.json topology.apps[] and
55
+ topology/reference-topology.json verticals[] (plus backendFederation.deliveryUnit
56
+ and backendFederation.versionBoundary.identityRoot) and regenerates
57
+ verticals/<id>/shared/ultramodern-build.ts. Idempotent: a second run writes
58
+ nothing.
59
+ `);
60
+ return 0;
61
+ }
62
+ const workspaceOverride = readOption(args, '--workspace');
63
+ const workspaceRoot = workspaceOverride ? node_path.resolve(context.invocationCwd, workspaceOverride) : context.workspaceRoot;
64
+ const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
65
+ if (!node_fs.existsSync(compactPath)) throw new Error(`Missing ${ULTRAMODERN_CONFIG_PATH}. sync-delivery-unit needs the compact UltraModern config; run \`modern-js-create ultramodern migrate-strict-effect\` first.`);
66
+ const config = readUltramodernConfig(workspaceRoot);
67
+ const scope = config.workspace.packageScope;
68
+ const apiApps = workspaceAppsFromToolingConfig(config).filter((app)=>Boolean(app.api));
69
+ const written = [];
70
+ const unchanged = [];
71
+ const track = (relativePath, changed)=>{
72
+ (changed ? written : unchanged).push(relativePath);
73
+ };
74
+ const appById = new Map(apiApps.map((app)=>[
75
+ app.id,
76
+ app
77
+ ]));
78
+ const compact = JSON.parse(node_fs.readFileSync(compactPath, 'utf-8'));
79
+ if (isPlainObject(compact) && Array.isArray(compact.topology?.apps)) for (const entry of compact.topology.apps){
80
+ if (!isPlainObject(entry)) continue;
81
+ const app = appById.get(String(entry.id));
82
+ if (app) stampDeliveryUnitIdentity(entry, scope, app);
83
+ }
84
+ track(ULTRAMODERN_CONFIG_PATH, writeJsonIfChanged(compactPath, compact));
85
+ const topologyPath = node_path.join(workspaceRoot, REFERENCE_TOPOLOGY_PATH);
86
+ if (node_fs.existsSync(topologyPath)) {
87
+ const topology = JSON.parse(node_fs.readFileSync(topologyPath, 'utf-8'));
88
+ if (isPlainObject(topology) && Array.isArray(topology.verticals)) for (const entry of topology.verticals){
89
+ if (!isPlainObject(entry)) continue;
90
+ const app = appById.get(String(entry.id));
91
+ if (app) stampDeliveryUnitIdentity(entry, scope, app);
92
+ }
93
+ track(REFERENCE_TOPOLOGY_PATH, writeJsonIfChanged(topologyPath, topology));
94
+ }
95
+ for (const app of apiApps){
96
+ const relativePath = node_path.join(app.directory, 'shared/ultramodern-build.ts');
97
+ const changed = writeTextIfChanged(node_path.join(workspaceRoot, relativePath), createUltramodernBuildModule(scope, app));
98
+ track(relativePath, changed);
99
+ }
100
+ if (0 === written.length) process.stdout.write('[ultramodern] sync-delivery-unit: already in sync; no files written.\n');
101
+ else {
102
+ process.stdout.write(`[ultramodern] sync-delivery-unit: wrote ${written.length} file(s):\n`);
103
+ for (const relativePath of written)process.stdout.write(` wrote ${relativePath}\n`);
104
+ for (const relativePath of unchanged)process.stdout.write(` in-sync ${relativePath}\n`);
105
+ }
106
+ return 0;
107
+ }
108
+ export { runSyncDeliveryUnit };
@@ -0,0 +1,31 @@
1
+ import node_crypto from "node:crypto";
2
+ import { packageName } from "./naming.js";
3
+ const DELIVERY_UNIT_SCHEMA_VERSION = 1;
4
+ function createBuildMarker(scope, app) {
5
+ return node_crypto.createHash('sha256').update(`${scope}:${app.packageSuffix}:${app.id}:0.1.0`).digest('hex').slice(0, 16);
6
+ }
7
+ function deliveryUnitContractBlock(record) {
8
+ return {
9
+ schemaVersion: record.schemaVersion,
10
+ kind: record.kind,
11
+ unitId: record.unitId,
12
+ packageName: record.packageName,
13
+ version: record.version,
14
+ buildMarker: record.buildMarker,
15
+ sourceRevision: record.sourceRevision
16
+ };
17
+ }
18
+ function createDeliveryUnitRecord(scope, app) {
19
+ return {
20
+ schemaVersion: DELIVERY_UNIT_SCHEMA_VERSION,
21
+ kind: 'microvertical-delivery-unit',
22
+ unitId: `${scope}/${app.domain ?? app.id}`,
23
+ appId: app.id,
24
+ packageName: packageName(scope, app.packageSuffix),
25
+ version: '0.1.0',
26
+ sourceRevision: 'workspace',
27
+ buildMarker: createBuildMarker(scope, app),
28
+ deployProfile: 'cloudflare-ssr-mf-effect-v1'
29
+ };
30
+ }
31
+ export { DELIVERY_UNIT_SCHEMA_VERSION, createBuildMarker, createDeliveryUnitRecord, deliveryUnitContractBlock };
@@ -112,6 +112,18 @@ function resolveRemoteRefs(app, remotes = []) {
112
112
  function createRemoteManifestEnv(remote) {
113
113
  return `VERTICAL_${toEnvSegment(remote.domain ?? remote.id)}_MF_MANIFEST`;
114
114
  }
115
+ function createBackendFederationManifestEnv(remote) {
116
+ return `VERTICAL_${toEnvSegment(remote.domain ?? remote.id)}_BACKEND_MF_MANIFEST`;
117
+ }
118
+ function createBackendFederationName(app) {
119
+ return `${app.mfName}Backend`;
120
+ }
121
+ function createBackendFederationManifestUrl(app) {
122
+ return `http://localhost:${app.port}/backend-mf-manifest.json`;
123
+ }
124
+ function createBackendFederationContainerEntry(app) {
125
+ return `http://localhost:${app.port}/backendRemoteEntry.mjs`;
126
+ }
115
127
  function createModuleFederationRemoteContracts(app, remotes = []) {
116
128
  return resolveRemoteRefs(app, remotes).map((remote)=>({
117
129
  id: remote.id,
@@ -130,4 +142,4 @@ function createCloudflarePublicUrlEnv(app) {
130
142
  function appI18nNamespace(app) {
131
143
  return 'shell' === app.kind ? 'shell' : app.domain ?? app.id;
132
144
  }
133
- export { ULTRAMODERN_CONFIG_PATH, appHasApi, appI18nNamespace, createCloudflarePublicUrlEnv, createCloudflareWorkerName, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, remoteDependencyAlias, resolveApiPrefix, resolveApiStem, resolveRemoteRefs, sharedPackages, shellApp, verticalApiApps, zephyrRemoteDependency };
145
+ export { ULTRAMODERN_CONFIG_PATH, appHasApi, appI18nNamespace, createBackendFederationContainerEntry, createBackendFederationManifestEnv, createBackendFederationManifestUrl, createBackendFederationName, createCloudflarePublicUrlEnv, createCloudflareWorkerName, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, remoteDependencyAlias, resolveApiPrefix, resolveApiStem, resolveRemoteRefs, sharedPackages, shellApp, verticalApiApps, zephyrRemoteDependency };
@@ -1,10 +1,11 @@
1
- import node_crypto from "node:crypto";
2
- import { appHasApi, createCloudflarePublicUrlEnv, createCloudflareWorkerName, createRemoteManifestEnv, remoteDependencyAlias, resolveApiPrefix, resolveRemoteRefs, shellApp } from "./descriptors.js";
3
- import { createRspackChunkLoadingGlobal, createRspackUniqueName, packageName } from "./naming.js";
1
+ import { createDispatchWorkerNameEnv, createWorkerBindingEnv, createWorkerBindingName } from "./backend-federation.js";
2
+ import { createBuildMarker, createDeliveryUnitRecord } from "./delivery-unit.js";
3
+ import { appHasApi, createBackendFederationName, createCloudflarePublicUrlEnv, createCloudflareWorkerName, createRemoteManifestEnv, remoteDependencyAlias, resolveApiPrefix, resolveRemoteRefs, shellApp } from "./descriptors.js";
4
+ import { createRspackChunkLoadingGlobal, createRspackUniqueName } from "./naming.js";
4
5
  import { createCloudflareSecurityContract, formatTsJsonValue } from "./policy.js";
5
6
  import { sortJsonValue } from "./types.js";
6
7
  import { CLOUDFLARE_COMPATIBILITY_DATE } from "./versions.js";
7
- function createAppModernConfig(scope, app) {
8
+ function createAppModernConfig(scope, app, remotes = []) {
8
9
  const bffImport = appHasApi(app) ? "import { bffPlugin } from '@modern-js/plugin-bff';\n" : '';
9
10
  const bffConfig = appHasApi(app) ? ` bff: {
10
11
  effect: {
@@ -19,12 +20,30 @@ function createAppModernConfig(scope, app) {
19
20
  },
20
21
  ` : '';
21
22
  const bffPluginEntry = appHasApi(app) ? ' bffPlugin(),\n' : '';
23
+ const serviceBindings = 'shell' === app.kind ? remotes.filter(appHasApi) : [];
24
+ const serviceBindingsConfig = serviceBindings.length > 0 ? ` services: [
25
+ ${serviceBindings.map((service)=>` {
26
+ binding:
27
+ envValue('${createWorkerBindingEnv(service)}') ??
28
+ '${createWorkerBindingName(service)}',
29
+ prefix: '${resolveApiPrefix(service)}',
30
+ service:
31
+ envValue('${createDispatchWorkerNameEnv(service)}') ??
32
+ '${createCloudflareWorkerName(scope, service)}',
33
+ },`).join('\n')}
34
+ ],
35
+ ` : '';
22
36
  const defaultAssetPrefixSource = 'shell' === app.kind ? "const defaultAssetPrefix = '/';" : `const remoteAssetOrigin =
23
37
  configuredCloudflareUrl ||
24
38
  inferredCloudflareUrl ||
25
39
  (cloudflareDeployEnabled ? '/' : \`http://localhost:\${port}\`);
26
40
  const defaultRemoteAssetPrefix = \`\${remoteAssetOrigin.replace(/\\/+$/u, '')}/\`;
27
41
  const defaultAssetPrefix = defaultRemoteAssetPrefix;`;
42
+ const devAssetPrefixSource = 'shell' === app.kind ? ` // Keep shell dev assets origin-relative so the shell works through
43
+ // tunnels and local previews without rewriting its own chunks.
44
+ assetPrefix: '/',` : ` // Remote dev manifests must publish an absolute publicPath so host
45
+ // shells load remoteEntry.js and exposed chunks from this dev server.
46
+ assetPrefix,`;
28
47
  return `// @effect-diagnostics processEnv:off
29
48
  import {
30
49
  appTools,
@@ -43,6 +62,22 @@ const zephyrEnabled = process.env['ULTRAMODERN_ZEPHYR'] !== 'false';
43
62
  const cloudflareDeployEnabled =
44
63
  process.env['MODERNJS_DEPLOY'] === 'cloudflare';
45
64
 
65
+ const parsedZephyrTimeoutMs = Number.parseInt(
66
+ process.env['ULTRAMODERN_ZEPHYR_TIMEOUT_MS'] ?? '',
67
+ 10,
68
+ );
69
+ const zephyrTimeoutMs =
70
+ Number.isFinite(parsedZephyrTimeoutMs) && parsedZephyrTimeoutMs > 0
71
+ ? parsedZephyrTimeoutMs
72
+ : 45000;
73
+
74
+ const zephyrWarn = (error: unknown) => {
75
+ const message = error instanceof Error ? error.message : String(error);
76
+ console.warn(
77
+ \`[ultramodern] zephyr-rspack-plugin failed; continuing without Zephyr (set ULTRAMODERN_ZEPHYR=false to disable it): \${message}\`,
78
+ );
79
+ };
80
+
46
81
  const zephyrRspackPlugin = () => ({
47
82
  name: 'ultramodern-zephyr-rspack-plugin',
48
83
  pre: ['@modern-js/plugin-module-federation-config'],
@@ -56,7 +91,34 @@ const zephyrRspackPlugin = () => ({
56
91
  if (!zephyrEnabled) {
57
92
  return;
58
93
  }
59
- api.modifyRspackConfig(config => withZephyrRspack()(config));
94
+ api.modifyRspackConfig(config => {
95
+ try {
96
+ // Zephyr can not only throw/reject but also hang on a stalled network
97
+ // call, wedging the whole build. Race it against a watchdog so a hang
98
+ // degrades to an unmodified config instead of blocking indefinitely.
99
+ const zephyrConfig = Promise.resolve(withZephyrRspack()(config)).catch(
100
+ error => {
101
+ zephyrWarn(error);
102
+ return config;
103
+ },
104
+ );
105
+ const watchdog = new Promise<ZephyrRspackConfig>(resolve => {
106
+ const timer = setTimeout(() => {
107
+ zephyrWarn(
108
+ \`timed out after \${zephyrTimeoutMs}ms (override with ULTRAMODERN_ZEPHYR_TIMEOUT_MS)\`,
109
+ );
110
+ resolve(config);
111
+ }, zephyrTimeoutMs);
112
+ if (typeof timer.unref === 'function') {
113
+ timer.unref();
114
+ }
115
+ });
116
+ return Promise.race([zephyrConfig, watchdog]);
117
+ } catch (error) {
118
+ zephyrWarn(error);
119
+ return config;
120
+ }
121
+ });
60
122
  },
61
123
  });
62
124
 
@@ -93,8 +155,10 @@ ${defaultAssetPrefixSource}
93
155
  // load remoteEntry.js and exposed chunks from the remote origin, not the host.
94
156
  const assetPrefix =
95
157
  configuredModernAssetPrefix || configuredUltramodernAssetPrefix || defaultAssetPrefix;
96
- const buildCacheTarget = cloudflareDeployEnabled ? 'cloudflare' : 'web';
97
- const buildCacheDirectory = \`node_modules/.cache/rspack-\${appId}-\${buildCacheTarget}\`;
158
+ const buildTarget = cloudflareDeployEnabled ? 'cloudflare' : 'web';
159
+ const buildOutputRoot = cloudflareDeployEnabled ? 'dist-cloudflare' : 'dist';
160
+ const buildTempDirectory = \`node_modules/.modern-js-\${appId}-\${buildTarget}\`;
161
+ const buildCacheDirectory = \`node_modules/.cache/rspack-\${appId}-\${buildTarget}\`;
98
162
 
99
163
  if (
100
164
  cloudflareDeployEnabled &&
@@ -115,18 +179,16 @@ ${bffConfig} ...(cloudflareDeployEnabled
115
179
  ? {
116
180
  deploy: {
117
181
  worker: {
118
- compatibilityDate: '${CLOUDFLARE_COMPATIBILITY_DATE}',
119
- name: cloudflareWorkerName,
120
- security: ${formatTsJsonValue(sortJsonValue(createCloudflareSecurityContract()), 16)},
121
- ssr: true,
122
- },
182
+ compatibilityDate: '${CLOUDFLARE_COMPATIBILITY_DATE}',
183
+ name: cloudflareWorkerName,
184
+ security: ${formatTsJsonValue(sortJsonValue(createCloudflareSecurityContract()), 16)},
185
+ ${serviceBindingsConfig} ssr: true,
123
186
  },
187
+ },
124
188
  }
125
189
  : {}),
126
190
  dev: {
127
- // Keep dev assets origin-relative too; the default absolute
128
- // http://localhost:<port> prefix breaks pages served through tunnels.
129
- assetPrefix: '/',
191
+ ${devAssetPrefixSource}
130
192
  },
131
193
  html: {
132
194
  outputStructure: 'flat',
@@ -136,13 +198,15 @@ ${bffConfig} ...(cloudflareDeployEnabled
136
198
  disableTsChecker: false,
137
199
  distPath: {
138
200
  html: './',
201
+ root: buildOutputRoot,
139
202
  },
140
203
  polyfill: 'off',
141
204
  splitRouteChunks: true,
205
+ tempDir: buildTempDirectory,
142
206
  },
143
207
  performance: {
144
208
  buildCache: {
145
- cacheDigest: [appId, buildCacheTarget],
209
+ cacheDigest: [appId, buildTarget],
146
210
  cacheDirectory: buildCacheDirectory,
147
211
  },
148
212
  rsdoctor: {
@@ -339,6 +403,19 @@ ${remoteEntries}
339
403
  },
340
404
  `;
341
405
  }
406
+ function createModuleFederationDtsConfig(hasExposes) {
407
+ return hasExposes ? ` dts: {
408
+ displayErrorInTerminal: true,
409
+ generateTypes: {
410
+ compilerInstance: 'tsgo',
411
+ },
412
+ tsConfigPath: './tsconfig.mf-types.json',
413
+ },` : ` dts: {
414
+ consumeTypes: true,
415
+ generateTypes: false,
416
+ tsConfigPath: './tsconfig.mf-types.json',
417
+ },`;
418
+ }
342
419
  function createShellModuleFederationConfig(scope, remotes = []) {
343
420
  const shellHost = {
344
421
  ...shellApp,
@@ -367,13 +444,7 @@ const moduleFederationConfig: Parameters<
367
444
  dev: {
368
445
  disableDynamicRemoteTypeHints: true,
369
446
  },
370
- dts: {
371
- displayErrorInTerminal: true,
372
- generateTypes: {
373
- compilerInstance: 'tsgo',
374
- },
375
- tsConfigPath: './tsconfig.mf-types.json',
376
- },
447
+ ${createModuleFederationDtsConfig(false)}
377
448
  filename: 'remoteEntry.js',
378
449
  name: '${shellApp.mfName}',
379
450
  ${createModuleFederationRemotesConfig(scope, shellHost, remotes)}${createSharedModuleFederationConfig()},
@@ -383,25 +454,75 @@ ${createModuleFederationRemotesConfig(scope, shellHost, remotes)}${createSharedM
383
454
  export default moduleFederationConfig;
384
455
  `;
385
456
  }
386
- function createBuildMarker(scope, app) {
387
- return node_crypto.createHash('sha256').update(`${scope}:${app.packageSuffix}:${app.id}:0.1.0`).digest('hex').slice(0, 16);
457
+ function createBackendModuleFederationConfig(app) {
458
+ return `// @effect-diagnostics nodeBuiltinImport:off
459
+ import { createRequire } from 'node:module';
460
+ import { createModuleFederationConfig } from '@module-federation/modern-js-v3';
461
+ import { dependencies } from './package.json';
462
+
463
+ const require = createRequire(import.meta.url);
464
+ const bffVersion = (
465
+ require('@modern-js/plugin-bff/package.json') as { version: string }
466
+ ).version;
467
+ const effectVersion = (
468
+ require('effect/package.json') as { version: string }
469
+ ).version;
470
+
471
+ const moduleFederationConfig: Parameters<
472
+ typeof createModuleFederationConfig
473
+ >[0] = createModuleFederationConfig({
474
+ dts: false,
475
+ exposes: {
476
+ './effect-api': './api/effect-api.ts',
477
+ },
478
+ filename: 'backendRemoteEntry.mjs',
479
+ name: '${createBackendFederationName(app)}',
480
+ shared: {
481
+ '@modern-js/plugin-bff': {
482
+ requiredVersion: bffVersion,
483
+ singleton: true,
484
+ treeShaking: false,
485
+ },
486
+ '@module-federation/runtime': {
487
+ requiredVersion: dependencies['@module-federation/runtime'],
488
+ singleton: true,
489
+ treeShaking: false,
490
+ },
491
+ effect: {
492
+ requiredVersion: effectVersion,
493
+ singleton: true,
494
+ treeShaking: false,
495
+ },
496
+ },
497
+ treeShakingSharedExcludePlugins: ['RspackModuleFederationPlugin'],
498
+ });
499
+
500
+ export default moduleFederationConfig;
501
+ `;
388
502
  }
389
503
  function createUltramodernBuildModule(scope, app) {
390
- return `export const ultramodernVerticalIdentity = {
391
- appId: '${app.id}',
392
- build: '${createBuildMarker(scope, app)}',
393
- deployProfile: 'cloudflare-ssr-mf-effect-v1',
394
- packageName: '${packageName(scope, app.packageSuffix)}',
395
- version: '0.1.0',
504
+ const record = createDeliveryUnitRecord(scope, app);
505
+ return `export const ultramodernDeliveryUnit = {
506
+ appId: '${record.appId}',
507
+ build: '${record.buildMarker}',
508
+ deployProfile: '${record.deployProfile}',
509
+ kind: '${record.kind}',
510
+ packageName: '${record.packageName}',
511
+ schemaVersion: ${record.schemaVersion},
512
+ sourceRevision: '${record.sourceRevision}',
513
+ unitId: '${record.unitId}',
514
+ version: '${record.version}',
396
515
  } as const;
397
516
 
517
+ export const ultramodernVerticalIdentity = ultramodernDeliveryUnit;
518
+
398
519
  export const ultramodernUiMarker = {
399
- ...ultramodernVerticalIdentity,
520
+ ...ultramodernDeliveryUnit,
400
521
  surface: 'ui',
401
522
  } as const;
402
523
 
403
524
  export const ultramodernApiMarker = {
404
- ...ultramodernVerticalIdentity,
525
+ ...ultramodernDeliveryUnit,
405
526
  surface: 'api',
406
527
  } as const;
407
528
  `;
@@ -409,6 +530,7 @@ export const ultramodernApiMarker = {
409
530
  function createUltramodernBuildReexportModule() {
410
531
  return `export {
411
532
  ultramodernApiMarker,
533
+ ultramodernDeliveryUnit,
412
534
  ultramodernUiMarker,
413
535
  ultramodernVerticalIdentity,
414
536
  } from '../shared/ultramodern-build';
@@ -416,7 +538,9 @@ function createUltramodernBuildReexportModule() {
416
538
  }
417
539
  function createRemoteModuleFederationConfig(scope, app, remotes = []) {
418
540
  const exposes = formatTsObjectLiteral(app.exposes ?? {});
419
- return `// @effect-diagnostics nodeBuiltinImport:off
541
+ const hasExposes = Object.keys(app.exposes ?? {}).length > 0;
542
+ const hostOnlyMarker = hasExposes ? '' : '\n// ultramodern-mf: no-exposes';
543
+ return `// @effect-diagnostics nodeBuiltinImport:off${hostOnlyMarker}
420
544
  import { createRequire } from 'node:module';
421
545
  import { createModuleFederationConfig } from '@module-federation/modern-js-v3';
422
546
  import { dependencies } from './package.json';
@@ -438,13 +562,7 @@ const moduleFederationConfig: Parameters<
438
562
  dev: {
439
563
  disableDynamicRemoteTypeHints: true,
440
564
  },
441
- dts: {
442
- displayErrorInTerminal: true,
443
- generateTypes: {
444
- compilerInstance: 'tsgo',
445
- },
446
- tsConfigPath: './tsconfig.mf-types.json',
447
- },
565
+ ${createModuleFederationDtsConfig(hasExposes)}
448
566
  exposes: ${exposes},
449
567
  filename: 'remoteEntry.js',
450
568
  name: '${app.mfName}',
@@ -455,4 +573,4 @@ ${createModuleFederationRemotesConfig(scope, app, remotes)}${createSharedModuleF
455
573
  export default moduleFederationConfig;
456
574
  `;
457
575
  }
458
- export { createAppModernConfig, createBuildMarker, createModuleFederationRemoteUrlHelpers, createModuleFederationRemotesConfig, createRemoteModuleFederationConfig, createSharedModuleFederationConfig, createShellModuleFederationConfig, createUltramodernBuildModule, createUltramodernBuildReexportModule, formatTsObjectLiteral };
576
+ export { createAppModernConfig, createBackendModuleFederationConfig, createBuildMarker, createModuleFederationRemoteUrlHelpers, createModuleFederationRemotesConfig, createRemoteModuleFederationConfig, createSharedModuleFederationConfig, createShellModuleFederationConfig, createUltramodernBuildModule, createUltramodernBuildReexportModule, formatTsObjectLiteral };
@@ -1,10 +1,10 @@
1
+ import node_path from "node:path";
1
2
  import { WORKSPACE_PACKAGE_VERSION, modernPackageSpecifier } from "../ultramodern-package-source.js";
2
3
  import { appHasApi, remoteDependencyAlias, resolveRemoteRefs, sharedPackages, shellApp, verticalApiApps, zephyrRemoteDependency } from "./descriptors.js";
3
4
  import { readFileTemplate } from "./fs-io.js";
4
5
  import { packageName, relativeRootFor } from "./naming.js";
5
- import { createPublicSurfaceGenerationCommand } from "./public-surface.js";
6
6
  import { EFFECT_TSGO_VERSION, I18NEXT_VERSION, LEFTHOOK_VERSION, MODULE_FEDERATION_VERSION, NODE_FETCH_VERSION, OXFMT_VERSION, OXLINT_VERSION, PNPM_VERSION, POSTCSS_VERSION, REACT_DOM_VERSION, REACT_ROUTER_VERSION, REACT_VERSION, TAILWIND_POSTCSS_VERSION, TAILWIND_VERSION, TANSTACK_ROUTER_VERSION, TYPESCRIPT_NATIVE_PREVIEW_VERSION, TYPESCRIPT_VERSION, TYPES_REACT_DOM_VERSION, TYPES_REACT_VERSION, ULTRACITE_VERSION, WRANGLER_VERSION, ZEPHYR_AGENT_VERSION, ZEPHYR_RSPACK_PLUGIN_VERSION } from "./versions.js";
7
- const createStrictTsgoTypecheckCommand = (packageDir)=>`node ${relativeRootFor(packageDir)}/scripts/ultramodern-typecheck.mts --project tsconfig.json`;
7
+ import { createStrictTsgoTypecheckCommand, createWorkspaceAppPackageScripts, createWorkspaceRootPackageScripts } from "./workspace-script-plan.js";
8
8
  const effectDiagnostics = [
9
9
  'anyUnknownInErrorContext',
10
10
  'classSelfMismatch',
@@ -95,8 +95,7 @@ function appDependencies(scope, packageSource, app, remotes = [], bridge) {
95
95
  [packageName(scope, 'shared-design-tokens')]: WORKSPACE_PACKAGE_VERSION,
96
96
  react: REACT_VERSION,
97
97
  'react-dom': REACT_DOM_VERSION,
98
- 'react-router': REACT_ROUTER_VERSION,
99
- 'react-router-dom': REACT_ROUTER_VERSION
98
+ 'react-router': REACT_ROUTER_VERSION
100
99
  };
101
100
  for (const dependency of bridge?.dependencies ?? [])dependencies[dependency] = WORKSPACE_PACKAGE_VERSION;
102
101
  if ('shell' === app.kind) {
@@ -128,11 +127,6 @@ function appDevDependencies(packageSource, enableTailwind) {
128
127
  function createRootPackageJson(scope, packageSource, remotes = [], bridge) {
129
128
  const shellFilter = `--filter ${packageName(scope, shellApp.packageSuffix)}`;
130
129
  const remoteFilters = remotes.map((remote)=>`--filter ${packageName(scope, remote.packageSuffix)}`);
131
- const remoteBuildPrefix = remotes.length > 0 ? 'ULTRAMODERN_ZEPHYR=false pnpm -r --filter "./verticals/*" run build && ' : '';
132
- const remoteCloudflareBuildPrefix = remotes.length > 0 ? 'pnpm -r --filter "./verticals/*" run cloudflare:build && ' : '';
133
- const remoteCloudflareDeployPrefix = remotes.length > 0 ? 'pnpm -r --filter "./verticals/*" run cloudflare:deploy && ' : '';
134
- const generatedPackageTypecheck = 'pnpm -r --filter "./apps/*" --filter "./verticals/*" --filter "./packages/*" run typecheck';
135
- const typecheck = bridge ? generatedPackageTypecheck : "node ./scripts/ultramodern-typecheck.mts --build tsconfig.json";
136
130
  const bridgeScripts = bridge ? {
137
131
  ...Object.fromEntries(bridge.gates.map((gate)=>[
138
132
  `bridge:${gate.name}`,
@@ -141,6 +135,11 @@ function createRootPackageJson(scope, packageSource, remotes = [], bridge) {
141
135
  'bridge:check': bridge.gates.map((gate)=>`pnpm run bridge:${gate.name}`).join(' && ')
142
136
  } : {};
143
137
  const bridgeCheck = bridge ? ' && pnpm bridge:check' : '';
138
+ const bridgeTypecheck = bridge ? 'pnpm -r --filter "./apps/*" --filter "./verticals/*" --filter "./packages/*" run typecheck' : void 0;
139
+ const rootPackageScripts = createWorkspaceRootPackageScripts(remotes, {
140
+ bridgeCheck,
141
+ typecheck: bridgeTypecheck
142
+ });
144
143
  const workspacePackages = [
145
144
  'apps/*',
146
145
  'verticals/*',
@@ -163,28 +162,19 @@ function createRootPackageJson(scope, packageSource, remotes = [], bridge) {
163
162
  `dev:${remote.packageSuffix}`,
164
163
  `pnpm --filter ${packageName(scope, remote.packageSuffix)} dev`
165
164
  ])),
166
- build: `${remoteBuildPrefix}ULTRAMODERN_ZEPHYR=false pnpm --filter "./apps/shell-super-app" run build && pnpm mf:types && pnpm performance:readiness`,
165
+ ...rootPackageScripts,
167
166
  format: "oxfmt . '!repos/**'",
168
167
  'format:check': "oxfmt --check . '!repos/**'",
169
168
  lint: 'oxlint apps verticals packages',
170
169
  'lint:fix': 'oxlint apps verticals packages --fix',
171
- typecheck,
172
- 'cloudflare:build': `${remoteCloudflareBuildPrefix}pnpm --filter "./apps/shell-super-app" run cloudflare:build && pnpm mf:types`,
173
- 'cloudflare:deploy': `${remoteCloudflareDeployPrefix}pnpm --filter "./apps/shell-super-app" run cloudflare:deploy`,
174
- 'cloudflare:proof': "node ./scripts/proof-cloudflare-version.mts --out .codex/reports/cloudflare-version-proof/public-url-proof.json",
175
170
  'skills:install': "node ./scripts/bootstrap-agent-skills.mts",
176
171
  'skills:check': "node ./scripts/bootstrap-agent-skills.mts --check",
177
172
  'agents:refs:install': "node ./scripts/setup-agent-reference-repos.mts",
178
173
  'agents:refs:check': "node ./scripts/setup-agent-reference-repos.mts --check",
179
- 'mf:types': "node ./scripts/assert-mf-types.mts",
180
- 'performance:readiness': "node ./scripts/ultramodern-performance-readiness.mts",
181
- 'migrate:strict-effect': "node ./scripts/migrate-strict-effect.mts",
182
- 'contract:check': "node ./scripts/validate-ultramodern-workspace.mts",
183
174
  'api:check': "node ./scripts/check-ultramodern-api-boundaries.mts",
184
175
  'i18n:boundaries': "node ./scripts/check-ultramodern-i18n-boundaries.mts",
185
176
  ...bridgeScripts,
186
- postinstall: "oxfmt . '!repos/**' && node ./scripts/bootstrap-agent-skills.mts --postinstall",
187
- check: `pnpm format:check && pnpm lint && pnpm typecheck && pnpm skills:check && pnpm i18n:boundaries && pnpm api:check && pnpm contract:check && pnpm performance:readiness${bridgeCheck}`
177
+ postinstall: "oxfmt . '!repos/**' && node ./scripts/bootstrap-agent-skills.mts --postinstall"
188
178
  },
189
179
  engines: {
190
180
  node: '>=26',
@@ -205,6 +195,7 @@ function createRootPackageJson(scope, packageSource, remotes = [], bridge) {
205
195
  '@effect/tsgo': EFFECT_TSGO_VERSION,
206
196
  '@modern-js/code-tools': modernPackageSpecifier('@modern-js/code-tools', packageSource),
207
197
  '@modern-js/create': modernPackageSpecifier('@modern-js/create', packageSource),
198
+ '@modern-js/plugin-bff': modernPackageSpecifier('@modern-js/plugin-bff', packageSource),
208
199
  "@typescript/native-preview": TYPESCRIPT_NATIVE_PREVIEW_VERSION,
209
200
  lefthook: LEFTHOOK_VERSION,
210
201
  oxlint: OXLINT_VERSION,
@@ -248,7 +239,6 @@ function createTsConfigBase() {
248
239
  noFallthroughCasesInSwitch: true,
249
240
  noPropertyAccessFromIndexSignature: true,
250
241
  noImplicitReturns: true,
251
- skipLibCheck: true,
252
242
  resolveJsonModule: true,
253
243
  plugins: [
254
244
  {
@@ -280,7 +270,7 @@ function createReferences(packageDir, references) {
280
270
  return [
281
271
  ...new Set(references)
282
272
  ].filter((reference)=>reference !== packageDir).map((reference)=>({
283
- path: `${relativeRootFor(packageDir)}/${reference}`
273
+ path: node_path.relative(packageDir, reference).split(node_path.sep).join('/')
284
274
  }));
285
275
  }
286
276
  function createPackageTsConfig(packageDir, options = {}) {
@@ -290,8 +280,6 @@ function createPackageTsConfig(packageDir, options = {}) {
290
280
  const include = resolvedOptions.include ?? [
291
281
  'src',
292
282
  'locales/**/*.json',
293
- 'modern.config.ts',
294
- 'module-federation.config.ts',
295
283
  'package.json',
296
284
  'shared'
297
285
  ];
@@ -325,9 +313,9 @@ function createAppTsConfig(app, remotes = []) {
325
313
  });
326
314
  }
327
315
  function createAppMfTypesTsConfig(app) {
328
- const exposedFiles = Object.values(app.exposes ?? {}).map((exposePath)=>exposePath.replace(/^\.\//u, ''));
316
+ const exposedFiles = Object.entries(app.exposes ?? {}).sort(([left], [right])=>'./Route' === left ? -1 : './Route' === right ? 1 : 0).map(([, exposePath])=>exposePath.replace(/^\.\//u, ''));
329
317
  return {
330
- extends: './tsconfig.json',
318
+ extends: `${relativeRootFor(app.directory)}/tsconfig.base.json`,
331
319
  include: [
332
320
  ...new Set([
333
321
  ...exposedFiles,
@@ -357,9 +345,6 @@ function createRootTsConfig(apps = []) {
357
345
  };
358
346
  }
359
347
  function createAppPackage(scope, app, packageSource, enableTailwind, remotes = [], bridge) {
360
- const publicSurfaceBuildCommand = createPublicSurfaceGenerationCommand(app, 'dist');
361
- const publicSurfaceCloudflareBuildCommand = createPublicSurfaceGenerationCommand(app, 'dist');
362
- const publicSurfaceCloudflareOutputCommand = createPublicSurfaceGenerationCommand(app, 'cloudflare');
363
348
  const packageExports = Object.fromEntries(Object.entries(app.exposes ?? {}).map(([expose, source])=>[
364
349
  expose,
365
350
  source
@@ -368,16 +353,7 @@ function createAppPackage(scope, app, packageSource, enableTailwind, remotes = [
368
353
  private: true,
369
354
  name: packageName(scope, app.packageSuffix),
370
355
  version: '0.1.0',
371
- scripts: {
372
- dev: 'modern dev',
373
- build: app.exposes ? `ULTRAMODERN_ZEPHYR=false modern build && ${publicSurfaceBuildCommand} && node ${relativeRootFor(app.directory)}/scripts/assert-mf-types.mts` : `ULTRAMODERN_ZEPHYR=false modern build && ${publicSurfaceBuildCommand}`,
374
- 'cloudflare:build': `ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern build && ${publicSurfaceCloudflareBuildCommand} && ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy && ${publicSurfaceCloudflareOutputCommand}`,
375
- 'cloudflare:deploy': 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true pnpm run cloudflare:build && wrangler deploy --config .output/wrangler.json',
376
- 'cloudflare:preview': 'pnpm run cloudflare:build && wrangler dev --config .output/wrangler.json',
377
- 'cloudflare:proof': `node ${relativeRootFor(app.directory)}/scripts/proof-cloudflare-version.mts --app ${app.id}`,
378
- serve: 'modern serve',
379
- typecheck: createStrictTsgoTypecheckCommand(app.directory)
380
- },
356
+ scripts: createWorkspaceAppPackageScripts(app),
381
357
  modernjs: {
382
358
  preset: 'presetUltramodern',
383
359
  role: 'shell' === app.kind ? 'shell' : 'module-federation-remote',
@@ -428,4 +404,4 @@ function createSharedPackage(scope, id, description) {
428
404
  function createSharedContractsIndex() {
429
405
  return readFileTemplate('packages/shared-contracts-index.ts');
430
406
  }
431
- export { appDependencies, appDevDependencies, createAppMfTypesTsConfig, createAppPackage, createAppTsConfig, createPackageTsConfig, createRootPackageJson, createRootTsConfig, createSharedContractsIndex, createSharedPackage, createSharedPackageTsConfig, createStrictTsgoTypecheckCommand, createTsConfigBase, createZephyrDependencies, effectDiagnostics };
407
+ export { appDependencies, appDevDependencies, createAppMfTypesTsConfig, createAppPackage, createAppTsConfig, createPackageTsConfig, createRootPackageJson, createRootTsConfig, createSharedContractsIndex, createSharedPackage, createSharedPackageTsConfig, createTsConfigBase, createZephyrDependencies, effectDiagnostics };