@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
@@ -6,28 +6,30 @@ import { fileURLToPath } from "node:url";
6
6
  import { resolveCreatePackageRoot } from "../create-package-root.js";
7
7
  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";
8
8
  import { createAppEnvDts } from "../ultramodern-workspace/app-files.js";
9
+ import { createBackendFederationContractFile } from "../ultramodern-workspace/backend-federation.js";
10
+ import { runSyncDeliveryUnit } from "../ultramodern-workspace/delivery-unit-sync.js";
9
11
  import { validateModuleFederationTypes } from "../ultramodern-workspace/mf-validation.js";
10
- import { createAppModernConfig } from "../ultramodern-workspace/module-federation.js";
12
+ import { createAppModernConfig, createUltramodernBuildModule, createUltramodernBuildReexportModule } from "../ultramodern-workspace/module-federation.js";
11
13
  import { createAppMfTypesTsConfig, createAppTsConfig, createSharedPackageTsConfig, createTsConfigBase } from "../ultramodern-workspace/package-json.js";
14
+ import { GENERATED_TOOLING_COMMANDS, generatedToolingCommandList, generatedToolingCommands } from "../ultramodern-workspace/tooling-command-catalog.js";
12
15
  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";
13
- import { createWorkspaceValidationScript } from "../ultramodern-workspace/workspace-scripts.js";
14
- import { readUltramodernConfig, workspaceAppsFromToolingConfig } from "./config.js";
16
+ import { createWorkspaceAppPackageScripts, createWorkspaceRootPackageScripts } from "../ultramodern-workspace/workspace-script-plan.js";
17
+ import { createWorkspaceValidationScript, createZeropsRuntimeMaterializationScript, writeGeneratedToolWrapperScripts } from "../ultramodern-workspace/workspace-scripts.js";
18
+ import { createZeropsYaml } from "../ultramodern-workspace/zerops.js";
19
+ import { normalizeCompactUltramodernConfig, readUltramodernConfig, synthesizeCompactUltramodernConfig, workspaceAppsFromToolingConfig } from "./config.js";
15
20
  const commands_dirname = node_path.dirname(fileURLToPath(import.meta.url));
16
21
  const createPackageRoot = resolveCreatePackageRoot(commands_dirname);
17
22
  function printHelp() {
23
+ const commands = [
24
+ ...generatedToolingCommandList().map((command)=>` ${command}`),
25
+ ' skills install',
26
+ ' skills check'
27
+ ].join('\n');
18
28
  process.stdout.write(`Usage:
19
29
  modern-js-create ultramodern <command> [args]
20
30
 
21
31
  Commands:
22
- validate
23
- typecheck
24
- mf-types
25
- migrate-strict-effect
26
- public-surface
27
- cloudflare-proof
28
- performance-readiness
29
- skills install
30
- skills check
32
+ ${commands}
31
33
  `);
32
34
  }
