@bleedingdev/modern-js-create 3.5.0-ultramodern.2 → 3.5.0-ultramodern.21

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 (32) hide show
  1. package/README.md +22 -15
  2. package/dist/cjs/ultramodern-tooling/commands.cjs +393 -3
  3. package/dist/cjs/ultramodern-workspace/app-files.cjs +8 -11
  4. package/dist/cjs/ultramodern-workspace/module-federation.cjs +13 -6
  5. package/dist/cjs/ultramodern-workspace/package-json.cjs +15 -8
  6. package/dist/cjs/ultramodern-workspace/versions.cjs +7 -2
  7. package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +2 -0
  8. package/dist/cjs/ultramodern-workspace/write-workspace.cjs +2 -0
  9. package/dist/esm/ultramodern-tooling/commands.js +393 -3
  10. package/dist/esm/ultramodern-workspace/app-files.js +8 -11
  11. package/dist/esm/ultramodern-workspace/module-federation.js +13 -6
  12. package/dist/esm/ultramodern-workspace/package-json.js +5 -8
  13. package/dist/esm/ultramodern-workspace/versions.js +5 -3
  14. package/dist/esm/ultramodern-workspace/workspace-scripts.js +3 -1
  15. package/dist/esm/ultramodern-workspace/write-workspace.js +3 -1
  16. package/dist/esm-node/ultramodern-tooling/commands.js +393 -3
  17. package/dist/esm-node/ultramodern-workspace/app-files.js +8 -11
  18. package/dist/esm-node/ultramodern-workspace/module-federation.js +13 -6
  19. package/dist/esm-node/ultramodern-workspace/package-json.js +5 -8
  20. package/dist/esm-node/ultramodern-workspace/versions.js +5 -3
  21. package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +3 -1
  22. package/dist/esm-node/ultramodern-workspace/write-workspace.js +3 -1
  23. package/dist/types/ultramodern-workspace/versions.d.ts +4 -2
  24. package/package.json +3 -3
  25. package/template-workspace/.gitignore.handlebars +3 -0
  26. package/template-workspace/README.md.handlebars +19 -13
  27. package/template-workspace/patches/@module-federation__bridge-react@2.6.0.patch +140 -0
  28. package/template-workspace/patches/@module-federation__modern-js-v3@2.6.0.patch +57 -0
  29. package/template-workspace/patches/drizzle-orm-ts7-strict-declarations.patch +452 -0
  30. package/template-workspace/patches/effect-schema-error-type-id.patch +18 -0
  31. package/template-workspace/pnpm-workspace.yaml.handlebars +9 -0
  32. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +95 -5
package/README.md CHANGED
@@ -193,8 +193,8 @@ For strict Effect API migrations, update generated package metadata and Modern
193
193
  package aliases through the framework command before hand-editing app code:
194
194
 
195
195
  ```bash
196
- pnpm dlx @bleedingdev/modern-js-create@3.5.0-ultramodern.2 ultramodern \
197
- migrate-strict-effect --version 3.5.0-ultramodern.2
196
+ pnpm dlx @bleedingdev/modern-js-create@3.5.0-ultramodern.10 ultramodern \
197
+ migrate-strict-effect --version 3.5.0-ultramodern.10
198
198
  pnpm api:check
199
199
  pnpm contract:check
200
200
  pnpm check
@@ -202,16 +202,21 @@ pnpm build
202
202
  ```
203
203
 
204
204
  The command updates `.modernjs/ultramodern.json`, root `modernjs.packageSource`,
205
- generated Modern package aliases, old direct topology metadata, and the pnpm
205
+ generated Modern package aliases, framework-owned toolchain pins, old direct
206
+ topology metadata, strict Effect pnpm overrides/trust policy, and the pnpm
206
207
  lockfile. It does not invent compatibility shims or move business code behind
207
208
  your back. If `pnpm api:check` still fails, migrate the source to
208
209
  `shared/api.ts`, `api/index.ts`, and `src/api/*-client.ts` and delete
209
210
  `api/effect`, `api/lambda`, `shared/effect`, and `src/effect` paths.
210
211
 
211
212
  Generated strict Effect workspaces pin the compatible Effect cohort through
