@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
@@ -53,28 +53,30 @@ const external_node_url_namespaceObject = require("node:url");
53
53
  const external_create_package_root_cjs_namespaceObject = require("../create-package-root.cjs");
54
54
  const external_ultramodern_package_source_cjs_namespaceObject = require("../ultramodern-package-source.cjs");
55
55
  const app_files_cjs_namespaceObject = require("../ultramodern-workspace/app-files.cjs");
56
+ const backend_federation_cjs_namespaceObject = require("../ultramodern-workspace/backend-federation.cjs");
57
+ const delivery_unit_sync_cjs_namespaceObject = require("../ultramodern-workspace/delivery-unit-sync.cjs");
56
58
  const mf_validation_cjs_namespaceObject = require("../ultramodern-workspace/mf-validation.cjs");
57
59
  const module_federation_cjs_namespaceObject = require("../ultramodern-workspace/module-federation.cjs");
58
60
  const package_json_cjs_namespaceObject = require("../ultramodern-workspace/package-json.cjs");
61
+ const tooling_command_catalog_cjs_namespaceObject = require("../ultramodern-workspace/tooling-command-catalog.cjs");
59
62
  const versions_cjs_namespaceObject = require("../ultramodern-workspace/versions.cjs");
63
+ const workspace_script_plan_cjs_namespaceObject = require("../ultramodern-workspace/workspace-script-plan.cjs");
60
64
  const workspace_scripts_cjs_namespaceObject = require("../ultramodern-workspace/workspace-scripts.cjs");
65
+ const zerops_cjs_namespaceObject = require("../ultramodern-workspace/zerops.cjs");
61
66
  const external_config_cjs_namespaceObject = require("./config.cjs");
62
67
  const commands_dirname = external_node_path_default().dirname((0, external_node_url_namespaceObject.fileURLToPath)(__rslib_import_meta_url__));
63
68
  const createPackageRoot = (0, external_create_package_root_cjs_namespaceObject.resolveCreatePackageRoot)(commands_dirname);
64
69
  function printHelp() {
70
+ const commands = [
71
+ ...(0, tooling_command_catalog_cjs_namespaceObject.generatedToolingCommandList)().map((command)=>` ${command}`),
72
+ ' skills install',
73
+ ' skills check'
74
+ ].join('\n');
65
75
  process.stdout.write(`Usage:
66
76
  modern-js-create ultramodern <command> [args]
67
77
 
68
78
  Commands:
69
- validate
70
- typecheck
71
- mf-types
72
- migrate-strict-effect
73
- public-surface
74
- cloudflare-proof
75
- performance-readiness
76
- skills install
77
- skills check
79
+ ${commands}
78
80
  `);
79
81
  }
