@bleedingdev/modern-js-create 3.5.0-ultramodern.28 → 3.5.0-ultramodern.29

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 (79) hide show
  1. package/dist/cjs/ultramodern-tooling/commands.cjs +497 -155
  2. package/dist/cjs/ultramodern-tooling/config.cjs +149 -8
  3. package/dist/cjs/ultramodern-workspace/add-vertical.cjs +25 -0
  4. package/dist/cjs/ultramodern-workspace/api.cjs +44 -0
  5. package/dist/cjs/ultramodern-workspace/backend-federation.cjs +282 -0
  6. package/dist/cjs/ultramodern-workspace/contracts.cjs +24 -8
  7. package/dist/cjs/ultramodern-workspace/delivery-unit-sync.cjs +157 -0
  8. package/dist/cjs/ultramodern-workspace/delivery-unit.cjs +88 -0
  9. package/dist/cjs/ultramodern-workspace/descriptors.cjs +24 -0
  10. package/dist/cjs/ultramodern-workspace/module-federation.cjs +122 -44
  11. package/dist/cjs/ultramodern-workspace/package-json.cjs +11 -34
  12. package/dist/cjs/ultramodern-workspace/pnpm-workspace-policy-plan.cjs +166 -0
  13. package/dist/cjs/ultramodern-workspace/tooling-command-catalog.cjs +153 -0
  14. package/dist/cjs/ultramodern-workspace/workspace-script-plan.cjs +175 -0
  15. package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +43 -62
  16. package/dist/cjs/ultramodern-workspace/workspace-validation-contract.cjs +97 -0
  17. package/dist/cjs/ultramodern-workspace/write-workspace.cjs +9 -3
  18. package/dist/cjs/ultramodern-workspace/zerops.cjs +100 -0
  19. package/dist/esm/ultramodern-tooling/commands.js +500 -158
  20. package/dist/esm/ultramodern-tooling/config.js +144 -9
  21. package/dist/esm/ultramodern-workspace/add-vertical.js +26 -1
  22. package/dist/esm/ultramodern-workspace/api.js +43 -2
  23. package/dist/esm/ultramodern-workspace/backend-federation.js +217 -0
  24. package/dist/esm/ultramodern-workspace/contracts.js +24 -8
  25. package/dist/esm/ultramodern-workspace/delivery-unit-sync.js +108 -0
  26. package/dist/esm/ultramodern-workspace/delivery-unit.js +31 -0
  27. package/dist/esm/ultramodern-workspace/descriptors.js +13 -1
  28. package/dist/esm/ultramodern-workspace/module-federation.js +121 -36
  29. package/dist/esm/ultramodern-workspace/package-json.js +11 -31
  30. package/dist/esm/ultramodern-workspace/pnpm-workspace-policy-plan.js +113 -0
  31. package/dist/esm/ultramodern-workspace/tooling-command-catalog.js +100 -0
  32. package/dist/esm/ultramodern-workspace/workspace-script-plan.js +110 -0
  33. package/dist/esm/ultramodern-workspace/workspace-scripts.js +35 -63
  34. package/dist/esm/ultramodern-workspace/workspace-validation-contract.js +59 -0
  35. package/dist/esm/ultramodern-workspace/write-workspace.js +11 -5
  36. package/dist/esm/ultramodern-workspace/zerops.js +62 -0
  37. package/dist/esm-node/ultramodern-tooling/commands.js +500 -158
  38. package/dist/esm-node/ultramodern-tooling/config.js +144 -9
  39. package/dist/esm-node/ultramodern-workspace/add-vertical.js +26 -1
  40. package/dist/esm-node/ultramodern-workspace/api.js +43 -2
  41. package/dist/esm-node/ultramodern-workspace/backend-federation.js +218 -0
  42. package/dist/esm-node/ultramodern-workspace/contracts.js +24 -8
  43. package/dist/esm-node/ultramodern-workspace/delivery-unit-sync.js +109 -0
  44. package/dist/esm-node/ultramodern-workspace/delivery-unit.js +32 -0
  45. package/dist/esm-node/ultramodern-workspace/descriptors.js +13 -1
  46. package/dist/esm-node/ultramodern-workspace/module-federation.js +121 -36
  47. package/dist/esm-node/ultramodern-workspace/package-json.js +11 -31
  48. package/dist/esm-node/ultramodern-workspace/pnpm-workspace-policy-plan.js +114 -0
  49. package/dist/esm-node/ultramodern-workspace/tooling-command-catalog.js +101 -0
  50. package/dist/esm-node/ultramodern-workspace/workspace-script-plan.js +111 -0
  51. package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +35 -63
  52. package/dist/esm-node/ultramodern-workspace/workspace-validation-contract.js +60 -0
  53. package/dist/esm-node/ultramodern-workspace/write-workspace.js +11 -5
  54. package/dist/esm-node/ultramodern-workspace/zerops.js +63 -0
  55. package/dist/types/ultramodern-tooling/config.d.ts +6 -0
  56. package/dist/types/ultramodern-workspace/api.d.ts +1 -0
  57. package/dist/types/ultramodern-workspace/backend-federation.d.ts +11 -0
  58. package/dist/types/ultramodern-workspace/contracts.d.ts +1 -1
  59. package/dist/types/ultramodern-workspace/delivery-unit-sync.d.ts +6 -0
  60. package/dist/types/ultramodern-workspace/delivery-unit.d.ts +27 -0
  61. package/dist/types/ultramodern-workspace/descriptors.d.ts +4 -0
  62. package/dist/types/ultramodern-workspace/module-federation.d.ts +4 -9
  63. package/dist/types/ultramodern-workspace/package-json.d.ts +0 -1
  64. package/dist/types/ultramodern-workspace/pnpm-workspace-policy-plan.d.ts +39 -0
  65. package/dist/types/ultramodern-workspace/tooling-command-catalog.d.ts +18 -0
  66. package/dist/types/ultramodern-workspace/workspace-script-plan.d.ts +72 -0
  67. package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +3 -0
  68. package/dist/types/ultramodern-workspace/workspace-validation-contract.d.ts +42 -0
  69. package/dist/types/ultramodern-workspace/zerops.d.ts +2 -0
  70. package/package.json +3 -3
  71. package/template-workspace/.gitignore.handlebars +1 -0
  72. package/template-workspace/scripts/bootstrap-agent-skills.mjs +11 -3
  73. package/templates/workspace-scripts/check-ultramodern-api-boundaries.mts +40 -2
  74. package/templates/workspace-scripts/generate-node-backend-federation.mjs +270 -0
  75. package/templates/workspace-scripts/materialize-zerops-runtime.mjs +448 -0
  76. package/templates/workspace-scripts/proof-cloudflare-version.mjs +234 -8
  77. package/templates/workspace-scripts/proof-node-backend-federation.mjs +729 -0
  78. package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +188 -2
  79. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +322 -8