212
- `pnpm-workspace.yaml` overrides: `effect@4.0.0-beta.91` and
213
- `@effect/vitest@4.0.0-beta.91`. Do not override those in app packages; update
214
- the framework cohort when the runtime moves.
213
+ `pnpm-workspace.yaml` overrides: `effect@4.0.0-beta.92`,
214
+ `@effect/opentelemetry@4.0.0-beta.92`, and
215
+ `@effect/vitest@4.0.0-beta.92`. The generated pnpm policy also excludes the
216
+ known `effect@4.0.0-beta.92` and `@effect/opentelemetry@4.0.0-beta.92`
217
+ versions from the 24-hour minimum-release-age gate and their trusted-publisher
218
+ to provenance transitions from no-downgrade checks. Do not override those in
219
+ app packages; update the framework cohort when the runtime moves.
215
220
 
216
221
  Use `--ultramodern-package-source=install` for published cohort proof and pin a
217
222
  specific release with `--ultramodern-package-version` when CI must prove an
@@ -376,19 +381,21 @@ updating workspaces.
376
381
  | `ULTRAMODERN_ASSET_PREFIX` | UltraModern asset prefix fallback | Modern/Rspack-emitted asset URLs when `MODERN_ASSET_PREFIX` is unset |
377
382
  | `ULTRAMODERN_PUBLIC_URL_<APP_ID>` | Per-app deployment/proof URL | Cloudflare proof inputs and Module Federation remote URLs |
378
383
 
379
- Asset URLs use this precedence: `MODERN_ASSET_PREFIX` →
380
- `ULTRAMODERN_ASSET_PREFIX` → origin-relative `/`. `MODERN_PUBLIC_SITE_URL` is
381
- canonical/SEO-only and must not be used as an asset-prefix fallback.
384
+ Shell asset URLs use this precedence: `MODERN_ASSET_PREFIX` →
385
+ `ULTRAMODERN_ASSET_PREFIX` → origin-relative `/`. Module Federation remotes use
386
+ the same env precedence, then fall back to their per-app public origin:
387
+ configured public URL, inferred workers.dev URL, or local dev port.
388
+ `MODERN_PUBLIC_SITE_URL` is canonical/SEO-only and must not be used as an
389
+ asset-prefix fallback.
382
390
  SEO output uses `MODERN_PUBLIC_SITE_URL`; if it is unset, generated local and
383
391
  preview outputs remain non-public until deployment proof provides explicit
384
392
  public URLs.
385
393
 
386
- Without public URLs configured, asset paths are origin-relative (`/`), and the
387
- dev server uses `dev.assetPrefix: '/'` so apps work through tunnels and
388
- reverse proxies (ngrok, cloudflared) without triggering Chrome's Local Network
389
- Access prompt or mixed-content errors. Shell-only workspaces can set
390
- `MODERN_PUBLIC_SITE_URL` for SEO output without changing where assets load
391
- from.
394
+ Without public URLs configured, shell asset paths are origin-relative (`/`).
395
+ Remote dev manifests publish their own local origin so host shells load
396
+ `remoteEntry.js` and exposed chunks from the remote dev server. Shell-only
397
+ workspaces can set `MODERN_PUBLIC_SITE_URL` for SEO output without changing
398
+ where assets load from.
392
399
 
393
400
  ## Cloudflare And Zephyr Proof
394
401
 
@@ -52,7 +52,11 @@ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_no
52
52
  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
+ const app_files_cjs_namespaceObject = require("../ultramodern-workspace/app-files.cjs");
55
56
  const mf_validation_cjs_namespaceObject = require("../ultramodern-workspace/mf-validation.cjs");
57
+ const module_federation_cjs_namespaceObject = require("../ultramodern-workspace/module-federation.cjs");
58
+ const package_json_cjs_namespaceObject = require("../ultramodern-workspace/package-json.cjs");
59
+ const versions_cjs_namespaceObject = require("../ultramodern-workspace/versions.cjs");
56
60
  const workspace_scripts_cjs_namespaceObject = require("../ultramodern-workspace/workspace-scripts.cjs");
57
61
  const external_config_cjs_namespaceObject = require("./config.cjs");
58
62
  const commands_dirname = external_node_path_default().dirname((0, external_node_url_namespaceObject.fileURLToPath)(__rslib_import_meta_url__));
@@ -198,6 +202,91 @@ function updateModernDependencies(packageJson, packageSource) {
198
202
  }
199
203
  return changed;
200
204
  }
