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