@@ -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,6 +21,19 @@ 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 ||
@@ -49,6 +63,13 @@ const zephyrEnabled = process.env['ULTRAMODERN_ZEPHYR'] !== 'false';
49
63
  const cloudflareDeployEnabled =
50
64
  process.env['MODERNJS_DEPLOY'] === 'cloudflare';
51
65
 
66
+ const zephyrWarn = (error: unknown) => {
67
+ const message = error instanceof Error ? error.message : String(error);
68
+ console.warn(
69
+ \`[ultramodern] zephyr-rspack-plugin failed; continuing without Zephyr (set ULTRAMODERN_ZEPHYR=false to disable it): \${message}\`,
70
+ );
71
+ };
72
+
52
73
  const zephyrRspackPlugin = () => ({
53
74
  name: 'ultramodern-zephyr-rspack-plugin',
54
75
  pre: ['@modern-js/plugin-module-federation-config'],
@@ -62,7 +83,17 @@ const zephyrRspackPlugin = () => ({
62
83
  if (!zephyrEnabled) {
63
84
  return;
64
85
  }
65
- api.modifyRspackConfig(config => withZephyrRspack()(config));
86
+ api.modifyRspackConfig(config => {
87
+ try {
88
+ return Promise.resolve(withZephyrRspack()(config)).catch(error => {
89
+ zephyrWarn(error);
90
+ return config;
91
+ });
92
+ } catch (error) {
93
+ zephyrWarn(error);
94
+ return config;
95
+ }
96
+ });
66
97
  },
67
98
  });
68
99
 
@@ -123,12 +154,12 @@ ${bffConfig} ...(cloudflareDeployEnabled
123
154
  ? {
124
155
  deploy: {
125
156
  worker: {
126
- compatibilityDate: '${CLOUDFLARE_COMPATIBILITY_DATE}',
127
- name: cloudflareWorkerName,
128
- security: ${formatTsJsonValue(sortJsonValue(createCloudflareSecurityContract()), 16)},
129
- ssr: true,
130
- },
157
+ compatibilityDate: '${CLOUDFLARE_COMPATIBILITY_DATE}',
158
+ name: cloudflareWorkerName,
159
+ security: ${formatTsJsonValue(sortJsonValue(createCloudflareSecurityContract()), 16)},
160
+ ${serviceBindingsConfig} ssr: true,
131
161
  },
162
+ },
132
163
  }
133
164
  : {}),
134
165
  dev: {
@@ -347,6 +378,19 @@ ${remoteEntries}
347
378
  },
348
379
  `;
349
380
  }
381
+ function createModuleFederationDtsConfig(hasExposes) {
382
+ return hasExposes ? ` dts: {
383
+ displayErrorInTerminal: true,
384
+ generateTypes: {
385
+ compilerInstance: 'tsgo',
386
+ },
387
+ tsConfigPath: './tsconfig.mf-types.json',
388
+ },` : ` dts: {
389
+ consumeTypes: true,
390
+ generateTypes: false,
391
+ tsConfigPath: './tsconfig.mf-types.json',
392
+ },`;
393
+ }
350
394
  function createShellModuleFederationConfig(scope, remotes = []) {
351
395
  const shellHost = {
352
396
  ...shellApp,
@@ -375,13 +419,7 @@ const moduleFederationConfig: Parameters<
375
419
  dev: {
376
420
  disableDynamicRemoteTypeHints: true,
377
421
  },
378
- dts: {
379
- displayErrorInTerminal: true,
380
- generateTypes: {
381
- compilerInstance: 'tsgo',
382
- },
383
- tsConfigPath: './tsconfig.mf-types.json',
384
- },
422
+ ${createModuleFederationDtsConfig(false)}
385
423
  filename: 'remoteEntry.js',
386
424
  name: '${shellApp.mfName}',
387
425
  ${createModuleFederationRemotesConfig(scope, shellHost, remotes)}${createSharedModuleFederationConfig()},
@@ -391,25 +429,75 @@ ${createModuleFederationRemotesConfig(scope, shellHost, remotes)}${createSharedM
391
429
  export default moduleFederationConfig;
392
430
  `;
393
431
  }
394
- function createBuildMarker(scope, app) {
395
- return node_crypto.createHash('sha256').update(`${scope}:${app.packageSuffix}:${app.id}:0.1.0`).digest('hex').slice(0, 16);
432
+ function createBackendModuleFederationConfig(app) {
433
+ return `// @effect-diagnostics nodeBuiltinImport:off
434
+ import { createRequire } from 'node:module';
435
+ import { createModuleFederationConfig } from '@module-federation/modern-js-v3';
436
+ import { dependencies } from './package.json';
437
+
438
+ const require = createRequire(import.meta.url);
439
+ const bffVersion = (
440
+ require('@modern-js/plugin-bff/package.json') as { version: string }
441
+ ).version;
442
+ const effectVersion = (
443
+ require('effect/package.json') as { version: string }
444
+ ).version;
445
+
446
+ const moduleFederationConfig: Parameters<
447
+ typeof createModuleFederationConfig
448
+ >[0] = createModuleFederationConfig({
449
+ dts: false,
450
+ exposes: {
451
+ './effect-api': './api/effect-api.ts',
452
+ },
453
+ filename: 'backendRemoteEntry.mjs',
454
+ name: '${createBackendFederationName(app)}',
455
+ shared: {
456
+ '@modern-js/plugin-bff': {
457
+ requiredVersion: bffVersion,
458
+ singleton: true,
459
+ treeShaking: false,
460
+ },
461
+ '@module-federation/runtime': {
462
+ requiredVersion: dependencies['@module-federation/runtime'],
463
+ singleton: true,
464
+ treeShaking: false,
465
+ },
466
+ effect: {
467
+ requiredVersion: effectVersion,
468
+ singleton: true,
469
+ treeShaking: false,
470
+ },
471
+ },
472
+ treeShakingSharedExcludePlugins: ['RspackModuleFederationPlugin'],
473
+ });
474
+
475
+ export default moduleFederationConfig;
476
+ `;
396
477
  }
397
478
  function createUltramodernBuildModule(scope, app) {
398
- return `export const ultramodernVerticalIdentity = {
399
- appId: '${app.id}',
400
- build: '${createBuildMarker(scope, app)}',
401
- deployProfile: 'cloudflare-ssr-mf-effect-v1',
402
- packageName: '${packageName(scope, app.packageSuffix)}',
403
- version: '0.1.0',
479
+ const record = createDeliveryUnitRecord(scope, app);
480
+ return `export const ultramodernDeliveryUnit = {
481
+ appId: '${record.appId}',
482
+ build: '${record.buildMarker}',
483
+ deployProfile: '${record.deployProfile}',
484
+ kind: '${record.kind}',
485
+ packageName: '${record.packageName}',
486
+ schemaVersion: ${record.schemaVersion},
487
+ sourceRevision: '${record.sourceRevision}',
488
+ unitId: '${record.unitId}',
489
+ version: '${record.version}',
404
490
  } as const;
405
491
 
492
+ export const ultramodernVerticalIdentity = ultramodernDeliveryUnit;
493
+
406
494
  export const ultramodernUiMarker = {
407
- ...ultramodernVerticalIdentity,
495
+ ...ultramodernDeliveryUnit,
408
496
  surface: 'ui',
409
497
  } as const;
410
498
 
411
499
  export const ultramodernApiMarker = {
412
- ...ultramodernVerticalIdentity,
500
+ ...ultramodernDeliveryUnit,
413
501
  surface: 'api',
414
502
  } as const;
415
503
  `;
@@ -417,6 +505,7 @@ export const ultramodernApiMarker = {
417
505
  function createUltramodernBuildReexportModule() {
418
506
  return `export {
419
507
  ultramodernApiMarker,
508
+ ultramodernDeliveryUnit,
420
509
  ultramodernUiMarker,
421
510
  ultramodernVerticalIdentity,
422
511
  } from '../shared/ultramodern-build';
@@ -424,7 +513,9 @@ function createUltramodernBuildReexportModule() {
424
513
  }
425
514
  function createRemoteModuleFederationConfig(scope, app, remotes = []) {
426
515
  const exposes = formatTsObjectLiteral(app.exposes ?? {});
427
- return `// @effect-diagnostics nodeBuiltinImport:off
516
+ const hasExposes = Object.keys(app.exposes ?? {}).length > 0;
517
+ const hostOnlyMarker = hasExposes ? '' : '\n// ultramodern-mf: no-exposes';
518
+ return `// @effect-diagnostics nodeBuiltinImport:off${hostOnlyMarker}
428
519
  import { createRequire } from 'node:module';
429
520
  import { createModuleFederationConfig } from '@module-federation/modern-js-v3';
430
521
  import { dependencies } from './package.json';
@@ -446,13 +537,7 @@ const moduleFederationConfig: Parameters<
446
537
  dev: {
447
538
  disableDynamicRemoteTypeHints: true,
448
539
  },
449
- dts: {
450
- displayErrorInTerminal: true,
451
- generateTypes: {
452
- compilerInstance: 'tsgo',
453
- },
454
- tsConfigPath: './tsconfig.mf-types.json',
455
- },
540
+ ${createModuleFederationDtsConfig(hasExposes)}
456
541
  exposes: ${exposes},
457
542
  filename: 'remoteEntry.js',
458
543
  name: '${app.mfName}',
@@ -463,4 +548,4 @@ ${createModuleFederationRemotesConfig(scope, app, remotes)}${createSharedModuleF
463
548
  export default moduleFederationConfig;
464
549
  `;
465
550
  }
466
- export { createAppModernConfig, createBuildMarker, createModuleFederationRemoteUrlHelpers, createModuleFederationRemotesConfig, createRemoteModuleFederationConfig, createSharedModuleFederationConfig, createShellModuleFederationConfig, createUltramodernBuildModule, createUltramodernBuildReexportModule, formatTsObjectLiteral };
551
+ export { createAppModernConfig, createBackendModuleFederationConfig, createBuildMarker, createModuleFederationRemoteUrlHelpers, createModuleFederationRemotesConfig, createRemoteModuleFederationConfig, createSharedModuleFederationConfig, createShellModuleFederationConfig, createUltramodernBuildModule, createUltramodernBuildReexportModule, formatTsObjectLiteral };
@@ -4,9 +4,8 @@ import { WORKSPACE_PACKAGE_VERSION, modernPackageSpecifier } from "../ultramoder
4
4
  import { appHasApi, remoteDependencyAlias, resolveRemoteRefs, sharedPackages, shellApp, verticalApiApps, zephyrRemoteDependency } from "./descriptors.js";
5
5
  import { readFileTemplate } from "./fs-io.js";
6
6
  import { packageName, relativeRootFor } from "./naming.js";
7
- import { createPublicSurfaceGenerationCommand } from "./public-surface.js";
8
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";
9
- const createStrictTsgoTypecheckCommand = (packageDir)=>`node ${relativeRootFor(packageDir)}/scripts/ultramodern-typecheck.mts --project tsconfig.json`;
8
+ import { createStrictTsgoTypecheckCommand, createWorkspaceAppPackageScripts, createWorkspaceRootPackageScripts } from "./workspace-script-plan.js";
10
9
  const effectDiagnostics = [
11
10
  'anyUnknownInErrorContext',
12
11
  'classSelfMismatch',
@@ -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,
@@ -355,8 +346,6 @@ function createRootTsConfig(apps = []) {
355
346
  };
356
347
  }
357
348
  function createAppPackage(scope, app, packageSource, enableTailwind, remotes = [], bridge) {
358
- const publicSurfaceBuildCommand = createPublicSurfaceGenerationCommand(app, 'dist');
359
- const publicSurfaceCloudflareOutputCommand = createPublicSurfaceGenerationCommand(app, 'cloudflare');
360
349
  const packageExports = Object.fromEntries(Object.entries(app.exposes ?? {}).map(([expose, source])=>[
361
350
  expose,
362
351
  source
@@ -365,16 +354,7 @@ function createAppPackage(scope, app, packageSource, enableTailwind, remotes = [
365
354
  private: true,
366
355
  name: packageName(scope, app.packageSuffix),
367
356
  version: '0.1.0',
368
- scripts: {
369
- dev: 'modern dev',
370
- build: app.exposes ? `ULTRAMODERN_ZEPHYR=false modern build && ${publicSurfaceBuildCommand} && node ${relativeRootFor(app.directory)}/scripts/assert-mf-types.mts` : `ULTRAMODERN_ZEPHYR=false modern build && ${publicSurfaceBuildCommand}`,
371
- 'cloudflare:build': `ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern build && ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy --skip-build && ${publicSurfaceCloudflareOutputCommand}`,
372
- 'cloudflare:deploy': 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true pnpm run cloudflare:build && wrangler deploy --config .output/wrangler.json',
373
- 'cloudflare:preview': 'pnpm run cloudflare:build && wrangler dev --config .output/wrangler.json',
374
- 'cloudflare:proof': `node ${relativeRootFor(app.directory)}/scripts/proof-cloudflare-version.mts --app ${app.id}`,
375
- serve: 'modern serve',
376
- typecheck: createStrictTsgoTypecheckCommand(app.directory)
377
- },
357
+ scripts: createWorkspaceAppPackageScripts(app),
378
358
  modernjs: {
379
359
  preset: 'presetUltramodern',
380
360
  role: 'shell' === app.kind ? 'shell' : 'module-federation-remote',
@@ -425,4 +405,4 @@ function createSharedPackage(scope, id, description) {
425
405
  function createSharedContractsIndex() {
426
406
  return readFileTemplate('packages/shared-contracts-index.ts');
427
407
  }
428
- 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 };
@@ -0,0 +1,114 @@
1
+ import "node:module";
2
+ import { DRIZZLE_ORM_VERSION, EFFECT_VERSION, EFFECT_VITEST_VERSION, MODULE_FEDERATION_VERSION } from "./versions.js";
3
+ const strictEffectPackageVersionPolicyExclusions = [
4
+ `effect@${EFFECT_VERSION}`,
5
+ `@effect/opentelemetry@${EFFECT_VERSION}`
6
+ ];
7
+ const moduleFederationModernJsPatchPath = `patches/@module-federation__modern-js-v3@${MODULE_FEDERATION_VERSION}.patch`;
8
+ const moduleFederationBridgeReactPatchPath = `patches/@module-federation__bridge-react@${MODULE_FEDERATION_VERSION}.patch`;
9
+ const effectDeclarationPatchPath = 'patches/effect-schema-error-type-id.patch';
10
+ const drizzleOrmDeclarationPatchPath = 'patches/drizzle-orm-ts7-strict-declarations.patch';
11
+ function createPnpmWorkspacePolicyPlan(options) {
12
+ return {
13
+ yamlActions: [
14
+ {
15
+ kind: 'replace-line',
16
+ pattern: /^ {4}'@effect\/vitest>effect': .+$/mu,
17
+ replacement: ` '@effect/vitest>effect': '${EFFECT_VERSION}'`
18
+ },
19
+ {
20
+ kind: 'ensure-scalar-map-entry',
21
+ key: 'overrides',
22
+ entryKey: "'@effect/opentelemetry'",
23
+ value: EFFECT_VERSION
24
+ },
25
+ {
26
+ kind: 'ensure-scalar-map-entry',
27
+ key: 'overrides',
28
+ entryKey: "'@effect/vitest'",
29
+ value: EFFECT_VITEST_VERSION
30
+ },
31
+ {
32
+ kind: 'ensure-scalar-map-entry',
33
+ key: 'overrides',
34
+ entryKey: 'effect',
35
+ value: EFFECT_VERSION
36
+ },
37
+ {
38
+ kind: 'ensure-scalar-map-entry',
39
+ key: 'allowBuilds',
40
+ entryKey: "'@parcel/watcher'",
41
+ value: 'true'
42
+ },
43
+ ...strictEffectPackageVersionPolicyExclusions.flatMap((item)=>createStrictEffectPackageVersionPolicyActions(item)),
44
+ {
45
+ kind: 'ensure-map-entry',
46
+ key: 'patchedDependencies',
47
+ entryKey: `effect@${EFFECT_VERSION}`,
48
+ value: effectDeclarationPatchPath
49
+ },
50
+ {
51
+ kind: 'ensure-map-entry',
52
+ key: 'patchedDependencies',
53
+ entryKey: `@module-federation/modern-js-v3@${MODULE_FEDERATION_VERSION}`,
54
+ value: moduleFederationModernJsPatchPath
55
+ },
56
+ {
57
+ kind: 'ensure-map-entry',
58
+ key: 'patchedDependencies',
59
+ entryKey: `@module-federation/bridge-react@${MODULE_FEDERATION_VERSION}`,
60
+ value: moduleFederationBridgeReactPatchPath
61
+ },
62
+ options.usesDrizzleOrm ? {
63
+ kind: 'ensure-map-entry',
64
+ key: 'patchedDependencies',
65
+ entryKey: `drizzle-orm@${DRIZZLE_ORM_VERSION}`,
66
+ value: drizzleOrmDeclarationPatchPath
67
+ } : {
68
+ kind: 'remove-map-entry',
69
+ entryKey: `drizzle-orm@${DRIZZLE_ORM_VERSION}`
70
+ }
71
+ ],
72
+ declarationPatchActions: [
73
+ {
74
+ kind: 'ensure',
75
+ relativePatchPath: moduleFederationModernJsPatchPath
76
+ },
77
+ {
78
+ kind: 'ensure',
79
+ relativePatchPath: moduleFederationBridgeReactPatchPath
80
+ },
81
+ {
82
+ kind: 'ensure',
83
+ relativePatchPath: effectDeclarationPatchPath
84
+ },
85
+ {
86
+ kind: options.usesDrizzleOrm ? 'ensure' : 'remove-if-unchanged',
87
+ relativePatchPath: drizzleOrmDeclarationPatchPath
88
+ }
89
+ ],
90
+ requiresInstallOnChange: true
91
+ };
92
+ }
93
+ function createStrictEffectPackageVersionPolicyActions(item) {
94
+ const packageName = item.slice(0, item.lastIndexOf('@'));
95
+ const escapedPackageName = packageName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
96
+ return [
97
+ {
98
+ kind: 'replace-line',
99
+ pattern: new RegExp(`^ {2}- '${escapedPackageName}@[^']+'$`, 'gmu'),
100
+ replacement: ` - '${item}'`
101
+ },
102
+ {
103
+ kind: 'ensure-list-item',
104
+ key: 'minimumReleaseAgeExclude',
105
+ item
106
+ },
107
+ {
108
+ kind: 'ensure-list-item',
109
+ key: 'trustPolicyExclude',
110
+ item
111
+ }
112
+ ];
113
+ }
114
+ export { createPnpmWorkspacePolicyPlan, drizzleOrmDeclarationPatchPath, effectDeclarationPatchPath, moduleFederationBridgeReactPatchPath, moduleFederationModernJsPatchPath, strictEffectPackageVersionPolicyExclusions };