205
+ const generatedToolingDependencyPins = new Map([
206
+ [
207
+ '@effect/tsgo',
208
+ versions_cjs_namespaceObject.EFFECT_TSGO_VERSION
209
+ ],
210
+ [
211
+ "@typescript/native-preview",
212
+ versions_cjs_namespaceObject.TYPESCRIPT_NATIVE_PREVIEW_VERSION
213
+ ],
214
+ [
215
+ 'oxfmt',
216
+ versions_cjs_namespaceObject.OXFMT_VERSION
217
+ ],
218
+ [
219
+ 'oxlint',
220
+ versions_cjs_namespaceObject.OXLINT_VERSION
221
+ ],
222
+ [
223
+ 'wrangler',
224
+ versions_cjs_namespaceObject.WRANGLER_VERSION
225
+ ],
226
+ [
227
+ 'zephyr-agent',
228
+ versions_cjs_namespaceObject.ZEPHYR_AGENT_VERSION
229
+ ],
230
+ [
231
+ 'zephyr-rspack-plugin',
232
+ versions_cjs_namespaceObject.ZEPHYR_RSPACK_PLUGIN_VERSION
233
+ ]
234
+ ]);
235
+ const strictEffectPackageVersionPolicyExclusions = [
236
+ `effect@${versions_cjs_namespaceObject.EFFECT_VERSION}`,
237
+ `@effect/opentelemetry@${versions_cjs_namespaceObject.EFFECT_VERSION}`
238
+ ];
239
+ const moduleFederationModernJsPatchPath = `patches/@module-federation__modern-js-v3@${versions_cjs_namespaceObject.MODULE_FEDERATION_VERSION}.patch`;
240
+ const moduleFederationModernJsPatchSourcePath = external_node_path_default().join(createPackageRoot, 'template-workspace', moduleFederationModernJsPatchPath);
241
+ const moduleFederationBridgeReactPatchPath = `patches/@module-federation__bridge-react@${versions_cjs_namespaceObject.MODULE_FEDERATION_VERSION}.patch`;
242
+ const moduleFederationBridgeReactPatchSourcePath = external_node_path_default().join(createPackageRoot, 'template-workspace', moduleFederationBridgeReactPatchPath);
243
+ const effectDeclarationPatchPath = 'patches/effect-schema-error-type-id.patch';
244
+ const effectDeclarationPatchSourcePath = external_node_path_default().join(createPackageRoot, 'template-workspace', effectDeclarationPatchPath);
245
+ const drizzleOrmDeclarationPatchPath = 'patches/drizzle-orm-ts7-strict-declarations.patch';
246
+ const drizzleOrmDeclarationPatchSourcePath = external_node_path_default().join(createPackageRoot, 'template-workspace', drizzleOrmDeclarationPatchPath);
247
+ function updateGeneratedToolingDependencies(packageJson) {
248
+ let changed = false;
249
+ for (const section of [
250
+ 'dependencies',
251
+ 'devDependencies',
252
+ 'peerDependencies',
253
+ 'optionalDependencies'
254
+ ]){
255
+ const dependencies = packageJson[section];
256
+ if (!(!dependencies || 'object' != typeof dependencies || Array.isArray(dependencies))) {
257
+ for (const [packageName, version] of generatedToolingDependencyPins)if (Object.prototype.hasOwnProperty.call(dependencies, packageName) && dependencies[packageName] !== version) {
258
+ dependencies[packageName] = version;
259
+ changed = true;
260
+ }
261
+ }
262
+ }
263
+ return changed;
264
+ }
265
+ const cloudflareModernDeployCommand = 'ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy';
266
+ const cloudflareModernDeploySkipBuildCommand = `${cloudflareModernDeployCommand} --skip-build`;
267
+ const cloudflareWranglerDeployCommand = 'wrangler deploy --config .output/wrangler.json';
268
+ const cloudflareWranglerDeployInvalidSkipBuildCommand = `${cloudflareWranglerDeployCommand} --skip-build`;
269
+ function updateGeneratedPackageScripts(packageJson) {
270
+ const scripts = packageJson.scripts;
271
+ if (!scripts || 'object' != typeof scripts || Array.isArray(scripts)) return false;
272
+ let changed = false;
273
+ const cloudflareBuild = scripts['cloudflare:build'];
274
+ if ('string' == typeof cloudflareBuild) {
275
+ let nextCloudflareBuild = cloudflareBuild;
276
+ if (nextCloudflareBuild.includes(cloudflareModernDeployCommand) && !nextCloudflareBuild.includes(cloudflareModernDeploySkipBuildCommand)) nextCloudflareBuild = nextCloudflareBuild.replace(cloudflareModernDeployCommand, cloudflareModernDeploySkipBuildCommand);
277
+ 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, '');
278
+ if (nextCloudflareBuild !== cloudflareBuild) {
279
+ scripts['cloudflare:build'] = nextCloudflareBuild;
280
+ changed = true;
281
+ }
282
+ }
283
+ const cloudflareDeploy = scripts['cloudflare:deploy'];
284
+ if ('string' == typeof cloudflareDeploy && cloudflareDeploy.includes(cloudflareWranglerDeployInvalidSkipBuildCommand)) {
285
+ scripts['cloudflare:deploy'] = cloudflareDeploy.replace(cloudflareWranglerDeployInvalidSkipBuildCommand, cloudflareWranglerDeployCommand);
286
+ changed = true;
287
+ }
288
+ return changed;
289
+ }
201
290
  function normalizeStrictEffectApiMetadata(value) {
202
291
  const api = value.api;
203
292
  if (!api || 'object' != typeof api || Array.isArray(api)) return false;
@@ -263,6 +352,236 @@ function normalizeStrictEffectApiMetadata(value) {
263
352
  }
264
353
  return changed;
265
354
  }
