@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
@@ -6,26 +6,31 @@ import node_path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { resolveCreatePackageRoot } from "../create-package-root.js";
8
8
  import { BLEEDINGDEV_PACKAGE_NAME_PREFIX, BLEEDINGDEV_PACKAGE_SCOPE, ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES, ULTRAMODERN_WORKSPACE_MODERN_PACKAGES, WORKSPACE_PACKAGE_VERSION, modernPackageSpecifier } from "../ultramodern-package-source.js";
9
+ import { createAppEnvDts } from "../ultramodern-workspace/app-files.js";
10
+ import { createBackendFederationContractFile } from "../ultramodern-workspace/backend-federation.js";
11
+ import { runSyncDeliveryUnit } from "../ultramodern-workspace/delivery-unit-sync.js";
9
12
  import { validateModuleFederationTypes } from "../ultramodern-workspace/mf-validation.js";
10
- import { EFFECT_TSGO_VERSION, EFFECT_VERSION, EFFECT_VITEST_VERSION, OXFMT_VERSION, OXLINT_VERSION, TYPESCRIPT_NATIVE_PREVIEW_VERSION, WRANGLER_VERSION, ZEPHYR_AGENT_VERSION, ZEPHYR_RSPACK_PLUGIN_VERSION } from "../ultramodern-workspace/versions.js";
11
- import { createWorkspaceValidationScript } from "../ultramodern-workspace/workspace-scripts.js";
12
- import { readUltramodernConfig, workspaceAppsFromToolingConfig } from "./config.js";
13
+ import { createAppModernConfig, createUltramodernBuildModule, createUltramodernBuildReexportModule } from "../ultramodern-workspace/module-federation.js";
14
+ import { createAppMfTypesTsConfig, createAppTsConfig, createSharedPackageTsConfig, createTsConfigBase } from "../ultramodern-workspace/package-json.js";
15
+ import { GENERATED_TOOLING_COMMANDS, generatedToolingCommandList, generatedToolingCommands } from "../ultramodern-workspace/tooling-command-catalog.js";
16
+ import { DRIZZLE_ORM_VERSION, EFFECT_TSGO_VERSION, EFFECT_VERSION, EFFECT_VITEST_VERSION, MODULE_FEDERATION_VERSION, OXFMT_VERSION, OXLINT_VERSION, TYPESCRIPT_NATIVE_PREVIEW_VERSION, WRANGLER_VERSION, ZEPHYR_AGENT_VERSION, ZEPHYR_RSPACK_PLUGIN_VERSION } from "../ultramodern-workspace/versions.js";
17
+ import { createWorkspaceAppPackageScripts, createWorkspaceRootPackageScripts } from "../ultramodern-workspace/workspace-script-plan.js";
18
+ import { createWorkspaceValidationScript, createZeropsRuntimeMaterializationScript, migratedWorkspaceScriptArtifacts, migratedWorkspaceScriptBasenames } from "../ultramodern-workspace/workspace-scripts.js";
19
+ import { createZeropsYaml } from "../ultramodern-workspace/zerops.js";
20
+ import { normalizeCompactUltramodernConfig, readUltramodernConfig, synthesizeCompactUltramodernConfig, workspaceAppsFromToolingConfig } from "./config.js";
13
21
  const commands_dirname = node_path.dirname(fileURLToPath(import.meta.url));
14
22
  const createPackageRoot = resolveCreatePackageRoot(commands_dirname);
15
23
  function printHelp() {
24
+ const commands = [
25
+ ...generatedToolingCommandList().map((command)=>` ${command}`),
26
+ ' skills install',
27
+ ' skills check'
28
+ ].join('\n');
16
29
  process.stdout.write(`Usage:
17
30
  modern-js-create ultramodern <command> [args]
18
31
 
19
32
  Commands:
20
- validate
21
- typecheck
22
- mf-types
23
- migrate-strict-effect
24
- public-surface
25
- cloudflare-proof
26
- performance-readiness
27
- skills install
28
- skills check
33
+ ${commands}
29
34
  `);
30
35
  }
31
36
  function spawnNodeScript(relativeScriptPath, args, context, options = {}) {
@@ -44,6 +49,13 @@ function spawnNodeScript(relativeScriptPath, args, context, options = {}) {
44
49
  if (result.error) throw result.error;
45
50
  return result.status ?? 1;
46
51
  }
52
+ function runTemplateBackedToolingCommand(command, args, context) {
53
+ const toolingCommand = generatedToolingCommands.find((candidate)=>candidate.command === command);
54
+ if (!toolingCommand?.templatePath) return;
55
+ return spawnNodeScript(toolingCommand.templatePath, args, context, {
56
+ cwd: 'invocation' === toolingCommand.cwd ? context.invocationCwd : context.workspaceRoot
57
+ });
58
+ }
47
59
  function runRenderedModule(source, context) {
48
60
  const tempDir = node_fs.mkdtempSync(node_path.join(node_os.tmpdir(), 'ultramodern-tool-'));
49
61
  const tempFile = node_path.join(tempDir, 'command.mjs');
@@ -90,11 +102,45 @@ const modernPackageNames = new Set([
90
102
  ...ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES,
91
103
  ...ULTRAMODERN_WORKSPACE_MODERN_PACKAGES
92
104
  ]);
105
+ function createMigrationIo(workspaceRoot, dryRun) {
106
+ const plan = [];
107
+ const rel = (p)=>(node_path.relative(workspaceRoot, p) || node_path.basename(p)).split(node_path.sep).join('/');
108
+ return {
109
+ workspaceRoot,
110
+ dryRun,
111
+ plan,
112
+ write (filePath, content) {
113
+ if (node_fs.existsSync(filePath) && node_fs.readFileSync(filePath, 'utf-8') === content) return false;
114
+ if (dryRun) {
115
+ plan.push(`[dry-run] would write ${rel(filePath)}`);
116
+ return true;
117
+ }
118
+ node_fs.mkdirSync(node_path.dirname(filePath), {
119
+ recursive: true
120
+ });
121
+ node_fs.writeFileSync(filePath, content, 'utf-8');
122
+ return true;
123
+ },
124
+ remove (filePath) {
125
+ if (!node_fs.existsSync(filePath)) return false;
126
+ if (dryRun) {
127
+ plan.push(`[dry-run] would delete ${rel(filePath)}`);
128
+ return true;
129
+ }
130
+ node_fs.rmSync(filePath);
131
+ return true;
132
+ },
133
+ log (message) {
134
+ if (dryRun) plan.push(`[dry-run] ${message}`);
135
+ else process.stdout.write(`[ultramodern] ${message}\n`);
136
+ }
137
+ };
138
+ }
93
139
  function readJsonFile(filePath) {
94
140
  return JSON.parse(node_fs.readFileSync(filePath, 'utf-8'));
95
141
  }
96
- function writeJsonFile(filePath, value) {
97
- node_fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
142
+ function writeJsonFile(io, filePath, value) {
143
+ return io.write(filePath, `${JSON.stringify(value, null, 2)}\n`);
98
144
  }
99
145
  function readOption(args, name) {
100
146
  const prefix = `${name}=`;
@@ -127,7 +173,7 @@ function listWorkspacePackageFiles(workspaceRoot) {
127
173
  withFileTypes: true
128
174
  })){
129
175
  if (!entry.isDirectory()) continue;
130
- const packageFile = node_path.join(directory, entry.name, 'package.json');
176
+ const packageFile = `${directory}/${entry.name}/package.json`;
131
177
  if (node_fs.existsSync(node_path.join(workspaceRoot, packageFile))) packageFiles.push(packageFile);
132
178
  }
133
179
  }
@@ -183,6 +229,18 @@ const generatedToolingDependencyPins = new Map([
183
229
  ZEPHYR_RSPACK_PLUGIN_VERSION
184
230
  ]
185
231
  ]);
