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