33
35
  function spawnNodeScript(relativeScriptPath, args, context, options = {}) {
@@ -46,6 +48,13 @@ function spawnNodeScript(relativeScriptPath, args, context, options = {}) {
46
48
  if (result.error) throw result.error;
47
49
  return result.status ?? 1;
48
50
  }
51
+ function runTemplateBackedToolingCommand(command, args, context) {
52
+ const toolingCommand = generatedToolingCommands.find((candidate)=>candidate.command === command);
53
+ if (!toolingCommand?.templatePath) return;
54
+ return spawnNodeScript(toolingCommand.templatePath, args, context, {
55
+ cwd: 'invocation' === toolingCommand.cwd ? context.invocationCwd : context.workspaceRoot
56
+ });
57
+ }
49
58
  function runRenderedModule(source, context) {
50
59
  const tempDir = node_fs.mkdtempSync(node_path.join(node_os.tmpdir(), 'ultramodern-tool-'));
51
60
  const tempFile = node_path.join(tempDir, 'command.mjs');
@@ -92,11 +101,45 @@ const modernPackageNames = new Set([
92
101
  ...ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES,
93
102
  ...ULTRAMODERN_WORKSPACE_MODERN_PACKAGES
94
103
  ]);
104
+ function createMigrationIo(workspaceRoot, dryRun) {
105
+ const plan = [];
106
+ const rel = (p)=>(node_path.relative(workspaceRoot, p) || node_path.basename(p)).split(node_path.sep).join('/');
107
+ return {
108
+ workspaceRoot,
109
+ dryRun,
110
+ plan,
111
+ write (filePath, content) {
112
+ if (node_fs.existsSync(filePath) && node_fs.readFileSync(filePath, 'utf-8') === content) return false;
113
+ if (dryRun) {
114
+ plan.push(`[dry-run] would write ${rel(filePath)}`);
115
+ return true;
116
+ }
117
+ node_fs.mkdirSync(node_path.dirname(filePath), {
118
+ recursive: true
119
+ });
120
+ node_fs.writeFileSync(filePath, content, 'utf-8');
121
+ return true;
122
+ },
123
+ remove (filePath) {
124
+ if (!node_fs.existsSync(filePath)) return false;
125
+ if (dryRun) {
126
+ plan.push(`[dry-run] would delete ${rel(filePath)}`);
127
+ return true;
128
+ }
129
+ node_fs.rmSync(filePath);
130
+ return true;
131
+ },
132
+ log (message) {
133
+ if (dryRun) plan.push(`[dry-run] ${message}`);
134
+ else process.stdout.write(`[ultramodern] ${message}\n`);
135
+ }
136
+ };
137
+ }
95
138
  function readJsonFile(filePath) {
96
139
  return JSON.parse(node_fs.readFileSync(filePath, 'utf-8'));
97
140
  }
98
- function writeJsonFile(filePath, value) {
99
- node_fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
141
+ function writeJsonFile(io, filePath, value) {
142
+ return io.write(filePath, `${JSON.stringify(value, null, 2)}\n`);
100
143
  }
101
144
  function readOption(args, name) {
102
145
  const prefix = `${name}=`;
@@ -129,7 +172,7 @@ function listWorkspacePackageFiles(workspaceRoot) {
129
172
  withFileTypes: true
130
173
  })){
131
174
  if (!entry.isDirectory()) continue;
132
- const packageFile = node_path.join(directory, entry.name, 'package.json');
175
+ const packageFile = `${directory}/${entry.name}/package.json`;
133
176
  if (node_fs.existsSync(node_path.join(workspaceRoot, packageFile))) packageFiles.push(packageFile);
134
177
  }
135
178
  }
@@ -219,15 +262,36 @@ const cloudflareModernDeployCommand = 'ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=
219
262
  const cloudflareModernDeploySkipBuildCommand = `${cloudflareModernDeployCommand} --skip-build`;
220
263
  const cloudflareWranglerDeployCommand = 'wrangler deploy --config .output/wrangler.json';
221
264
  const cloudflareWranglerDeployInvalidSkipBuildCommand = `${cloudflareWranglerDeployCommand} --skip-build`;
222
- function updateGeneratedPackageScripts(packageJson) {
265
+ function removeStaleBackendFederationCommandSegments(command) {
266
+ 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, '');
267
+ }
268
+ function updateGeneratedPackageScripts(packageJson, options = {}) {
223
269
  const scripts = packageJson.scripts;
224
270
  if (!scripts || 'object' != typeof scripts || Array.isArray(scripts)) return false;
225
271
  let changed = false;
272
+ const apps = options.apps ?? [];
273
+ const app = apps.find((candidate)=>`${candidate.directory}/package.json` === options.relativePackageFile);
274
+ const expectedScripts = 'package.json' === options.relativePackageFile ? createWorkspaceRootPackageScripts(apps.filter((candidate)=>'shell' !== candidate.kind)) : app ? createWorkspaceAppPackageScripts(app) : void 0;
275
+ if (expectedScripts) {
276
+ for (const [name, value] of Object.entries(expectedScripts))if (scripts[name] !== value) {
277
+ scripts[name] = value;
278
+ changed = true;
279
+ }
280
+ }
281
+ const build = scripts.build;
282
+ if ('string' == typeof build) {
283
+ const nextBuild = removeStaleBackendFederationCommandSegments(build);
284
+ if (nextBuild !== build) {
285
+ scripts.build = nextBuild;
286
+ changed = true;
287
+ }
288
+ }
226
289
  const cloudflareBuild = scripts['cloudflare:build'];
227
290
  if ('string' == typeof cloudflareBuild) {
228
- let nextCloudflareBuild = cloudflareBuild;
291
+ let nextCloudflareBuild = removeStaleBackendFederationCommandSegments(cloudflareBuild);
229
292
  if (nextCloudflareBuild.includes(cloudflareModernDeployCommand) && !nextCloudflareBuild.includes(cloudflareModernDeploySkipBuildCommand)) nextCloudflareBuild = nextCloudflareBuild.replace(cloudflareModernDeployCommand, cloudflareModernDeploySkipBuildCommand);
230
293
  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, '');
294
+ nextCloudflareBuild = nextCloudflareBuild.replace(/ && node \S*scripts\/verify-cloudflare-output\.m[ct]s(?: --app [^&]+)?/u, '');
231
295
  if (nextCloudflareBuild !== cloudflareBuild) {
232
296
  scripts['cloudflare:build'] = nextCloudflareBuild;
233
297
  changed = true;
@@ -241,9 +305,18 @@ function updateGeneratedPackageScripts(packageJson) {
241
305
  return changed;
242
306
  }
243
307
  function normalizeStrictEffectApiMetadata(value) {
244
- const api = value.api;
245
- if (!api || 'object' != typeof api || Array.isArray(api)) return false;
246
308
  let changed = false;
309
+ const backendFederation = value.backendFederation;
310
+ if (backendFederation && 'object' == typeof backendFederation && !Array.isArray(backendFederation) && Object.hasOwn(backendFederation, 'entry')) {
311
+ delete backendFederation.entry;
312
+ changed = true;
313
+ }
314
+ const api = value.api;
315
+ if (!api || 'object' != typeof api || Array.isArray(api)) return changed;
316
+ if (void 0 !== api.backendFederation) {
317
+ delete api.backendFederation;
318
+ changed = true;
319
+ }
247
320
  const oldEffect = api.effect;
248
321
  if (oldEffect && 'object' == typeof oldEffect && !Array.isArray(oldEffect)) {
249
322
  if (void 0 === api.stem && 'string' == typeof oldEffect.stem) {
@@ -305,6 +378,38 @@ function normalizeStrictEffectApiMetadata(value) {
305
378
  }
306
379
  return changed;
307
380
  }
381
+ function removeGeneratedFileIfExists(io, relativePath) {
382
+ return io.remove(node_path.join(io.workspaceRoot, relativePath));
383
+ }
384
+ function removeStaleBackendFederationArtifacts(io, config) {
385
+ let changed = false;
386
+ for (const relativePath of [
387
+ "scripts/generate-node-backend-federation.mts",
388
+ "scripts/proof-node-backend-federation.mts",
389
+ "scripts/verify-cloudflare-output.mts"
390
+ ])changed = removeGeneratedFileIfExists(io, relativePath) || changed;
391
+ for (const app of workspaceAppsFromToolingConfig(config))changed = removeGeneratedFileIfExists(io, node_path.join(app.directory, 'api/backend-federation.ts')) || changed;
392
+ return changed;
393
+ }
394
+ function updateGeneratedZeropsArtifacts(io, config) {
395
+ const apps = workspaceAppsFromToolingConfig(config);
396
+ let changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, 'zerops.yaml'), `${createZeropsYaml(config.workspace.packageScope, apps)}\n`);
397
+ changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, "scripts/materialize-zerops-runtime.mjs"), createZeropsRuntimeMaterializationScript()) || changed;
398
+ return changed;
399
+ }
400
+ function updateGeneratedBuildIdentityModules(io, config) {
401
+ let changed = false;
402
+ for (const app of workspaceAppsFromToolingConfig(config)){
403
+ changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'src/ultramodern-build.ts'), createUltramodernBuildReexportModule()) || changed;
404
+ changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'shared/ultramodern-build.ts'), createUltramodernBuildModule(config.workspace.packageScope, app)) || changed;
405
+ }
406
+ return changed;
407
+ }
408
+ function updateGeneratedBackendFederationContractFiles(io, config) {
409
+ let changed = false;
410
+ 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;
411
+ return changed;
412
+ }
308
413
  function replaceYamlLine(source, pattern, replacement) {
309
414
  const updated = source.replace(pattern, replacement);
310
415
  return {
@@ -337,22 +442,35 @@ function ensureYamlListItem(source, key, item) {
337
442
  changed: true
338
443
  };
339
444
  }
340
- function ensureYamlMapEntry(source, key, entryKey, value) {
341
- const entryLine = ` '${entryKey}': ${value}`;
342
- const packageName = entryKey.includes('@') ? entryKey.slice(0, entryKey.lastIndexOf('@')) : entryKey;
343
- const escapedPackageName = packageName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
344
- const currentEntryPattern = new RegExp(`^ {2}'${escapedPackageName}@[^']+': .+$`, 'mu');
345
- const currentEntry = source.match(currentEntryPattern);
346
- if (currentEntry) {
347
- if (currentEntry[0] === entryLine) return {
348
- source,
349
- changed: false
350
- };
351
- return {
352
- source: source.replace(currentEntryPattern, entryLine),
353
- changed: true
354
- };
445
+ function yamlEntryPattern(entryKey, scalar = false) {
446
+ const bareKey = entryKey.replace(/^['"]|['"]$/gu, '');
447
+ if (scalar) {
448
+ const esc = bareKey.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
449
+ return new RegExp(`^ {2}(?:'${esc}'|"${esc}"|${esc}): .+$`, 'gmu');
355
450
  }
451
+ const packageName = bareKey.includes('@') ? bareKey.slice(0, bareKey.lastIndexOf('@')) : bareKey;
452
+ const esc = packageName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
453
+ return new RegExp(`^ {2}(?:'${esc}@[^']+'|"${esc}@[^"]+"|${esc}@[^:'"\\s]+): .+$`, 'gmu');
454
+ }
455
+ function upsertYamlEntry(source, key, entryLine, pattern) {
456
+ const linePattern = new RegExp(pattern.source, 'u');
457
+ const lines = source.split('\n');
458
+ let seen = false;
459
+ let changed = false;
460
+ const out = [];
461
+ for (const line of lines)if (linePattern.test(line)) {
462
+ if (seen) {
463
+ changed = true;
464
+ continue;
465
+ }
466
+ seen = true;
467
+ if (line !== entryLine) changed = true;
468
+ out.push(entryLine);
469
+ } else out.push(line);
470
+ if (seen) return {
471
+ source: out.join('\n'),
472
+ changed
473
+ };
356
474
  const headerPattern = new RegExp(`^${key}:\\n(?:(?: .+\\n)*)`, 'mu');
357
475
  const header = source.match(headerPattern);
358
476
  if (header) {
@@ -370,62 +488,43 @@ function ensureYamlMapEntry(source, key, entryKey, value) {
370
488
  changed: true
371
489
  };
372
490
  }
491
+ function ensureYamlMapEntry(source, key, entryKey, value) {
492
+ return upsertYamlEntry(source, key, ` '${entryKey}': ${value}`, yamlEntryPattern(entryKey));
493
+ }
373
494
  function ensureYamlScalarMapEntry(source, key, entryKey, value) {
374
- const entryLine = ` ${entryKey}: ${value}`;
375
- const escapedEntryKey = entryKey.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
376
- const currentEntryPattern = new RegExp(`^ {2}${escapedEntryKey}: .+$`, 'mu');
377
- const currentEntry = source.match(currentEntryPattern);
378
- if (currentEntry) {
379
- if (currentEntry[0] === entryLine) return {
380
- source,
381
- changed: false
382
- };
383
- return {
384
- source: source.replace(currentEntryPattern, entryLine),
385
- changed: true
386
- };
387
- }
388
- const headerPattern = new RegExp(`^${key}:\\n(?:(?: .+\\n)*)`, 'mu');
389
- const header = source.match(headerPattern);
390
- if (header) return {
391
- source: source.replace(headerPattern, `${header[0]}${entryLine}\n`),
392
- changed: true
393
- };
394
- return {
395
- source: `${source.trimEnd()}\n${key}:\n${entryLine}\n`,
396
- changed: true
397
- };
495
+ return upsertYamlEntry(source, key, ` ${entryKey}: ${value}`, yamlEntryPattern(entryKey, true));
398
496
  }
399
497
  function removeYamlMapEntry(source, entryKey) {
400
- const packageName = entryKey.includes('@') ? entryKey.slice(0, entryKey.lastIndexOf('@')) : entryKey;
401
- const escapedPackageName = packageName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
402
- const currentEntryPattern = new RegExp(`^ {2}'${escapedPackageName}@[^']+': .+\\n?`, 'mu');
403
- if (!currentEntryPattern.test(source)) return {
498
+ const linePattern = new RegExp(yamlEntryPattern(entryKey).source, 'u');
499
+ const lines = source.split('\n');
500
+ let changed = false;
501
+ const out = [];
502
+ for (const line of lines){
503
+ if (linePattern.test(line)) {
504
+ changed = true;
505
+ continue;
506
+ }
507
+ out.push(line);
508
+ }
509
+ return changed ? {
510
+ source: out.join('\n'),
511
+ changed
512
+ } : {
404
513
  source,
405
- changed: false
406
- };
407
- return {
408
- source: source.replace(currentEntryPattern, ''),
409
- changed: true
514
+ changed
410
515
  };
411
516
  }
412
- function ensureGeneratedPatchFile(workspaceRoot, relativePatchPath, sourcePatchPath) {
413
- const targetPath = node_path.join(workspaceRoot, relativePatchPath);
517
+ function ensureGeneratedPatchFile(io, relativePatchPath, sourcePatchPath) {
518
+ const targetPath = node_path.join(io.workspaceRoot, relativePatchPath);
414
519
  const patch = node_fs.readFileSync(sourcePatchPath, 'utf-8');
415
- if (node_fs.existsSync(targetPath) && node_fs.readFileSync(targetPath, 'utf-8') === patch) return false;
416
- node_fs.mkdirSync(node_path.dirname(targetPath), {
417
- recursive: true
418
- });
419
- node_fs.writeFileSync(targetPath, patch, 'utf-8');
420
- return true;
520
+ return io.write(targetPath, patch);
421
521
  }
422
- function removeGeneratedPatchFileIfUnchanged(workspaceRoot, relativePatchPath, sourcePatchPath) {
423
- const targetPath = node_path.join(workspaceRoot, relativePatchPath);
522
+ function removeGeneratedPatchFileIfUnchanged(io, relativePatchPath, sourcePatchPath) {
523
+ const targetPath = node_path.join(io.workspaceRoot, relativePatchPath);
424
524
  if (!node_fs.existsSync(targetPath)) return false;
425
525
  const patch = node_fs.readFileSync(sourcePatchPath, 'utf-8');
426
526
  if (node_fs.readFileSync(targetPath, 'utf-8') !== patch) return false;
427
- node_fs.rmSync(targetPath);
428
- return true;
527
+ return io.remove(targetPath);
429
528
  }
430
529
  function workspaceUsesDependency(workspaceRoot, packageName) {
431
530
  const packageJsonPaths = [
@@ -462,20 +561,20 @@ function workspaceUsesDependency(workspaceRoot, packageName) {
462
561
  }
463
562
  return false;
464
563
  }
465
- function ensureGeneratedDeclarationPatches(workspaceRoot, options) {
564
+ function ensureGeneratedDeclarationPatches(io, options) {
466
565
  let changed = false;
467
- changed = ensureGeneratedPatchFile(workspaceRoot, moduleFederationModernJsPatchPath, moduleFederationModernJsPatchSourcePath) || changed;
468
- changed = ensureGeneratedPatchFile(workspaceRoot, moduleFederationBridgeReactPatchPath, moduleFederationBridgeReactPatchSourcePath) || changed;
469
- changed = ensureGeneratedPatchFile(workspaceRoot, effectDeclarationPatchPath, effectDeclarationPatchSourcePath) || changed;
470
- changed = options.includeDrizzleOrmPatch ? ensureGeneratedPatchFile(workspaceRoot, drizzleOrmDeclarationPatchPath, drizzleOrmDeclarationPatchSourcePath) || changed : removeGeneratedPatchFileIfUnchanged(workspaceRoot, drizzleOrmDeclarationPatchPath, drizzleOrmDeclarationPatchSourcePath) || changed;
566
+ changed = ensureGeneratedPatchFile(io, moduleFederationModernJsPatchPath, moduleFederationModernJsPatchSourcePath) || changed;
567
+ changed = ensureGeneratedPatchFile(io, moduleFederationBridgeReactPatchPath, moduleFederationBridgeReactPatchSourcePath) || changed;
568
+ changed = ensureGeneratedPatchFile(io, effectDeclarationPatchPath, effectDeclarationPatchSourcePath) || changed;
569
+ changed = options.includeDrizzleOrmPatch ? ensureGeneratedPatchFile(io, drizzleOrmDeclarationPatchPath, drizzleOrmDeclarationPatchSourcePath) || changed : removeGeneratedPatchFileIfUnchanged(io, drizzleOrmDeclarationPatchPath, drizzleOrmDeclarationPatchSourcePath) || changed;
471
570
  return changed;
472
571
  }
473
- function updateGeneratedPnpmWorkspacePolicy(workspaceRoot) {
474
- const workspaceFile = node_path.join(workspaceRoot, 'pnpm-workspace.yaml');
572
+ function updateGeneratedPnpmWorkspacePolicy(io) {
573
+ const workspaceFile = node_path.join(io.workspaceRoot, 'pnpm-workspace.yaml');
475
574
  if (!node_fs.existsSync(workspaceFile)) return false;
476
575
  let source = node_fs.readFileSync(workspaceFile, 'utf-8');
477
576
  let changed = false;
478
- const usesDrizzleOrm = workspaceUsesDependency(workspaceRoot, 'drizzle-orm');
577
+ const usesDrizzleOrm = workspaceUsesDependency(io.workspaceRoot, 'drizzle-orm');
479
578
  const replacements = [
480
579
  [
481
580
  /^ {4}'@effect\/vitest>effect': .+$/mu,
@@ -535,12 +634,11 @@ function updateGeneratedPnpmWorkspacePolicy(workspaceRoot) {
535
634
  const drizzleOrmPatch = usesDrizzleOrm ? ensureYamlMapEntry(source, 'patchedDependencies', `drizzle-orm@${DRIZZLE_ORM_VERSION}`, drizzleOrmDeclarationPatchPath) : removeYamlMapEntry(source, `drizzle-orm@${DRIZZLE_ORM_VERSION}`);
536
635
  source = drizzleOrmPatch.source;
537
636
  changed = drizzleOrmPatch.changed || changed;
538
- if (changed) node_fs.writeFileSync(workspaceFile, source, 'utf-8');
637
+ if (changed) io.write(workspaceFile, source);
539
638
  return changed;
540
639
  }
541
- function updateUltramodernConfig(workspaceRoot, packageSource) {
542
- const configPath = node_path.join(workspaceRoot, '.modernjs/ultramodern.json');
543
- const config = readJsonFile(configPath);
640
+ function updateUltramodernConfig(io, config, packageSource) {
641
+ const configPath = node_path.join(io.workspaceRoot, '.modernjs/ultramodern.json');
544
642
  config.packageSource = {
545
643
  strategy: packageSource.strategy,
546
644
  modernPackageVersion: packageSource.modernPackageVersion,
@@ -555,73 +653,145 @@ function updateUltramodernConfig(workspaceRoot, packageSource) {
555
653
  } : {}
556
654
  };
557
655
  for (const app of config.topology?.apps ?? [])if (app && 'object' == typeof app && !Array.isArray(app)) normalizeStrictEffectApiMetadata(app);
558
- writeJsonFile(configPath, config);
656
+ writeJsonFile(io, configPath, config);
559
657
  }
560
- function writeJsonIfChanged(filePath, value) {
561
- const next = `${JSON.stringify(value, null, 2)}\n`;
562
- if (node_fs.existsSync(filePath) && node_fs.readFileSync(filePath, 'utf-8') === next) return false;
563
- node_fs.mkdirSync(node_path.dirname(filePath), {
564
- recursive: true
565
- });
566
- node_fs.writeFileSync(filePath, next, 'utf-8');
567
- return true;
658
+ function writeJsonIfChanged(io, filePath, value) {
659
+ return io.write(filePath, `${JSON.stringify(value, null, 2)}\n`);
568
660
  }
569
- function writeTextIfChanged(filePath, value) {
570
- if (node_fs.existsSync(filePath) && node_fs.readFileSync(filePath, 'utf-8') === value) return false;
571
- node_fs.mkdirSync(node_path.dirname(filePath), {
572
- recursive: true
573
- });
574
- node_fs.writeFileSync(filePath, value, 'utf-8');
575
- return true;
661
+ function writeTextIfChanged(io, filePath, value) {
662
+ return io.write(filePath, value);
576
663
  }
577
- function ensureGeneratedIgnoreRules(workspaceRoot) {
578
- const gitignorePath = node_path.join(workspaceRoot, '.gitignore');
664
+ function ensureGeneratedIgnoreRules(io) {
665
+ const gitignorePath = node_path.join(io.workspaceRoot, '.gitignore');
579
666
  const existing = node_fs.existsSync(gitignorePath) ? node_fs.readFileSync(gitignorePath, 'utf-8') : '';
580
667
  const lines = 0 === existing.trimEnd().length ? [] : existing.trimEnd().split(/\r?\n/u);
581
668
  let changed = false;
582
669
  for (const rule of [
583
670
  '.mf/',
584
671
  '**/.mf/',
585
- 'dist-cloudflare/'
672
+ 'dist-cloudflare/',
673
+ '.output/',
674
+ '**/.output/',
675
+ '.modern-js/',
676
+ '**/.modern-js/'
586
677
  ])if (!lines.includes(rule)) {
587
678
  lines.push(rule);
588
679
  changed = true;
589
680
  }
590
681
  if (!changed) return false;
591
- const next = `${lines.join('\n')}\n`;
592
- node_fs.writeFileSync(gitignorePath, next, 'utf-8');
593
- return true;
682
+ return io.write(gitignorePath, `${lines.join('\n')}\n`);
594
683
  }
595
- function updateGeneratedTypeScriptSurfaces(workspaceRoot, config) {
684
+ function updateGeneratedTypeScriptSurfaces(io, config) {
596
685
  let changed = false;
597
686
  const apps = workspaceAppsFromToolingConfig(config);
598
687
  const remotes = apps.filter((app)=>'shell' !== app.kind);
599
- changed = writeJsonIfChanged(node_path.join(workspaceRoot, 'tsconfig.base.json'), createTsConfigBase()) || changed;
600
- changed = ensureGeneratedIgnoreRules(workspaceRoot) || changed;
688
+ changed = writeJsonIfChanged(io, node_path.join(io.workspaceRoot, 'tsconfig.base.json'), createTsConfigBase()) || changed;
689
+ changed = ensureGeneratedIgnoreRules(io) || changed;
601
690
  for (const sharedPackage of [
602
691
  'packages/shared-contracts',
603
692
  'packages/shared-design-tokens'
604
- ])changed = writeJsonIfChanged(node_path.join(workspaceRoot, sharedPackage, 'tsconfig.json'), createSharedPackageTsConfig(sharedPackage)) || changed;
693
+ ])changed = writeJsonIfChanged(io, node_path.join(io.workspaceRoot, sharedPackage, 'tsconfig.json'), createSharedPackageTsConfig(sharedPackage)) || changed;
605
694
  for (const app of apps){
606
- changed = writeJsonIfChanged(node_path.join(workspaceRoot, app.directory, 'tsconfig.json'), createAppTsConfig(app, remotes)) || changed;
607
- changed = writeJsonIfChanged(node_path.join(workspaceRoot, app.directory, 'tsconfig.mf-types.json'), createAppMfTypesTsConfig(app)) || changed;
608
- changed = writeTextIfChanged(node_path.join(workspaceRoot, app.directory, 'src/modern-app-env.d.ts'), createAppEnvDts(app, remotes)) || changed;
695
+ changed = writeJsonIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'tsconfig.json'), createAppTsConfig(app, remotes)) || changed;
696
+ changed = writeJsonIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'tsconfig.mf-types.json'), createAppMfTypesTsConfig(app)) || changed;
697
+ changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'src/modern-app-env.d.ts'), createAppEnvDts(app, remotes)) || changed;
609
698
  }
610
699
  return changed;
611
700
  }
612
- function updateGeneratedModernConfigs(workspaceRoot, config) {
701
+ function updateGeneratedModernConfigs(io, config) {
613
702
  let changed = false;
614
703
  const apps = workspaceAppsFromToolingConfig(config);
615
- for (const app of apps)changed = writeTextIfChanged(node_path.join(workspaceRoot, app.directory, 'modern.config.ts'), createAppModernConfig(config.workspace.packageScope, app)) || changed;
704
+ const remotes = apps.filter((app)=>'shell' !== app.kind);
705
+ 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;
616
706
  return changed;
617
707
  }
618
- function updateReferenceTopology(workspaceRoot) {
619
- const topologyPath = node_path.join(workspaceRoot, 'topology/reference-topology.json');
708
+ function ensureGeneratedOxfmtIgnorePatterns(io) {
709
+ const configPath = node_path.join(io.workspaceRoot, 'oxfmt.config.ts');
710
+ if (!node_fs.existsSync(configPath)) return false;
711
+ const source = node_fs.readFileSync(configPath, 'utf-8');
712
+ const requiredPatterns = [
713
+ '.modernjs',
714
+ '.output',
715
+ '**/modern-tanstack/**',
716
+ '**/routeTree.gen.*'
717
+ ];
718
+ const warnUnparseable = ()=>{
719
+ const message = `Could not update oxfmt.config.ts ignorePatterns automatically; add these entries manually: ${requiredPatterns.join(', ')}.`;
720
+ if (io.dryRun) io.log(message);
721
+ else process.stderr.write(`[ultramodern] ${message}\n`);
722
+ };
723
+ const anchor = source.indexOf('ignorePatterns:');
724
+ if (-1 === anchor) {
725
+ warnUnparseable();
726
+ return false;
727
+ }
728
+ const openBracket = source.indexOf('[', anchor);
729
+ if (-1 === openBracket) {
730
+ warnUnparseable();
731
+ return false;
732
+ }
733
+ let depth = 0;
734
+ let closeBracket = -1;
735
+ let stringQuote;
736
+ for(let index = openBracket; index < source.length; index += 1){
737
+ const char = source[index];
738
+ if (stringQuote) {
739
+ if ('\\' === char) index += 1;
740
+ else if (char === stringQuote) stringQuote = void 0;
741
+ continue;
742
+ }
743
+ if ("'" === char || '"' === char || '`' === char) stringQuote = char;
744
+ else if ('[' === char) depth += 1;
745
+ else if (']' === char) {
746
+ depth -= 1;
747
+ if (0 === depth) {
748
+ closeBracket = index;
749
+ break;
750
+ }
751
+ }
752
+ }
753
+ if (-1 === closeBracket) {
754
+ warnUnparseable();
755
+ return false;
756
+ }
757
+ const body = source.slice(openBracket + 1, closeBracket);
758
+ if (body.includes('...')) {
759
+ warnUnparseable();
760
+ return false;
761
+ }
762
+ const literalPattern = /(['"`])((?:\\.|(?!\1).)*)\1/g;
763
+ const existing = new Set();
764
+ for (const match of body.matchAll(literalPattern))existing.add(match[2]);
765
+ const missing = requiredPatterns.filter((pattern)=>!existing.has(pattern));
766
+ if (0 === missing.length) return false;
767
+ const bodyLines = body.split('\n');
768
+ let indent = ' ';
769
+ let quote = "'";
770
+ for(let index = bodyLines.length - 1; index >= 0; index -= 1){
771
+ const literal = bodyLines[index].match(/^(\s*)(['"`])/u);
772
+ if (literal) {
773
+ indent = literal[1];
774
+ quote = literal[2];
775
+ break;
776
+ }
777
+ }
778
+ const head = source.slice(0, closeBracket);
779
+ const rest = source.slice(closeBracket);
780
+ const tailMatch = head.match(/(\r?\n[ \t]*)$/u);
781
+ const tail = tailMatch ? tailMatch[1] : '\n';
782
+ let bodyContent = tailMatch ? head.slice(0, head.length - tail.length) : head;
783
+ if (!/[[,]\s*$/u.test(bodyContent)) bodyContent = `${bodyContent},`;
784
+ const insertionLines = missing.map((pattern)=>`${indent}${quote}${pattern}${quote},`).join('\n');
785
+ const nextSource = `${bodyContent}\n${insertionLines}${tail}${rest}`;
786
+ return io.write(configPath, nextSource);
787
+ }
788
+ function updateReferenceTopology(io) {
789
+ const topologyPath = node_path.join(io.workspaceRoot, 'topology/reference-topology.json');
620
790
  if (!node_fs.existsSync(topologyPath)) return false;
621
791
  const topology = readJsonFile(topologyPath);
622
792
  let changed = false;
623
793
  for (const vertical of topology.verticals ?? [])if (vertical && 'object' == typeof vertical && !Array.isArray(vertical)) changed = normalizeStrictEffectApiMetadata(vertical) || changed;
624
- if (changed) writeJsonFile(topologyPath, topology);
794
+ if (changed) writeJsonFile(io, topologyPath, topology);
625
795
  return changed;
626
796
  }
627
797
  function createMigrationPackageSource(args, current) {
@@ -674,25 +844,56 @@ function runPnpmLockfileRefresh(context) {
674
844
  function runMigrateStrictEffect(args, context) {
675
845
  if (args.includes('--help') || args.includes('-h')) {
676
846
  process.stdout.write(`Usage:
677
- modern-js-create ultramodern migrate-strict-effect --version <version> [--skip-install]
847
+ modern-js-create ultramodern migrate-strict-effect --version <version> [--dry-run] [--skip-install]
678
848
 
679
849
  Updates generated UltraModern package-source metadata, Modern package aliases,
680
850
  framework-owned toolchain pins, direct Effect API topology metadata, strict
681
851
  Effect pnpm overrides/trust policy, framework-owned TypeScript config
682
852
  surfaces, and the pnpm lockfile. Source code still has to pass pnpm api:check
683
853
  and pnpm contract:check.
854
+
855
+ When the compact config is absent but legacy UltraModern 3.2 metadata is
856
+ present, the compact config is synthesized from it first. Shell-only
857
+ workspaces skip the backend-federation and Zerops runtime stages. Pass
858
+ --dry-run to print the planned filesystem changes without writing anything
859
+ (implies --skip-install).
684
860
  `);
685
861
  return 0;
686
862
  }
687
- const current = readUltramodernConfig(context.workspaceRoot);
863
+ const dryRun = hasFlag(args, '--dry-run');
864
+ const skipInstall = dryRun || hasFlag(args, '--skip-install');
865
+ const io = createMigrationIo(context.workspaceRoot, dryRun);
866
+ const compactPath = node_path.join(io.workspaceRoot, '.modernjs/ultramodern.json');
867
+ let raw;
868
+ if (node_fs.existsSync(compactPath)) raw = readJsonFile(compactPath);
869
+ else {
870
+ const synthesized = synthesizeCompactUltramodernConfig(io.workspaceRoot);
871
+ 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.");
872
+ raw = synthesized.compact;
873
+ io.write(compactPath, `${JSON.stringify(raw, null, 2)}\n`);
874
+ io.log(`Synthesized .modernjs/ultramodern.json from legacy metadata: ${synthesized.sources.join(', ')}.`);
875
+ if (synthesized.missing.length > 0) io.log(`Legacy metadata not found (using defaults): ${synthesized.missing.join(', ')}.`);
876
+ }
877
+ const current = normalizeCompactUltramodernConfig(io.workspaceRoot, raw);
688
878
  const packageSource = createMigrationPackageSource(args, current);
689
- updateUltramodernConfig(context.workspaceRoot, packageSource);
690
- updateReferenceTopology(context.workspaceRoot);
691
- const migrated = readUltramodernConfig(context.workspaceRoot);
692
- updateGeneratedTypeScriptSurfaces(context.workspaceRoot, migrated);
693
- updateGeneratedModernConfigs(context.workspaceRoot, migrated);
694
- for (const relativePackageFile of listWorkspacePackageFiles(context.workspaceRoot)){
695
- const packageFile = node_path.join(context.workspaceRoot, relativePackageFile);
879
+ updateUltramodernConfig(io, raw, packageSource);
880
+ updateReferenceTopology(io);
881
+ const migrated = normalizeCompactUltramodernConfig(io.workspaceRoot, raw);
882
+ const migratedApps = workspaceAppsFromToolingConfig(migrated);
883
+ const shellOnly = !migrated.topology.apps.some((app)=>app.api);
884
+ if (shellOnly) io.log('Shell-only workspace: skipping backend-federation and Zerops runtime stages.');
885
+ else {
886
+ removeStaleBackendFederationArtifacts(io, migrated);
887
+ updateGeneratedZeropsArtifacts(io, migrated);
888
+ updateGeneratedBackendFederationContractFiles(io, migrated);
889
+ }
890
+ if (dryRun) io.log("would refresh generated tool wrapper scripts under scripts/");
891
+ else writeGeneratedToolWrapperScripts(io.workspaceRoot);
892
+ updateGeneratedBuildIdentityModules(io, migrated);
893
+ updateGeneratedTypeScriptSurfaces(io, migrated);
894
+ updateGeneratedModernConfigs(io, migrated);
895
+ for (const relativePackageFile of listWorkspacePackageFiles(io.workspaceRoot)){
896
+ const packageFile = node_path.join(io.workspaceRoot, relativePackageFile);
696
897
  const packageJson = readJsonFile(packageFile);
697
898
  if ('package.json' === relativePackageFile) {
698
899
  packageJson.modernjs ??= {};
@@ -701,21 +902,28 @@ and pnpm contract:check.
701
902
  config: './.modernjs/ultramodern.json'
702
903
  };
703
904
  }
704
- const modernDependenciesChanged = updateModernDependencies(packageJson, packageSource);
705
- const toolingDependenciesChanged = updateGeneratedToolingDependencies(packageJson);
706
- const generatedScriptsChanged = updateGeneratedPackageScripts(packageJson);
707
- const changed = modernDependenciesChanged || toolingDependenciesChanged || generatedScriptsChanged;
708
- if (changed) writeJsonFile(packageFile, packageJson);
709
- else if ('package.json' === relativePackageFile) writeJsonFile(packageFile, packageJson);
710
- }
711
- updateGeneratedPnpmWorkspacePolicy(context.workspaceRoot);
712
- ensureGeneratedDeclarationPatches(context.workspaceRoot, {
713
- includeDrizzleOrmPatch: workspaceUsesDependency(context.workspaceRoot, 'drizzle-orm')
905
+ updateModernDependencies(packageJson, packageSource);
906
+ updateGeneratedToolingDependencies(packageJson);
907
+ updateGeneratedPackageScripts(packageJson, {
908
+ relativePackageFile,
909
+ apps: migratedApps
910
+ });
911
+ writeJsonFile(io, packageFile, packageJson);
912
+ }
913
+ updateGeneratedPnpmWorkspacePolicy(io);
914
+ ensureGeneratedDeclarationPatches(io, {
915
+ includeDrizzleOrmPatch: workspaceUsesDependency(io.workspaceRoot, 'drizzle-orm')
714
916
  });
715
- if (!hasFlag(args, '--skip-install')) {
917
+ ensureGeneratedOxfmtIgnorePatterns(io);
918
+ if (!skipInstall) {
716
919
  const status = runPnpmLockfileRefresh(context);
717
920
  if (0 !== status) return status;
718
921
  }
922
+ if (dryRun) {
923
+ for (const line of io.plan)process.stdout.write(`${line}\n`);
924
+ process.stdout.write(`[dry-run] migrate-strict-effect would migrate UltraModern strict Effect metadata to ${packageSource.modernPackageVersion}.\n`);
925
+ return 0;
926
+ }
719
927
  process.stdout.write(`UltraModern strict Effect metadata migrated to ${packageSource.modernPackageVersion}. Run pnpm api:check && pnpm contract:check next.\n`);
720
928
  return 0;
721
929
  }
@@ -728,6 +936,140 @@ function runSkills(args, context) {
728
936
  ], context);
729
937
  throw new Error('Usage: modern-js-create ultramodern skills <install|check>');
730
938
  }
939
+ const resolveCloudflareOutputVerifyTargets = (args, context)=>{
940
+ const outputDirectory = readOption(args, '--output');
941
+ const appId = readOption(args, '--app');
942
+ if (outputDirectory && appId) throw new Error('Use either --app or --output, not both.');
943
+ const targets = outputDirectory ? [
944
+ {
945
+ label: outputDirectory,
946
+ outputDirectory: node_path.resolve(context.invocationCwd, outputDirectory)
947
+ }
948
+ ] : workspaceAppsFromToolingConfig(readUltramodernConfig(context.workspaceRoot)).filter((app)=>!appId || app.id === appId).map((app)=>({
949
+ label: app.id,
950
+ outputDirectory: node_path.join(context.workspaceRoot, app.directory, '.output')
951
+ }));
952
+ if (0 === targets.length) throw new Error(`No generated UltraModern app matched ${appId}.`);
953
+ return targets;
954
+ };
955
+ const renderCloudflareOutputVerifyModule = ({ workspaceRoot, targets, scanRoots, importWorker })=>`
956
+ import { createRequire } from 'node:module';
957
+ import path from 'node:path';
958
+
959
+ const workspaceRoot = ${JSON.stringify(workspaceRoot)};
960
+ const targets = ${JSON.stringify(targets, null, 2)};
961
+ const scanRoots = ${JSON.stringify(scanRoots)};
962
+ const verifierRequire = createRequire(path.join(workspaceRoot, 'package.json'));
963
+ const {
964
+ verifyCloudflareOutput,
965
+ verifyCloudflareOutputMutationPolicy,
966
+ } = verifierRequire('@modern-js/app-tools/cloudflare-output-verifier');
967
+
968
+ let failed = false;
969
+ for (const target of targets) {
970
+ const result = await verifyCloudflareOutput({
971
+ outputDirectory: target.outputDirectory,
972
+ importWorker: ${JSON.stringify(importWorker)},
973
+ });
974
+ if (result.ok) {
975
+ console.log(\`[ultramodern] Cloudflare output verified: \${target.label}\`);
976
+ } else {
977
+ failed = true;
978
+ console.error(\`[ultramodern] Cloudflare output failed: \${target.label}\`);
979
+ for (const issue of result.issues) {
980
+ console.error(\`- \${issue.code}: \${issue.message}\${issue.path ? \` (\${issue.path})\` : ''}\`);
981
+ }
982
+ }
983
+ }
984
+
985
+ if (scanRoots.length > 0) {
986
+ const policyResult = await verifyCloudflareOutputMutationPolicy({ scanRoots });
987
+ if (!policyResult.ok) {
988
+ failed = true;
989
+ console.error('[ultramodern] generated-output mutation policy failed');
990
+ for (const issue of policyResult.issues) {
991
+ console.error(\`- \${issue.code}: \${issue.message}\${issue.path ? \` (\${issue.path})\` : ''}\`);
992
+ }
993
+ }
994
+ }
995
+
996
+ process.exit(failed ? 1 : 0);
997
+ `;
998
+ function runCloudflareOutputVerify(args, context) {
999
+ if (args.includes('--help') || args.includes('-h')) {
1000
+ process.stdout.write(`Usage:
1001
+ modern-js-create ultramodern cloudflare-output-verify [--app <id> | --output <dir>] [--no-import-worker] [--no-source-scan]
1002
+
1003
+ Verifies generated Cloudflare output against the UltraModern worker contract.
1004
+ Without --app or --output, every generated workspace app is verified.
1005
+ `);
1006
+ return 0;
1007
+ }
1008
+ const source = renderCloudflareOutputVerifyModule({
1009
+ workspaceRoot: context.workspaceRoot,
1010
+ targets: resolveCloudflareOutputVerifyTargets(args, context),
1011
+ scanRoots: hasFlag(args, '--no-source-scan') ? [] : [
1012
+ context.workspaceRoot
1013
+ ],
1014
+ importWorker: !hasFlag(args, '--no-import-worker')
1015
+ });
1016
+ return runRenderedModule(source, context);
1017
+ }
1018
+ const resolveRoutesGenerateTargets = (args, context)=>{
1019
+ const appId = readOption(args, '--app');
1020
+ const targets = workspaceAppsFromToolingConfig(readUltramodernConfig(context.workspaceRoot)).filter((app)=>!appId || app.id === appId).map((app)=>({
1021
+ label: app.id,
1022
+ appDirectory: node_path.join(context.workspaceRoot, app.directory)
1023
+ }));
1024
+ if (0 === targets.length) throw new Error(`No generated UltraModern app matched ${appId ?? '<any>'}.`);
1025
+ return targets;
1026
+ };
1027
+ const renderRoutesGenerateModule = ({ workspaceRoot, targets })=>`
1028
+ import { createRequire } from 'node:module';
1029
+ import path from 'node:path';
1030
+ import { pathToFileURL } from 'node:url';
1031
+
1032
+ const workspaceRoot = ${JSON.stringify(workspaceRoot)};
1033
+ const targets = ${JSON.stringify(targets, null, 2)};
1034
+ const appRequire = createRequire(path.join(workspaceRoot, 'package.json'));
1035
+ const pluginUrl = pathToFileURL(
1036
+ appRequire.resolve('@modern-js/plugin-tanstack'),
1037
+ ).href;
1038
+ const { generateTanstackRouteArtifacts } = await import(pluginUrl);
1039
+
1040
+ let failed = false;
1041
+ // Sequential — the app-tools cli singleton is not re-entrant.
1042
+ for (const target of targets) {
1043
+ try {
1044
+ await generateTanstackRouteArtifacts({ appDirectory: target.appDirectory });
1045
+ console.log(\`[ultramodern] TanStack route artifacts generated: \${target.label}\`);
1046
+ } catch (error) {
1047
+ failed = true;
1048
+ const message = error instanceof Error ? error.message : String(error);
1049
+ console.error(\`[ultramodern] TanStack route generation failed: \${target.label}\`);
1050
+ console.error(\`- \${message}\`);
1051
+ }
1052
+ }
1053
+
1054
+ process.exit(failed ? 1 : 0);
1055
+ `;
1056
+ function runRoutesGenerate(args, context) {
1057
+ if (args.includes('--help') || args.includes('-h')) {
1058
+ process.stdout.write(`Usage:
1059
+ modern-js-create ultramodern routes-generate [--app <id>]
1060
+
1061
+ Regenerates TanStack route artifacts (router.gen.ts, register.gen.d.ts) for
1062
+ generated UltraModern apps without running dev or build. Without --app, every
1063
+ generated workspace app is regenerated.
1064
+ `);
1065
+ return 0;
1066
+ }
1067
+ const source = renderRoutesGenerateModule({
1068
+ workspaceRoot: context.workspaceRoot,
1069
+ targets: resolveRoutesGenerateTargets(args, context)
1070
+ });
1071
+ return runRenderedModule(source, context);
1072
+ }
731
1073
  async function runUltramodernToolingCli(args, workspaceRoot = process.env.ULTRAMODERN_WORKSPACE_ROOT ?? process.cwd()) {
732
1074
  try {
733
1075
  const [command, ...rest] = args;
@@ -741,25 +1083,25 @@ async function runUltramodernToolingCli(args, workspaceRoot = process.env.ULTRAM
741
1083
  case '-h':
742
1084
  printHelp();
743
1085
  return 0;
744
- case 'validate':
1086
+ case GENERATED_TOOLING_COMMANDS.validate.command:
745
1087
  return runValidate(context);
746
- case 'typecheck':
747
- return spawnNodeScript("templates/workspace-scripts/ultramodern-typecheck.mjs", rest, context, {
748
- cwd: context.invocationCwd
749
- });
750
- case 'mf-types':
1088
+ case GENERATED_TOOLING_COMMANDS.mfTypes.command:
751
1089
  return runMfTypes(rest, context);
752
- case 'migrate-strict-effect':
1090
+ case GENERATED_TOOLING_COMMANDS.migrateStrictEffect.command:
753
1091
  return runMigrateStrictEffect(rest, context);
754
- case 'public-surface':
755
- return spawnNodeScript("templates/workspace-scripts/generate-public-surface-assets.mjs", rest, context);
756
- case 'cloudflare-proof':
757
- return spawnNodeScript("templates/workspace-scripts/proof-cloudflare-version.mjs", rest, context);
758
- case 'performance-readiness':
759
- return spawnNodeScript("templates/workspace-scripts/ultramodern-performance-readiness.mjs", rest, context);
1092
+ case GENERATED_TOOLING_COMMANDS.cloudflareOutputVerify.command:
1093
+ return runCloudflareOutputVerify(rest, context);
1094
+ case GENERATED_TOOLING_COMMANDS.routesGenerate.command:
1095
+ return runRoutesGenerate(rest, context);
1096
+ case 'sync-delivery-unit':
1097
+ return runSyncDeliveryUnit(rest, context);
760
1098
  case 'skills':
761
1099
  return runSkills(rest, context);
762
1100
  default:
1101
+ {
1102
+ const templateBackedStatus = runTemplateBackedToolingCommand(command ?? '', rest, context);
1103
+ if (void 0 !== templateBackedStatus) return templateBackedStatus;
1104
+ }
763
1105
  throw new Error(`Unknown UltraModern command: ${command}`);
764
1106
  }
765
1107
  } catch (error) {