355
+ function replaceYamlLine(source, pattern, replacement) {
356
+ const updated = source.replace(pattern, replacement);
357
+ return {
358
+ source: updated,
359
+ changed: updated !== source
360
+ };
361
+ }
362
+ function ensureYamlListItem(source, key, item) {
363
+ const itemLine = ` - '${item}'`;
364
+ const headerPattern = new RegExp(`^${key}:\\n(?:(?: - .+\\n)*)`, 'mu');
365
+ const header = source.match(headerPattern);
366
+ if (header) {
367
+ if (header[0].split('\n').includes(itemLine)) return {
368
+ source,
369
+ changed: false
370
+ };
371
+ return {
372
+ source: source.replace(headerPattern, `${header[0]}${itemLine}\n`),
373
+ changed: true
374
+ };
375
+ }
376
+ const block = `${key}:\n${itemLine}\n`;
377
+ const afterTrustPolicyIgnore = source.replace(/^(trustPolicyIgnoreAfter: .+\n)/mu, `$1${block}`);
378
+ if (afterTrustPolicyIgnore !== source) return {
379
+ source: afterTrustPolicyIgnore,
380
+ changed: true
381
+ };
382
+ return {
383
+ source: `${source.trimEnd()}\n${block}`,
384
+ changed: true
385
+ };
386
+ }
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
+ };
402
+ }
403
+ const headerPattern = new RegExp(`^${key}:\\n(?:(?: .+\\n)*)`, 'mu');
404
+ const header = source.match(headerPattern);
405
+ if (header) {
406
+ if (header[0].split('\n').includes(entryLine)) return {
407
+ source,
408
+ changed: false
409
+ };
410
+ return {
411
+ source: source.replace(headerPattern, `${header[0]}${entryLine}\n`),
412
+ changed: true
413
+ };
414
+ }
415
+ return {
416
+ source: `${source.trimEnd()}\n${key}:\n${entryLine}\n`,
417
+ changed: true
418
+ };
419
+ }
420
+ 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
+ };
445
+ }
446
+ 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 {
451
+ source,
452
+ changed: false
453
+ };
454
+ return {
455
+ source: source.replace(currentEntryPattern, ''),
456
+ changed: true
457
+ };
458
+ }
459
+ function ensureGeneratedPatchFile(workspaceRoot, relativePatchPath, sourcePatchPath) {
460
+ const targetPath = external_node_path_default().join(workspaceRoot, relativePatchPath);
461
+ 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;
468
+ }
469
+ function removeGeneratedPatchFileIfUnchanged(workspaceRoot, relativePatchPath, sourcePatchPath) {
470
+ const targetPath = external_node_path_default().join(workspaceRoot, relativePatchPath);
471
+ if (!external_node_fs_default().existsSync(targetPath)) return false;
472
+ const patch = external_node_fs_default().readFileSync(sourcePatchPath, 'utf-8');
473
+ if (external_node_fs_default().readFileSync(targetPath, 'utf-8') !== patch) return false;
474
+ external_node_fs_default().rmSync(targetPath);
475
+ return true;
476
+ }
477
+ function workspaceUsesDependency(workspaceRoot, packageName) {
478
+ const packageJsonPaths = [
479
+ external_node_path_default().join(workspaceRoot, 'package.json')
480
+ ];
481
+ for (const workspaceDir of [
482
+ 'apps',
483
+ 'verticals',
484
+ 'packages'
485
+ ]){
486
+ const absoluteWorkspaceDir = external_node_path_default().join(workspaceRoot, workspaceDir);
487
+ if (external_node_fs_default().existsSync(absoluteWorkspaceDir)) for (const entry of external_node_fs_default().readdirSync(absoluteWorkspaceDir, {
488
+ withFileTypes: true
489
+ })){
490
+ if (!entry.isDirectory()) continue;
491
+ const packageJsonPath = external_node_path_default().join(absoluteWorkspaceDir, entry.name, 'package.json');
492
+ if (external_node_fs_default().existsSync(packageJsonPath)) packageJsonPaths.push(packageJsonPath);
493
+ }
494
+ }
495
+ for (const packageJsonPath of packageJsonPaths){
496
+ const packageJson = JSON.parse(external_node_fs_default().readFileSync(packageJsonPath, 'utf-8'));
497
+ for (const field of [
498
+ 'dependencies',
499
+ 'devDependencies',
500
+ 'peerDependencies',
501
+ 'optionalDependencies'
502
+ ]){
503
+ const dependencies = packageJson[field];
504
+ if (dependencies && 'object' == typeof dependencies) {
505
+ if (Object.prototype.hasOwnProperty.call(dependencies, packageName)) return true;
506
+ for (const specifier of Object.values(dependencies))if ('string' == typeof specifier && specifier.startsWith(`npm:${packageName}@`)) return true;
507
+ }
508
+ }
509
+ }
510
+ return false;
511
+ }
512
+ function ensureGeneratedDeclarationPatches(workspaceRoot, options) {
513
+ 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;
518
+ return changed;
519
+ }
520
+ function updateGeneratedPnpmWorkspacePolicy(workspaceRoot) {
521
+ const workspaceFile = external_node_path_default().join(workspaceRoot, 'pnpm-workspace.yaml');
522
+ if (!external_node_fs_default().existsSync(workspaceFile)) return false;
523
+ let source = external_node_fs_default().readFileSync(workspaceFile, 'utf-8');
524
+ let changed = false;
525
+ const usesDrizzleOrm = workspaceUsesDependency(workspaceRoot, 'drizzle-orm');
526
+ const replacements = [
527
+ [
528
+ /^ {4}'@effect\/vitest>effect': .+$/mu,
529
+ ` '@effect/vitest>effect': '${versions_cjs_namespaceObject.EFFECT_VERSION}'`
530
+ ]
531
+ ];
532
+ for (const [pattern, replacement] of replacements){
533
+ const result = replaceYamlLine(source, pattern, replacement);
534
+ source = result.source;
535
+ changed = result.changed || changed;
536
+ }
537
+ for (const [entryKey, version] of [
538
+ [
539
+ "'@effect/opentelemetry'",
540
+ versions_cjs_namespaceObject.EFFECT_VERSION
541
+ ],
542
+ [
543
+ "'@effect/vitest'",
544
+ versions_cjs_namespaceObject.EFFECT_VITEST_VERSION
545
+ ],
546
+ [
547
+ 'effect',
548
+ versions_cjs_namespaceObject.EFFECT_VERSION
549
+ ]
550
+ ]){
551
+ const result = ensureYamlScalarMapEntry(source, 'overrides', entryKey, version);
552
+ source = result.source;
553
+ changed = result.changed || changed;
554
+ }
555
+ for (const item of strictEffectPackageVersionPolicyExclusions){
556
+ const packageName = item.slice(0, item.lastIndexOf('@'));
557
+ const escapedPackageName = packageName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
558
+ const currentVersion = replaceYamlLine(source, new RegExp(`^ {2}- '${escapedPackageName}@[^']+'$`, 'gmu'), ` - '${item}'`);
559
+ source = currentVersion.source;
560
+ changed = currentVersion.changed || changed;
561
+ for (const policyKey of [
562
+ 'minimumReleaseAgeExclude',
563
+ 'trustPolicyExclude'
564
+ ]){
565
+ const policyExclude = ensureYamlListItem(source, policyKey, item);
566
+ source = policyExclude.source;
567
+ changed = policyExclude.changed || changed;
568
+ }
569
+ }
570
+ const effectPatch = ensureYamlMapEntry(source, 'patchedDependencies', `effect@${versions_cjs_namespaceObject.EFFECT_VERSION}`, effectDeclarationPatchPath);
571
+ source = effectPatch.source;
572
+ changed = effectPatch.changed || changed;
573
+ const moduleFederationModernJsPatch = ensureYamlMapEntry(source, 'patchedDependencies', `@module-federation/modern-js-v3@${versions_cjs_namespaceObject.MODULE_FEDERATION_VERSION}`, moduleFederationModernJsPatchPath);
574
+ source = moduleFederationModernJsPatch.source;
575
+ changed = moduleFederationModernJsPatch.changed || changed;
576
+ const moduleFederationBridgeReactPatch = ensureYamlMapEntry(source, 'patchedDependencies', `@module-federation/bridge-react@${versions_cjs_namespaceObject.MODULE_FEDERATION_VERSION}`, moduleFederationBridgeReactPatchPath);
577
+ source = moduleFederationBridgeReactPatch.source;
578
+ changed = moduleFederationBridgeReactPatch.changed || changed;
579
+ 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}`);
580
+ source = drizzleOrmPatch.source;
581
+ changed = drizzleOrmPatch.changed || changed;
582
+ if (changed) external_node_fs_default().writeFileSync(workspaceFile, source, 'utf-8');
583
+ return changed;
584
+ }
266
585
  function updateUltramodernConfig(workspaceRoot, packageSource) {
267
586
  const configPath = external_node_path_default().join(workspaceRoot, '.modernjs/ultramodern.json');
268
587
  const config = readJsonFile(configPath);
@@ -282,6 +601,64 @@ function updateUltramodernConfig(workspaceRoot, packageSource) {
282
601
  for (const app of config.topology?.apps ?? [])if (app && 'object' == typeof app && !Array.isArray(app)) normalizeStrictEffectApiMetadata(app);
283
602
  writeJsonFile(configPath, config);
284
603
  }
604
+ function writeJsonIfChanged(filePath, value) {
605
+ const next = `${JSON.stringify(value, null, 2)}\n`;
606
+ if (external_node_fs_default().existsSync(filePath) && external_node_fs_default().readFileSync(filePath, 'utf-8') === next) return false;
607
+ external_node_fs_default().mkdirSync(external_node_path_default().dirname(filePath), {
608
+ recursive: true
609
+ });
610
+ external_node_fs_default().writeFileSync(filePath, next, 'utf-8');
611
+ return true;
612
+ }
613
+ function writeTextIfChanged(filePath, value) {
614
+ if (external_node_fs_default().existsSync(filePath) && external_node_fs_default().readFileSync(filePath, 'utf-8') === value) return false;
615
+ external_node_fs_default().mkdirSync(external_node_path_default().dirname(filePath), {
616
+ recursive: true
617
+ });
618
+ external_node_fs_default().writeFileSync(filePath, value, 'utf-8');
619
+ return true;
620
+ }
621
+ function ensureGeneratedIgnoreRules(workspaceRoot) {
622
+ const gitignorePath = external_node_path_default().join(workspaceRoot, '.gitignore');
623
+ const existing = external_node_fs_default().existsSync(gitignorePath) ? external_node_fs_default().readFileSync(gitignorePath, 'utf-8') : '';
624
+ const lines = 0 === existing.trimEnd().length ? [] : existing.trimEnd().split(/\r?\n/u);
625
+ let changed = false;
626
+ for (const rule of [
627
+ '.mf/',
628
+ '**/.mf/',
629
+ 'dist-cloudflare/'
630
+ ])if (!lines.includes(rule)) {
631
+ lines.push(rule);
632
+ changed = true;
633
+ }
634
+ if (!changed) return false;
635
+ const next = `${lines.join('\n')}\n`;
636
+ external_node_fs_default().writeFileSync(gitignorePath, next, 'utf-8');
637
+ return true;
638
+ }
639
+ function updateGeneratedTypeScriptSurfaces(workspaceRoot, config) {
640
+ let changed = false;
641
+ const apps = (0, external_config_cjs_namespaceObject.workspaceAppsFromToolingConfig)(config);
642
+ const remotes = apps.filter((app)=>'shell' !== app.kind);
643
+ changed = writeJsonIfChanged(external_node_path_default().join(workspaceRoot, 'tsconfig.base.json'), (0, package_json_cjs_namespaceObject.createTsConfigBase)()) || changed;
644
+ changed = ensureGeneratedIgnoreRules(workspaceRoot) || changed;
645
+ for (const sharedPackage of [
646
+ 'packages/shared-contracts',
647
+ 'packages/shared-design-tokens'
648
+ ])changed = writeJsonIfChanged(external_node_path_default().join(workspaceRoot, sharedPackage, 'tsconfig.json'), (0, package_json_cjs_namespaceObject.createSharedPackageTsConfig)(sharedPackage)) || changed;
649
+ for (const app of apps){
650
+ changed = writeJsonIfChanged(external_node_path_default().join(workspaceRoot, app.directory, 'tsconfig.json'), (0, package_json_cjs_namespaceObject.createAppTsConfig)(app, remotes)) || changed;
651
+ changed = writeJsonIfChanged(external_node_path_default().join(workspaceRoot, app.directory, 'tsconfig.mf-types.json'), (0, package_json_cjs_namespaceObject.createAppMfTypesTsConfig)(app)) || changed;
652
+ 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;
653
+ }
654
+ return changed;
655
+ }
656
+ function updateGeneratedModernConfigs(workspaceRoot, config) {
657
+ let changed = false;
658
+ const apps = (0, external_config_cjs_namespaceObject.workspaceAppsFromToolingConfig)(config);
659
+ 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;
660
+ return changed;
661
+ }
285
662
  function updateReferenceTopology(workspaceRoot) {
286
663
  const topologyPath = external_node_path_default().join(workspaceRoot, 'topology/reference-topology.json');
287
664
  if (!external_node_fs_default().existsSync(topologyPath)) return false;
@@ -344,8 +721,10 @@ function runMigrateStrictEffect(args, context) {
344
721
  modern-js-create ultramodern migrate-strict-effect --version <version> [--skip-install]
345
722
 
346
723
  Updates generated UltraModern package-source metadata, Modern package aliases,
347
- direct Effect API topology metadata, and the pnpm lockfile for strict Effect
348
- workspaces. Source code still has to pass pnpm api:check and pnpm contract:check.
724
+ framework-owned toolchain pins, direct Effect API topology metadata, strict
725
+ Effect pnpm overrides/trust policy, framework-owned TypeScript config
726
+ surfaces, and the pnpm lockfile. Source code still has to pass pnpm api:check
727
+ and pnpm contract:check.
349
728
  `);