232
+ const strictEffectPackageVersionPolicyExclusions = [
233
+ `effect@${EFFECT_VERSION}`,
234
+ `@effect/opentelemetry@${EFFECT_VERSION}`
235
+ ];
236
+ const moduleFederationModernJsPatchPath = `patches/@module-federation__modern-js-v3@${MODULE_FEDERATION_VERSION}.patch`;
237
+ const moduleFederationModernJsPatchSourcePath = node_path.join(createPackageRoot, 'template-workspace', moduleFederationModernJsPatchPath);
238
+ const moduleFederationBridgeReactPatchPath = `patches/@module-federation__bridge-react@${MODULE_FEDERATION_VERSION}.patch`;
239
+ const moduleFederationBridgeReactPatchSourcePath = node_path.join(createPackageRoot, 'template-workspace', moduleFederationBridgeReactPatchPath);
240
+ const effectDeclarationPatchPath = 'patches/effect-schema-error-type-id.patch';
241
+ const effectDeclarationPatchSourcePath = node_path.join(createPackageRoot, 'template-workspace', effectDeclarationPatchPath);
242
+ const drizzleOrmDeclarationPatchPath = 'patches/drizzle-orm-ts7-strict-declarations.patch';
243
+ const drizzleOrmDeclarationPatchSourcePath = node_path.join(createPackageRoot, 'template-workspace', drizzleOrmDeclarationPatchPath);
186
244
  function updateGeneratedToolingDependencies(packageJson) {
187
245
  let changed = false;
188
246
  for (const section of [
@@ -201,10 +259,117 @@ function updateGeneratedToolingDependencies(packageJson) {
201
259
  }
202
260
  return changed;
203
261
  }
262
+ const cloudflareModernDeployCommand = 'ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy';
263
+ const cloudflareModernDeploySkipBuildCommand = `${cloudflareModernDeployCommand} --skip-build`;
264
+ const cloudflareWranglerDeployCommand = 'wrangler deploy --config .output/wrangler.json';
265
+ const cloudflareWranglerDeployInvalidSkipBuildCommand = `${cloudflareWranglerDeployCommand} --skip-build`;
266
+ function removeStaleBackendFederationCommandSegments(command) {
267
+ return command.replace(/\s+&&\s+node\s+\S*scripts\/generate-node-backend-federation\.m[ct]s(?:\s+--app\s+\S+)?(?:\s+--target\s+\S+)?(?=\s+&&|$)/gu, '');
268
+ }
269
+ const SHELL_ONLY_OMITTED_ROOT_SCRIPTS = [
270
+ 'node:backend-federation:generate',
271
+ 'node:proof',
272
+ 'zerops:materialize'
273
+ ];
274
+ const splitScriptSegments = (command)=>command.split('&&').map((segment)=>segment.trim()).filter((segment)=>segment.length > 0);
275
+ const scriptSegmentTarget = (segment)=>segment.replace(/^pnpm\s+/u, '').split(/\s+/u)[0] ?? segment;
276
+ const FRAMEWORK_CHECK_TARGETS = new Set([
277
+ 'format:check',
278
+ 'lint',
279
+ 'typecheck',
280
+ 'skills:check',
281
+ 'i18n:boundaries',
282
+ 'api:check',
283
+ 'contract:check',
284
+ 'node:backend-federation:generate',
285
+ 'node:proof',
286
+ 'performance:readiness',
287
+ 'bridge:check'
288
+ ]);
289
+ function mergeAggregateCheckScript(consumer, framework) {
290
+ const frameworkSegments = splitScriptSegments(framework);
291
+ const consumerExtras = splitScriptSegments(consumer).filter((segment)=>!FRAMEWORK_CHECK_TARGETS.has(scriptSegmentTarget(segment)));
292
+ return [
293
+ ...consumerExtras,
294
+ ...frameworkSegments
295
+ ].join(' && ');
296
+ }
297
+ function rewriteMigratedScriptReferences(scripts) {
298
+ let changed = false;
299
+ const pattern = new RegExp(`(scripts/(?:${migratedWorkspaceScriptBasenames.join('|')}))\\.mjs`, 'gu');
300
+ for (const [name, value] of Object.entries(scripts)){
301
+ if ('string' != typeof value) continue;
302
+ const next = value.replace(pattern, '$1.mts');
303
+ if (next !== value) {
304
+ scripts[name] = next;
305
+ changed = true;
306
+ }
307
+ }
308
+ return changed;
309
+ }
310
+ function updateGeneratedPackageScripts(packageJson, options = {}) {
311
+ const scripts = packageJson.scripts;
312
+ if (!scripts || 'object' != typeof scripts || Array.isArray(scripts)) return false;
313
+ let changed = false;
314
+ const apps = options.apps ?? [];
315
+ const shellOnly = options.shellOnly ?? false;
316
+ const app = apps.find((candidate)=>`${candidate.directory}/package.json` === options.relativePackageFile);
317
+ const isRootPackage = 'package.json' === options.relativePackageFile;
318
+ const expectedScripts = isRootPackage ? createWorkspaceRootPackageScripts(apps.filter((candidate)=>'shell' !== candidate.kind)) : app ? createWorkspaceAppPackageScripts(app) : void 0;
319
+ if (expectedScripts) for (const [name, value] of Object.entries(expectedScripts)){
320
+ if (isRootPackage && shellOnly && SHELL_ONLY_OMITTED_ROOT_SCRIPTS.includes(name)) {
321
+ if (name in scripts) {
322
+ delete scripts[name];
323
+ changed = true;
324
+ }
325
+ continue;
326
+ }
327
+ const nextValue = 'check' === name && 'string' == typeof scripts[name] ? mergeAggregateCheckScript(scripts[name], value) : value;
328
+ if (scripts[name] !== nextValue) {
329
+ scripts[name] = nextValue;
330
+ changed = true;
331
+ }
332
+ }
333
+ if (rewriteMigratedScriptReferences(scripts)) changed = true;
334
+ const build = scripts.build;
335
+ if ('string' == typeof build) {
336
+ const nextBuild = removeStaleBackendFederationCommandSegments(build);
337
+ if (nextBuild !== build) {
338
+ scripts.build = nextBuild;
339
+ changed = true;
340
+ }
341
+ }
342
+ const cloudflareBuild = scripts['cloudflare:build'];
343
+ if ('string' == typeof cloudflareBuild) {
344
+ let nextCloudflareBuild = removeStaleBackendFederationCommandSegments(cloudflareBuild);
345
+ if (nextCloudflareBuild.includes(cloudflareModernDeployCommand) && !nextCloudflareBuild.includes(cloudflareModernDeploySkipBuildCommand)) nextCloudflareBuild = nextCloudflareBuild.replace(cloudflareModernDeployCommand, cloudflareModernDeploySkipBuildCommand);
346
+ nextCloudflareBuild = nextCloudflareBuild.replace(/ && node \S*scripts\/generate-public-surface-assets\.m[ct]s --app [^&]+ --target dist(?= && ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy --skip-build)/u, '');
347
+ nextCloudflareBuild = nextCloudflareBuild.replace(/ && node \S*scripts\/verify-cloudflare-output\.m[ct]s(?: --app [^&]+)?/u, '');
348
+ if (nextCloudflareBuild !== cloudflareBuild) {
349
+ scripts['cloudflare:build'] = nextCloudflareBuild;
350
+ changed = true;
351
+ }
352
+ }
353
+ const cloudflareDeploy = scripts['cloudflare:deploy'];
354
+ if ('string' == typeof cloudflareDeploy && cloudflareDeploy.includes(cloudflareWranglerDeployInvalidSkipBuildCommand)) {
355
+ scripts['cloudflare:deploy'] = cloudflareDeploy.replace(cloudflareWranglerDeployInvalidSkipBuildCommand, cloudflareWranglerDeployCommand);
356
+ changed = true;
357
+ }
358
+ return changed;
359
+ }
204
360
  function normalizeStrictEffectApiMetadata(value) {
205
- const api = value.api;
206
- if (!api || 'object' != typeof api || Array.isArray(api)) return false;
207
361
  let changed = false;
362
+ const backendFederation = value.backendFederation;
363
+ if (backendFederation && 'object' == typeof backendFederation && !Array.isArray(backendFederation) && Object.hasOwn(backendFederation, 'entry')) {
364
+ delete backendFederation.entry;
365
+ changed = true;
366
+ }
367
+ const api = value.api;
368
+ if (!api || 'object' != typeof api || Array.isArray(api)) return changed;
369
+ if (void 0 !== api.backendFederation) {
370
+ delete api.backendFederation;
371
+ changed = true;
372
+ }
208
373
  const oldEffect = api.effect;
209
374
  if (oldEffect && 'object' == typeof oldEffect && !Array.isArray(oldEffect)) {
210
375
  if (void 0 === api.stem && 'string' == typeof oldEffect.stem) {
@@ -266,6 +431,38 @@ function normalizeStrictEffectApiMetadata(value) {
266
431
  }
267
432
  return changed;
268
433
  }
434
+ function removeGeneratedFileIfExists(io, relativePath) {
435
+ return io.remove(node_path.join(io.workspaceRoot, relativePath));
436
+ }
437
+ function removeStaleBackendFederationArtifacts(io, config) {
438
+ let changed = false;
439
+ for (const relativePath of [
440
+ "scripts/generate-node-backend-federation.mts",
441
+ "scripts/proof-node-backend-federation.mts",
442
+ "scripts/verify-cloudflare-output.mts"
443
+ ])changed = removeGeneratedFileIfExists(io, relativePath) || changed;
444
+ for (const app of workspaceAppsFromToolingConfig(config))changed = removeGeneratedFileIfExists(io, node_path.join(app.directory, 'api/backend-federation.ts')) || changed;
445
+ return changed;
446
+ }
447
+ function updateGeneratedZeropsArtifacts(io, config) {
448
+ const apps = workspaceAppsFromToolingConfig(config);
449
+ let changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, 'zerops.yaml'), `${createZeropsYaml(config.workspace.packageScope, apps)}\n`);
450
+ changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, "scripts/materialize-zerops-runtime.mjs"), createZeropsRuntimeMaterializationScript()) || changed;
451
+ return changed;
452
+ }
453
+ function updateGeneratedBuildIdentityModules(io, config) {
454
+ let changed = false;
455
+ for (const app of workspaceAppsFromToolingConfig(config)){
456
+ changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'src/ultramodern-build.ts'), createUltramodernBuildReexportModule()) || changed;
457
+ changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'shared/ultramodern-build.ts'), createUltramodernBuildModule(config.workspace.packageScope, app)) || changed;
458
+ }
459
+ return changed;
460
+ }
461
+ function updateGeneratedBackendFederationContractFiles(io, config) {
462
+ let changed = false;
463
+ for (const app of workspaceAppsFromToolingConfig(config))if (app.api) changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'api/backend-federation.ts'), createBackendFederationContractFile(app)) || changed;
464
+ return changed;
465
+ }
269
466
  function replaceYamlLine(source, pattern, replacement) {
270
467
  const updated = source.replace(pattern, replacement);
271
468
  return {
@@ -276,16 +473,17 @@ function replaceYamlLine(source, pattern, replacement) {
276
473
  function ensureYamlListItem(source, key, item) {
277
474
  const itemLine = ` - '${item}'`;
278
475
  const headerPattern = new RegExp(`^${key}:\\n(?:(?: - .+\\n)*)`, 'mu');
279
- const existingItemPattern = new RegExp(`^ - '${item.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&')}'$`, 'mu');
280
- if (existingItemPattern.test(source)) return {
281
- source,
282
- changed: false
283
- };
284
476
  const header = source.match(headerPattern);
285
- if (header) return {
286
- source: source.replace(headerPattern, `${header[0]}${itemLine}\n`),
287
- changed: true
288
- };
477
+ if (header) {
478
+ if (header[0].split('\n').includes(itemLine)) return {
479
+ source,
480
+ changed: false
481
+ };
482
+ return {
483
+ source: source.replace(headerPattern, `${header[0]}${itemLine}\n`),
484
+ changed: true
485
+ };
486
+ }
289
487
  const block = `${key}:\n${itemLine}\n`;
290
488
  const afterTrustPolicyIgnore = source.replace(/^(trustPolicyIgnoreAfter: .+\n)/mu, `$1${block}`);
291
489
  if (afterTrustPolicyIgnore !== source) return {
@@ -297,43 +495,203 @@ function ensureYamlListItem(source, key, item) {
297
495
  changed: true
298
496
  };
299
497
  }
300
- function updateGeneratedPnpmWorkspacePolicy(workspaceRoot) {
301
- const workspaceFile = node_path.join(workspaceRoot, 'pnpm-workspace.yaml');
498
+ function yamlEntryPattern(entryKey, scalar = false) {
499
+ const bareKey = entryKey.replace(/^['"]|['"]$/gu, '');
500
+ if (scalar) {
501
+ const esc = bareKey.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
502
+ return new RegExp(`^ {2}(?:'${esc}'|"${esc}"|${esc}): .+$`, 'gmu');
503
+ }
504
+ const packageName = bareKey.includes('@') ? bareKey.slice(0, bareKey.lastIndexOf('@')) : bareKey;
505
+ const esc = packageName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
506
+ return new RegExp(`^ {2}(?:'${esc}@[^']+'|"${esc}@[^"]+"|${esc}@[^:'"\\s]+): .+$`, 'gmu');
507
+ }
508
+ function upsertYamlEntry(source, key, entryLine, pattern) {
509
+ const linePattern = new RegExp(pattern.source, 'u');
510
+ const lines = source.split('\n');
511
+ let seen = false;
512
+ let changed = false;
513
+ const out = [];
514
+ for (const line of lines)if (linePattern.test(line)) {
515
+ if (seen) {
516
+ changed = true;
517
+ continue;
518
+ }
519
+ seen = true;
520
+ if (line !== entryLine) changed = true;
521
+ out.push(entryLine);
522
+ } else out.push(line);
523
+ if (seen) return {
524
+ source: out.join('\n'),
525
+ changed
526
+ };
527
+ const headerPattern = new RegExp(`^${key}:\\n(?:(?: .+\\n)*)`, 'mu');
528
+ const header = source.match(headerPattern);
529
+ if (header) {
530
+ if (header[0].split('\n').includes(entryLine)) return {
531
+ source,
532
+ changed: false
533
+ };
534
+ return {
535
+ source: source.replace(headerPattern, `${header[0]}${entryLine}\n`),
536
+ changed: true
537
+ };
538
+ }
539
+ return {
540
+ source: `${source.trimEnd()}\n${key}:\n${entryLine}\n`,
541
+ changed: true
542
+ };
543
+ }
544
+ function ensureYamlMapEntry(source, key, entryKey, value) {
545
+ return upsertYamlEntry(source, key, ` '${entryKey}': ${value}`, yamlEntryPattern(entryKey));
546
+ }
547
+ function ensureYamlScalarMapEntry(source, key, entryKey, value) {
548
+ return upsertYamlEntry(source, key, ` ${entryKey}: ${value}`, yamlEntryPattern(entryKey, true));
549
+ }
550
+ function removeYamlMapEntry(source, entryKey) {
551
+ const linePattern = new RegExp(yamlEntryPattern(entryKey).source, 'u');
552
+ const lines = source.split('\n');
553
+ let changed = false;
554
+ const out = [];
555
+ for (const line of lines){
556
+ if (linePattern.test(line)) {
557
+ changed = true;
558
+ continue;
559
+ }
560
+ out.push(line);
561
+ }
562
+ return changed ? {
563
+ source: out.join('\n'),
564
+ changed
565
+ } : {
566
+ source,
567
+ changed
568
+ };
569
+ }
570
+ function ensureGeneratedPatchFile(io, relativePatchPath, sourcePatchPath) {
571
+ const targetPath = node_path.join(io.workspaceRoot, relativePatchPath);
572
+ const patch = node_fs.readFileSync(sourcePatchPath, 'utf-8');
573
+ return io.write(targetPath, patch);
574
+ }
575
+ function removeGeneratedPatchFileIfUnchanged(io, relativePatchPath, sourcePatchPath) {
576
+ const targetPath = node_path.join(io.workspaceRoot, relativePatchPath);
577
+ if (!node_fs.existsSync(targetPath)) return false;
578
+ const patch = node_fs.readFileSync(sourcePatchPath, 'utf-8');
579
+ if (node_fs.readFileSync(targetPath, 'utf-8') !== patch) return false;
580
+ return io.remove(targetPath);
581
+ }
582
+ function workspaceUsesDependency(workspaceRoot, packageName) {
583
+ const packageJsonPaths = [
584
+ node_path.join(workspaceRoot, 'package.json')
585
+ ];
586
+ for (const workspaceDir of [
587
+ 'apps',
588
+ 'verticals',
589
+ 'packages'
590
+ ]){
591
+ const absoluteWorkspaceDir = node_path.join(workspaceRoot, workspaceDir);
592
+ if (node_fs.existsSync(absoluteWorkspaceDir)) for (const entry of node_fs.readdirSync(absoluteWorkspaceDir, {
593
+ withFileTypes: true
594
+ })){
595
+ if (!entry.isDirectory()) continue;
596
+ const packageJsonPath = node_path.join(absoluteWorkspaceDir, entry.name, 'package.json');
597
+ if (node_fs.existsSync(packageJsonPath)) packageJsonPaths.push(packageJsonPath);
598
+ }
599
+ }
600
+ for (const packageJsonPath of packageJsonPaths){
601
+ const packageJson = JSON.parse(node_fs.readFileSync(packageJsonPath, 'utf-8'));
602
+ for (const field of [
603
+ 'dependencies',
604
+ 'devDependencies',
605
+ 'peerDependencies',
606
+ 'optionalDependencies'
607
+ ]){
608
+ const dependencies = packageJson[field];
609
+ if (dependencies && 'object' == typeof dependencies) {
610
+ if (Object.prototype.hasOwnProperty.call(dependencies, packageName)) return true;
611
+ for (const specifier of Object.values(dependencies))if ('string' == typeof specifier && specifier.startsWith(`npm:${packageName}@`)) return true;
612
+ }
613
+ }
614
+ }
615
+ return false;
616
+ }
617
+ function ensureGeneratedDeclarationPatches(io, options) {
618
+ let changed = false;
619
+ changed = ensureGeneratedPatchFile(io, moduleFederationModernJsPatchPath, moduleFederationModernJsPatchSourcePath) || changed;
620
+ changed = ensureGeneratedPatchFile(io, moduleFederationBridgeReactPatchPath, moduleFederationBridgeReactPatchSourcePath) || changed;
621
+ changed = ensureGeneratedPatchFile(io, effectDeclarationPatchPath, effectDeclarationPatchSourcePath) || changed;
622
+ changed = options.includeDrizzleOrmPatch ? ensureGeneratedPatchFile(io, drizzleOrmDeclarationPatchPath, drizzleOrmDeclarationPatchSourcePath) || changed : removeGeneratedPatchFileIfUnchanged(io, drizzleOrmDeclarationPatchPath, drizzleOrmDeclarationPatchSourcePath) || changed;
623
+ return changed;
624
+ }
625
+ function updateGeneratedPnpmWorkspacePolicy(io) {
626
+ const workspaceFile = node_path.join(io.workspaceRoot, 'pnpm-workspace.yaml');
302
627
  if (!node_fs.existsSync(workspaceFile)) return false;
303
628
  let source = node_fs.readFileSync(workspaceFile, 'utf-8');
304
629
  let changed = false;
630
+ const usesDrizzleOrm = workspaceUsesDependency(io.workspaceRoot, 'drizzle-orm');
305
631
  const replacements = [
306
632
  [
307
633
  /^ {4}'@effect\/vitest>effect': .+$/mu,
308
634
  ` '@effect/vitest>effect': '${EFFECT_VERSION}'`
309
- ],
635
+ ]
636
+ ];
637
+ for (const [pattern, replacement] of replacements){
638
+ const result = replaceYamlLine(source, pattern, replacement);
639
+ source = result.source;
640
+ changed = result.changed || changed;
641
+ }
642
+ for (const [entryKey, version] of [
310
643
  [
311
- /^ {2}'@effect\/vitest': .+$/mu,
312
- ` '@effect/vitest': ${EFFECT_VITEST_VERSION}`
644
+ "'@effect/opentelemetry'",
645
+ EFFECT_VERSION
313
646
  ],
314
647
  [
315
- /^ {2}effect: .+$/mu,
316
- ` effect: ${EFFECT_VERSION}`
648
+ "'@effect/vitest'",
649
+ EFFECT_VITEST_VERSION
317
650
  ],
318
651
  [
319
- /^ {2}- '@effect\/opentelemetry@[^']+'$/mu,
320
- ` - '@effect/opentelemetry@${EFFECT_VERSION}'`
652
+ 'effect',
653
+ EFFECT_VERSION
321
654
  ]
322
- ];
323
- for (const [pattern, replacement] of replacements){
324
- const result = replaceYamlLine(source, pattern, replacement);
655
+ ]){
656
+ const result = ensureYamlScalarMapEntry(source, 'overrides', entryKey, version);
325
657
  source = result.source;
326
658
  changed = result.changed || changed;
327
659
  }
328
- const trustPolicyExclude = ensureYamlListItem(source, 'trustPolicyExclude', `@effect/opentelemetry@${EFFECT_VERSION}`);
329
- source = trustPolicyExclude.source;
330
- changed = trustPolicyExclude.changed || changed;
331
- if (changed) node_fs.writeFileSync(workspaceFile, source, 'utf-8');
660
+ const parcelWatcherBuildPolicy = ensureYamlScalarMapEntry(source, 'allowBuilds', "'@parcel/watcher'", 'true');
661
+ source = parcelWatcherBuildPolicy.source;
662
+ changed = parcelWatcherBuildPolicy.changed || changed;
663
+ for (const item of strictEffectPackageVersionPolicyExclusions){
664
+ const packageName = item.slice(0, item.lastIndexOf('@'));
665
+ const escapedPackageName = packageName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
666
+ const currentVersion = replaceYamlLine(source, new RegExp(`^ {2}- '${escapedPackageName}@[^']+'$`, 'gmu'), ` - '${item}'`);
667
+ source = currentVersion.source;
668
+ changed = currentVersion.changed || changed;
669
+ for (const policyKey of [
670
+ 'minimumReleaseAgeExclude',
671
+ 'trustPolicyExclude'
672
+ ]){
673
+ const policyExclude = ensureYamlListItem(source, policyKey, item);
674
+ source = policyExclude.source;
675
+ changed = policyExclude.changed || changed;
676
+ }
677
+ }
678
+ const effectPatch = ensureYamlMapEntry(source, 'patchedDependencies', `effect@${EFFECT_VERSION}`, effectDeclarationPatchPath);
679
+ source = effectPatch.source;
680
+ changed = effectPatch.changed || changed;
681
+ const moduleFederationModernJsPatch = ensureYamlMapEntry(source, 'patchedDependencies', `@module-federation/modern-js-v3@${MODULE_FEDERATION_VERSION}`, moduleFederationModernJsPatchPath);
682
+ source = moduleFederationModernJsPatch.source;
683
+ changed = moduleFederationModernJsPatch.changed || changed;
684
+ const moduleFederationBridgeReactPatch = ensureYamlMapEntry(source, 'patchedDependencies', `@module-federation/bridge-react@${MODULE_FEDERATION_VERSION}`, moduleFederationBridgeReactPatchPath);
685
+ source = moduleFederationBridgeReactPatch.source;
686
+ changed = moduleFederationBridgeReactPatch.changed || changed;
687
+ const drizzleOrmPatch = usesDrizzleOrm ? ensureYamlMapEntry(source, 'patchedDependencies', `drizzle-orm@${DRIZZLE_ORM_VERSION}`, drizzleOrmDeclarationPatchPath) : removeYamlMapEntry(source, `drizzle-orm@${DRIZZLE_ORM_VERSION}`);
688
+ source = drizzleOrmPatch.source;
689
+ changed = drizzleOrmPatch.changed || changed;
690
+ if (changed) io.write(workspaceFile, source);
332
691
  return changed;
333
692
  }
334
- function updateUltramodernConfig(workspaceRoot, packageSource) {
335
- const configPath = node_path.join(workspaceRoot, '.modernjs/ultramodern.json');
336
- const config = readJsonFile(configPath);
693
+ function updateUltramodernConfig(io, config, packageSource) {
694
+ const configPath = node_path.join(io.workspaceRoot, '.modernjs/ultramodern.json');
337
695
  config.packageSource = {
338
696
  strategy: packageSource.strategy,
339
697
  modernPackageVersion: packageSource.modernPackageVersion,
@@ -347,16 +705,147 @@ function updateUltramodernConfig(workspaceRoot, packageSource) {
347
705
  aliasPackageNamePrefix: packageSource.aliasPackageNamePrefix
348
706
  } : {}
349
707
  };
708
+ if (config.generator && 'object' == typeof config.generator && !Array.isArray(config.generator) && 'string' == typeof config.generator.version) config.generator.version = packageSource.modernPackageVersion;
350
709
  for (const app of config.topology?.apps ?? [])if (app && 'object' == typeof app && !Array.isArray(app)) normalizeStrictEffectApiMetadata(app);
351
- writeJsonFile(configPath, config);
710
+ writeJsonFile(io, configPath, config);
711
+ }
712
+ function writeJsonIfChanged(io, filePath, value) {
713
+ return io.write(filePath, `${JSON.stringify(value, null, 2)}\n`);
714
+ }
715
+ function writeTextIfChanged(io, filePath, value) {
716
+ return io.write(filePath, value);
717
+ }
718
+ function ensureGeneratedIgnoreRules(io) {
719
+ const gitignorePath = node_path.join(io.workspaceRoot, '.gitignore');
720
+ const existing = node_fs.existsSync(gitignorePath) ? node_fs.readFileSync(gitignorePath, 'utf-8') : '';
721
+ const lines = 0 === existing.trimEnd().length ? [] : existing.trimEnd().split(/\r?\n/u);
722
+ let changed = false;
723
+ for (const rule of [
724
+ '.mf/',
725
+ '**/.mf/',
726
+ 'dist-cloudflare/',
727
+ '.output/',
728
+ '**/.output/',
729
+ '.modern-js/',
730
+ '**/.modern-js/'
731
+ ])if (!lines.includes(rule)) {
732
+ lines.push(rule);
733
+ changed = true;
734
+ }
735
+ if (!changed) return false;
736
+ return io.write(gitignorePath, `${lines.join('\n')}\n`);
737
+ }
738
+ function updateGeneratedTypeScriptSurfaces(io, config) {
739
+ let changed = false;
740
+ const apps = workspaceAppsFromToolingConfig(config);
741
+ const remotes = apps.filter((app)=>'shell' !== app.kind);
742
+ changed = writeJsonIfChanged(io, node_path.join(io.workspaceRoot, 'tsconfig.base.json'), createTsConfigBase()) || changed;
743
+ changed = ensureGeneratedIgnoreRules(io) || changed;
744
+ for (const sharedPackage of [
745
+ 'packages/shared-contracts',
746
+ 'packages/shared-design-tokens'
747
+ ])changed = writeJsonIfChanged(io, node_path.join(io.workspaceRoot, sharedPackage, 'tsconfig.json'), createSharedPackageTsConfig(sharedPackage)) || changed;
748
+ for (const app of apps){
749
+ changed = writeJsonIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'tsconfig.json'), createAppTsConfig(app, remotes)) || changed;
750
+ changed = writeJsonIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'tsconfig.mf-types.json'), createAppMfTypesTsConfig(app)) || changed;
751
+ changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'src/modern-app-env.d.ts'), createAppEnvDts(app, remotes)) || changed;
752
+ }
753
+ return changed;
754
+ }
755
+ function updateGeneratedModernConfigs(io, config) {
756
+ let changed = false;
757
+ const apps = workspaceAppsFromToolingConfig(config);
758
+ const remotes = apps.filter((app)=>'shell' !== app.kind);
759
+ for (const app of apps)changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'modern.config.ts'), createAppModernConfig(config.workspace.packageScope, app, remotes)) || changed;
760
+ return changed;
761
+ }
762
+ function ensureGeneratedOxfmtIgnorePatterns(io) {
763
+ const configPath = node_path.join(io.workspaceRoot, 'oxfmt.config.ts');
764
+ if (!node_fs.existsSync(configPath)) return false;
765
+ const source = node_fs.readFileSync(configPath, 'utf-8');
766
+ const requiredPatterns = [
767
+ '.modernjs',
768
+ '.output',
769
+ '**/modern-tanstack/**',
770
+ '**/routeTree.gen.*'
771
+ ];
772
+ const warnUnparseable = ()=>{
773
+ const message = `Could not update oxfmt.config.ts ignorePatterns automatically; add these entries manually: ${requiredPatterns.join(', ')}.`;
774
+ if (io.dryRun) io.log(message);
775
+ else process.stderr.write(`[ultramodern] ${message}\n`);
776
+ };
777
+ const anchor = source.indexOf('ignorePatterns:');
778
+ if (-1 === anchor) {
779
+ warnUnparseable();
780
+ return false;
781
+ }
782
+ const openBracket = source.indexOf('[', anchor);
783
+ if (-1 === openBracket) {
784
+ warnUnparseable();
785
+ return false;
786
+ }
787
+ let depth = 0;
788
+ let closeBracket = -1;
789
+ let stringQuote;
790
+ for(let index = openBracket; index < source.length; index += 1){
791
+ const char = source[index];
792
+ if (stringQuote) {
793
+ if ('\\' === char) index += 1;
794
+ else if (char === stringQuote) stringQuote = void 0;
795
+ continue;
796
+ }
797
+ if ("'" === char || '"' === char || '`' === char) stringQuote = char;
798
+ else if ('[' === char) depth += 1;
799
+ else if (']' === char) {
800
+ depth -= 1;
801
+ if (0 === depth) {
802
+ closeBracket = index;
803
+ break;
804
+ }
805
+ }
806
+ }
807
+ if (-1 === closeBracket) {
808
+ warnUnparseable();
809
+ return false;
810
+ }
811
+ const body = source.slice(openBracket + 1, closeBracket);
812
+ if (body.includes('...')) {
813
+ warnUnparseable();
814
+ return false;
815
+ }
816
+ const literalPattern = /(['"`])((?:\\.|(?!\1).)*)\1/g;
817
+ const existing = new Set();
818
+ for (const match of body.matchAll(literalPattern))existing.add(match[2]);
819
+ const missing = requiredPatterns.filter((pattern)=>!existing.has(pattern));
820
+ if (0 === missing.length) return false;
821
+ const bodyLines = body.split('\n');
822
+ let indent = ' ';
823
+ let quote = "'";
824
+ for(let index = bodyLines.length - 1; index >= 0; index -= 1){
825
+ const literal = bodyLines[index].match(/^(\s*)(['"`])/u);
826
+ if (literal) {
827
+ indent = literal[1];
828
+ quote = literal[2];
829
+ break;
830
+ }
831
+ }
832
+ const head = source.slice(0, closeBracket);
833
+ const rest = source.slice(closeBracket);
834
+ const tailMatch = head.match(/(\r?\n[ \t]*)$/u);
835
+ const tail = tailMatch ? tailMatch[1] : '\n';
836
+ let bodyContent = tailMatch ? head.slice(0, head.length - tail.length) : head;
837
+ if (!/[[,]\s*$/u.test(bodyContent)) bodyContent = `${bodyContent},`;
838
+ const insertionLines = missing.map((pattern)=>`${indent}${quote}${pattern}${quote},`).join('\n');
839
+ const nextSource = `${bodyContent}\n${insertionLines}${tail}${rest}`;
840
+ return io.write(configPath, nextSource);
352
841
  }
353
- function updateReferenceTopology(workspaceRoot) {
354
- const topologyPath = node_path.join(workspaceRoot, 'topology/reference-topology.json');
842
+ function updateReferenceTopology(io) {
843
+ const topologyPath = node_path.join(io.workspaceRoot, 'topology/reference-topology.json');
355
844
  if (!node_fs.existsSync(topologyPath)) return false;
356
845
  const topology = readJsonFile(topologyPath);
357
846
  let changed = false;
358
847
  for (const vertical of topology.verticals ?? [])if (vertical && 'object' == typeof vertical && !Array.isArray(vertical)) changed = normalizeStrictEffectApiMetadata(vertical) || changed;
359
- if (changed) writeJsonFile(topologyPath, topology);
848
+ if (changed) writeJsonFile(io, topologyPath, topology);
360
849
  return changed;
361
850
  }
362
851
  function createMigrationPackageSource(args, current) {
@@ -409,21 +898,69 @@ function runPnpmLockfileRefresh(context) {
409
898
  function runMigrateStrictEffect(args, context) {
410
899
  if (args.includes('--help') || args.includes('-h')) {
411
900
  process.stdout.write(`Usage:
412
- modern-js-create ultramodern migrate-strict-effect --version <version> [--skip-install]
901
+ modern-js-create ultramodern migrate-strict-effect --version <version> [--dry-run] [--skip-install]
413
902
 
414
903
  Updates generated UltraModern package-source metadata, Modern package aliases,
415
904
  framework-owned toolchain pins, direct Effect API topology metadata, strict
416
- Effect pnpm overrides/trust policy, and the pnpm lockfile. Source code still
417
- has to pass pnpm api:check and pnpm contract:check.
905
+ Effect pnpm overrides/trust policy, framework-owned TypeScript config
906
+ surfaces, and the pnpm lockfile. Source code still has to pass pnpm api:check
907
+ and pnpm contract:check.
908
+
909
+ When the compact config is absent but legacy UltraModern 3.2 metadata is
910
+ present, the compact config is synthesized from it first. Shell-only
911
+ workspaces skip the backend-federation and Zerops runtime stages. Pass
912
+ --dry-run to print the planned filesystem changes without writing anything
913
+ (implies --skip-install).
418
914
  `);
419
915
  return 0;
420
916
  }
421
- const current = readUltramodernConfig(context.workspaceRoot);
917
+ const dryRun = hasFlag(args, '--dry-run');
918
+ const skipInstall = dryRun || hasFlag(args, '--skip-install');
919
+ const io = createMigrationIo(context.workspaceRoot, dryRun);
920
+ const compactPath = node_path.join(io.workspaceRoot, '.modernjs/ultramodern.json');
921
+ let raw;
922
+ if (node_fs.existsSync(compactPath)) raw = readJsonFile(compactPath);
923
+ else {
924
+ const synthesized = synthesizeCompactUltramodernConfig(io.workspaceRoot);
925
+ if (!synthesized) throw new Error("Missing .modernjs/ultramodern.json and no legacy UltraModern metadata (.modernjs/ultramodern-generated-contract.json) was found to synthesize it from.");
926
+ raw = synthesized.compact;
927
+ io.write(compactPath, `${JSON.stringify(raw, null, 2)}\n`);
928
+ io.log(`Synthesized .modernjs/ultramodern.json from legacy metadata: ${synthesized.sources.join(', ')}.`);
929
+ if (synthesized.missing.length > 0) io.log(`Legacy metadata not found (using defaults): ${synthesized.missing.join(', ')}.`);
930
+ }
931
+ const current = normalizeCompactUltramodernConfig(io.workspaceRoot, raw);
422
932
  const packageSource = createMigrationPackageSource(args, current);
423
- updateUltramodernConfig(context.workspaceRoot, packageSource);
424
- updateReferenceTopology(context.workspaceRoot);
425
- for (const relativePackageFile of listWorkspacePackageFiles(context.workspaceRoot)){
426
- const packageFile = node_path.join(context.workspaceRoot, relativePackageFile);
933
+ updateUltramodernConfig(io, raw, packageSource);
934
+ updateReferenceTopology(io);
935
+ const migrated = normalizeCompactUltramodernConfig(io.workspaceRoot, raw);
936
+ const migratedApps = workspaceAppsFromToolingConfig(migrated);
937
+ const shellOnly = !migrated.topology.apps.some((app)=>app.api);
938
+ if (shellOnly) {
939
+ io.log('Shell-only workspace: skipping backend-federation and Zerops runtime stages.');
940
+ for (const relativePath of [
941
+ "scripts/generate-node-backend-federation.mts",
942
+ "scripts/generate-node-backend-federation.mjs",
943
+ "scripts/proof-node-backend-federation.mts",
944
+ "scripts/proof-node-backend-federation.mjs",
945
+ "scripts/materialize-zerops-runtime.mjs",
946
+ 'zerops.yaml'
947
+ ])removeGeneratedFileIfExists(io, relativePath);
948
+ } else {
949
+ removeStaleBackendFederationArtifacts(io, migrated);
950
+ updateGeneratedZeropsArtifacts(io, migrated);
951
+ updateGeneratedBackendFederationContractFiles(io, migrated);
952
+ }
953
+ for (const artifact of migratedWorkspaceScriptArtifacts({
954
+ shellOnly
955
+ })){
956
+ if (artifact.legacyPath) io.remove(node_path.join(io.workspaceRoot, artifact.legacyPath));
957
+ io.write(node_path.join(io.workspaceRoot, artifact.relativePath), artifact.content);
958
+ }
959
+ updateGeneratedBuildIdentityModules(io, migrated);
960
+ updateGeneratedTypeScriptSurfaces(io, migrated);
961
+ updateGeneratedModernConfigs(io, migrated);
962
+ for (const relativePackageFile of listWorkspacePackageFiles(io.workspaceRoot)){
963
+ const packageFile = node_path.join(io.workspaceRoot, relativePackageFile);
427
964
  const packageJson = readJsonFile(packageFile);
428
965
  if ('package.json' === relativePackageFile) {
429
966
  packageJson.modernjs ??= {};
@@ -432,17 +969,29 @@ has to pass pnpm api:check and pnpm contract:check.
432
969
  config: './.modernjs/ultramodern.json'
433
970
  };
434
971
  }
435
- const modernDependenciesChanged = updateModernDependencies(packageJson, packageSource);
436
- const toolingDependenciesChanged = updateGeneratedToolingDependencies(packageJson);
437
- const changed = modernDependenciesChanged || toolingDependenciesChanged;
438
- if (changed) writeJsonFile(packageFile, packageJson);
439
- else if ('package.json' === relativePackageFile) writeJsonFile(packageFile, packageJson);
440
- }
441
- updateGeneratedPnpmWorkspacePolicy(context.workspaceRoot);
442
- if (!hasFlag(args, '--skip-install')) {
972
+ updateModernDependencies(packageJson, packageSource);
973
+ updateGeneratedToolingDependencies(packageJson);
974
+ updateGeneratedPackageScripts(packageJson, {
975
+ relativePackageFile,
976
+ apps: migratedApps,
977
+ shellOnly
978
+ });
979
+ writeJsonFile(io, packageFile, packageJson);
980
+ }
981
+ updateGeneratedPnpmWorkspacePolicy(io);
982
+ ensureGeneratedDeclarationPatches(io, {
983
+ includeDrizzleOrmPatch: workspaceUsesDependency(io.workspaceRoot, 'drizzle-orm')
984
+ });
985
+ ensureGeneratedOxfmtIgnorePatterns(io);
986
+ if (!skipInstall) {
443
987
  const status = runPnpmLockfileRefresh(context);
444
988
  if (0 !== status) return status;
445
989
  }
990
+ if (dryRun) {
991
+ for (const line of io.plan)process.stdout.write(`${line}\n`);
992
+ process.stdout.write(`[dry-run] migrate-strict-effect would migrate UltraModern strict Effect metadata to ${packageSource.modernPackageVersion}.\n`);
993
+ return 0;
994
+ }
446
995
  process.stdout.write(`UltraModern strict Effect metadata migrated to ${packageSource.modernPackageVersion}. Run pnpm api:check && pnpm contract:check next.\n`);
447
996
  return 0;
448
997
  }
@@ -455,6 +1004,154 @@ function runSkills(args, context) {
455
1004
  ], context);
456
1005
  throw new Error('Usage: modern-js-create ultramodern skills <install|check>');
457
1006
  }
1007
+ const resolveCloudflareOutputVerifyTargets = (args, context)=>{
1008
+ const outputDirectory = readOption(args, '--output');
1009
+ const appId = readOption(args, '--app');
1010
+ if (outputDirectory && appId) throw new Error('Use either --app or --output, not both.');
1011
+ const targets = outputDirectory ? [
1012
+ {
1013
+ label: outputDirectory,
1014
+ outputDirectory: node_path.resolve(context.invocationCwd, outputDirectory)
1015
+ }
1016
+ ] : workspaceAppsFromToolingConfig(readUltramodernConfig(context.workspaceRoot)).filter((app)=>!appId || app.id === appId).map((app)=>({
1017
+ label: app.id,
1018
+ outputDirectory: node_path.join(context.workspaceRoot, app.directory, '.output')
1019
+ }));
1020
+ if (0 === targets.length) throw new Error(`No generated UltraModern app matched ${appId}.`);
1021
+ return targets;
1022
+ };
1023
+ const renderCloudflareOutputVerifyModule = ({ workspaceRoot, targets, scanRoots, importWorker })=>`
1024
+ import { createRequire } from 'node:module';
1025
+ import path from 'node:path';
1026
+
1027
+ const workspaceRoot = ${JSON.stringify(workspaceRoot)};
1028
+ const targets = ${JSON.stringify(targets, null, 2)};
1029
+ const scanRoots = ${JSON.stringify(scanRoots)};
1030
+ const verifierRequire = createRequire(path.join(workspaceRoot, 'package.json'));
1031
+ const {
1032
+ verifyCloudflareOutput,
1033
+ verifyCloudflareOutputMutationPolicy,
1034
+ } = verifierRequire('@modern-js/app-tools/cloudflare-output-verifier');
1035
+
1036
+ let failed = false;
1037
+ for (const target of targets) {
1038
+ const result = await verifyCloudflareOutput({
1039
+ outputDirectory: target.outputDirectory,
1040
+ importWorker: ${JSON.stringify(importWorker)},
1041
+ });
1042
+ if (result.ok) {
1043
+ console.log(\`[ultramodern] Cloudflare output verified: \${target.label}\`);
1044
+ } else {
1045
+ failed = true;
1046
+ console.error(\`[ultramodern] Cloudflare output failed: \${target.label}\`);
1047
+ for (const issue of result.issues) {
1048
+ console.error(\`- \${issue.code}: \${issue.message}\${issue.path ? \` (\${issue.path})\` : ''}\`);
1049
+ }
1050
+ }
1051
+ }
1052
+
1053
+ if (scanRoots.length > 0) {
1054
+ const policyResult = await verifyCloudflareOutputMutationPolicy({ scanRoots });
1055
+ if (!policyResult.ok) {
1056
+ failed = true;
1057
+ console.error('[ultramodern] generated-output mutation policy failed');
1058
+ for (const issue of policyResult.issues) {
1059
+ console.error(\`- \${issue.code}: \${issue.message}\${issue.path ? \` (\${issue.path})\` : ''}\`);
1060
+ }
1061
+ }
1062
+ }
1063
+
1064
+ process.exit(failed ? 1 : 0);
1065
+ `;
1066
+ function runCloudflareOutputVerify(args, context) {
1067
+ if (args.includes('--help') || args.includes('-h')) {
1068
+ process.stdout.write(`Usage:
1069
+ modern-js-create ultramodern cloudflare-output-verify [--app <id> | --output <dir>] [--no-import-worker] [--no-source-scan]
1070
+
1071
+ Verifies generated Cloudflare output against the UltraModern worker contract.
1072
+ Without --app or --output, every generated workspace app is verified.
1073
+ `);
1074
+ return 0;
1075
+ }
1076
+ const source = renderCloudflareOutputVerifyModule({
1077
+ workspaceRoot: context.workspaceRoot,
1078
+ targets: resolveCloudflareOutputVerifyTargets(args, context),
1079
+ scanRoots: hasFlag(args, '--no-source-scan') ? [] : [
1080
+ context.workspaceRoot
1081
+ ],
1082
+ importWorker: !hasFlag(args, '--no-import-worker')
1083
+ });
1084
+ return runRenderedModule(source, context);
1085
+ }
1086
+ const resolveRoutesGenerateTargets = (args, context)=>{
1087
+ const appId = readOption(args, '--app');
1088
+ const targets = workspaceAppsFromToolingConfig(readUltramodernConfig(context.workspaceRoot)).filter((app)=>!appId || app.id === appId).map((app)=>({
1089
+ label: app.id,
1090
+ appDirectory: node_path.join(context.workspaceRoot, app.directory)
1091
+ }));
1092
+ if (0 === targets.length) throw new Error(`No generated UltraModern app matched ${appId ?? '<any>'}.`);
1093
+ return targets;
1094
+ };
1095
+ const renderRoutesGenerateModule = ({ workspaceRoot, targets })=>`
1096
+ import { createRequire } from 'node:module';
1097
+ import path from 'node:path';
1098
+ import { pathToFileURL } from 'node:url';
1099
+
1100
+ const workspaceRoot = ${JSON.stringify(workspaceRoot)};
1101
+ const targets = ${JSON.stringify(targets, null, 2)};
1102
+ const appRequire = createRequire(path.join(workspaceRoot, 'package.json'));
1103
+ const pluginUrl = pathToFileURL(
1104
+ appRequire.resolve('@modern-js/plugin-tanstack'),
1105
+ ).href;
1106
+ const { generateTanstackRouteArtifacts } = await import(pluginUrl);
1107
+
1108
+ let failed = false;
1109
+ // Sequential — the app-tools cli singleton is not re-entrant.
1110
+ for (const target of targets) {
1111
+ try {
1112
+ await generateTanstackRouteArtifacts({ appDirectory: target.appDirectory });
1113
+ console.log(\`[ultramodern] TanStack route artifacts generated: \${target.label}\`);
1114
+ } catch (error) {
1115
+ failed = true;
1116
+ console.error(\`[ultramodern] TanStack route generation failed: \${target.label}\`);
1117
+ // Print the full underlying failure, including the cause chain. The
1118
+ // route-generate crash is often an opaque node error thrown deep inside
1119
+ // app-tools/plugin (e.g. a path TypeError), so surfacing only
1120
+ // \`error.message\` swallows the stack that points at the real culprit.
1121
+ let current = error;
1122
+ let depth = 0;
1123
+ while (current) {
1124
+ const label = depth === 0 ? '-' : ' caused by:';
1125
+ const detail =
1126
+ current instanceof Error
1127
+ ? current.stack ?? \`\${current.name}: \${current.message}\`
1128
+ : String(current);
1129
+ console.error(\`\${label} \${detail}\`);
1130
+ current = current instanceof Error ? current.cause : undefined;
1131
+ depth += 1;
1132
+ }
1133
+ }
1134
+ }
1135
+
1136
+ process.exit(failed ? 1 : 0);
1137
+ `;
1138
+ function runRoutesGenerate(args, context) {
1139
+ if (args.includes('--help') || args.includes('-h')) {
1140
+ process.stdout.write(`Usage:
1141
+ modern-js-create ultramodern routes-generate [--app <id>]
1142
+
1143
+ Regenerates TanStack route artifacts (router.gen.ts, register.gen.d.ts) for
1144
+ generated UltraModern apps without running dev or build. Without --app, every
1145
+ generated workspace app is regenerated.
1146
+ `);
1147
+ return 0;
1148
+ }
1149
+ const source = renderRoutesGenerateModule({
1150
+ workspaceRoot: context.workspaceRoot,
1151
+ targets: resolveRoutesGenerateTargets(args, context)
1152
+ });
1153
+ return runRenderedModule(source, context);
1154
+ }
458
1155
  async function runUltramodernToolingCli(args, workspaceRoot = process.env.ULTRAMODERN_WORKSPACE_ROOT ?? process.cwd()) {
459
1156
  try {
460
1157
  const [command, ...rest] = args;
@@ -468,25 +1165,25 @@ async function runUltramodernToolingCli(args, workspaceRoot = process.env.ULTRAM
468
1165
  case '-h':
469
1166
  printHelp();
470
1167
  return 0;
471
- case 'validate':
1168
+ case GENERATED_TOOLING_COMMANDS.validate.command:
472
1169
  return runValidate(context);
473
- case 'typecheck':
474
- return spawnNodeScript("templates/workspace-scripts/ultramodern-typecheck.mjs", rest, context, {
475
- cwd: context.invocationCwd
476
- });
477
- case 'mf-types':
1170
+ case GENERATED_TOOLING_COMMANDS.mfTypes.command:
478
1171
  return runMfTypes(rest, context);
479
- case 'migrate-strict-effect':
1172
+ case GENERATED_TOOLING_COMMANDS.migrateStrictEffect.command:
480
1173
  return runMigrateStrictEffect(rest, context);
481
- case 'public-surface':
482
- return spawnNodeScript("templates/workspace-scripts/generate-public-surface-assets.mjs", rest, context);
483
- case 'cloudflare-proof':
484
- return spawnNodeScript("templates/workspace-scripts/proof-cloudflare-version.mjs", rest, context);
485
- case 'performance-readiness':
486
- return spawnNodeScript("templates/workspace-scripts/ultramodern-performance-readiness.mjs", rest, context);
1174
+ case GENERATED_TOOLING_COMMANDS.cloudflareOutputVerify.command:
1175
+ return runCloudflareOutputVerify(rest, context);
1176
+ case GENERATED_TOOLING_COMMANDS.routesGenerate.command:
1177
+ return runRoutesGenerate(rest, context);
1178
+ case 'sync-delivery-unit':
1179
+ return runSyncDeliveryUnit(rest, context);
487
1180
  case 'skills':
488
1181
  return runSkills(rest, context);
489
1182
  default:
1183
+ {
1184
+ const templateBackedStatus = runTemplateBackedToolingCommand(command ?? '', rest, context);
1185
+ if (void 0 !== templateBackedStatus) return templateBackedStatus;
1186
+ }
490
1187
  throw new Error(`Unknown UltraModern command: ${command}`);
491
1188
  }
492
1189
  } catch (error) {