80
82
  function spawnNodeScript(relativeScriptPath, args, context, options = {}) {
@@ -93,6 +95,13 @@ function spawnNodeScript(relativeScriptPath, args, context, options = {}) {
93
95
  if (result.error) throw result.error;
94
96
  return result.status ?? 1;
95
97
  }
98
+ function runTemplateBackedToolingCommand(command, args, context) {
99
+ const toolingCommand = tooling_command_catalog_cjs_namespaceObject.generatedToolingCommands.find((candidate)=>candidate.command === command);
100
+ if (!toolingCommand?.templatePath) return;
101
+ return spawnNodeScript(toolingCommand.templatePath, args, context, {
102
+ cwd: 'invocation' === toolingCommand.cwd ? context.invocationCwd : context.workspaceRoot
103
+ });
104
+ }
96
105
  function runRenderedModule(source, context) {
97
106
  const tempDir = external_node_fs_default().mkdtempSync(external_node_path_default().join(external_node_os_default().tmpdir(), 'ultramodern-tool-'));
98
107
  const tempFile = external_node_path_default().join(tempDir, 'command.mjs');
@@ -139,11 +148,45 @@ const modernPackageNames = new Set([
139
148
  ...external_ultramodern_package_source_cjs_namespaceObject.ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES,
140
149
  ...external_ultramodern_package_source_cjs_namespaceObject.ULTRAMODERN_WORKSPACE_MODERN_PACKAGES
141
150
  ]);
151
+ function createMigrationIo(workspaceRoot, dryRun) {
152
+ const plan = [];
153
+ const rel = (p)=>(external_node_path_default().relative(workspaceRoot, p) || external_node_path_default().basename(p)).split(external_node_path_default().sep).join('/');
154
+ return {
155
+ workspaceRoot,
156
+ dryRun,
157
+ plan,
158
+ write (filePath, content) {
159
+ if (external_node_fs_default().existsSync(filePath) && external_node_fs_default().readFileSync(filePath, 'utf-8') === content) return false;
160
+ if (dryRun) {
161
+ plan.push(`[dry-run] would write ${rel(filePath)}`);
162
+ return true;
163
+ }
164
+ external_node_fs_default().mkdirSync(external_node_path_default().dirname(filePath), {
165
+ recursive: true
166
+ });
167
+ external_node_fs_default().writeFileSync(filePath, content, 'utf-8');
168
+ return true;
169
+ },
170
+ remove (filePath) {
171
+ if (!external_node_fs_default().existsSync(filePath)) return false;
172
+ if (dryRun) {
173
+ plan.push(`[dry-run] would delete ${rel(filePath)}`);
174
+ return true;
175
+ }
176
+ external_node_fs_default().rmSync(filePath);
177
+ return true;
178
+ },
179
+ log (message) {
180
+ if (dryRun) plan.push(`[dry-run] ${message}`);
181
+ else process.stdout.write(`[ultramodern] ${message}\n`);
182
+ }
183
+ };
184
+ }
142
185
  function readJsonFile(filePath) {
143
186
  return JSON.parse(external_node_fs_default().readFileSync(filePath, 'utf-8'));
144
187
  }
145
- function writeJsonFile(filePath, value) {
146
- external_node_fs_default().writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
188
+ function writeJsonFile(io, filePath, value) {
189
+ return io.write(filePath, `${JSON.stringify(value, null, 2)}\n`);
147
190
  }
148
191
  function readOption(args, name) {
149
192
  const prefix = `${name}=`;
@@ -176,7 +219,7 @@ function listWorkspacePackageFiles(workspaceRoot) {
176
219
  withFileTypes: true
177
220
  })){
178
221
  if (!entry.isDirectory()) continue;
179
- const packageFile = external_node_path_default().join(directory, entry.name, 'package.json');
222
+ const packageFile = `${directory}/${entry.name}/package.json`;
180
223
  if (external_node_fs_default().existsSync(external_node_path_default().join(workspaceRoot, packageFile))) packageFiles.push(packageFile);
181
224
  }
182
225
  }
@@ -266,15 +309,36 @@ const cloudflareModernDeployCommand = 'ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=
266
309
  const cloudflareModernDeploySkipBuildCommand = `${cloudflareModernDeployCommand} --skip-build`;
267
310
  const cloudflareWranglerDeployCommand = 'wrangler deploy --config .output/wrangler.json';
268
311
  const cloudflareWranglerDeployInvalidSkipBuildCommand = `${cloudflareWranglerDeployCommand} --skip-build`;
269
- function updateGeneratedPackageScripts(packageJson) {
312
+ function removeStaleBackendFederationCommandSegments(command) {
313
+ 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, '');
314
+ }
315
+ function updateGeneratedPackageScripts(packageJson, options = {}) {
270
316
  const scripts = packageJson.scripts;
271
317
  if (!scripts || 'object' != typeof scripts || Array.isArray(scripts)) return false;
272
318
  let changed = false;
319
+ const apps = options.apps ?? [];
320
+ const app = apps.find((candidate)=>`${candidate.directory}/package.json` === options.relativePackageFile);
321
+ const expectedScripts = 'package.json' === options.relativePackageFile ? (0, workspace_script_plan_cjs_namespaceObject.createWorkspaceRootPackageScripts)(apps.filter((candidate)=>'shell' !== candidate.kind)) : app ? (0, workspace_script_plan_cjs_namespaceObject.createWorkspaceAppPackageScripts)(app) : void 0;
322
+ if (expectedScripts) {
323
+ for (const [name, value] of Object.entries(expectedScripts))if (scripts[name] !== value) {
324
+ scripts[name] = value;
325
+ changed = true;
326
+ }
327
+ }
328
+ const build = scripts.build;
329
+ if ('string' == typeof build) {
330
+ const nextBuild = removeStaleBackendFederationCommandSegments(build);
331
+ if (nextBuild !== build) {
332
+ scripts.build = nextBuild;
333
+ changed = true;
334
+ }
335
+ }
273
336
  const cloudflareBuild = scripts['cloudflare:build'];
274
337
  if ('string' == typeof cloudflareBuild) {
275
- let nextCloudflareBuild = cloudflareBuild;
338
+ let nextCloudflareBuild = removeStaleBackendFederationCommandSegments(cloudflareBuild);
276
339
  if (nextCloudflareBuild.includes(cloudflareModernDeployCommand) && !nextCloudflareBuild.includes(cloudflareModernDeploySkipBuildCommand)) nextCloudflareBuild = nextCloudflareBuild.replace(cloudflareModernDeployCommand, cloudflareModernDeploySkipBuildCommand);
277
340
  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, '');
341
+ nextCloudflareBuild = nextCloudflareBuild.replace(/ && node \S*scripts\/verify-cloudflare-output\.m[ct]s(?: --app [^&]+)?/u, '');
278
342
  if (nextCloudflareBuild !== cloudflareBuild) {
279
343
  scripts['cloudflare:build'] = nextCloudflareBuild;
280
344
  changed = true;
@@ -288,9 +352,18 @@ function updateGeneratedPackageScripts(packageJson) {
288
352
  return changed;
289
353
  }
290
354
  function normalizeStrictEffectApiMetadata(value) {
291
- const api = value.api;
292
- if (!api || 'object' != typeof api || Array.isArray(api)) return false;
293
355
  let changed = false;
356
+ const backendFederation = value.backendFederation;
357
+ if (backendFederation && 'object' == typeof backendFederation && !Array.isArray(backendFederation) && Object.hasOwn(backendFederation, 'entry')) {
358
+ delete backendFederation.entry;
359
+ changed = true;
360
+ }
361
+ const api = value.api;
362
+ if (!api || 'object' != typeof api || Array.isArray(api)) return changed;
363
+ if (void 0 !== api.backendFederation) {
364
+ delete api.backendFederation;
365
+ changed = true;
366
+ }
294
367
  const oldEffect = api.effect;
295
368
  if (oldEffect && 'object' == typeof oldEffect && !Array.isArray(oldEffect)) {
296
369
  if (void 0 === api.stem && 'string' == typeof oldEffect.stem) {
@@ -352,6 +425,38 @@ function normalizeStrictEffectApiMetadata(value) {
352
425
  }
353
426
  return changed;
354
427
  }
428
+ function removeGeneratedFileIfExists(io, relativePath) {
429
+ return io.remove(external_node_path_default().join(io.workspaceRoot, relativePath));
430
+ }
431
+ function removeStaleBackendFederationArtifacts(io, config) {
432
+ let changed = false;
433
+ for (const relativePath of [
434
+ "scripts/generate-node-backend-federation.mts",
435
+ "scripts/proof-node-backend-federation.mts",
436
+ "scripts/verify-cloudflare-output.mts"
437
+ ])changed = removeGeneratedFileIfExists(io, relativePath) || changed;
438
+ for (const app of (0, external_config_cjs_namespaceObject.workspaceAppsFromToolingConfig)(config))changed = removeGeneratedFileIfExists(io, external_node_path_default().join(app.directory, 'api/backend-federation.ts')) || changed;
439
+ return changed;
440
+ }
441
+ function updateGeneratedZeropsArtifacts(io, config) {
442
+ const apps = (0, external_config_cjs_namespaceObject.workspaceAppsFromToolingConfig)(config);
443
+ let changed = writeTextIfChanged(io, external_node_path_default().join(io.workspaceRoot, 'zerops.yaml'), `${(0, zerops_cjs_namespaceObject.createZeropsYaml)(config.workspace.packageScope, apps)}\n`);
444
+ changed = writeTextIfChanged(io, external_node_path_default().join(io.workspaceRoot, "scripts/materialize-zerops-runtime.mjs"), (0, workspace_scripts_cjs_namespaceObject.createZeropsRuntimeMaterializationScript)()) || changed;
445
+ return changed;
446
+ }
447
+ function updateGeneratedBuildIdentityModules(io, config) {
448
+ let changed = false;
449
+ for (const app of (0, external_config_cjs_namespaceObject.workspaceAppsFromToolingConfig)(config)){
450
+ changed = writeTextIfChanged(io, external_node_path_default().join(io.workspaceRoot, app.directory, 'src/ultramodern-build.ts'), (0, module_federation_cjs_namespaceObject.createUltramodernBuildReexportModule)()) || changed;
451
+ changed = writeTextIfChanged(io, external_node_path_default().join(io.workspaceRoot, app.directory, 'shared/ultramodern-build.ts'), (0, module_federation_cjs_namespaceObject.createUltramodernBuildModule)(config.workspace.packageScope, app)) || changed;
452
+ }
453
+ return changed;
454
+ }
455
+ function updateGeneratedBackendFederationContractFiles(io, config) {
456
+ let changed = false;
457
+ for (const app of (0, external_config_cjs_namespaceObject.workspaceAppsFromToolingConfig)(config))if (app.api) changed = writeTextIfChanged(io, external_node_path_default().join(io.workspaceRoot, app.directory, 'api/backend-federation.ts'), (0, backend_federation_cjs_namespaceObject.createBackendFederationContractFile)(app)) || changed;
458
+ return changed;
459
+ }
355
460
  function replaceYamlLine(source, pattern, replacement) {
356
461
  const updated = source.replace(pattern, replacement);
357
462
  return {
@@ -384,22 +489,35 @@ function ensureYamlListItem(source, key, item) {
384
489
  changed: true
385
490
  };
386
491
  }
387
- function ensureYamlMapEntry(source, key, entryKey, value) {
388
- const entryLine = ` '${entryKey}': ${value}`;
389
- const packageName = entryKey.includes('@') ? entryKey.slice(0, entryKey.lastIndexOf('@')) : entryKey;
390
- const escapedPackageName = packageName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
391
- const currentEntryPattern = new RegExp(`^ {2}'${escapedPackageName}@[^']+': .+$`, 'mu');
392
- const currentEntry = source.match(currentEntryPattern);
393
- if (currentEntry) {
394
- if (currentEntry[0] === entryLine) return {
395
- source,
396
- changed: false
397
- };
398
- return {
399
- source: source.replace(currentEntryPattern, entryLine),
400
- changed: true
401
- };
492
+ function yamlEntryPattern(entryKey, scalar = false) {
493
+ const bareKey = entryKey.replace(/^['"]|['"]$/gu, '');
494
+ if (scalar) {
495
+ const esc = bareKey.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
496
+ return new RegExp(`^ {2}(?:'${esc}'|"${esc}"|${esc}): .+$`, 'gmu');
402
497
  }
498
+ const packageName = bareKey.includes('@') ? bareKey.slice(0, bareKey.lastIndexOf('@')) : bareKey;
499
+ const esc = packageName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
500
+ return new RegExp(`^ {2}(?:'${esc}@[^']+'|"${esc}@[^"]+"|${esc}@[^:'"\\s]+): .+$`, 'gmu');
501
+ }
502
+ function upsertYamlEntry(source, key, entryLine, pattern) {
503
+ const linePattern = new RegExp(pattern.source, 'u');
504
+ const lines = source.split('\n');
505
+ let seen = false;
506
+ let changed = false;
507
+ const out = [];
508
+ for (const line of lines)if (linePattern.test(line)) {
509
+ if (seen) {
510
+ changed = true;
511
+ continue;
512
+ }
513
+ seen = true;
514
+ if (line !== entryLine) changed = true;
515
+ out.push(entryLine);
516
+ } else out.push(line);
517
+ if (seen) return {
518
+ source: out.join('\n'),
519
+ changed
520
+ };
403
521
  const headerPattern = new RegExp(`^${key}:\\n(?:(?: .+\\n)*)`, 'mu');
404
522
  const header = source.match(headerPattern);
405
523
  if (header) {
@@ -417,62 +535,43 @@ function ensureYamlMapEntry(source, key, entryKey, value) {
417
535
  changed: true
418
536
  };
419
537
  }
538
+ function ensureYamlMapEntry(source, key, entryKey, value) {
539
+ return upsertYamlEntry(source, key, ` '${entryKey}': ${value}`, yamlEntryPattern(entryKey));
540
+ }
420
541
  function ensureYamlScalarMapEntry(source, key, entryKey, value) {
421
- const entryLine = ` ${entryKey}: ${value}`;
422
- const escapedEntryKey = entryKey.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
423
- const currentEntryPattern = new RegExp(`^ {2}${escapedEntryKey}: .+$`, 'mu');
424
- const currentEntry = source.match(currentEntryPattern);
425
- if (currentEntry) {
426
- if (currentEntry[0] === entryLine) return {
427
- source,
428
- changed: false
429
- };
430
- return {
431
- source: source.replace(currentEntryPattern, entryLine),
432
- changed: true
433
- };
434
- }
435
- const headerPattern = new RegExp(`^${key}:\\n(?:(?: .+\\n)*)`, 'mu');
436
- const header = source.match(headerPattern);
437
- if (header) return {
438
- source: source.replace(headerPattern, `${header[0]}${entryLine}\n`),
439
- changed: true
440
- };
441
- return {
442
- source: `${source.trimEnd()}\n${key}:\n${entryLine}\n`,
443
- changed: true
444
- };
542
+ return upsertYamlEntry(source, key, ` ${entryKey}: ${value}`, yamlEntryPattern(entryKey, true));
445
543
  }
446
544
  function removeYamlMapEntry(source, entryKey) {
447
- const packageName = entryKey.includes('@') ? entryKey.slice(0, entryKey.lastIndexOf('@')) : entryKey;
448
- const escapedPackageName = packageName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
449
- const currentEntryPattern = new RegExp(`^ {2}'${escapedPackageName}@[^']+': .+\\n?`, 'mu');
450
- if (!currentEntryPattern.test(source)) return {
545
+ const linePattern = new RegExp(yamlEntryPattern(entryKey).source, 'u');
546
+ const lines = source.split('\n');
547
+ let changed = false;
548
+ const out = [];
549
+ for (const line of lines){
550
+ if (linePattern.test(line)) {
551
+ changed = true;
552
+ continue;
553
+ }
554
+ out.push(line);
555
+ }
556
+ return changed ? {
557
+ source: out.join('\n'),
558
+ changed
559
+ } : {
451
560
  source,
452
- changed: false
453
- };
454
- return {
455
- source: source.replace(currentEntryPattern, ''),
456
- changed: true
561
+ changed
457
562
  };
458
563
  }
459
- function ensureGeneratedPatchFile(workspaceRoot, relativePatchPath, sourcePatchPath) {
460
- const targetPath = external_node_path_default().join(workspaceRoot, relativePatchPath);
564
+ function ensureGeneratedPatchFile(io, relativePatchPath, sourcePatchPath) {
565
+ const targetPath = external_node_path_default().join(io.workspaceRoot, relativePatchPath);
461
566
  const patch = external_node_fs_default().readFileSync(sourcePatchPath, 'utf-8');
462
- if (external_node_fs_default().existsSync(targetPath) && external_node_fs_default().readFileSync(targetPath, 'utf-8') === patch) return false;
463
- external_node_fs_default().mkdirSync(external_node_path_default().dirname(targetPath), {
464
- recursive: true
465
- });
466
- external_node_fs_default().writeFileSync(targetPath, patch, 'utf-8');
467
- return true;
567
+ return io.write(targetPath, patch);
468
568
  }
469
- function removeGeneratedPatchFileIfUnchanged(workspaceRoot, relativePatchPath, sourcePatchPath) {
470
- const targetPath = external_node_path_default().join(workspaceRoot, relativePatchPath);
569
+ function removeGeneratedPatchFileIfUnchanged(io, relativePatchPath, sourcePatchPath) {
570
+ const targetPath = external_node_path_default().join(io.workspaceRoot, relativePatchPath);
471
571
  if (!external_node_fs_default().existsSync(targetPath)) return false;
472
572
  const patch = external_node_fs_default().readFileSync(sourcePatchPath, 'utf-8');
473
573
  if (external_node_fs_default().readFileSync(targetPath, 'utf-8') !== patch) return false;
474
- external_node_fs_default().rmSync(targetPath);
475
- return true;
574
+ return io.remove(targetPath);
476
575
  }
477
576
  function workspaceUsesDependency(workspaceRoot, packageName) {
478
577
  const packageJsonPaths = [
@@ -509,20 +608,20 @@ function workspaceUsesDependency(workspaceRoot, packageName) {
509
608
  }
510
609
  return false;
511
610
  }
512
- function ensureGeneratedDeclarationPatches(workspaceRoot, options) {
611
+ function ensureGeneratedDeclarationPatches(io, options) {
513
612
  let changed = false;
514
- changed = ensureGeneratedPatchFile(workspaceRoot, moduleFederationModernJsPatchPath, moduleFederationModernJsPatchSourcePath) || changed;
515
- changed = ensureGeneratedPatchFile(workspaceRoot, moduleFederationBridgeReactPatchPath, moduleFederationBridgeReactPatchSourcePath) || changed;
516
- changed = ensureGeneratedPatchFile(workspaceRoot, effectDeclarationPatchPath, effectDeclarationPatchSourcePath) || changed;
517
- changed = options.includeDrizzleOrmPatch ? ensureGeneratedPatchFile(workspaceRoot, drizzleOrmDeclarationPatchPath, drizzleOrmDeclarationPatchSourcePath) || changed : removeGeneratedPatchFileIfUnchanged(workspaceRoot, drizzleOrmDeclarationPatchPath, drizzleOrmDeclarationPatchSourcePath) || changed;
613
+ changed = ensureGeneratedPatchFile(io, moduleFederationModernJsPatchPath, moduleFederationModernJsPatchSourcePath) || changed;
614
+ changed = ensureGeneratedPatchFile(io, moduleFederationBridgeReactPatchPath, moduleFederationBridgeReactPatchSourcePath) || changed;
615
+ changed = ensureGeneratedPatchFile(io, effectDeclarationPatchPath, effectDeclarationPatchSourcePath) || changed;
616
+ changed = options.includeDrizzleOrmPatch ? ensureGeneratedPatchFile(io, drizzleOrmDeclarationPatchPath, drizzleOrmDeclarationPatchSourcePath) || changed : removeGeneratedPatchFileIfUnchanged(io, drizzleOrmDeclarationPatchPath, drizzleOrmDeclarationPatchSourcePath) || changed;
518
617
  return changed;
519
618
  }
520
- function updateGeneratedPnpmWorkspacePolicy(workspaceRoot) {
521
- const workspaceFile = external_node_path_default().join(workspaceRoot, 'pnpm-workspace.yaml');
619
+ function updateGeneratedPnpmWorkspacePolicy(io) {
620
+ const workspaceFile = external_node_path_default().join(io.workspaceRoot, 'pnpm-workspace.yaml');
522
621
  if (!external_node_fs_default().existsSync(workspaceFile)) return false;
523
622
  let source = external_node_fs_default().readFileSync(workspaceFile, 'utf-8');
524
623
  let changed = false;
525
- const usesDrizzleOrm = workspaceUsesDependency(workspaceRoot, 'drizzle-orm');
624
+ const usesDrizzleOrm = workspaceUsesDependency(io.workspaceRoot, 'drizzle-orm');
526
625
  const replacements = [
527
626
  [
528
627
  /^ {4}'@effect\/vitest>effect': .+$/mu,
@@ -582,12 +681,11 @@ function updateGeneratedPnpmWorkspacePolicy(workspaceRoot) {
582
681
  const drizzleOrmPatch = usesDrizzleOrm ? ensureYamlMapEntry(source, 'patchedDependencies', `drizzle-orm@${versions_cjs_namespaceObject.DRIZZLE_ORM_VERSION}`, drizzleOrmDeclarationPatchPath) : removeYamlMapEntry(source, `drizzle-orm@${versions_cjs_namespaceObject.DRIZZLE_ORM_VERSION}`);
583
682
  source = drizzleOrmPatch.source;
584
683
  changed = drizzleOrmPatch.changed || changed;
585
- if (changed) external_node_fs_default().writeFileSync(workspaceFile, source, 'utf-8');
684
+ if (changed) io.write(workspaceFile, source);
586
685
  return changed;
587
686
  }
588
- function updateUltramodernConfig(workspaceRoot, packageSource) {
589
- const configPath = external_node_path_default().join(workspaceRoot, '.modernjs/ultramodern.json');
590
- const config = readJsonFile(configPath);
687
+ function updateUltramodernConfig(io, config, packageSource) {
688
+ const configPath = external_node_path_default().join(io.workspaceRoot, '.modernjs/ultramodern.json');
591
689
  config.packageSource = {
592
690
  strategy: packageSource.strategy,
593
691
  modernPackageVersion: packageSource.modernPackageVersion,
@@ -602,73 +700,145 @@ function updateUltramodernConfig(workspaceRoot, packageSource) {
602
700
  } : {}
603
701
  };
604
702
  for (const app of config.topology?.apps ?? [])if (app && 'object' == typeof app && !Array.isArray(app)) normalizeStrictEffectApiMetadata(app);
605
- writeJsonFile(configPath, config);
703
+ writeJsonFile(io, configPath, config);
606
704
  }
607
- function writeJsonIfChanged(filePath, value) {
608
- const next = `${JSON.stringify(value, null, 2)}\n`;
609
- if (external_node_fs_default().existsSync(filePath) && external_node_fs_default().readFileSync(filePath, 'utf-8') === next) return false;
610
- external_node_fs_default().mkdirSync(external_node_path_default().dirname(filePath), {
611
- recursive: true
612
- });
613
- external_node_fs_default().writeFileSync(filePath, next, 'utf-8');
614
- return true;
705
+ function writeJsonIfChanged(io, filePath, value) {
706
+ return io.write(filePath, `${JSON.stringify(value, null, 2)}\n`);
615
707
  }
616
- function writeTextIfChanged(filePath, value) {
617
- if (external_node_fs_default().existsSync(filePath) && external_node_fs_default().readFileSync(filePath, 'utf-8') === value) return false;
618
- external_node_fs_default().mkdirSync(external_node_path_default().dirname(filePath), {
619
- recursive: true
620
- });
621
- external_node_fs_default().writeFileSync(filePath, value, 'utf-8');
622
- return true;
708
+ function writeTextIfChanged(io, filePath, value) {
709
+ return io.write(filePath, value);
623
710
  }
624
- function ensureGeneratedIgnoreRules(workspaceRoot) {
625
- const gitignorePath = external_node_path_default().join(workspaceRoot, '.gitignore');
711
+ function ensureGeneratedIgnoreRules(io) {
712
+ const gitignorePath = external_node_path_default().join(io.workspaceRoot, '.gitignore');
626
713
  const existing = external_node_fs_default().existsSync(gitignorePath) ? external_node_fs_default().readFileSync(gitignorePath, 'utf-8') : '';
627
714
  const lines = 0 === existing.trimEnd().length ? [] : existing.trimEnd().split(/\r?\n/u);
628
715
  let changed = false;
629
716
  for (const rule of [
630
717
  '.mf/',
631
718
  '**/.mf/',
632
- 'dist-cloudflare/'
719
+ 'dist-cloudflare/',
720
+ '.output/',
721
+ '**/.output/',
722
+ '.modern-js/',
723
+ '**/.modern-js/'
633
724
  ])if (!lines.includes(rule)) {
634
725
  lines.push(rule);
635
726
  changed = true;
636
727
  }
637
728
  if (!changed) return false;
638
- const next = `${lines.join('\n')}\n`;
639
- external_node_fs_default().writeFileSync(gitignorePath, next, 'utf-8');
640
- return true;
729
+ return io.write(gitignorePath, `${lines.join('\n')}\n`);
641
730
  }
642
- function updateGeneratedTypeScriptSurfaces(workspaceRoot, config) {
731
+ function updateGeneratedTypeScriptSurfaces(io, config) {
643
732
  let changed = false;
644
733
  const apps = (0, external_config_cjs_namespaceObject.workspaceAppsFromToolingConfig)(config);
645
734
  const remotes = apps.filter((app)=>'shell' !== app.kind);
646
- changed = writeJsonIfChanged(external_node_path_default().join(workspaceRoot, 'tsconfig.base.json'), (0, package_json_cjs_namespaceObject.createTsConfigBase)()) || changed;
647
- changed = ensureGeneratedIgnoreRules(workspaceRoot) || changed;
735
+ changed = writeJsonIfChanged(io, external_node_path_default().join(io.workspaceRoot, 'tsconfig.base.json'), (0, package_json_cjs_namespaceObject.createTsConfigBase)()) || changed;
736
+ changed = ensureGeneratedIgnoreRules(io) || changed;
648
737
  for (const sharedPackage of [
649
738
  'packages/shared-contracts',
650
739
  'packages/shared-design-tokens'
651
- ])changed = writeJsonIfChanged(external_node_path_default().join(workspaceRoot, sharedPackage, 'tsconfig.json'), (0, package_json_cjs_namespaceObject.createSharedPackageTsConfig)(sharedPackage)) || changed;
740
+ ])changed = writeJsonIfChanged(io, external_node_path_default().join(io.workspaceRoot, sharedPackage, 'tsconfig.json'), (0, package_json_cjs_namespaceObject.createSharedPackageTsConfig)(sharedPackage)) || changed;
652
741
  for (const app of apps){
653
- changed = writeJsonIfChanged(external_node_path_default().join(workspaceRoot, app.directory, 'tsconfig.json'), (0, package_json_cjs_namespaceObject.createAppTsConfig)(app, remotes)) || changed;
654
- changed = writeJsonIfChanged(external_node_path_default().join(workspaceRoot, app.directory, 'tsconfig.mf-types.json'), (0, package_json_cjs_namespaceObject.createAppMfTypesTsConfig)(app)) || changed;
655
- changed = writeTextIfChanged(external_node_path_default().join(workspaceRoot, app.directory, 'src/modern-app-env.d.ts'), (0, app_files_cjs_namespaceObject.createAppEnvDts)(app, remotes)) || changed;
742
+ changed = writeJsonIfChanged(io, external_node_path_default().join(io.workspaceRoot, app.directory, 'tsconfig.json'), (0, package_json_cjs_namespaceObject.createAppTsConfig)(app, remotes)) || changed;
743
+ changed = writeJsonIfChanged(io, external_node_path_default().join(io.workspaceRoot, app.directory, 'tsconfig.mf-types.json'), (0, package_json_cjs_namespaceObject.createAppMfTypesTsConfig)(app)) || changed;
744
+ changed = writeTextIfChanged(io, external_node_path_default().join(io.workspaceRoot, app.directory, 'src/modern-app-env.d.ts'), (0, app_files_cjs_namespaceObject.createAppEnvDts)(app, remotes)) || changed;
656
745
  }
657
746
  return changed;
658
747
  }
659
- function updateGeneratedModernConfigs(workspaceRoot, config) {
748
+ function updateGeneratedModernConfigs(io, config) {
660
749
  let changed = false;
661
750
  const apps = (0, external_config_cjs_namespaceObject.workspaceAppsFromToolingConfig)(config);
662
- for (const app of apps)changed = writeTextIfChanged(external_node_path_default().join(workspaceRoot, app.directory, 'modern.config.ts'), (0, module_federation_cjs_namespaceObject.createAppModernConfig)(config.workspace.packageScope, app)) || changed;
751
+ const remotes = apps.filter((app)=>'shell' !== app.kind);
752
+ for (const app of apps)changed = writeTextIfChanged(io, external_node_path_default().join(io.workspaceRoot, app.directory, 'modern.config.ts'), (0, module_federation_cjs_namespaceObject.createAppModernConfig)(config.workspace.packageScope, app, remotes)) || changed;
663
753
  return changed;
664
754
  }
665
- function updateReferenceTopology(workspaceRoot) {
666
- const topologyPath = external_node_path_default().join(workspaceRoot, 'topology/reference-topology.json');
755
+ function ensureGeneratedOxfmtIgnorePatterns(io) {
756
+ const configPath = external_node_path_default().join(io.workspaceRoot, 'oxfmt.config.ts');
757
+ if (!external_node_fs_default().existsSync(configPath)) return false;
758
+ const source = external_node_fs_default().readFileSync(configPath, 'utf-8');
759
+ const requiredPatterns = [
760
+ '.modernjs',
761
+ '.output',
762
+ '**/modern-tanstack/**',
763
+ '**/routeTree.gen.*'
764
+ ];
765
+ const warnUnparseable = ()=>{
766
+ const message = `Could not update oxfmt.config.ts ignorePatterns automatically; add these entries manually: ${requiredPatterns.join(', ')}.`;
767
+ if (io.dryRun) io.log(message);
768
+ else process.stderr.write(`[ultramodern] ${message}\n`);
769
+ };
770
+ const anchor = source.indexOf('ignorePatterns:');
771
+ if (-1 === anchor) {
772
+ warnUnparseable();
773
+ return false;
774
+ }
775
+ const openBracket = source.indexOf('[', anchor);
776
+ if (-1 === openBracket) {
777
+ warnUnparseable();
778
+ return false;
779
+ }
780
+ let depth = 0;
781
+ let closeBracket = -1;
782
+ let stringQuote;
783
+ for(let index = openBracket; index < source.length; index += 1){
784
+ const char = source[index];
785
+ if (stringQuote) {
786
+ if ('\\' === char) index += 1;
787
+ else if (char === stringQuote) stringQuote = void 0;
788
+ continue;
789
+ }
790
+ if ("'" === char || '"' === char || '`' === char) stringQuote = char;
791
+ else if ('[' === char) depth += 1;
792
+ else if (']' === char) {
793
+ depth -= 1;
794
+ if (0 === depth) {
795
+ closeBracket = index;
796
+ break;
797
+ }
798
+ }
799
+ }
800
+ if (-1 === closeBracket) {
801
+ warnUnparseable();
802
+ return false;
803
+ }
804
+ const body = source.slice(openBracket + 1, closeBracket);
805
+ if (body.includes('...')) {
806
+ warnUnparseable();
807
+ return false;
808
+ }
809
+ const literalPattern = /(['"`])((?:\\.|(?!\1).)*)\1/g;
810
+ const existing = new Set();
811
+ for (const match of body.matchAll(literalPattern))existing.add(match[2]);
812
+ const missing = requiredPatterns.filter((pattern)=>!existing.has(pattern));
813
+ if (0 === missing.length) return false;
814
+ const bodyLines = body.split('\n');
815
+ let indent = ' ';
816
+ let quote = "'";
817
+ for(let index = bodyLines.length - 1; index >= 0; index -= 1){
818
+ const literal = bodyLines[index].match(/^(\s*)(['"`])/u);
819
+ if (literal) {
820
+ indent = literal[1];
821
+ quote = literal[2];
822
+ break;
823
+ }
824
+ }
825
+ const head = source.slice(0, closeBracket);
826
+ const rest = source.slice(closeBracket);
827
+ const tailMatch = head.match(/(\r?\n[ \t]*)$/u);
828
+ const tail = tailMatch ? tailMatch[1] : '\n';
829
+ let bodyContent = tailMatch ? head.slice(0, head.length - tail.length) : head;
830
+ if (!/[[,]\s*$/u.test(bodyContent)) bodyContent = `${bodyContent},`;
831
+ const insertionLines = missing.map((pattern)=>`${indent}${quote}${pattern}${quote},`).join('\n');
832
+ const nextSource = `${bodyContent}\n${insertionLines}${tail}${rest}`;
833
+ return io.write(configPath, nextSource);
834
+ }
835
+ function updateReferenceTopology(io) {
836
+ const topologyPath = external_node_path_default().join(io.workspaceRoot, 'topology/reference-topology.json');
667
837
  if (!external_node_fs_default().existsSync(topologyPath)) return false;
668
838
  const topology = readJsonFile(topologyPath);
669
839
  let changed = false;
670
840
  for (const vertical of topology.verticals ?? [])if (vertical && 'object' == typeof vertical && !Array.isArray(vertical)) changed = normalizeStrictEffectApiMetadata(vertical) || changed;
671
- if (changed) writeJsonFile(topologyPath, topology);
841
+ if (changed) writeJsonFile(io, topologyPath, topology);
672
842
  return changed;
673
843
  }
674
844
  function createMigrationPackageSource(args, current) {
@@ -721,25 +891,56 @@ function runPnpmLockfileRefresh(context) {
721
891
  function runMigrateStrictEffect(args, context) {
722
892
  if (args.includes('--help') || args.includes('-h')) {
723
893
  process.stdout.write(`Usage:
724
- modern-js-create ultramodern migrate-strict-effect --version <version> [--skip-install]
894
+ modern-js-create ultramodern migrate-strict-effect --version <version> [--dry-run] [--skip-install]
725
895
 
726
896
  Updates generated UltraModern package-source metadata, Modern package aliases,
727
897
  framework-owned toolchain pins, direct Effect API topology metadata, strict
728
898
  Effect pnpm overrides/trust policy, framework-owned TypeScript config
729
899
  surfaces, and the pnpm lockfile. Source code still has to pass pnpm api:check
730
900
  and pnpm contract:check.
901
+
902
+ When the compact config is absent but legacy UltraModern 3.2 metadata is
903
+ present, the compact config is synthesized from it first. Shell-only
904
+ workspaces skip the backend-federation and Zerops runtime stages. Pass
905
+ --dry-run to print the planned filesystem changes without writing anything
906
+ (implies --skip-install).
731
907
  `);
732
908
  return 0;
733
909
  }
734
- const current = (0, external_config_cjs_namespaceObject.readUltramodernConfig)(context.workspaceRoot);
910
+ const dryRun = hasFlag(args, '--dry-run');
911
+ const skipInstall = dryRun || hasFlag(args, '--skip-install');
912
+ const io = createMigrationIo(context.workspaceRoot, dryRun);
913
+ const compactPath = external_node_path_default().join(io.workspaceRoot, '.modernjs/ultramodern.json');
914
+ let raw;
915
+ if (external_node_fs_default().existsSync(compactPath)) raw = readJsonFile(compactPath);
916
+ else {
917
+ const synthesized = (0, external_config_cjs_namespaceObject.synthesizeCompactUltramodernConfig)(io.workspaceRoot);
918
+ 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.");
919
+ raw = synthesized.compact;
920
+ io.write(compactPath, `${JSON.stringify(raw, null, 2)}\n`);
921
+ io.log(`Synthesized .modernjs/ultramodern.json from legacy metadata: ${synthesized.sources.join(', ')}.`);
922
+ if (synthesized.missing.length > 0) io.log(`Legacy metadata not found (using defaults): ${synthesized.missing.join(', ')}.`);
923
+ }
924
+ const current = (0, external_config_cjs_namespaceObject.normalizeCompactUltramodernConfig)(io.workspaceRoot, raw);
735
925
  const packageSource = createMigrationPackageSource(args, current);
736
- updateUltramodernConfig(context.workspaceRoot, packageSource);
737
- updateReferenceTopology(context.workspaceRoot);
738
- const migrated = (0, external_config_cjs_namespaceObject.readUltramodernConfig)(context.workspaceRoot);
739
- updateGeneratedTypeScriptSurfaces(context.workspaceRoot, migrated);
740
- updateGeneratedModernConfigs(context.workspaceRoot, migrated);
741
- for (const relativePackageFile of listWorkspacePackageFiles(context.workspaceRoot)){
742
- const packageFile = external_node_path_default().join(context.workspaceRoot, relativePackageFile);
926
+ updateUltramodernConfig(io, raw, packageSource);
927
+ updateReferenceTopology(io);
928
+ const migrated = (0, external_config_cjs_namespaceObject.normalizeCompactUltramodernConfig)(io.workspaceRoot, raw);
929
+ const migratedApps = (0, external_config_cjs_namespaceObject.workspaceAppsFromToolingConfig)(migrated);
930
+ const shellOnly = !migrated.topology.apps.some((app)=>app.api);
931
+ if (shellOnly) io.log('Shell-only workspace: skipping backend-federation and Zerops runtime stages.');
932
+ else {
933
+ removeStaleBackendFederationArtifacts(io, migrated);
934
+ updateGeneratedZeropsArtifacts(io, migrated);
935
+ updateGeneratedBackendFederationContractFiles(io, migrated);
936
+ }
937
+ if (dryRun) io.log("would refresh generated tool wrapper scripts under scripts/");
938
+ else (0, workspace_scripts_cjs_namespaceObject.writeGeneratedToolWrapperScripts)(io.workspaceRoot);
939
+ updateGeneratedBuildIdentityModules(io, migrated);
940
+ updateGeneratedTypeScriptSurfaces(io, migrated);
941
+ updateGeneratedModernConfigs(io, migrated);
942
+ for (const relativePackageFile of listWorkspacePackageFiles(io.workspaceRoot)){
943
+ const packageFile = external_node_path_default().join(io.workspaceRoot, relativePackageFile);
743
944
  const packageJson = readJsonFile(packageFile);
744
945
  if ('package.json' === relativePackageFile) {
745
946
  packageJson.modernjs ??= {};
@@ -748,21 +949,28 @@ and pnpm contract:check.
748
949
  config: './.modernjs/ultramodern.json'
749
950
  };
750
951
  }
751
- const modernDependenciesChanged = updateModernDependencies(packageJson, packageSource);
752
- const toolingDependenciesChanged = updateGeneratedToolingDependencies(packageJson);
753
- const generatedScriptsChanged = updateGeneratedPackageScripts(packageJson);
754
- const changed = modernDependenciesChanged || toolingDependenciesChanged || generatedScriptsChanged;
755
- if (changed) writeJsonFile(packageFile, packageJson);
756
- else if ('package.json' === relativePackageFile) writeJsonFile(packageFile, packageJson);
757
- }
758
- updateGeneratedPnpmWorkspacePolicy(context.workspaceRoot);
759
- ensureGeneratedDeclarationPatches(context.workspaceRoot, {
760
- includeDrizzleOrmPatch: workspaceUsesDependency(context.workspaceRoot, 'drizzle-orm')
952
+ updateModernDependencies(packageJson, packageSource);
953
+ updateGeneratedToolingDependencies(packageJson);
954
+ updateGeneratedPackageScripts(packageJson, {
955
+ relativePackageFile,
956
+ apps: migratedApps
957
+ });
958
+ writeJsonFile(io, packageFile, packageJson);
959
+ }
960
+ updateGeneratedPnpmWorkspacePolicy(io);
961
+ ensureGeneratedDeclarationPatches(io, {
962
+ includeDrizzleOrmPatch: workspaceUsesDependency(io.workspaceRoot, 'drizzle-orm')
761
963
  });
762
- if (!hasFlag(args, '--skip-install')) {
964
+ ensureGeneratedOxfmtIgnorePatterns(io);
965
+ if (!skipInstall) {
763
966
  const status = runPnpmLockfileRefresh(context);
764
967
  if (0 !== status) return status;
765
968
  }
969
+ if (dryRun) {
970
+ for (const line of io.plan)process.stdout.write(`${line}\n`);
971
+ process.stdout.write(`[dry-run] migrate-strict-effect would migrate UltraModern strict Effect metadata to ${packageSource.modernPackageVersion}.\n`);
972
+ return 0;
973
+ }
766
974
  process.stdout.write(`UltraModern strict Effect metadata migrated to ${packageSource.modernPackageVersion}. Run pnpm api:check && pnpm contract:check next.\n`);
767
975
  return 0;
768
976
  }
@@ -775,6 +983,140 @@ function runSkills(args, context) {
775
983
  ], context);
776
984
  throw new Error('Usage: modern-js-create ultramodern skills <install|check>');
777
985
  }
986
+ const resolveCloudflareOutputVerifyTargets = (args, context)=>{
987
+ const outputDirectory = readOption(args, '--output');
988
+ const appId = readOption(args, '--app');
989
+ if (outputDirectory && appId) throw new Error('Use either --app or --output, not both.');
990
+ const targets = outputDirectory ? [
991
+ {
992
+ label: outputDirectory,
993
+ outputDirectory: external_node_path_default().resolve(context.invocationCwd, outputDirectory)
994
+ }
995
+ ] : (0, external_config_cjs_namespaceObject.workspaceAppsFromToolingConfig)((0, external_config_cjs_namespaceObject.readUltramodernConfig)(context.workspaceRoot)).filter((app)=>!appId || app.id === appId).map((app)=>({
996
+ label: app.id,
997
+ outputDirectory: external_node_path_default().join(context.workspaceRoot, app.directory, '.output')
998
+ }));
999
+ if (0 === targets.length) throw new Error(`No generated UltraModern app matched ${appId}.`);
1000
+ return targets;
1001
+ };
1002
+ const renderCloudflareOutputVerifyModule = ({ workspaceRoot, targets, scanRoots, importWorker })=>`
1003
+ import { createRequire } from 'node:module';
1004
+ import path from 'node:path';
1005
+
1006
+ const workspaceRoot = ${JSON.stringify(workspaceRoot)};
1007
+ const targets = ${JSON.stringify(targets, null, 2)};
1008
+ const scanRoots = ${JSON.stringify(scanRoots)};
1009
+ const verifierRequire = createRequire(path.join(workspaceRoot, 'package.json'));
1010
+ const {
1011
+ verifyCloudflareOutput,
1012
+ verifyCloudflareOutputMutationPolicy,
1013
+ } = verifierRequire('@modern-js/app-tools/cloudflare-output-verifier');
1014
+
1015
+ let failed = false;
1016
+ for (const target of targets) {
1017
+ const result = await verifyCloudflareOutput({
1018
+ outputDirectory: target.outputDirectory,
1019
+ importWorker: ${JSON.stringify(importWorker)},
1020
+ });
1021
+ if (result.ok) {
1022
+ console.log(\`[ultramodern] Cloudflare output verified: \${target.label}\`);
1023
+ } else {
1024
+ failed = true;
1025
+ console.error(\`[ultramodern] Cloudflare output failed: \${target.label}\`);
1026
+ for (const issue of result.issues) {
1027
+ console.error(\`- \${issue.code}: \${issue.message}\${issue.path ? \` (\${issue.path})\` : ''}\`);
1028
+ }
1029
+ }
1030
+ }
1031
+
1032
+ if (scanRoots.length > 0) {
1033
+ const policyResult = await verifyCloudflareOutputMutationPolicy({ scanRoots });
1034
+ if (!policyResult.ok) {
1035
+ failed = true;
1036
+ console.error('[ultramodern] generated-output mutation policy failed');
1037
+ for (const issue of policyResult.issues) {
1038
+ console.error(\`- \${issue.code}: \${issue.message}\${issue.path ? \` (\${issue.path})\` : ''}\`);
1039
+ }
1040
+ }
1041
+ }
1042
+
1043
+ process.exit(failed ? 1 : 0);
1044
+ `;
1045
+ function runCloudflareOutputVerify(args, context) {
1046
+ if (args.includes('--help') || args.includes('-h')) {
1047
+ process.stdout.write(`Usage:
1048
+ modern-js-create ultramodern cloudflare-output-verify [--app <id> | --output <dir>] [--no-import-worker] [--no-source-scan]
1049
+
1050
+ Verifies generated Cloudflare output against the UltraModern worker contract.
1051
+ Without --app or --output, every generated workspace app is verified.
1052
+ `);
1053
+ return 0;
1054
+ }
1055
+ const source = renderCloudflareOutputVerifyModule({
1056
+ workspaceRoot: context.workspaceRoot,
1057
+ targets: resolveCloudflareOutputVerifyTargets(args, context),
1058
+ scanRoots: hasFlag(args, '--no-source-scan') ? [] : [
1059
+ context.workspaceRoot
1060
+ ],
1061
+ importWorker: !hasFlag(args, '--no-import-worker')
1062
+ });
1063
+ return runRenderedModule(source, context);
1064
+ }
1065
+ const resolveRoutesGenerateTargets = (args, context)=>{
1066
+ const appId = readOption(args, '--app');
1067
+ const targets = (0, external_config_cjs_namespaceObject.workspaceAppsFromToolingConfig)((0, external_config_cjs_namespaceObject.readUltramodernConfig)(context.workspaceRoot)).filter((app)=>!appId || app.id === appId).map((app)=>({
1068
+ label: app.id,
1069
+ appDirectory: external_node_path_default().join(context.workspaceRoot, app.directory)
1070
+ }));
1071
+ if (0 === targets.length) throw new Error(`No generated UltraModern app matched ${appId ?? '<any>'}.`);
1072
+ return targets;
1073
+ };
1074
+ const renderRoutesGenerateModule = ({ workspaceRoot, targets })=>`
1075
+ import { createRequire } from 'node:module';
1076
+ import path from 'node:path';
1077
+ import { pathToFileURL } from 'node:url';
1078
+
1079
+ const workspaceRoot = ${JSON.stringify(workspaceRoot)};
1080
+ const targets = ${JSON.stringify(targets, null, 2)};
1081
+ const appRequire = createRequire(path.join(workspaceRoot, 'package.json'));
1082
+ const pluginUrl = pathToFileURL(
1083
+ appRequire.resolve('@modern-js/plugin-tanstack'),
1084
+ ).href;
1085
+ const { generateTanstackRouteArtifacts } = await import(pluginUrl);
1086
+
1087
+ let failed = false;
1088
+ // Sequential — the app-tools cli singleton is not re-entrant.
1089
+ for (const target of targets) {
1090
+ try {
1091
+ await generateTanstackRouteArtifacts({ appDirectory: target.appDirectory });
1092
+ console.log(\`[ultramodern] TanStack route artifacts generated: \${target.label}\`);
1093
+ } catch (error) {
1094
+ failed = true;
1095
+ const message = error instanceof Error ? error.message : String(error);
1096
+ console.error(\`[ultramodern] TanStack route generation failed: \${target.label}\`);
1097
+ console.error(\`- \${message}\`);
1098
+ }
1099
+ }
1100
+
1101
+ process.exit(failed ? 1 : 0);
1102
+ `;
1103
+ function runRoutesGenerate(args, context) {
1104
+ if (args.includes('--help') || args.includes('-h')) {
1105
+ process.stdout.write(`Usage:
1106
+ modern-js-create ultramodern routes-generate [--app <id>]
1107
+
1108
+ Regenerates TanStack route artifacts (router.gen.ts, register.gen.d.ts) for
1109
+ generated UltraModern apps without running dev or build. Without --app, every
1110
+ generated workspace app is regenerated.
1111
+ `);
1112
+ return 0;
1113
+ }
1114
+ const source = renderRoutesGenerateModule({
1115
+ workspaceRoot: context.workspaceRoot,
1116
+ targets: resolveRoutesGenerateTargets(args, context)
1117
+ });
1118
+ return runRenderedModule(source, context);
1119
+ }
778
1120
  async function runUltramodernToolingCli(args, workspaceRoot = process.env.ULTRAMODERN_WORKSPACE_ROOT ?? process.cwd()) {
779
1121
  try {
780
1122
  const [command, ...rest] = args;
@@ -788,25 +1130,25 @@ async function runUltramodernToolingCli(args, workspaceRoot = process.env.ULTRAM
788
1130
  case '-h':
789
1131
  printHelp();
790
1132
  return 0;
791
- case 'validate':
1133
+ case tooling_command_catalog_cjs_namespaceObject.GENERATED_TOOLING_COMMANDS.validate.command:
792
1134
  return runValidate(context);
793
- case 'typecheck':
794
- return spawnNodeScript("templates/workspace-scripts/ultramodern-typecheck.mjs", rest, context, {
795
- cwd: context.invocationCwd
796
- });
797
- case 'mf-types':
1135
+ case tooling_command_catalog_cjs_namespaceObject.GENERATED_TOOLING_COMMANDS.mfTypes.command:
798
1136
  return runMfTypes(rest, context);
799
- case 'migrate-strict-effect':
1137
+ case tooling_command_catalog_cjs_namespaceObject.GENERATED_TOOLING_COMMANDS.migrateStrictEffect.command:
800
1138
  return runMigrateStrictEffect(rest, context);
801
- case 'public-surface':
802
- return spawnNodeScript("templates/workspace-scripts/generate-public-surface-assets.mjs", rest, context);
803
- case 'cloudflare-proof':
804
- return spawnNodeScript("templates/workspace-scripts/proof-cloudflare-version.mjs", rest, context);
805
- case 'performance-readiness':
806
- return spawnNodeScript("templates/workspace-scripts/ultramodern-performance-readiness.mjs", rest, context);
1139
+ case tooling_command_catalog_cjs_namespaceObject.GENERATED_TOOLING_COMMANDS.cloudflareOutputVerify.command:
1140
+ return runCloudflareOutputVerify(rest, context);
1141
+ case tooling_command_catalog_cjs_namespaceObject.GENERATED_TOOLING_COMMANDS.routesGenerate.command:
1142
+ return runRoutesGenerate(rest, context);
1143
+ case 'sync-delivery-unit':
1144
+ return (0, delivery_unit_sync_cjs_namespaceObject.runSyncDeliveryUnit)(rest, context);
807
1145
  case 'skills':
808
1146
  return runSkills(rest, context);
809
1147
  default:
1148
+ {
1149
+ const templateBackedStatus = runTemplateBackedToolingCommand(command ?? '', rest, context);
1150
+ if (void 0 !== templateBackedStatus) return templateBackedStatus;
1151
+ }
810
1152
  throw new Error(`Unknown UltraModern command: ${command}`);
811
1153
  }
812
1154
  } catch (error) {