350
729
  return 0;
351
730
  }
@@ -353,6 +732,9 @@ workspaces. Source code still has to pass pnpm api:check and pnpm contract:check
353
732
  const packageSource = createMigrationPackageSource(args, current);
354
733
  updateUltramodernConfig(context.workspaceRoot, packageSource);
355
734
  updateReferenceTopology(context.workspaceRoot);
735
+ const migrated = (0, external_config_cjs_namespaceObject.readUltramodernConfig)(context.workspaceRoot);
736
+ updateGeneratedTypeScriptSurfaces(context.workspaceRoot, migrated);
737
+ updateGeneratedModernConfigs(context.workspaceRoot, migrated);
356
738
  for (const relativePackageFile of listWorkspacePackageFiles(context.workspaceRoot)){
357
739
  const packageFile = external_node_path_default().join(context.workspaceRoot, relativePackageFile);
358
740
  const packageJson = readJsonFile(packageFile);
@@ -363,9 +745,17 @@ workspaces. Source code still has to pass pnpm api:check and pnpm contract:check
363
745
  config: './.modernjs/ultramodern.json'
364
746
  };
365
747
  }
366
- if (updateModernDependencies(packageJson, packageSource)) writeJsonFile(packageFile, packageJson);
748
+ const modernDependenciesChanged = updateModernDependencies(packageJson, packageSource);
749
+ const toolingDependenciesChanged = updateGeneratedToolingDependencies(packageJson);
750
+ const generatedScriptsChanged = updateGeneratedPackageScripts(packageJson);
751
+ const changed = modernDependenciesChanged || toolingDependenciesChanged || generatedScriptsChanged;
752
+ if (changed) writeJsonFile(packageFile, packageJson);
367
753
  else if ('package.json' === relativePackageFile) writeJsonFile(packageFile, packageJson);
368
754
  }
755
+ updateGeneratedPnpmWorkspacePolicy(context.workspaceRoot);
756
+ ensureGeneratedDeclarationPatches(context.workspaceRoot, {
757
+ includeDrizzleOrmPatch: workspaceUsesDependency(context.workspaceRoot, 'drizzle-orm')
758
+ });
369
759
  if (!hasFlag(args, '--skip-install')) {
370
760
  const status = runPnpmLockfileRefresh(context);
371
761
  if (0 !== status) return status;
@@ -71,17 +71,14 @@ function createAppEnvDts(app, remotes = []) {
71
71
  }
72
72
  `;
73
73
  })).join('\n');
74
- const reactTypeReference = remoteModuleDeclarations ? "/// <reference types='react' />\n" : '';
75
- const siteUrlDeclaration = 'declare const ULTRAMODERN_SITE_URL: string;';
76
- return `${reactTypeReference}/// <reference types='@modern-js/app-tools/types' />
77
-
78
- ${siteUrlDeclaration}
79
- declare module '*.svg' {
80
- const url: string;
81
- export default url;
82
- }
83
- declare module '*.css';
84
- ${remoteModuleDeclarations ? `\n${remoteModuleDeclarations}` : ''}`;
74
+ const reactTypeImport = remoteModuleDeclarations ? "import type React from 'react';\n" : '';
75
+ return [
76
+ `import '@modern-js/app-tools/types';\n${reactTypeImport}`.trimEnd(),
77
+ `declare global {
78
+ const ULTRAMODERN_SITE_URL: string;
79
+ }`,
80
+ remoteModuleDeclarations.trimEnd()
81
+ ].filter((section)=>section.length > 0).join('\n\n').concat('\n');
85
82
  }
86
83
  function createAppRuntimeConfig(app, scope, remotes = []) {
87
84
  const pluginsConfig = 'shell' === app.kind ? ` plugins: [
@@ -76,6 +76,11 @@ function createAppModernConfig(scope, app) {
76
76
  (cloudflareDeployEnabled ? '/' : \`http://localhost:\${port}\`);
77
77
  const defaultRemoteAssetPrefix = \`\${remoteAssetOrigin.replace(/\\/+$/u, '')}/\`;
78
78
  const defaultAssetPrefix = defaultRemoteAssetPrefix;`;
79
+ const devAssetPrefixSource = 'shell' === app.kind ? ` // Keep shell dev assets origin-relative so the shell works through
80
+ // tunnels and local previews without rewriting its own chunks.
81
+ assetPrefix: '/',` : ` // Remote dev manifests must publish an absolute publicPath so host
82
+ // shells load remoteEntry.js and exposed chunks from this dev server.
83
+ assetPrefix,`;
79
84
  return `// @effect-diagnostics processEnv:off
80
85
  import {
81
86
  appTools,
@@ -144,8 +149,10 @@ ${defaultAssetPrefixSource}
144
149
  // load remoteEntry.js and exposed chunks from the remote origin, not the host.
145
150
  const assetPrefix =
146
151
  configuredModernAssetPrefix || configuredUltramodernAssetPrefix || defaultAssetPrefix;
147
- const buildCacheTarget = cloudflareDeployEnabled ? 'cloudflare' : 'web';
148
- const buildCacheDirectory = \`node_modules/.cache/rspack-\${appId}-\${buildCacheTarget}\`;
152
+ const buildTarget = cloudflareDeployEnabled ? 'cloudflare' : 'web';
153
+ const buildOutputRoot = cloudflareDeployEnabled ? 'dist-cloudflare' : 'dist';
154
+ const buildTempDirectory = \`node_modules/.modern-js-\${appId}-\${buildTarget}\`;
155
+ const buildCacheDirectory = \`node_modules/.cache/rspack-\${appId}-\${buildTarget}\`;
149
156
 
150
157
  if (
151
158
  cloudflareDeployEnabled &&
@@ -175,9 +182,7 @@ ${bffConfig} ...(cloudflareDeployEnabled
175
182
  }
176
183
  : {}),
177
184
  dev: {
178
- // Keep dev assets origin-relative too; the default absolute
179
- // http://localhost:<port> prefix breaks pages served through tunnels.
180
- assetPrefix: '/',
185
+ ${devAssetPrefixSource}
181
186
  },
182
187
  html: {
183
188
  outputStructure: 'flat',
@@ -187,13 +192,15 @@ ${bffConfig} ...(cloudflareDeployEnabled
187
192
  disableTsChecker: false,
188
193
  distPath: {
189
194
  html: './',
195
+ root: buildOutputRoot,
190
196
  },
191
197
  polyfill: 'off',
192
198
  splitRouteChunks: true,
199
+ tempDir: buildTempDirectory,
193
200
  },
194
201
  performance: {
195
202
  buildCache: {
196
- cacheDigest: [appId, buildCacheTarget],
203
+ cacheDigest: [appId, buildTarget],
197
204
  cacheDirectory: buildCacheDirectory,
198
205
  },
199
206
  rsdoctor: {