@bleedingdev/modern-js-create 3.2.0-ultramodern.3 → 3.2.0-ultramodern.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +32 -14
  2. package/dist/index.js +2753 -324
  3. package/dist/types/locale/en.d.ts +2 -0
  4. package/dist/types/locale/zh.d.ts +2 -0
  5. package/dist/types/ultramodern-workspace.d.ts +13 -0
  6. package/package.json +8 -5
  7. package/template/.agents/skills-lock.json +34 -0
  8. package/template/.github/renovate.json +53 -0
  9. package/template/.github/workflows/ultramodern-gates.yml.handlebars +34 -10
  10. package/template/.mise.toml.handlebars +2 -0
  11. package/template/AGENTS.md +27 -0
  12. package/template/README.md +20 -14
  13. package/template/api/effect/index.ts.handlebars +7 -45
  14. package/template/config/public/locales/cs/translation.json +39 -0
  15. package/template/config/public/locales/en/translation.json +39 -0
  16. package/template/modern.config.ts.handlebars +44 -23
  17. package/template/oxfmt.config.ts +8 -0
  18. package/template/oxlint.config.ts +12 -0
  19. package/template/package.json.handlebars +59 -30
  20. package/template/pnpm-workspace.yaml +26 -0
  21. package/template/rstest.config.mts +7 -0
  22. package/template/scripts/bootstrap-agent-skills.mjs +95 -0
  23. package/template/scripts/check-i18n-strings.mjs +83 -0
  24. package/template/scripts/validate-ultramodern.mjs.handlebars +438 -16
  25. package/template/shared/effect/api.ts.handlebars +1 -2
  26. package/template/src/modern-app-env.d.ts +2 -0
  27. package/template/src/modern.runtime.ts.handlebars +17 -3
  28. package/template/src/routes/[lang]/page.tsx.handlebars +210 -0
  29. package/template/src/routes/index.css.handlebars +14 -3
  30. package/template/src/routes/layout.tsx.handlebars +2 -1
  31. package/template/tests/tsconfig.json +7 -0
  32. package/template/tests/ultramodern.contract.test.ts.handlebars +78 -0
  33. package/template/tsconfig.json +106 -2
  34. package/template-workspace/.agents/agent-reference-repos.json +24 -0
  35. package/template-workspace/.agents/rstackjs-agent-skills-LICENSE +21 -0
  36. package/template-workspace/.agents/skills/rsbuild-best-practices/SKILL.md +57 -0
  37. package/template-workspace/.agents/skills/rsdoctor-analysis/SKILL.md +96 -0
  38. package/template-workspace/.agents/skills/rsdoctor-analysis/references/command-map.md +113 -0
  39. package/template-workspace/.agents/skills/rsdoctor-analysis/references/common-analysis-patterns.md +190 -0
  40. package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor-common.md +88 -0
  41. package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor-rspack.md +138 -0
  42. package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor-webpack.md +71 -0
  43. package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor.md +39 -0
  44. package/template-workspace/.agents/skills/rsdoctor-analysis/references/rsdoctor-data-types.md +103 -0
  45. package/template-workspace/.agents/skills/rslib-best-practices/SKILL.md +58 -0
  46. package/template-workspace/.agents/skills/rslib-modern-package/SKILL.md +173 -0
  47. package/template-workspace/.agents/skills/rspack-best-practices/SKILL.md +70 -0
  48. package/template-workspace/.agents/skills/rspack-tracing/SKILL.md +75 -0
  49. package/template-workspace/.agents/skills/rspack-tracing/references/bottlenecks.md +47 -0
  50. package/template-workspace/.agents/skills/rspack-tracing/references/tracing-guide.md +38 -0
  51. package/template-workspace/.agents/skills/rspack-tracing/scripts/analyze_trace.js +184 -0
  52. package/template-workspace/.agents/skills/rstest-best-practices/SKILL.md +133 -0
  53. package/template-workspace/.agents/skills-lock.json +114 -0
  54. package/template-workspace/.github/renovate.json +29 -0
  55. package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +54 -0
  56. package/template-workspace/.gitignore.handlebars +5 -0
  57. package/template-workspace/.mise.toml.handlebars +2 -0
  58. package/template-workspace/AGENTS.md +61 -0
  59. package/template-workspace/README.md.handlebars +26 -5
  60. package/template-workspace/oxfmt.config.ts +16 -0
  61. package/template-workspace/oxlint.config.ts +19 -0
  62. package/template-workspace/pnpm-workspace.yaml +34 -8
  63. package/template-workspace/scripts/assert-mf-types.mjs +44 -0
  64. package/template-workspace/scripts/bootstrap-agent-skills.mjs +155 -0
  65. package/template-workspace/scripts/setup-agent-reference-repos.mjs +364 -0
  66. package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +755 -87
  67. package/template/biome.json +0 -41
  68. package/template/src/routes/page.tsx.handlebars +0 -119
package/dist/index.js CHANGED
@@ -453,8 +453,8 @@ const EN_LOCALE = {
453
453
  success: '✨ Created successfully!',
454
454
  nextSteps: '📋 Next steps:',
455
455
  step1: 'cd {projectName}',
456
- step2: 'pnpm install',
457
- step3: 'pnpm dev'
456
+ step2: 'mise exec -- pnpm install',
457
+ step3: 'mise exec -- pnpm dev'
458
458
  },
459
459
  help: {
460
460
  title: '🚀 Modern.js Project Creator',
@@ -468,12 +468,13 @@ const EN_LOCALE = {
468
468
  optionRouter: ' -r, --router Select router framework (react-router or tanstack)',
469
469
  optionBff: ' --bff Enable BFF scaffold (default runtime: effect)',
470
470
  optionBffRuntime: ' --bff-runtime Select BFF runtime (hono or effect)',
471
- optionTailwind: ' --tailwind Enable Tailwind CSS v4 scaffold (PostCSS + starter styles)',
471
+ optionTailwind: ' --no-tailwind Disable default Tailwind CSS v4 scaffold',
472
472
  optionWorkspace: ' --workspace Use workspace protocol for @modern-js dependencies (for local monorepo testing)',
473
473
  optionUltramodernWorkspace: ' --ultramodern-workspace Generate the canonical UltraModern SuperApp workspace',
474
474
  optionUltramodernPackageSource: ' --ultramodern-package-source Select UltraModern package source (workspace or install)',
475
475
  optionUltramodernPackageScope: ' --ultramodern-package-scope Publish scope for npm alias installs (for example bleedingdev)',
476
476
  optionUltramodernPackageNamePrefix: ' --ultramodern-package-name-prefix Prefix for npm alias package names (default: modern-js-)',
477
+ optionMicroVertical: ' --microvertical Add a MicroVertical package to an existing UltraModern workspace (remote, horizontal-remote, service, shared)',
477
478
  optionSub: ' -s, --sub Mark as a subproject (package in monorepo)',
478
479
  examples: '💡 Examples:',
479
480
  example1: ' create my-app',
@@ -481,11 +482,12 @@ const EN_LOCALE = {
481
482
  example3: ' create my-app --sub',
482
483
  example4: ' create --help',
483
484
  example5: ' create my-app --router tanstack',
484
- example6: ' create my-app --router tanstack --tailwind',
485
+ example6: ' create my-app --router tanstack --no-tailwind',
485
486
  example7: ' create my-app --bff',
486
487
  example8: ' create my-app --router tanstack --bff-runtime effect',
487
488
  example9: ' create my-app --router tanstack --bff-runtime effect --workspace',
488
- example10: ' create my-super-app --ultramodern-workspace --ultramodern-package-source install --ultramodern-package-scope bleedingdev',
489
+ example10: ' pnpm dlx @bleedingdev/modern-js-create my-super-app',
490
+ example11: ' create catalog --microvertical remote',
489
491
  moreInfo: '📚 Learn more: https://modernjs.dev'
490
492
  },
491
493
  version: {
@@ -508,8 +510,8 @@ const ZH_LOCALE = {
508
510
  success: '✨ 创建成功!',
509
511
  nextSteps: '📋 下一步:',
510
512
  step1: 'cd {projectName}',
511
- step2: 'pnpm install',
512
- step3: 'pnpm dev'
513
+ step2: 'mise exec -- pnpm install',
514
+ step3: 'mise exec -- pnpm dev'
513
515
  },
514
516
  help: {
515
517
  title: '🚀 Modern.js 项目创建工具',
@@ -523,12 +525,13 @@ const ZH_LOCALE = {
523
525
  optionRouter: ' -r, --router 选择路由框架 (react-router 或 tanstack)',
524
526
  optionBff: ' --bff 启用 BFF 模板(默认运行时:effect)',
525
527
  optionBffRuntime: ' --bff-runtime 选择 BFF 运行时(hono 或 effect)',
526
- optionTailwind: ' --tailwind 启用 Tailwind CSS v4 模板(PostCSS + 示例样式)',
528
+ optionTailwind: ' --no-tailwind 禁用默认 Tailwind CSS v4 模板',
527
529
  optionWorkspace: ' --workspace 对 @modern-js 依赖使用 workspace 协议(用于本地 monorepo 联调)',
528
530
  optionUltramodernWorkspace: ' --ultramodern-workspace 生成标准 UltraModern SuperApp 工作区',
529
531
  optionUltramodernPackageSource: ' --ultramodern-package-source 选择 UltraModern 依赖来源(workspace 或 install)',
530
532
  optionUltramodernPackageScope: ' --ultramodern-package-scope npm alias 安装使用的发布 scope(例如 bleedingdev)',
531
533
  optionUltramodernPackageNamePrefix: ' --ultramodern-package-name-prefix npm alias 包名前缀(默认:modern-js-)',
534
+ optionMicroVertical: ' --microvertical 向现有 UltraModern 工作区添加 MicroVertical 包(remote、horizontal-remote、service、shared)',
532
535
  optionSub: ' -s, --sub 标记为子项目(monorepo 中的子包)',
533
536
  examples: '💡 示例:',
534
537
  example1: ' create my-app',
@@ -536,11 +539,12 @@ const ZH_LOCALE = {
536
539
  example3: ' create my-app --sub',
537
540
  example4: ' create --help',
538
541
  example5: ' create my-app --router tanstack',
539
- example6: ' create my-app --router tanstack --tailwind',
542
+ example6: ' create my-app --router tanstack --no-tailwind',
540
543
  example7: ' create my-app --bff',
541
544
  example8: ' create my-app --router tanstack --bff-runtime effect',
542
545
  example9: ' create my-app --router tanstack --bff-runtime effect --workspace',
543
546
  example10: ' create my-super-app --ultramodern-workspace --ultramodern-package-source install --ultramodern-package-scope bleedingdev',
547
+ example11: ' create catalog --microvertical remote',
544
548
  moreInfo: '📚 更多信息: https://modernjs.dev'
545
549
  },
546
550
  version: {
@@ -554,15 +558,51 @@ const localeKeys = i18n.init('en', {
554
558
  });
555
559
  const ultramodern_workspace_dirname = node_path.dirname(fileURLToPath(import.meta.url));
556
560
  const workspaceTemplateDir = node_path.resolve(ultramodern_workspace_dirname, '..', 'template-workspace');
557
- const TANSTACK_ROUTER_VERSION = '1.170.1';
558
- const MODULE_FEDERATION_VERSION = '2.4.0';
561
+ const TANSTACK_ROUTER_VERSION = '1.170.8';
562
+ const MODULE_FEDERATION_VERSION = '2.5.0';
563
+ const ZEPHYR_RSPACK_PLUGIN_VERSION = '1.1.1';
564
+ const ZEPHYR_AGENT_VERSION = '1.1.1';
565
+ const WRANGLER_VERSION = '4.95.0';
566
+ const TAILWIND_VERSION = '4.3.0';
567
+ const TAILWIND_POSTCSS_VERSION = '4.3.0';
568
+ const EFFECT_TSGO_VERSION = '0.11.0';
559
569
  const TYPESCRIPT_VERSION = '6.0.3';
570
+ const TYPESCRIPT_NATIVE_PREVIEW_VERSION = '7.0.0-dev.20260527.2';
571
+ const OXLINT_VERSION = '1.66.0';
572
+ const OXFMT_VERSION = '0.51.0';
573
+ const ULTRACITE_VERSION = '7.7.0';
574
+ const I18NEXT_VERSION = '26.2.0';
560
575
  const REACT_VERSION = '^19.2.6';
561
576
  const REACT_DOM_VERSION = '^19.2.6';
577
+ const PNPM_VERSION = '11.4.0';
562
578
  const WORKSPACE_PACKAGE_VERSION = 'workspace:*';
579
+ const GENERATED_CONTRACT_PATH = '.modernjs/ultramodern-generated-contract.json';
580
+ const RSTACK_AGENT_SKILLS_COMMIT = '61c948b42512e223bad44b83af4080eba48b2677';
581
+ const MODULE_FEDERATION_AGENT_SKILLS_COMMIT = '07bb5b6c43ad457609e00c081b72d4c42508ec76';
582
+ const baselineAgentSkills = [
583
+ 'rsbuild-best-practices',
584
+ 'rspack-best-practices',
585
+ 'rspack-tracing',
586
+ 'rsdoctor-analysis',
587
+ 'rslib-best-practices',
588
+ 'rslib-modern-package',
589
+ 'rstest-best-practices'
590
+ ];
591
+ const moduleFederationAgentSkills = [
592
+ 'mf'
593
+ ];
594
+ const privateAgentSkills = [
595
+ 'plan-graph',
596
+ 'dag',
597
+ 'subagent-graph',
598
+ 'helm',
599
+ 'debugger-mode'
600
+ ];
601
+ const effectTsgoTypecheckCommand = "node -e \"const fs = require('node:fs'); const { execFileSync, spawnSync } = require('node:child_process'); const bin = execFileSync('effect-tsgo', ['get-exe-path'], { encoding: 'utf8' }).trim(); if (process.platform !== 'win32') fs.chmodSync(bin, 0o755); const result = spawnSync(bin, ['--noEmit', '-p', 'tsconfig.json'], { stdio: 'inherit' }); process.exit(result.status ?? 1);\"";
563
602
  const modernPackageNames = [
564
603
  '@modern-js/app-tools',
565
604
  '@modern-js/plugin-bff',
605
+ '@modern-js/plugin-i18n',
566
606
  '@modern-js/plugin-tanstack',
567
607
  '@modern-js/runtime'
568
608
  ];
@@ -609,7 +649,15 @@ const remoteApps = [
609
649
  mfName: 'remoteCommerce',
610
650
  exposes: {
611
651
  './Route': './src/remote-entry.tsx',
612
- './Widget': './src/components/CommerceWidget.tsx'
652
+ './Widget': './src/components/commerce-widget.tsx'
653
+ },
654
+ effectApi: {
655
+ stem: 'recommendations',
656
+ prefix: '/commerce-api',
657
+ consumedBy: [
658
+ shellApp.id,
659
+ 'remote-commerce'
660
+ ]
613
661
  },
614
662
  ownership: {
615
663
  team: 'commerce-experience',
@@ -638,7 +686,15 @@ const remoteApps = [
638
686
  mfName: 'remoteIdentity',
639
687
  exposes: {
640
688
  './Route': './src/remote-entry.tsx',
641
- './Widget': './src/components/IdentityWidget.tsx'
689
+ './Widget': './src/components/identity-widget.tsx'
690
+ },
691
+ effectApi: {
692
+ stem: 'identity',
693
+ prefix: '/identity-api',
694
+ consumedBy: [
695
+ shellApp.id,
696
+ 'remote-identity'
697
+ ]
642
698
  },
643
699
  ownership: {
644
700
  team: 'identity-platform',
@@ -666,7 +722,7 @@ const remoteApps = [
666
722
  port: 3023,
667
723
  mfName: 'remoteDesignSystem',
668
724
  exposes: {
669
- './Button': './src/components/Button.tsx',
725
+ './Button': './src/components/button.tsx',
670
726
  './tokens': './src/tokens.ts'
671
727
  },
672
728
  ownership: {
@@ -706,6 +762,81 @@ const effectService = {
706
762
  }
707
763
  }
708
764
  };
765
+ const effectDiagnostics = [
766
+ 'anyUnknownInErrorContext',
767
+ 'classSelfMismatch',
768
+ 'duplicatePackage',
769
+ 'effectFnImplicitAny',
770
+ 'floatingEffect',
771
+ 'genericEffectServices',
772
+ 'missingEffectContext',
773
+ 'missingEffectError',
774
+ 'missingLayerContext',
775
+ 'missingReturnYieldStar',
776
+ 'missingStarInYieldEffectGen',
777
+ 'nonObjectEffectServiceType',
778
+ 'outdatedApi',
779
+ 'overriddenSchemaConstructor',
780
+ 'catchUnfailableEffect',
781
+ 'effectFnIife',
782
+ 'effectGenUsesAdapter',
783
+ 'effectInFailure',
784
+ 'effectInVoidSuccess',
785
+ 'globalErrorInEffectCatch',
786
+ 'globalErrorInEffectFailure',
787
+ 'layerMergeAllWithDependencies',
788
+ 'lazyPromiseInEffectSync',
789
+ 'leakingRequirements',
790
+ 'multipleEffectProvide',
791
+ 'returnEffectInGen',
792
+ 'runEffectInsideEffect',
793
+ 'schemaSyncInEffect',
794
+ 'scopeInLayerEffect',
795
+ 'strictEffectProvide',
796
+ 'tryCatchInEffectGen',
797
+ 'unknownInEffectCatch',
798
+ 'asyncFunction',
799
+ 'cryptoRandomUUID',
800
+ 'cryptoRandomUUIDInEffect',
801
+ 'extendsNativeError',
802
+ 'globalConsole',
803
+ 'globalConsoleInEffect',
804
+ 'globalDate',
805
+ 'globalDateInEffect',
806
+ 'globalFetch',
807
+ 'globalFetchInEffect',
808
+ 'globalRandom',
809
+ 'globalRandomInEffect',
810
+ 'globalTimers',
811
+ 'globalTimersInEffect',
812
+ 'instanceOfSchema',
813
+ 'newPromise',
814
+ 'nodeBuiltinImport',
815
+ 'preferSchemaOverJson',
816
+ 'processEnv',
817
+ 'processEnvInEffect',
818
+ 'unsafeEffectTypeAssertion',
819
+ 'catchAllToMapError',
820
+ 'deterministicKeys',
821
+ 'effectDoNotation',
822
+ 'effectFnOpportunity',
823
+ 'effectMapFlatten',
824
+ 'effectMapVoid',
825
+ 'effectSucceedWithVoid',
826
+ 'missedPipeableOpportunity',
827
+ 'missingEffectServiceDependency',
828
+ 'nestedEffectGenYield',
829
+ 'redundantSchemaTagIdentifier',
830
+ 'schemaStructWithTag',
831
+ 'schemaUnionOfLiterals',
832
+ 'serviceNotAsClass',
833
+ 'strictBooleanExpressions',
834
+ 'unnecessaryArrowBlock',
835
+ 'unnecessaryEffectGen',
836
+ 'unnecessaryFailYieldableError',
837
+ 'unnecessaryPipe',
838
+ 'unnecessaryPipeChain'
839
+ ];
709
840
  const sharedPackages = [
710
841
  {
711
842
  id: 'shared-contracts',
@@ -723,6 +854,89 @@ const sharedPackages = [
723
854
  description: 'Shared Effect API type placeholders for services and clients.'
724
855
  }
725
856
  ];
857
+ function createNeutralOwnership(id, tier = 'tier-2-microvertical') {
858
+ return {
859
+ team: 'super-app-platform',
860
+ slack: '#super-app-platform',
861
+ pagerDuty: 'pd-super-app-platform',
862
+ runbookRef: `runbooks/microverticals/${id}.md`,
863
+ adrRef: `docs/super-app-rfc-adr/microverticals.md#${id}`,
864
+ blastRadius: {
865
+ tier,
866
+ references: [
867
+ `docs/super-app-rfc-adr/blast-radius.md#${id}`
868
+ ]
869
+ }
870
+ };
871
+ }
872
+ function createRemoteDescriptor(name, kind, port) {
873
+ const domain = toKebabCase(name);
874
+ const id = `remote-${domain}`;
875
+ const displayPrefix = toPascalCase(domain).replace(/([a-z])([A-Z])/g, '$1 $2');
876
+ return {
877
+ id,
878
+ directory: `apps/remotes/${id}`,
879
+ packageSuffix: id,
880
+ displayName: `${displayPrefix} Remote`,
881
+ kind: 'horizontal-remote' === kind ? 'horizontal-remote' : 'vertical',
882
+ domain,
883
+ portEnv: `REMOTE_${toEnvSegment(domain)}_PORT`,
884
+ port,
885
+ mfName: `remote${toPascalCase(domain)}`,
886
+ exposes: {
887
+ './Route': './src/remote-entry.tsx',
888
+ './Widget': `./src/components/${domain}-widget.tsx`
889
+ },
890
+ ...'remote' === kind ? {
891
+ effectApi: {
892
+ stem: domain,
893
+ prefix: `/${domain}-api`,
894
+ consumedBy: [
895
+ shellApp.id,
896
+ id
897
+ ]
898
+ }
899
+ } : {},
900
+ ownership: createNeutralOwnership(id)
901
+ };
902
+ }
903
+ function createServiceDescriptor(name, port) {
904
+ const normalized = toKebabCase(name);
905
+ const suffix = normalized.endsWith('-effect') ? normalized : `service-${normalized.replace(/^service-/, '')}-effect`;
906
+ return {
907
+ id: suffix,
908
+ directory: `services/${suffix}`,
909
+ packageSuffix: suffix,
910
+ portEnv: `${toEnvSegment(suffix)}_PORT`,
911
+ port,
912
+ ownership: createNeutralOwnership(suffix, 'tier-2-effect-service')
913
+ };
914
+ }
915
+ function serviceApiPrefix(service) {
916
+ const name = service.id.replace(/^service-/, '').replace(/-effect$/, '');
917
+ return name.endsWith('-api') ? `/${name}` : `/${name}-api`;
918
+ }
919
+ function appHasEffectApi(app) {
920
+ return void 0 !== app.effectApi;
921
+ }
922
+ function effectApiPrefix(target) {
923
+ return target.effectApi?.prefix ?? serviceApiPrefix(target);
924
+ }
925
+ function effectApiStem(target) {
926
+ return target.effectApi?.stem ?? target.id.replace(/^service-/, '').replace(/-effect$/, '').replace(/-api$/, '');
927
+ }
928
+ function verticalEffectApps(remotes = remoteApps) {
929
+ return remotes.filter(appHasEffectApi);
930
+ }
931
+ function createSharedPackageDescriptor(name) {
932
+ const normalized = toKebabCase(name);
933
+ const id = normalized.startsWith('shared-') ? normalized : `shared-${normalized}`;
934
+ return {
935
+ id,
936
+ directory: `packages/${id}`,
937
+ description: `Shared ${normalized.replace(/^shared-/, '')} package placeholder.`
938
+ };
939
+ }
726
940
  function normalizePath(filePath) {
727
941
  return filePath.split(node_path.sep).join('/');
728
942
  }
@@ -743,6 +957,15 @@ function writeFile(targetDir, relativePath, content) {
743
957
  });
744
958
  node_fs.writeFileSync(filePath, content, 'utf-8');
745
959
  }
960
+ function writeFileReplacing(targetDir, relativePath, content) {
961
+ assertSafeRelativePath(relativePath);
962
+ const filePath = node_path.join(targetDir, relativePath);
963
+ ensureInsideRoot(targetDir, filePath);
964
+ node_fs.mkdirSync(node_path.dirname(filePath), {
965
+ recursive: true
966
+ });
967
+ node_fs.writeFileSync(filePath, content, 'utf-8');
968
+ }
746
969
  function writeJson(targetDir, relativePath, value) {
747
970
  writeFile(targetDir, relativePath, `${JSON.stringify(value, null, 2)}\n`);
748
971
  }
@@ -788,6 +1011,17 @@ function toPackageScope(packageName) {
788
1011
  const normalized = packageName.replace(/^@/, '').replace(/[\\/]+/g, '-').toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^[._-]+|[._-]+$/g, '').replace(/-{2,}/g, '-');
789
1012
  return normalized || 'ultramodern-superapp';
790
1013
  }
1014
+ function toKebabCase(value) {
1015
+ const normalized = value.trim().replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/[._]+/g, '-').toLowerCase().replace(/-+/g, '-').replace(/^-+|-+$/g, '');
1016
+ return normalized;
1017
+ }
1018
+ function toCamelCase(value) {
1019
+ const pascal = toPascalCase(value);
1020
+ return `${pascal.charAt(0).toLowerCase()}${pascal.slice(1)}`;
1021
+ }
1022
+ function toEnvSegment(value) {
1023
+ return toKebabCase(value).replace(/-/g, '_').toUpperCase();
1024
+ }
791
1025
  function ultramodern_workspace_packageName(scope, suffix) {
792
1026
  return `@${scope}/${suffix}`;
793
1027
  }
@@ -818,26 +1052,44 @@ function modernPackageSpecifier(packageName, packageSource) {
818
1052
  if (!packageSource.aliasScope) return packageSource.modernPackageVersion;
819
1053
  return `npm:${modernAliasPackageName(packageName, packageSource)}@${packageSource.modernPackageVersion}`;
820
1054
  }
821
- function appDependencies(scope, packageSource) {
822
- return {
1055
+ function appDependencies(scope, packageSource, app) {
1056
+ const dependencies = {
823
1057
  '@modern-js/plugin-tanstack': modernPackageSpecifier('@modern-js/plugin-tanstack', packageSource),
1058
+ '@modern-js/plugin-i18n': modernPackageSpecifier('@modern-js/plugin-i18n', packageSource),
824
1059
  '@modern-js/runtime': modernPackageSpecifier('@modern-js/runtime', packageSource),
825
1060
  '@module-federation/modern-js-v3': MODULE_FEDERATION_VERSION,
826
1061
  '@module-federation/runtime': MODULE_FEDERATION_VERSION,
827
1062
  '@tanstack/react-router': TANSTACK_ROUTER_VERSION,
1063
+ i18next: I18NEXT_VERSION,
1064
+ 'node-fetch': '^3.3.2',
828
1065
  [ultramodern_workspace_packageName(scope, 'shared-contracts')]: WORKSPACE_PACKAGE_VERSION,
829
1066
  [ultramodern_workspace_packageName(scope, 'shared-design-tokens')]: WORKSPACE_PACKAGE_VERSION,
830
1067
  react: REACT_VERSION,
831
1068
  'react-dom': REACT_DOM_VERSION
832
1069
  };
1070
+ if ('shell' === app.kind) {
1071
+ dependencies['@modern-js/plugin-bff'] = modernPackageSpecifier('@modern-js/plugin-bff', packageSource);
1072
+ for (const remote of verticalEffectApps())dependencies[ultramodern_workspace_packageName(scope, remote.packageSuffix)] = WORKSPACE_PACKAGE_VERSION;
1073
+ }
1074
+ if (appHasEffectApi(app)) dependencies['@modern-js/plugin-bff'] = modernPackageSpecifier('@modern-js/plugin-bff', packageSource);
1075
+ return dependencies;
833
1076
  }
834
- function appDevDependencies(packageSource) {
1077
+ function appDevDependencies(packageSource, enableTailwind) {
835
1078
  return {
836
1079
  '@modern-js/app-tools': modernPackageSpecifier('@modern-js/app-tools', packageSource),
1080
+ '@effect/tsgo': EFFECT_TSGO_VERSION,
1081
+ ...enableTailwind ? {
1082
+ '@tailwindcss/postcss': `^${TAILWIND_POSTCSS_VERSION}`,
1083
+ postcss: '^8.5.6',
1084
+ tailwindcss: `^${TAILWIND_VERSION}`
1085
+ } : {},
1086
+ "@typescript/native-preview": TYPESCRIPT_NATIVE_PREVIEW_VERSION,
837
1087
  '@types/node': '^20',
838
1088
  '@types/react': '^19.1.8',
839
1089
  '@types/react-dom': '^19.1.6',
840
- typescript: TYPESCRIPT_VERSION
1090
+ typescript: TYPESCRIPT_VERSION,
1091
+ 'zephyr-rspack-plugin': ZEPHYR_RSPACK_PLUGIN_VERSION,
1092
+ wrangler: WRANGLER_VERSION
841
1093
  };
842
1094
  }
843
1095
  function createRootPackageJson(scope, packageSource) {
@@ -845,21 +1097,33 @@ function createRootPackageJson(scope, packageSource) {
845
1097
  private: true,
846
1098
  name: scope,
847
1099
  version: '0.1.0',
1100
+ type: 'module',
1101
+ packageManager: `pnpm@${PNPM_VERSION}`,
848
1102
  scripts: {
849
1103
  dev: `pnpm --parallel --filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)} --filter ${ultramodern_workspace_packageName(scope, 'remote-commerce')} --filter ${ultramodern_workspace_packageName(scope, 'remote-identity')} --filter ${ultramodern_workspace_packageName(scope, 'remote-design-system')} dev`,
850
1104
  'dev:shell': `pnpm --filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)} dev`,
851
1105
  'dev:commerce': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'remote-commerce')} dev`,
852
1106
  'dev:identity': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'remote-identity')} dev`,
853
1107
  'dev:design-system': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'remote-design-system')} dev`,
854
- 'dev:recommendations': `pnpm --filter ${ultramodern_workspace_packageName(scope, effectService.packageSuffix)} dev`,
855
- build: 'pnpm -r --filter ./apps/** --filter ./services/** build',
856
- typecheck: 'pnpm -r --filter ./apps/** --filter ./services/** --filter ./packages/** typecheck',
1108
+ build: 'pnpm -r --filter "./apps/remotes/**" run build && pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types',
1109
+ format: 'oxfmt .',
1110
+ 'format:check': 'oxfmt --check .',
1111
+ lint: 'oxlint .',
1112
+ 'lint:fix': 'oxlint . --fix',
1113
+ typecheck: `pnpm -r --filter "@${scope}/*" typecheck`,
1114
+ 'cloudflare:build': 'pnpm -r --filter "./apps/remotes/**" run cloudflare:build && pnpm --filter "./apps/shell-super-app" run cloudflare:build && pnpm ultramodern:assert-mf-types',
1115
+ 'skills:install': "node ./scripts/bootstrap-agent-skills.mjs",
1116
+ 'skills:check': "node ./scripts/bootstrap-agent-skills.mjs --check",
1117
+ 'agents:refs:install': "node ./scripts/setup-agent-reference-repos.mjs",
1118
+ 'agents:refs:check': "node ./scripts/setup-agent-reference-repos.mjs --check",
1119
+ 'ultramodern:assert-mf-types': "node ./scripts/assert-mf-types.mjs",
857
1120
  'ultramodern:check': "node ./scripts/validate-ultramodern-workspace.mjs",
858
- check: 'pnpm ultramodern:check'
1121
+ postinstall: "node ./scripts/setup-agent-reference-repos.mjs && node ./scripts/bootstrap-agent-skills.mjs",
1122
+ check: 'pnpm format:check && pnpm lint && pnpm typecheck && pnpm skills:check && pnpm ultramodern:check'
859
1123
  },
860
1124
  engines: {
861
1125
  node: '>=20',
862
- pnpm: '>=10.0.0'
1126
+ pnpm: `>=${PNPM_VERSION} <11.5.0`
863
1127
  },
864
1128
  workspaces: [
865
1129
  'apps/*',
@@ -878,35 +1142,71 @@ function createRootPackageJson(scope, packageSource) {
878
1142
  }
879
1143
  },
880
1144
  devDependencies: {
881
- '@biomejs/biome': '1.9.4',
882
- typescript: TYPESCRIPT_VERSION
1145
+ '@effect/tsgo': EFFECT_TSGO_VERSION,
1146
+ "@typescript/native-preview": TYPESCRIPT_NATIVE_PREVIEW_VERSION,
1147
+ oxlint: OXLINT_VERSION,
1148
+ oxfmt: OXFMT_VERSION,
1149
+ ultracite: ULTRACITE_VERSION,
1150
+ wrangler: WRANGLER_VERSION,
1151
+ 'zephyr-agent': ZEPHYR_AGENT_VERSION
883
1152
  }
884
1153
  };
885
1154
  }
886
- function createTsConfigBase(scope) {
1155
+ function remoteDependencyAlias(remote) {
1156
+ return toCamelCase(remote.domain ?? remote.id.replace(/^remote-/, ''));
1157
+ }
1158
+ function zephyrRemoteDependency(scope, remote) {
1159
+ return `${ultramodern_workspace_packageName(scope, remote.packageSuffix)}@workspace:*`;
1160
+ }
1161
+ function createZephyrDependencies(scope, app, remotes = remoteApps) {
1162
+ if ('shell' !== app.kind) return {};
1163
+ return Object.fromEntries(remotes.map((remote)=>[
1164
+ remoteDependencyAlias(remote),
1165
+ zephyrRemoteDependency(scope, remote)
1166
+ ]));
1167
+ }
1168
+ function createTsConfigBase() {
887
1169
  return {
888
1170
  compilerOptions: {
889
- target: 'ES2022',
1171
+ target: 'ESNext',
890
1172
  lib: [
1173
+ 'ESNext',
891
1174
  'DOM',
892
- 'DOM.Iterable',
893
- 'ES2022'
1175
+ 'DOM.Iterable'
894
1176
  ],
895
- module: 'ESNext',
1177
+ module: 'preserve',
896
1178
  moduleResolution: 'Bundler',
1179
+ moduleDetection: 'force',
897
1180
  jsx: 'preserve',
1181
+ isolatedModules: true,
1182
+ verbatimModuleSyntax: true,
898
1183
  strict: true,
899
1184
  noEmit: true,
1185
+ allowJs: true,
900
1186
  esModuleInterop: true,
1187
+ noUncheckedIndexedAccess: true,
1188
+ exactOptionalPropertyTypes: true,
1189
+ noImplicitOverride: true,
1190
+ noFallthroughCasesInSwitch: true,
1191
+ noPropertyAccessFromIndexSignature: true,
1192
+ noImplicitReturns: true,
901
1193
  skipLibCheck: true,
902
1194
  resolveJsonModule: true,
903
- baseUrl: '.',
904
- paths: Object.fromEntries(sharedPackages.map((sharedPackage)=>[
905
- ultramodern_workspace_packageName(scope, sharedPackage.id),
906
- [
907
- `${sharedPackage.directory}/src/index.ts`
908
- ]
909
- ]))
1195
+ plugins: [
1196
+ {
1197
+ name: '@effect/language-service',
1198
+ diagnostics: true,
1199
+ includeSuggestionsInTsc: true,
1200
+ ignoreEffectSuggestionsInTscExitCode: false,
1201
+ ignoreEffectWarningsInTscExitCode: false,
1202
+ ignoreEffectErrorsInTscExitCode: false,
1203
+ skipDisabledOptimization: true,
1204
+ diagnosticSeverity: Object.fromEntries(effectDiagnostics.map((name)=>[
1205
+ name,
1206
+ 'error'
1207
+ ]))
1208
+ }
1209
+ ]
910
1210
  }
911
1211
  };
912
1212
  }
@@ -919,60 +1219,60 @@ function createPackageTsConfig(packageDir, includeApi = false) {
919
1219
  if (includeApi) include.push('api', 'shared');
920
1220
  return {
921
1221
  extends: `${relativeRootFor(packageDir)}/tsconfig.base.json`,
922
- compilerOptions: {
923
- baseUrl: '.',
924
- paths: {
925
- '@/*': [
926
- './src/*'
927
- ],
928
- '@api/*': [
929
- './api/*'
930
- ],
931
- '@shared/*': [
932
- './shared/*'
933
- ]
934
- }
935
- },
936
- include
1222
+ include,
1223
+ exclude: [
1224
+ 'src/modern-tanstack'
1225
+ ]
937
1226
  };
938
1227
  }
939
- function createAppPackage(scope, app, packageSource) {
940
- return {
1228
+ function createAppPackage(scope, app, packageSource, enableTailwind) {
1229
+ const packageJson = {
941
1230
  private: true,
942
1231
  name: ultramodern_workspace_packageName(scope, app.packageSuffix),
943
1232
  version: '0.1.0',
944
1233
  scripts: {
945
1234
  dev: 'modern dev',
946
- build: 'modern build',
1235
+ build: app.exposes ? `modern build && node ${relativeRootFor(app.directory)}/scripts/assert-mf-types.mjs` : 'modern build',
1236
+ 'cloudflare:build': 'MODERNJS_DEPLOY=cloudflare modern build && MODERNJS_DEPLOY=cloudflare modern deploy',
1237
+ 'cloudflare:preview': 'MODERNJS_DEPLOY=cloudflare modern build && MODERNJS_DEPLOY=cloudflare modern deploy && wrangler dev --config .output/wrangler.json',
947
1238
  serve: 'modern serve',
948
- typecheck: 'tsgo --noEmit -p tsconfig.json'
1239
+ typecheck: effectTsgoTypecheckCommand
949
1240
  },
950
1241
  modernjs: {
951
1242
  preset: 'presetUltramodern',
952
1243
  role: 'shell' === app.kind ? 'shell' : 'module-federation-remote',
953
1244
  appId: app.id,
954
- topology: `${relativeRootFor(app.directory)}/topology/reference-topology.json`
1245
+ topology: `${relativeRootFor(app.directory)}/topology/reference-topology.json`,
1246
+ ...appHasEffectApi(app) ? {
1247
+ apiRuntime: 'effect-bff'
1248
+ } : {}
955
1249
  },
956
- dependencies: appDependencies(scope, packageSource),
957
- devDependencies: appDevDependencies(packageSource)
1250
+ 'zephyr:dependencies': createZephyrDependencies(scope, app),
1251
+ dependencies: appDependencies(scope, packageSource, app),
1252
+ devDependencies: appDevDependencies(packageSource, enableTailwind)
1253
+ };
1254
+ if (appHasEffectApi(app)) packageJson.exports = {
1255
+ './effect/client': `./src/effect/${app.effectApi.stem}-client.ts`,
1256
+ './shared/effect/api': './shared/effect/api.ts'
958
1257
  };
1258
+ return packageJson;
959
1259
  }
960
- function createServicePackage(scope, packageSource) {
1260
+ function createServicePackage(scope, packageSource, enableTailwind, service = effectService) {
961
1261
  return {
962
1262
  private: true,
963
- name: ultramodern_workspace_packageName(scope, effectService.packageSuffix),
1263
+ name: ultramodern_workspace_packageName(scope, service.packageSuffix),
964
1264
  version: '0.1.0',
965
1265
  scripts: {
966
1266
  dev: 'modern dev',
967
1267
  build: 'modern build',
968
1268
  serve: 'modern serve',
969
- typecheck: 'tsgo --noEmit -p tsconfig.json'
1269
+ typecheck: effectTsgoTypecheckCommand
970
1270
  },
971
1271
  modernjs: {
972
1272
  preset: 'presetUltramodern',
973
1273
  role: 'effect-service',
974
- appId: effectService.id,
975
- topology: `${relativeRootFor(effectService.directory)}/topology/reference-topology.json`
1274
+ appId: service.id,
1275
+ topology: `${relativeRootFor(service.directory)}/topology/reference-topology.json`
976
1276
  },
977
1277
  dependencies: {
978
1278
  '@modern-js/runtime': modernPackageSpecifier('@modern-js/runtime', packageSource),
@@ -983,6 +1283,13 @@ function createServicePackage(scope, packageSource) {
983
1283
  devDependencies: {
984
1284
  '@modern-js/app-tools': modernPackageSpecifier('@modern-js/app-tools', packageSource),
985
1285
  '@modern-js/plugin-bff': modernPackageSpecifier('@modern-js/plugin-bff', packageSource),
1286
+ '@effect/tsgo': EFFECT_TSGO_VERSION,
1287
+ ...enableTailwind ? {
1288
+ '@tailwindcss/postcss': `^${TAILWIND_POSTCSS_VERSION}`,
1289
+ postcss: '^8.5.6',
1290
+ tailwindcss: `^${TAILWIND_VERSION}`
1291
+ } : {},
1292
+ "@typescript/native-preview": TYPESCRIPT_NATIVE_PREVIEW_VERSION,
986
1293
  '@types/node': '^20',
987
1294
  '@types/react': '^19.1.8',
988
1295
  '@types/react-dom': '^19.1.6',
@@ -990,8 +1297,8 @@ function createServicePackage(scope, packageSource) {
990
1297
  }
991
1298
  };
992
1299
  }
993
- function createSharedPackage(scope, id, description) {
994
- return {
1300
+ function createSharedPackage(scope, id, description, packageSource) {
1301
+ const packageJson = {
995
1302
  private: true,
996
1303
  name: ultramodern_workspace_packageName(scope, id),
997
1304
  version: '0.1.0',
@@ -1001,46 +1308,162 @@ function createSharedPackage(scope, id, description) {
1001
1308
  '.': './src/index.ts'
1002
1309
  },
1003
1310
  scripts: {
1004
- typecheck: 'tsgo --noEmit -p tsconfig.json'
1311
+ typecheck: effectTsgoTypecheckCommand
1005
1312
  },
1006
1313
  devDependencies: {
1007
- typescript: TYPESCRIPT_VERSION
1314
+ '@effect/tsgo': EFFECT_TSGO_VERSION,
1315
+ "@typescript/native-preview": TYPESCRIPT_NATIVE_PREVIEW_VERSION
1008
1316
  }
1009
1317
  };
1318
+ if ('shared-effect-api' === id) packageJson.dependencies = {
1319
+ '@modern-js/plugin-bff': modernPackageSpecifier('@modern-js/plugin-bff', packageSource)
1320
+ };
1321
+ return packageJson;
1010
1322
  }
1011
1323
  function createAppModernConfig(app) {
1012
- return `import { appTools, defineConfig, presetUltramodern } from '@modern-js/app-tools';
1324
+ const bffImport = appHasEffectApi(app) ? "import { bffPlugin } from '@modern-js/plugin-bff';\n" : '';
1325
+ const bffConfig = appHasEffectApi(app) ? ` bff: {
1326
+ effect: {
1327
+ openapi: {
1328
+ path: '/openapi.json',
1329
+ },
1330
+ },
1331
+ prefix: '${effectApiPrefix(app)}',
1332
+ runtimeFramework: 'effect',
1333
+ },
1334
+ ` : '';
1335
+ const bffPluginEntry = appHasEffectApi(app) ? ' bffPlugin(),\n' : '';
1336
+ return `// @effect-diagnostics processEnv:off
1337
+ import {
1338
+ appTools,
1339
+ defineConfig,
1340
+ presetUltramodern,
1341
+ } from '@modern-js/app-tools';
1342
+ ${bffImport}import { i18nPlugin } from '@modern-js/plugin-i18n';
1013
1343
  import { tanstackRouterPlugin } from '@modern-js/plugin-tanstack';
1014
1344
  import { moduleFederationPlugin } from '@module-federation/modern-js-v3';
1345
+ import { withZephyr as withZephyrRspack } from 'zephyr-rspack-plugin';
1346
+
1347
+ type ZephyrRspackConfig = Parameters<ReturnType<typeof withZephyrRspack>>[0];
1348
+
1349
+ const zephyrRspackPlugin = () => ({
1350
+ name: 'ultramodern-zephyr-rspack-plugin',
1351
+ pre: ['@modern-js/plugin-module-federation-config'],
1352
+ setup(api: {
1353
+ modifyRspackConfig: (
1354
+ handler: (
1355
+ config: ZephyrRspackConfig,
1356
+ ) => ZephyrRspackConfig | Promise<ZephyrRspackConfig>,
1357
+ ) => void;
1358
+ }) {
1359
+ api.modifyRspackConfig(config => withZephyrRspack()(config));
1360
+ },
1361
+ });
1015
1362
 
1016
1363
  const appId = '${app.id}';
1017
- const port = Number(process.env.${app.portEnv} ?? ${app.port});
1364
+ const port = Number(process.env['${app.portEnv}'] ?? ${app.port});
1365
+ const configuredSiteUrl = process.env['MODERN_PUBLIC_SITE_URL'];
1366
+ const hasConfiguredSiteUrl =
1367
+ typeof configuredSiteUrl === 'string' && configuredSiteUrl.length > 0;
1368
+ const isProductionBuild =
1369
+ process.env['NODE_ENV'] === 'production' || process.argv.includes('build');
1370
+
1371
+ if (isProductionBuild && !hasConfiguredSiteUrl) {
1372
+ throw new Error(
1373
+ 'MODERN_PUBLIC_SITE_URL must be set for production builds so canonical and hreflang URLs use the deployed origin.',
1374
+ );
1375
+ }
1376
+
1377
+ const siteUrl = hasConfiguredSiteUrl
1378
+ ? configuredSiteUrl
1379
+ : \`http://localhost:\${port}\`;
1018
1380
 
1019
1381
  export default defineConfig(
1020
1382
  presetUltramodern(
1021
1383
  {
1022
- server: {
1023
- port,
1024
- ssr: {
1025
- mode: 'string',
1026
- moduleFederationAppSSR: true,
1384
+ ${bffConfig} output: {
1385
+ disableTsChecker: true,
1386
+ distPath: {
1387
+ html: './',
1027
1388
  },
1028
- },
1029
- output: {
1030
1389
  polyfill: 'off',
1031
- disableTsChecker: true,
1032
1390
  splitRouteChunks: false,
1033
1391
  },
1392
+ performance: {
1393
+ rsdoctor: {
1394
+ enabled: process.env['ULTRAMODERN_RSDOCTOR'] === 'true',
1395
+ disableClientServer: true,
1396
+ },
1397
+ },
1398
+ html: {
1399
+ outputStructure: 'flat',
1400
+ },
1034
1401
  plugins: [
1035
1402
  appTools(),
1036
1403
  tanstackRouterPlugin(),
1037
- moduleFederationPlugin(),
1404
+ i18nPlugin({
1405
+ backend: {
1406
+ enabled: true,
1407
+ },
1408
+ reactI18next: false,
1409
+ localeDetection: {
1410
+ fallbackLanguage: 'en',
1411
+ languages: ['en', 'cs'],
1412
+ localePathRedirect: true,
1413
+ ignoreRedirectRoutes: [
1414
+ '/@mf-types',
1415
+ '/bundles',
1416
+ '${effectApiPrefix(app)}',
1417
+ '/locales',
1418
+ '/mf-manifest.json',
1419
+ '/mf-stats.json',
1420
+ '/remoteEntry.js',
1421
+ '/static',
1422
+ '/zephyr-manifest.json',
1423
+ ],
1424
+ },
1425
+ }),
1426
+ ${bffPluginEntry} moduleFederationPlugin(),
1427
+ zephyrRspackPlugin(),
1038
1428
  ],
1429
+ tools: {
1430
+ autoprefixer: {
1431
+ overrideBrowserslist: ['defaults'],
1432
+ },
1433
+ bundlerChain: chain => {
1434
+ chain.ignoreWarnings([
1435
+ {
1436
+ message: /the request of a dependency is an expression/u,
1437
+ module: /modern-js-plugin-i18n/u,
1438
+ },
1439
+ ]);
1440
+ },
1441
+ },
1442
+ deploy: {
1443
+ target: 'cloudflare',
1444
+ worker: {
1445
+ ssr: true,
1446
+ },
1447
+ },
1448
+ server: {
1449
+ port,
1450
+ publicDir: './locales',
1451
+ ssr: {
1452
+ mode: 'stream',
1453
+ moduleFederationAppSSR: true,
1454
+ },
1455
+ },
1456
+ source: {
1457
+ mainEntryName: 'index',
1458
+ globalVars: {
1459
+ ULTRAMODERN_SITE_URL: siteUrl,
1460
+ },
1461
+ },
1039
1462
  },
1040
1463
  {
1041
1464
  appId,
1042
- enableModuleFederationSSR: true,
1043
1465
  enableBffRequestId: true,
1466
+ enableModuleFederationSSR: true,
1044
1467
  enableTelemetryExporters: true,
1045
1468
  telemetryFailLoudStartup: false,
1046
1469
  },
@@ -1048,129 +1471,162 @@ export default defineConfig(
1048
1471
  );
1049
1472
  `;
1050
1473
  }
1051
- function createShellModuleFederationConfig() {
1052
- return `import { createRequire } from 'node:module';
1053
- import { createModuleFederationConfig } from '@module-federation/modern-js-v3';
1054
- import { dependencies } from './package.json';
1055
-
1056
- const require = createRequire(import.meta.url);
1057
- const runtimeVersion = (
1058
- require('@modern-js/runtime/package.json') as { version: string }
1059
- ).version;
1060
- const reactVersion = (require('react/package.json') as { version: string })
1061
- .version;
1062
- const reactDomVersion = (
1063
- require('react-dom/package.json') as { version: string }
1064
- ).version;
1065
-
1066
- export default createModuleFederationConfig({
1067
- name: '${shellApp.mfName}',
1068
- dts: false,
1069
- remotes: {
1070
- commerce:
1071
- process.env.REMOTE_COMMERCE_MF_MANIFEST ??
1072
- 'remoteCommerce@http://localhost:3021/mf-manifest.json',
1073
- identity:
1074
- process.env.REMOTE_IDENTITY_MF_MANIFEST ??
1075
- 'remoteIdentity@http://localhost:3022/mf-manifest.json',
1076
- designSystem:
1077
- process.env.REMOTE_DESIGN_SYSTEM_MF_MANIFEST ??
1078
- 'remoteDesignSystem@http://localhost:3023/mf-manifest.json',
1079
- },
1080
- shared: {
1081
- react: {
1474
+ function createSharedModuleFederationConfig() {
1475
+ return ` shared: {
1476
+ '@modern-js/runtime': {
1477
+ requiredVersion: runtimeVersion,
1082
1478
  singleton: true,
1083
- requiredVersion: reactVersion,
1084
1479
  treeShaking: false,
1085
1480
  },
1086
- 'react-dom': {
1481
+ '@tanstack/react-router': {
1482
+ requiredVersion: dependencies['@tanstack/react-router'],
1087
1483
  singleton: true,
1088
- requiredVersion: reactDomVersion,
1089
1484
  treeShaking: false,
1090
1485
  },
1091
- '@tanstack/react-router': {
1486
+ react: {
1487
+ requiredVersion: reactVersion,
1092
1488
  singleton: true,
1093
- requiredVersion: dependencies['@tanstack/react-router'],
1094
1489
  treeShaking: false,
1095
1490
  },
1096
- '@modern-js/runtime': {
1491
+ 'react-dom': {
1492
+ requiredVersion: reactDomVersion,
1097
1493
  singleton: true,
1098
- requiredVersion: runtimeVersion,
1099
1494
  treeShaking: false,
1100
1495
  },
1496
+ 'react-dom/client': {
1497
+ requiredVersion: reactDomVersion,
1498
+ singleton: true,
1499
+ treeShaking: false,
1500
+ },
1501
+ }`;
1502
+ }
1503
+ function formatTsObjectLiteral(value) {
1504
+ const entries = Object.entries(value).sort(([left], [right])=>left.localeCompare(right));
1505
+ if (0 === entries.length) return '{}';
1506
+ return `{
1507
+ ${entries.map(([key, entryValue])=>` '${key}': '${entryValue}',`).join('\n')}
1508
+ }`;
1509
+ }
1510
+ function createRemoteManifestEnv(remote) {
1511
+ return `REMOTE_${toEnvSegment(remote.domain ?? remote.id)}_MF_MANIFEST`;
1512
+ }
1513
+ function createShellModuleFederationConfig(remotes = remoteApps) {
1514
+ const remoteEntries = remotes.map((remote)=>{
1515
+ const key = remoteDependencyAlias(remote);
1516
+ return ` ${key}:
1517
+ process.env['${createRemoteManifestEnv(remote)}'] ??
1518
+ '${remote.mfName}@http://localhost:${remote.port}/mf-manifest.json',`;
1519
+ }).join('\n');
1520
+ return `// @effect-diagnostics nodeBuiltinImport:off processEnv:off
1521
+ import { createRequire } from 'node:module';
1522
+ import { createModuleFederationConfig } from '@module-federation/modern-js-v3';
1523
+ import { dependencies } from './package.json';
1524
+
1525
+ const require = createRequire(import.meta.url);
1526
+ const runtimeVersion = (require('@modern-js/runtime/package.json') as { version: string }).version;
1527
+ const reactVersion = (require('react/package.json') as { version: string }).version;
1528
+ const reactDomVersion = (require('react-dom/package.json') as { version: string }).version;
1529
+
1530
+ export default createModuleFederationConfig({
1531
+ treeShakingSharedExcludePlugins: ['RspackModuleFederationPlugin'],
1532
+ dev: {
1533
+ disableDynamicRemoteTypeHints: true,
1534
+ },
1535
+ dts: {
1536
+ displayErrorInTerminal: true,
1537
+ generateTypes: {
1538
+ compilerInstance: '--package typescript -- tsc',
1539
+ },
1101
1540
  },
1541
+ filename: 'remoteEntry.js',
1542
+ name: '${shellApp.mfName}',
1543
+ remotes: {
1544
+ ${remoteEntries}
1545
+ },
1546
+ ${createSharedModuleFederationConfig()},
1102
1547
  });
1103
1548
  `;
1104
1549
  }
1550
+ function createBuildMarker(scope, app) {
1551
+ return node_crypto.createHash('sha256').update(`${scope}:${app.packageSuffix}:${app.id}:0.1.0`).digest('hex').slice(0, 16);
1552
+ }
1553
+ function createUltramodernBuildModule(scope, app) {
1554
+ return `export const ultramodernVerticalIdentity = {
1555
+ appId: '${app.id}',
1556
+ packageName: '${ultramodern_workspace_packageName(scope, app.packageSuffix)}',
1557
+ version: '0.1.0',
1558
+ build: '${createBuildMarker(scope, app)}',
1559
+ deployProfile: 'cloudflare-ssr-mf-effect-v1',
1560
+ } as const;
1561
+
1562
+ export const ultramodernUiMarker = {
1563
+ ...ultramodernVerticalIdentity,
1564
+ surface: 'ui',
1565
+ } as const;
1566
+
1567
+ export const ultramodernApiMarker = {
1568
+ ...ultramodernVerticalIdentity,
1569
+ surface: 'effect-bff',
1570
+ } as const;
1571
+ `;
1572
+ }
1105
1573
  function createRemoteModuleFederationConfig(app) {
1106
- const exposes = JSON.stringify(app.exposes ?? {}, null, 4).replace(/^/gm, ' ');
1107
- return `import { createRequire } from 'node:module';
1574
+ const exposes = formatTsObjectLiteral(app.exposes ?? {});
1575
+ return `// @effect-diagnostics nodeBuiltinImport:off
1576
+ import { createRequire } from 'node:module';
1108
1577
  import { createModuleFederationConfig } from '@module-federation/modern-js-v3';
1109
1578
  import { dependencies } from './package.json';
1110
1579
 
1111
1580
  const require = createRequire(import.meta.url);
1112
- const runtimeVersion = (
1113
- require('@modern-js/runtime/package.json') as { version: string }
1114
- ).version;
1115
- const reactVersion = (require('react/package.json') as { version: string })
1116
- .version;
1117
- const reactDomVersion = (
1118
- require('react-dom/package.json') as { version: string }
1119
- ).version;
1581
+ const runtimeVersion = (require('@modern-js/runtime/package.json') as { version: string }).version;
1582
+ const reactVersion = (require('react/package.json') as { version: string }).version;
1583
+ const reactDomVersion = (require('react-dom/package.json') as { version: string }).version;
1120
1584
 
1121
1585
  export default createModuleFederationConfig({
1122
- name: '${app.mfName}',
1123
- dts: false,
1124
- filename: 'remoteEntry.js',
1125
- exposes: ${exposes},
1126
- shared: {
1127
- react: {
1128
- singleton: true,
1129
- requiredVersion: reactVersion,
1130
- treeShaking: false,
1131
- },
1132
- 'react-dom': {
1133
- singleton: true,
1134
- requiredVersion: reactDomVersion,
1135
- treeShaking: false,
1136
- },
1137
- '@tanstack/react-router': {
1138
- singleton: true,
1139
- requiredVersion: dependencies['@tanstack/react-router'],
1140
- treeShaking: false,
1141
- },
1142
- '@modern-js/runtime': {
1143
- singleton: true,
1144
- requiredVersion: runtimeVersion,
1145
- treeShaking: false,
1586
+ treeShakingSharedExcludePlugins: ['RspackModuleFederationPlugin'],
1587
+ dev: {
1588
+ disableDynamicRemoteTypeHints: true,
1589
+ },
1590
+ dts: {
1591
+ displayErrorInTerminal: true,
1592
+ generateTypes: {
1593
+ compilerInstance: '--package typescript -- tsc',
1146
1594
  },
1147
1595
  },
1596
+ exposes: ${exposes},
1597
+ filename: 'remoteEntry.js',
1598
+ name: '${app.mfName}',
1599
+ ${createSharedModuleFederationConfig()},
1148
1600
  });
1149
1601
  `;
1150
1602
  }
1151
- function createServiceModernConfig() {
1152
- return `import { appTools, defineConfig, presetUltramodern } from '@modern-js/app-tools';
1603
+ function remoteWidgetFile(app) {
1604
+ return `${app.domain ?? app.id.replace(/^remote-/, '')}-widget`;
1605
+ }
1606
+ function createServiceModernConfigFor(service = effectService) {
1607
+ return `// @effect-diagnostics processEnv:off
1608
+ import { appTools, defineConfig, presetUltramodern } from '@modern-js/app-tools';
1153
1609
  import { bffPlugin } from '@modern-js/plugin-bff';
1154
1610
 
1155
- const appId = '${effectService.id}';
1156
- const port = Number(process.env.${effectService.portEnv} ?? ${effectService.port});
1611
+ const appId = '${service.id}';
1612
+ const port = Number(process.env['${service.portEnv}'] ?? ${service.port});
1157
1613
 
1158
1614
  export default defineConfig(
1159
1615
  presetUltramodern(
1160
1616
  {
1161
- server: {
1162
- port,
1163
- },
1164
1617
  bff: {
1165
- prefix: '/recommendations-api',
1166
- runtimeFramework: 'effect',
1167
1618
  effect: {
1168
1619
  openapi: {
1169
1620
  path: '/openapi.json',
1170
1621
  },
1171
1622
  },
1623
+ prefix: '${serviceApiPrefix(service)}',
1624
+ runtimeFramework: 'effect',
1172
1625
  },
1173
1626
  plugins: [appTools(), bffPlugin()],
1627
+ server: {
1628
+ port,
1629
+ },
1174
1630
  },
1175
1631
  {
1176
1632
  appId,
@@ -1182,21 +1638,443 @@ export default defineConfig(
1182
1638
  );
1183
1639
  `;
1184
1640
  }
1641
+ function createAppRuntimeConfig(app) {
1642
+ const resources = {
1643
+ cs: {
1644
+ translation: createAppLocaleMessages(app, 'cs')
1645
+ },
1646
+ en: {
1647
+ translation: createAppLocaleMessages(app, 'en')
1648
+ }
1649
+ };
1650
+ return `import { defineRuntimeConfig } from '@modern-js/runtime';
1651
+ import { createInstance } from 'i18next';
1652
+
1653
+ const i18nInstance = createInstance();
1654
+
1655
+ export default defineRuntimeConfig({
1656
+ i18n: {
1657
+ i18nInstance,
1658
+ initOptions: {
1659
+ defaultNS: 'translation',
1660
+ fallbackLng: 'en',
1661
+ interpolation: {
1662
+ escapeValue: false,
1663
+ },
1664
+ ns: ['translation'],
1665
+ resources: ${JSON.stringify(resources, null, 8).split('\n').join('\n ')},
1666
+ supportedLngs: ['en', 'cs'],
1667
+ },
1668
+ },
1669
+ router: {
1670
+ framework: 'tanstack',
1671
+ },
1672
+ });
1673
+ `;
1674
+ }
1675
+ function createAppStyles(enableTailwind) {
1676
+ return `${enableTailwind ? "@import 'tailwindcss';\n\n" : ''}:root {
1677
+ color: #10231c;
1678
+ background: #f1eadc;
1679
+ font-family:
1680
+ Geist,
1681
+ Inter,
1682
+ ui-sans-serif,
1683
+ system-ui,
1684
+ -apple-system,
1685
+ BlinkMacSystemFont,
1686
+ "Segoe UI",
1687
+ sans-serif;
1688
+ }
1689
+
1690
+ body {
1691
+ margin: 0;
1692
+ }
1693
+
1694
+ main {
1695
+ min-height: 100vh;
1696
+ padding: 2rem;
1697
+ }
1698
+
1699
+ nav {
1700
+ display: flex;
1701
+ gap: 0.75rem;
1702
+ margin-bottom: 2rem;
1703
+ }
1704
+
1705
+ a {
1706
+ color: #166b4b;
1707
+ }
1708
+
1709
+ .commerce-shell {
1710
+ background: #f1eadc;
1711
+ color: #0b0a08;
1712
+ min-height: 100vh;
1713
+ padding: 1.5rem clamp(1rem, 4vw, 3rem) 4rem;
1714
+ }
1715
+
1716
+ .commerce-header,
1717
+ .commerce-footer {
1718
+ align-items: center;
1719
+ background: rgba(255, 255, 255, 0.86);
1720
+ box-shadow: 0 0.625rem 1.875rem rgba(25, 20, 12, 0.08);
1721
+ display: flex;
1722
+ gap: 1.25rem;
1723
+ justify-content: space-between;
1724
+ margin: 0 auto;
1725
+ max-width: 88rem;
1726
+ padding: 1.25rem 1.75rem;
1727
+ }
1728
+
1729
+ .commerce-logo {
1730
+ font-size: 1.35rem;
1731
+ font-weight: 800;
1732
+ }
1733
+
1734
+ .commerce-nav,
1735
+ .commerce-actions,
1736
+ .commerce-language {
1737
+ align-items: center;
1738
+ display: flex;
1739
+ flex-wrap: wrap;
1740
+ gap: 0.75rem;
1741
+ }
1742
+
1743
+ .commerce-nav {
1744
+ margin: 0;
1745
+ }
1746
+
1747
+ .commerce-pill,
1748
+ .commerce-button,
1749
+ .commerce-link-button,
1750
+ .commerce-cart-button,
1751
+ .commerce-quantity-button {
1752
+ align-items: center;
1753
+ border-radius: 999px;
1754
+ border: 0.0625rem solid rgba(23, 23, 23, 0.14);
1755
+ box-shadow: 0 0.25rem 0.75rem rgba(20, 17, 10, 0.08);
1756
+ color: #14120d;
1757
+ display: inline-flex;
1758
+ font: inherit;
1759
+ font-weight: 750;
1760
+ justify-content: center;
1761
+ min-height: 2.5rem;
1762
+ padding: 0.65rem 1.05rem;
1763
+ text-decoration: none;
1764
+ }
1765
+
1766
+ .commerce-button {
1767
+ background: #00624b;
1768
+ border-color: #00624b;
1769
+ color: #ffffff;
1770
+ }
1771
+
1772
+ .commerce-link-button,
1773
+ .commerce-pill,
1774
+ .commerce-cart-button,
1775
+ .commerce-quantity-button {
1776
+ background: rgba(255, 255, 255, 0.92);
1777
+ }
1778
+
1779
+ .commerce-page {
1780
+ margin: 3rem auto 0;
1781
+ max-width: 88rem;
1782
+ }
1783
+
1784
+ .commerce-product {
1785
+ align-items: center;
1786
+ display: grid;
1787
+ gap: clamp(2rem, 5vw, 4rem);
1788
+ grid-template-columns: minmax(0, 1fr) minmax(20rem, 0.95fr);
1789
+ }
1790
+
1791
+ .commerce-product-media {
1792
+ aspect-ratio: 1 / 0.92;
1793
+ background:
1794
+ linear-gradient(180deg, rgba(255, 255, 255, 0.62), rgba(255, 255, 255, 0) 42%),
1795
+ linear-gradient(135deg, #97c66d 0 20%, #6f9748 20% 34%, #d6c15d 34% 36%, #6a8f3e 36% 46%, #315824 46% 64%, #8bb85e 64% 100%);
1796
+ border: 1.25rem solid #ffe987;
1797
+ border-radius: 1.6rem;
1798
+ box-shadow: inset 0 -7rem 8rem rgba(58, 77, 35, 0.22);
1799
+ overflow: hidden;
1800
+ }
1801
+
1802
+ .commerce-product-media::after {
1803
+ background:
1804
+ radial-gradient(circle at 27% 76%, #1e2422 0 5%, transparent 5.4%),
1805
+ radial-gradient(circle at 55% 76%, #1e2422 0 6%, transparent 6.4%),
1806
+ linear-gradient(0deg, #004b7b 0 100%);
1807
+ border-radius: 1.2rem;
1808
+ content: "";
1809
+ display: block;
1810
+ height: 19%;
1811
+ margin: 58% auto 0;
1812
+ width: 42%;
1813
+ }
1814
+
1815
+ .commerce-eyebrow {
1816
+ color: #00624b;
1817
+ font-size: 0.85rem;
1818
+ font-weight: 850;
1819
+ letter-spacing: 0.16rem;
1820
+ text-transform: uppercase;
1821
+ }
1822
+
1823
+ .commerce-title {
1824
+ font-size: clamp(2.5rem, 6vw, 4.8rem);
1825
+ line-height: 0.95;
1826
+ margin: 0.65rem 0 1.4rem;
1827
+ }
1828
+
1829
+ .commerce-lede {
1830
+ color: #555149;
1831
+ font-size: 1.2rem;
1832
+ line-height: 1.65;
1833
+ max-width: 42rem;
1834
+ }
1835
+
1836
+ .commerce-facts {
1837
+ display: grid;
1838
+ gap: 1rem;
1839
+ grid-template-columns: repeat(3, minmax(0, 1fr));
1840
+ margin: 2rem 0;
1841
+ }
1842
+
1843
+ .commerce-fact,
1844
+ .commerce-card,
1845
+ .commerce-cart-panel {
1846
+ background: rgba(255, 255, 255, 0.92);
1847
+ border-radius: 1rem;
1848
+ box-shadow: 0 0.5rem 1.25rem rgba(25, 20, 12, 0.08);
1849
+ padding: 1.25rem;
1850
+ }
1851
+
1852
+ .commerce-fact span,
1853
+ .commerce-card span {
1854
+ color: #767067;
1855
+ display: block;
1856
+ font-weight: 750;
1857
+ margin-bottom: 0.45rem;
1858
+ }
1859
+
1860
+ .commerce-fact strong {
1861
+ font-size: 1.1rem;
1862
+ }
1863
+
1864
+ .commerce-checkout {
1865
+ align-items: center;
1866
+ display: flex;
1867
+ flex-wrap: wrap;
1868
+ gap: 0.75rem;
1869
+ }
1870
+
1871
+ .commerce-section-title {
1872
+ font-size: 1.8rem;
1873
+ margin: 4.5rem 0 1.5rem;
1874
+ }
1875
+
1876
+ .commerce-grid {
1877
+ display: grid;
1878
+ gap: 1rem;
1879
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1880
+ }
1881
+
1882
+ .commerce-card strong {
1883
+ display: block;
1884
+ font-size: 1.45rem;
1885
+ }
1886
+
1887
+ .commerce-cart-panel {
1888
+ margin-top: 2rem;
1889
+ }
1890
+
1891
+ .commerce-cart-line {
1892
+ align-items: center;
1893
+ border-top: 0.0625rem solid rgba(23, 23, 23, 0.12);
1894
+ display: grid;
1895
+ gap: 1rem;
1896
+ grid-template-columns: minmax(0, 1fr) auto;
1897
+ padding: 1rem 0;
1898
+ }
1899
+
1900
+ .commerce-cart-line:first-of-type {
1901
+ border-top: 0;
1902
+ }
1903
+
1904
+ .commerce-quantity {
1905
+ align-items: center;
1906
+ display: flex;
1907
+ gap: 0.45rem;
1908
+ }
1909
+
1910
+ .commerce-quantity-button {
1911
+ min-height: 2rem;
1912
+ min-width: 2rem;
1913
+ padding: 0.25rem;
1914
+ }
1915
+
1916
+ .commerce-boundary-toggle {
1917
+ align-items: center;
1918
+ background: rgba(255, 255, 255, 0.92);
1919
+ border-radius: 0.8rem;
1920
+ bottom: 1.5rem;
1921
+ box-shadow: 0 0.75rem 2rem rgba(18, 15, 10, 0.14);
1922
+ display: flex;
1923
+ gap: 0.65rem;
1924
+ left: 1.5rem;
1925
+ padding: 0.8rem 1rem;
1926
+ position: fixed;
1927
+ z-index: 80;
1928
+ }
1929
+
1930
+ .commerce-boundary-toggle input {
1931
+ accent-color: #00624b;
1932
+ height: 1rem;
1933
+ width: 1rem;
1934
+ }
1935
+
1936
+ .boundary-overlay {
1937
+ inset: 0;
1938
+ pointer-events: none;
1939
+ position: fixed;
1940
+ z-index: 70;
1941
+ }
1942
+
1943
+ .boundary-overlay__box {
1944
+ border: 0.0625rem solid var(--boundary-color);
1945
+ border-radius: 0.55rem;
1946
+ box-shadow:
1947
+ 0 0 0 0.0625rem rgba(255, 255, 255, 0.72),
1948
+ 0 0.35rem 1.25rem color-mix(in srgb, var(--boundary-color) 20%, transparent);
1949
+ position: fixed;
1950
+ }
1951
+
1952
+ .boundary-overlay__label {
1953
+ background: color-mix(in srgb, var(--boundary-color) 88%, white);
1954
+ border-radius: 999px;
1955
+ color: #0b0a08;
1956
+ font-size: 0.7rem;
1957
+ font-weight: 850;
1958
+ line-height: 1;
1959
+ padding: 0.3rem 0.55rem;
1960
+ position: absolute;
1961
+ right: 0.35rem;
1962
+ top: 0.35rem;
1963
+ white-space: nowrap;
1964
+ }
1965
+
1966
+ .boundary-overlay__box[data-label-placement="above"] .boundary-overlay__label {
1967
+ bottom: calc(100% + 0.25rem);
1968
+ top: auto;
1969
+ }
1970
+
1971
+ @media (max-width: 860px) {
1972
+ .commerce-header,
1973
+ .commerce-footer,
1974
+ .commerce-product,
1975
+ .commerce-grid,
1976
+ .commerce-facts {
1977
+ grid-template-columns: 1fr;
1978
+ }
1979
+
1980
+ .commerce-header,
1981
+ .commerce-footer {
1982
+ align-items: flex-start;
1983
+ flex-direction: column;
1984
+ }
1985
+
1986
+ .commerce-product-media {
1987
+ min-height: 20rem;
1988
+ }
1989
+ }
1990
+ `;
1991
+ }
1992
+ function createPostcssConfig() {
1993
+ return `export default {
1994
+ plugins: {
1995
+ '@tailwindcss/postcss': {},
1996
+ },
1997
+ };
1998
+ `;
1999
+ }
2000
+ function createTailwindConfig() {
2001
+ return `import type { Config } from 'tailwindcss';
2002
+
2003
+ export default {
2004
+ content: ['./src/**/*.{js,jsx,ts,tsx}'],
2005
+ } satisfies Config;
2006
+ `;
2007
+ }
2008
+ function createLocalizedHeadComponent() {
2009
+ return `const fallbackLanguage = 'en';
2010
+ const supportedLanguages = ['en', 'cs'] as const;
2011
+ type SupportedLanguage = (typeof supportedLanguages)[number];
2012
+
2013
+ const localizedPath = (language: SupportedLanguage) => \`/\${language}\`;
2014
+
2015
+ const absoluteUrl = (pathname: string) => {
2016
+ const origin = ULTRAMODERN_SITE_URL.replace(/\\/+$/u, '');
2017
+ return \`\${origin}\${pathname}\`;
2018
+ };
2019
+ const LocalizedHead = () => {
2020
+ const canonicalPath = localizedPath(fallbackLanguage);
2021
+
2022
+ return (
2023
+ <>
2024
+ <link rel="canonical" href={absoluteUrl(canonicalPath)} />
2025
+ {supportedLanguages.map(code => (
2026
+ <link
2027
+ href={absoluteUrl(localizedPath(code))}
2028
+ hrefLang={code}
2029
+ key={code}
2030
+ rel="alternate"
2031
+ />
2032
+ ))}
2033
+ <link
2034
+ href={absoluteUrl(localizedPath(fallbackLanguage))}
2035
+ hrefLang="x-default"
2036
+ rel="alternate"
2037
+ />
2038
+ </>
2039
+ );
2040
+ };
2041
+ `;
2042
+ }
1185
2043
  function createShellPage() {
1186
- return `const remotes = [
1187
- 'remote-commerce',
1188
- 'remote-identity',
1189
- 'remote-design-system',
1190
- ];
2044
+ return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2045
+ import { ultramodernUiMarker } from '../../ultramodern-build';
2046
+
2047
+ const languageCodes = ['en', 'cs'] as const;
1191
2048
 
2049
+ const remoteKeys = ['commerce', 'identity', 'designSystem'] as const;
2050
+
2051
+ ${createLocalizedHeadComponent()}
1192
2052
  export default function ShellHome() {
2053
+ const { i18nInstance, language } = useModernI18n();
2054
+ const t = i18nInstance.t.bind(i18nInstance);
2055
+
1193
2056
  return (
1194
2057
  <main>
1195
- <h1>UltraModern SuperApp Shell</h1>
2058
+ <LocalizedHead />
2059
+ <nav aria-label={t('shell.language.switcher')}>
2060
+ {languageCodes.map(code => (
2061
+ <a
2062
+ aria-current={language === code ? 'page' : undefined}
2063
+ href={\`/\${code}\`}
2064
+ key={code}
2065
+ >
2066
+ {t(\`shell.language.\${code}\`)}
2067
+ </a>
2068
+ ))}
2069
+ </nav>
2070
+ <h1>{t('shell.title')}</h1>
1196
2071
  <p data-testid="ultramodern-preset">presetUltramodern workspace</p>
2072
+ <p data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
2073
+ {ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
2074
+ </p>
1197
2075
  <ul>
1198
- {remotes.map(remote => (
1199
- <li key={remote}>{remote}</li>
2076
+ {remoteKeys.map(remote => (
2077
+ <li key={remote}>{t(\`shell.remotes.\${remote}\`)}</li>
1200
2078
  ))}
1201
2079
  </ul>
1202
2080
  </main>
@@ -1205,45 +2083,603 @@ export default function ShellHome() {
1205
2083
  `;
1206
2084
  }
1207
2085
  function createRemotePage(app) {
1208
- return `export default function ${toPascalCase(app.id)}Home() {
1209
- return (
2086
+ if ('remote-commerce' === app.id) return createCommerceRemotePage(app);
2087
+ const effectBffImport = appHasEffectApi(app) ? `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2088
+ import { useEffect, useState } from 'react';
2089
+ import { ultramodernUiMarker } from '../../ultramodern-build';
2090
+ ` : "import { useModernI18n } from '@modern-js/plugin-i18n/runtime';\nimport { ultramodernUiMarker } from '../../ultramodern-build';\n";
2091
+ const effectBffState = appHasEffectApi(app) ? ` const [effectApiStatus, setEffectApiStatus] = useState('pending');
2092
+
2093
+ useEffect(() => {
2094
+ void fetch('${effectApiPrefix(app)}/effect/${effectApiStem(app)}?limit=1', {
2095
+ headers: {
2096
+ accept: 'application/json',
2097
+ },
2098
+ })
2099
+ .then(response => {
2100
+ if (!response.ok) {
2101
+ throw new Error(\`Effect BFF request failed: \${response.status}\`);
2102
+ }
2103
+
2104
+ return response.json() as Promise<{ items?: Array<{ title?: string }> }>;
2105
+ })
2106
+ .then(data => {
2107
+ setEffectApiStatus(data.items[0]?.title ?? 'empty');
2108
+ })
2109
+ .catch(() => {
2110
+ setEffectApiStatus('unavailable');
2111
+ });
2112
+ }, []);
2113
+
2114
+ ` : '';
2115
+ const effectBffMarkup = appHasEffectApi(app) ? ` <p data-testid="effect-bff-status">{effectApiStatus}</p>
2116
+ ` : '';
2117
+ return `${effectBffImport}
2118
+ ${createLocalizedHeadComponent()}
2119
+ export default function ${toPascalCase(app.id)}Home() {
2120
+ const { i18nInstance, language } = useModernI18n();
2121
+ const t = i18nInstance.t.bind(i18nInstance);
2122
+ ${effectBffState} return (
1210
2123
  <main>
1211
- <h1>${app.displayName}</h1>
1212
- <p data-mf-role="${app.kind}">${app.domain ?? app.kind}</p>
2124
+ <LocalizedHead />
2125
+ <nav aria-label={t('${app.domain}.language.switcher')}>
2126
+ {supportedLanguages.map(code => (
2127
+ <a
2128
+ aria-current={language === code ? 'page' : undefined}
2129
+ href={\`/\${code}\`}
2130
+ key={code}
2131
+ >
2132
+ {t(\`${app.domain}.language.\${code}\`)}
2133
+ </a>
2134
+ ))}
2135
+ </nav>
2136
+ <h1>{t('${app.domain}.title')}</h1>
2137
+ <p data-mf-role="${app.kind}">{t('${app.domain}.role')}</p>
2138
+ <p data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
2139
+ {ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
2140
+ </p>
2141
+ ${effectBffMarkup} </main>
2142
+ );
2143
+ }
2144
+ `;
2145
+ }
2146
+ function createCommerceRemotePage(app) {
2147
+ return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2148
+ import { useEffect, useState, type CSSProperties } from 'react';
2149
+ import { ultramodernUiMarker } from '../../ultramodern-build';
2150
+
2151
+ const languageCodes = ['en', 'cs'] as const;
2152
+
2153
+ const boundaryDefinitions = [
2154
+ {
2155
+ color: '#ff5a57',
2156
+ id: 'explore',
2157
+ labelKey: 'commerce.boundaries.explore',
2158
+ },
2159
+ {
2160
+ color: '#24d671',
2161
+ id: 'decide',
2162
+ labelKey: 'commerce.boundaries.decide',
2163
+ },
2164
+ {
2165
+ color: '#f4d044',
2166
+ id: 'checkout',
2167
+ labelKey: 'commerce.boundaries.checkout',
2168
+ },
2169
+ ] as const;
2170
+
2171
+ type BoundaryId = (typeof boundaryDefinitions)[number]['id'];
2172
+ type BoundaryDefinition = (typeof boundaryDefinitions)[number];
2173
+
2174
+ const boundaryMetadata: Record<BoundaryId, BoundaryDefinition> = {
2175
+ checkout: boundaryDefinitions[2],
2176
+ decide: boundaryDefinitions[1],
2177
+ explore: boundaryDefinitions[0],
2178
+ };
2179
+
2180
+ const products = [
2181
+ {
2182
+ id: 'field-loader-112',
2183
+ titleKey: 'commerce.products.fieldLoader.title',
2184
+ descriptionKey: 'commerce.products.fieldLoader.description',
2185
+ priceKey: 'commerce.products.fieldLoader.price',
2186
+ powerKey: 'commerce.products.fieldLoader.power',
2187
+ availabilityKey: 'commerce.products.fieldLoader.availability',
2188
+ },
2189
+ {
2190
+ id: 'orchard-tractor',
2191
+ titleKey: 'commerce.products.orchard.title',
2192
+ badgeKey: 'commerce.products.orchard.badge',
2193
+ },
2194
+ {
2195
+ id: 'autonomy-kit',
2196
+ titleKey: 'commerce.products.autonomy.title',
2197
+ badgeKey: 'commerce.products.autonomy.badge',
2198
+ },
2199
+ ] as const;
2200
+
2201
+ type ProductId = (typeof products)[number]['id'];
2202
+ type CartState = Partial<Record<ProductId, number>>;
2203
+
2204
+ type BoundaryLabels = Record<BoundaryId, string>;
2205
+ type BoundaryBox = {
2206
+ color: string;
2207
+ height: number;
2208
+ id: BoundaryId;
2209
+ label: string;
2210
+ labelPlacement: 'above' | 'inside';
2211
+ left: number;
2212
+ top: number;
2213
+ width: number;
2214
+ };
2215
+
2216
+ const featuredProduct = products[0];
2217
+ const recommendations = [products[1], products[2]] as const;
2218
+
2219
+ ${createLocalizedHeadComponent()}
2220
+ const isBoundaryId = (value: string): value is BoundaryId =>
2221
+ Object.prototype.hasOwnProperty.call(boundaryMetadata, value);
2222
+
2223
+ function collectBoundaryBoxes(labels: BoundaryLabels): BoundaryBox[] {
2224
+ return Array.from(
2225
+ document.querySelectorAll<HTMLElement>('[data-boundary], [data-boundary-page]'),
2226
+ )
2227
+ .map(element => {
2228
+ const id = element.dataset.boundary ?? element.dataset.boundaryPage;
2229
+
2230
+ if (id === undefined || !isBoundaryId(id)) {
2231
+ return undefined;
2232
+ }
2233
+
2234
+ const rect = element.getBoundingClientRect();
2235
+
2236
+ if (rect.width <= 0 || rect.height <= 0) {
2237
+ return undefined;
2238
+ }
2239
+
2240
+ return {
2241
+ color: boundaryMetadata[id].color,
2242
+ height: rect.height,
2243
+ id,
2244
+ label: labels[id],
2245
+ labelPlacement: rect.top > 28 ? 'above' : 'inside',
2246
+ left: rect.left,
2247
+ top: rect.top,
2248
+ width: rect.width,
2249
+ };
2250
+ })
2251
+ .filter((box): box is BoundaryBox => box !== undefined);
2252
+ }
2253
+
2254
+ function BoundaryOverlay({
2255
+ labels,
2256
+ visible,
2257
+ }: {
2258
+ labels: BoundaryLabels;
2259
+ visible: boolean;
2260
+ }) {
2261
+ const [boxes, setBoxes] = useState<BoundaryBox[]>([]);
2262
+
2263
+ useEffect(() => {
2264
+ if (!visible) {
2265
+ setBoxes([]);
2266
+ return;
2267
+ }
2268
+
2269
+ let animationFrame = 0;
2270
+ const update = () => {
2271
+ cancelAnimationFrame(animationFrame);
2272
+ animationFrame = requestAnimationFrame(() => {
2273
+ setBoxes(collectBoundaryBoxes(labels));
2274
+ });
2275
+ };
2276
+ const observer = new ResizeObserver(update);
2277
+
2278
+ observer.observe(document.body);
2279
+ update();
2280
+ window.addEventListener('resize', update);
2281
+ window.addEventListener('scroll', update, true);
2282
+
2283
+ return () => {
2284
+ cancelAnimationFrame(animationFrame);
2285
+ observer.disconnect();
2286
+ window.removeEventListener('resize', update);
2287
+ window.removeEventListener('scroll', update, true);
2288
+ };
2289
+ }, [labels, visible]);
2290
+
2291
+ if (!visible) {
2292
+ return null;
2293
+ }
2294
+
2295
+ return (
2296
+ <div aria-hidden="true" className="boundary-overlay">
2297
+ {boxes.map((box, index) => (
2298
+ <div
2299
+ className="boundary-overlay__box"
2300
+ data-boundary-id={box.id}
2301
+ data-label-placement={box.labelPlacement}
2302
+ key={\`\${box.id}-\${index}\`}
2303
+ style={{
2304
+ '--boundary-color': box.color,
2305
+ height: box.height,
2306
+ left: box.left,
2307
+ top: box.top,
2308
+ width: box.width,
2309
+ } as CSSProperties}
2310
+ >
2311
+ <span className="boundary-overlay__label">{box.label}</span>
2312
+ </div>
2313
+ ))}
2314
+ </div>
2315
+ );
2316
+ }
2317
+
2318
+ export default function ${toPascalCase(app.id)}Home() {
2319
+ const { i18nInstance, language } = useModernI18n();
2320
+ const t = i18nInstance.t.bind(i18nInstance);
2321
+ const [cart, setCart] = useState<CartState>({});
2322
+ const [showBoundaries, setShowBoundaries] = useState(false);
2323
+ const [effectApiStatus, setEffectApiStatus] = useState('pending');
2324
+ const boundaryLabels = {
2325
+ checkout: t('commerce.boundaries.checkout'),
2326
+ decide: t('commerce.boundaries.decide'),
2327
+ explore: t('commerce.boundaries.explore'),
2328
+ } satisfies BoundaryLabels;
2329
+ const cartLines = products
2330
+ .map(product => ({
2331
+ product,
2332
+ quantity: cart[product.id] ?? 0,
2333
+ }))
2334
+ .filter(line => line.quantity > 0);
2335
+ const cartCount = cartLines.reduce((total, line) => total + line.quantity, 0);
2336
+
2337
+ useEffect(() => {
2338
+ void fetch('${effectApiPrefix(app)}/effect/${effectApiStem(app)}?limit=1', {
2339
+ headers: {
2340
+ accept: 'application/json',
2341
+ },
2342
+ })
2343
+ .then(response => {
2344
+ if (!response.ok) {
2345
+ throw new Error(\`Effect BFF request failed: \${response.status}\`);
2346
+ }
2347
+
2348
+ return response.json() as Promise<{ items?: Array<{ title?: string }> }>;
2349
+ })
2350
+ .then(data => {
2351
+ setEffectApiStatus(data.items[0]?.title ?? 'empty');
2352
+ })
2353
+ .catch(() => {
2354
+ setEffectApiStatus('unavailable');
2355
+ });
2356
+ }, []);
2357
+
2358
+ const addToCart = (id: ProductId) => {
2359
+ setCart(current => ({
2360
+ ...current,
2361
+ [id]: (current[id] ?? 0) + 1,
2362
+ }));
2363
+ };
2364
+
2365
+ const reduceQuantity = (id: ProductId) => {
2366
+ setCart(current => {
2367
+ const quantity = current[id] ?? 0;
2368
+ const next = { ...current };
2369
+
2370
+ if (quantity <= 1) {
2371
+ delete next[id];
2372
+ } else {
2373
+ next[id] = quantity - 1;
2374
+ }
2375
+
2376
+ return next;
2377
+ });
2378
+ };
2379
+
2380
+ const removeFromCart = (id: ProductId) => {
2381
+ setCart(current => {
2382
+ const next = { ...current };
2383
+
2384
+ delete next[id];
2385
+ return next;
2386
+ });
2387
+ };
2388
+
2389
+ return (
2390
+ <main className="commerce-shell">
2391
+ <LocalizedHead />
2392
+ <BoundaryOverlay labels={boundaryLabels} visible={showBoundaries} />
2393
+ <header className="commerce-header" data-boundary="explore">
2394
+ <strong className="commerce-logo">{t('commerce.brand')}</strong>
2395
+ <nav aria-label={t('commerce.navigation.primary')} className="commerce-nav">
2396
+ <a className="commerce-pill" href="#machines">
2397
+ {t('commerce.navigation.machines')}
2398
+ </a>
2399
+ <a className="commerce-pill" href="#checkout">
2400
+ {t('commerce.navigation.checkout')}
2401
+ </a>
2402
+ </nav>
2403
+ <div className="commerce-actions">
2404
+ <a className="commerce-cart-button" data-boundary="checkout" href="#cart">
2405
+ {t('commerce.cart.button', { count: cartCount })}
2406
+ </a>
2407
+ <nav aria-label={t('commerce.language.switcher')} className="commerce-language">
2408
+ {languageCodes.map(code => (
2409
+ <a
2410
+ aria-current={language === code ? 'page' : undefined}
2411
+ className="commerce-pill"
2412
+ href={\`/\${code}\`}
2413
+ key={code}
2414
+ >
2415
+ {t(\`commerce.language.\${code}\`)}
2416
+ </a>
2417
+ ))}
2418
+ </nav>
2419
+ </div>
2420
+ </header>
2421
+
2422
+ <div className="commerce-page">
2423
+ <section className="commerce-product" data-boundary-page="decide" id="machines">
2424
+ <div
2425
+ aria-label={t('commerce.products.fieldLoader.imageAlt')}
2426
+ className="commerce-product-media"
2427
+ role="img"
2428
+ />
2429
+ <div>
2430
+ <p className="commerce-eyebrow">{t('commerce.detail.eyebrow')}</p>
2431
+ <h1 className="commerce-title">{t(featuredProduct.titleKey)}</h1>
2432
+ <p className="commerce-lede">{t(featuredProduct.descriptionKey)}</p>
2433
+ <div className="commerce-facts">
2434
+ <div className="commerce-fact">
2435
+ <span>{t('commerce.detail.price')}</span>
2436
+ <strong>{t(featuredProduct.priceKey)}</strong>
2437
+ </div>
2438
+ <div className="commerce-fact">
2439
+ <span>{t('commerce.detail.power')}</span>
2440
+ <strong>{t(featuredProduct.powerKey)}</strong>
2441
+ </div>
2442
+ <div className="commerce-fact">
2443
+ <span>{t('commerce.detail.availability')}</span>
2444
+ <strong>{t(featuredProduct.availabilityKey)}</strong>
2445
+ </div>
2446
+ </div>
2447
+ <div className="commerce-checkout" data-boundary="checkout" id="checkout">
2448
+ <button
2449
+ className="commerce-button"
2450
+ onClick={() => addToCart(featuredProduct.id)}
2451
+ type="button"
2452
+ >
2453
+ {t('commerce.cart.add')}
2454
+ </button>
2455
+ <a className="commerce-link-button" href="#cart">
2456
+ {t('commerce.cart.view')}
2457
+ </a>
2458
+ </div>
2459
+ </div>
2460
+ </section>
2461
+
2462
+ <section data-boundary="explore">
2463
+ <h2 className="commerce-section-title">{t('commerce.recommendations.title')}</h2>
2464
+ <div className="commerce-grid">
2465
+ {recommendations.map(product => (
2466
+ <article className="commerce-card" key={product.id}>
2467
+ <span>{t(product.badgeKey)}</span>
2468
+ <strong>{t(product.titleKey)}</strong>
2469
+ </article>
2470
+ ))}
2471
+ </div>
2472
+ </section>
2473
+
2474
+ <section className="commerce-cart-panel" data-boundary="checkout" id="cart">
2475
+ <h2>{t('commerce.cart.title')}</h2>
2476
+ {cartLines.length === 0 ? (
2477
+ <p>{t('commerce.cart.empty')}</p>
2478
+ ) : (
2479
+ cartLines.map(line => (
2480
+ <div className="commerce-cart-line" key={line.product.id}>
2481
+ <strong>{t(line.product.titleKey)}</strong>
2482
+ <div className="commerce-quantity">
2483
+ <button
2484
+ aria-label={t('commerce.cart.decrease', {
2485
+ name: t(line.product.titleKey),
2486
+ })}
2487
+ className="commerce-quantity-button"
2488
+ onClick={() => reduceQuantity(line.product.id)}
2489
+ type="button"
2490
+ >
2491
+ -
2492
+ </button>
2493
+ <span>{line.quantity}</span>
2494
+ <button
2495
+ aria-label={t('commerce.cart.increase', {
2496
+ name: t(line.product.titleKey),
2497
+ })}
2498
+ className="commerce-quantity-button"
2499
+ onClick={() => addToCart(line.product.id)}
2500
+ type="button"
2501
+ >
2502
+ +
2503
+ </button>
2504
+ <button
2505
+ className="commerce-link-button"
2506
+ onClick={() => removeFromCart(line.product.id)}
2507
+ type="button"
2508
+ >
2509
+ {t('commerce.cart.remove')}
2510
+ </button>
2511
+ </div>
2512
+ </div>
2513
+ ))
2514
+ )}
2515
+ </section>
2516
+ </div>
2517
+
2518
+ <footer className="commerce-footer" data-boundary="explore">
2519
+ <span>{t('commerce.footer.stack')}</span>
2520
+ <span data-testid="effect-bff-status">{effectApiStatus}</span>
2521
+ <span data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
2522
+ {ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
2523
+ </span>
2524
+ </footer>
2525
+
2526
+ <label className="commerce-boundary-toggle">
2527
+ <input
2528
+ checked={showBoundaries}
2529
+ onChange={event => setShowBoundaries(event.currentTarget.checked)}
2530
+ type="checkbox"
2531
+ />
2532
+ {t('commerce.boundaries.toggle')}
2533
+ </label>
1213
2534
  </main>
1214
2535
  );
1215
2536
  }
1216
2537
  `;
1217
2538
  }
1218
2539
  function createLayout(appId) {
1219
- return `import type { ReactNode } from 'react';
2540
+ return `import { Outlet } from '@modern-js/plugin-tanstack/runtime';
2541
+ import './index.css';
1220
2542
 
1221
- export default function Layout({ children }: { children: ReactNode }) {
1222
- return <div data-app-id="${appId}">{children}</div>;
2543
+ export default function Layout() {
2544
+ return (
2545
+ <div data-app-id="${appId}">
2546
+ <Outlet />
2547
+ </div>
2548
+ );
1223
2549
  }
1224
2550
  `;
1225
2551
  }
1226
2552
  function createRemoteEntry(app) {
1227
- const componentName = 'remote-identity' === app.id ? 'IdentityWidget' : 'CommerceWidget';
1228
- return `export { default } from './components/${componentName}';
2553
+ return `export { default } from './components/${remoteWidgetFile(app)}';
1229
2554
  `;
1230
2555
  }
1231
2556
  function createRemoteWidget(app) {
1232
- const componentName = 'remote-identity' === app.id ? 'IdentityWidget' : 'CommerceWidget';
2557
+ const componentName = `${toPascalCase(app.domain ?? app.id)}Widget`;
2558
+ const body = 'vertical' === app.kind ? `Owns the ${app.domain} vertical route surface.` : 'Provides shared UI primitives for the workspace.';
1233
2559
  return `export default function ${componentName}() {
1234
2560
  return (
1235
2561
  <section data-mf-remote="${app.id}">
1236
2562
  <h2>${app.displayName}</h2>
1237
- <p>Owns the ${app.domain} vertical route surface.</p>
2563
+ <p>${body}</p>
1238
2564
  </section>
1239
2565
  );
1240
2566
  }
1241
2567
  `;
1242
2568
  }
2569
+ function createAppLocaleMessages(app, language) {
2570
+ const czechLabels = {
2571
+ commerce: {
2572
+ role: 'obchod',
2573
+ title: 'Obchodní remote'
2574
+ },
2575
+ 'design-system': {
2576
+ role: 'design system',
2577
+ title: 'Design system remote'
2578
+ },
2579
+ identity: {
2580
+ role: 'identita',
2581
+ title: 'Identitní remote'
2582
+ }
2583
+ };
2584
+ if ('shell' === app.kind) return {
2585
+ shell: {
2586
+ language: {
2587
+ cs: 'en' === language ? 'Czech' : 'Čeština',
2588
+ en: 'en' === language ? 'English' : 'Angličtina',
2589
+ switcher: 'en' === language ? 'Language' : 'Jazyk'
2590
+ },
2591
+ remotes: {
2592
+ commerce: 'en' === language ? 'Commerce Remote' : 'Obchodní remote',
2593
+ designSystem: 'en' === language ? 'Design System Remote' : 'Design system remote',
2594
+ identity: 'en' === language ? 'Identity Remote' : 'Identitní remote'
2595
+ },
2596
+ title: 'en' === language ? 'UltraModern SuperApp Shell' : 'UltraModern SuperApp shell'
2597
+ }
2598
+ };
2599
+ const domain = app.domain ?? app.id;
2600
+ const czechLabel = czechLabels[domain] ?? {
2601
+ role: domain,
2602
+ title: `${app.displayName} CZ`
2603
+ };
2604
+ if ('commerce' === domain) return {
2605
+ commerce: {
2606
+ boundaries: {
2607
+ checkout: 'en' === language ? 'checkout' : 'pokladna',
2608
+ decide: 'en' === language ? 'decide' : 'rozhodování',
2609
+ explore: 'en' === language ? 'explore' : 'procházení',
2610
+ toggle: 'en' === language ? 'show team boundaries' : 'zobrazit hranice týmů'
2611
+ },
2612
+ brand: 'Acre & Iron',
2613
+ cart: {
2614
+ add: 'en' === language ? 'Add to cart' : 'Přidat do košíku',
2615
+ button: 'en' === language ? 'Your cart ({{count}})' : 'Košík ({{count}})',
2616
+ decrease: 'en' === language ? 'Decrease {{name}} quantity' : 'Snížit množství položky {{name}}',
2617
+ empty: 'en' === language ? 'Your cart is empty.' : 'Košík je prázdný.',
2618
+ increase: 'en' === language ? 'Increase {{name}} quantity' : 'Zvýšit množství položky {{name}}',
2619
+ remove: 'en' === language ? 'Remove' : 'Odebrat',
2620
+ title: 'en' === language ? 'Cart' : 'Košík',
2621
+ view: 'en' === language ? 'View cart' : 'Zobrazit košík'
2622
+ },
2623
+ detail: {
2624
+ availability: 'en' === language ? 'Availability' : 'Dostupnost',
2625
+ eyebrow: 'en' === language ? 'Machine detail' : 'Detail stroje',
2626
+ power: 'en' === language ? 'Power' : 'Výkon',
2627
+ price: 'en' === language ? 'Price' : 'Cena'
2628
+ },
2629
+ footer: {
2630
+ stack: 'en' === language ? 'SPA, SSR-ready Module Federation, React, Effect BFF' : 'SPA, SSR-ready Module Federation, React, Effect BFF'
2631
+ },
2632
+ language: {
2633
+ cs: 'en' === language ? 'Czech' : 'Čeština',
2634
+ en: 'en' === language ? 'English' : 'Angličtina',
2635
+ switcher: 'en' === language ? 'Language' : 'Jazyk'
2636
+ },
2637
+ navigation: {
2638
+ checkout: 'en' === language ? 'Checkout' : 'Pokladna',
2639
+ machines: 'en' === language ? 'Machines' : 'Stroje',
2640
+ primary: 'en' === language ? 'Primary commerce navigation' : 'Hlavní navigace obchodu'
2641
+ },
2642
+ products: {
2643
+ autonomy: {
2644
+ badge: 'en' === language ? 'AI-first option' : 'AI varianta',
2645
+ title: 'en' === language ? 'Autonomy Retrofit Kit' : 'Sada pro autonomní řízení'
2646
+ },
2647
+ fieldLoader: {
2648
+ availability: 'en' === language ? 'In stock' : 'Skladem',
2649
+ description: 'en' === language ? 'A loader-ready tractor for feed, hay, gravel, and winter road work.' : 'Traktor připravený na nakladač pro krmivo, seno, štěrk i zimní údržbu cest.',
2650
+ imageAlt: 'en' === language ? 'Field Loader 112 tractor working on a bright farm lane' : 'Traktor Field Loader 112 pracuje na světlé polní cestě',
2651
+ power: '112 hp',
2652
+ price: 'EUR 42,500',
2653
+ title: 'Field Loader 112'
2654
+ },
2655
+ orchard: {
2656
+ badge: 'en' === language ? 'Best for tight rows' : 'Nejlepší do úzkých řádků',
2657
+ title: 'en' === language ? 'Narrow Orchard Tractor' : 'Úzký sadový traktor'
2658
+ }
2659
+ },
2660
+ recommendations: {
2661
+ title: 'en' === language ? 'Compare alternatives' : 'Porovnat alternativy'
2662
+ },
2663
+ role: 'en' === language ? 'commerce' : 'obchod',
2664
+ title: 'en' === language ? app.displayName : czechLabel.title
2665
+ }
2666
+ };
2667
+ return {
2668
+ [domain]: {
2669
+ language: {
2670
+ cs: 'en' === language ? 'Czech' : 'Čeština',
2671
+ en: 'en' === language ? 'English' : 'Angličtina',
2672
+ switcher: 'en' === language ? 'Language' : 'Jazyk'
2673
+ },
2674
+ role: 'en' === language ? app.domain ?? app.kind : czechLabel.role,
2675
+ title: 'en' === language ? app.displayName : czechLabel.title
2676
+ }
2677
+ };
2678
+ }
1243
2679
  function createDesignButton() {
1244
2680
  return `import { designTokens } from '../tokens';
1245
2681
 
1246
- export default function Button({ label = 'Design System Button' }: { label?: string }) {
2682
+ export default function Button({ label }: { label: string }) {
1247
2683
  return (
1248
2684
  <button
1249
2685
  type="button"
@@ -1261,8 +2697,8 @@ export default function Button({ label = 'Design System Button' }: { label?: str
1261
2697
  function createDesignTokens() {
1262
2698
  return `export const designTokens = {
1263
2699
  color: {
1264
- foreground: '#133225',
1265
2700
  accent: '#2f8f68',
2701
+ foreground: '#133225',
1266
2702
  },
1267
2703
  radius: {
1268
2704
  control: '999px',
@@ -1270,70 +2706,373 @@ function createDesignTokens() {
1270
2706
  } as const;
1271
2707
  `;
1272
2708
  }
1273
- function createEffectSharedApi() {
2709
+ function serviceEffectApiExport(service = effectService) {
2710
+ return `${toCamelCase(effectApiStem(service))}EffectApi`;
2711
+ }
2712
+ function serviceEffectGroupName(service = effectService) {
2713
+ return toCamelCase(effectApiStem(service));
2714
+ }
2715
+ function serviceEffectApiName(service = effectService) {
2716
+ return `${toPascalCase(effectApiStem(service))}EffectApi`;
2717
+ }
2718
+ function serviceEffectSchemaExport(service = effectService) {
2719
+ return `${toCamelCase(effectApiStem(service))}ItemSchema`;
2720
+ }
2721
+ function serviceEffectErrorStem(service = effectService) {
2722
+ const stem = effectApiStem(service);
2723
+ return 'recommendations' === stem ? 'recommendation' : stem;
2724
+ }
2725
+ function serviceEffectCreatePayloadSchemaExport(service = effectService) {
2726
+ return `${toCamelCase(effectApiStem(service))}CreatePayloadSchema`;
2727
+ }
2728
+ function serviceEffectNotFoundErrorExport(service = effectService) {
2729
+ return `${toPascalCase(serviceEffectErrorStem(service))}NotFound`;
2730
+ }
2731
+ function serviceEffectNotFoundSchemaExport(service = effectService) {
2732
+ return `${toCamelCase(serviceEffectErrorStem(service))}NotFoundSchema`;
2733
+ }
2734
+ function createEffectSharedApiImports() {
1274
2735
  return `import {
1275
2736
  HttpApi,
1276
2737
  HttpApiEndpoint,
1277
2738
  HttpApiGroup,
2739
+ HttpApiSchema,
1278
2740
  Schema,
1279
2741
  } from '@modern-js/plugin-bff/effect-client';
1280
-
1281
- const recommendationSchema = Schema.Struct({
2742
+ `;
2743
+ }
2744
+ function createEffectSharedApiContract(service = effectService) {
2745
+ const schemaExport = serviceEffectSchemaExport(service);
2746
+ const createPayloadSchemaExport = serviceEffectCreatePayloadSchemaExport(service);
2747
+ const notFoundErrorExport = serviceEffectNotFoundErrorExport(service);
2748
+ const notFoundSchemaExport = serviceEffectNotFoundSchemaExport(service);
2749
+ const apiExport = serviceEffectApiExport(service);
2750
+ const apiName = serviceEffectApiName(service);
2751
+ const groupName = serviceEffectGroupName(service);
2752
+ const stem = effectApiStem(service);
2753
+ const servicePrefix = effectApiPrefix(service);
2754
+ return `export const ${schemaExport} = Schema.Struct({
1282
2755
  id: Schema.String,
2756
+ marker: Schema.Struct({
2757
+ appId: Schema.String,
2758
+ packageName: Schema.String,
2759
+ version: Schema.String,
2760
+ build: Schema.String,
2761
+ deployProfile: Schema.String,
2762
+ surface: Schema.String,
2763
+ }),
2764
+ title: Schema.String,
2765
+ });
2766
+
2767
+ export const ${createPayloadSchemaExport} = Schema.Struct({
1283
2768
  title: Schema.String,
1284
2769
  });
1285
2770
 
1286
- export const recommendationsEffectApi = HttpApi.make(
1287
- 'RecommendationsEffectApi',
1288
- ).add(
1289
- HttpApiGroup.make('recommendations').add(
1290
- HttpApiEndpoint.get('list', '/effect/recommendations', {
1291
- success: Schema.Struct({
1292
- items: Schema.Array(recommendationSchema),
2771
+ export class ${notFoundErrorExport} extends Schema.TaggedErrorClass<${notFoundErrorExport}>()(
2772
+ '${notFoundErrorExport}',
2773
+ {
2774
+ id: Schema.String,
2775
+ },
2776
+ ) {}
2777
+
2778
+ export const ${notFoundSchemaExport} = ${notFoundErrorExport}.pipe(
2779
+ HttpApiSchema.status(404),
2780
+ );
2781
+
2782
+ export type OperationContext = {
2783
+ operationId: string;
2784
+ routePath: string;
2785
+ method: string;
2786
+ source: string;
2787
+ traceId?: string;
2788
+ };
2789
+
2790
+ export const ${apiExport} = HttpApi.make('${apiName}').add(
2791
+ HttpApiGroup.make('${groupName}')
2792
+ .add(
2793
+ HttpApiEndpoint.get('list', '/effect/${stem}', {
2794
+ query: {
2795
+ limit: Schema.optional(Schema.NumberFromString),
2796
+ },
2797
+ success: Schema.Struct({
2798
+ items: Schema.Array(${schemaExport}),
2799
+ }),
1293
2800
  }),
1294
- }),
1295
- ),
2801
+ )
2802
+ .add(
2803
+ HttpApiEndpoint.get('get', '/effect/${stem}/:id', {
2804
+ params: {
2805
+ id: Schema.String,
2806
+ },
2807
+ success: ${schemaExport},
2808
+ error: ${notFoundSchemaExport},
2809
+ }),
2810
+ )
2811
+ .add(
2812
+ HttpApiEndpoint.post('create', '/effect/${stem}', {
2813
+ payload: ${createPayloadSchemaExport},
2814
+ success: Schema.Struct({
2815
+ item: ${schemaExport},
2816
+ }),
2817
+ }),
2818
+ ),
1296
2819
  );
2820
+
2821
+ export const ${groupName}OperationContexts = {
2822
+ list: {
2823
+ operationId: '${apiName}:${groupName}:list',
2824
+ routePath: '/effect/${stem}',
2825
+ method: 'GET',
2826
+ source: 'generated-client',
2827
+ },
2828
+ get: {
2829
+ operationId: '${apiName}:${groupName}:get',
2830
+ routePath: '/effect/${stem}/:id',
2831
+ method: 'GET',
2832
+ source: 'generated-client',
2833
+ },
2834
+ create: {
2835
+ operationId: '${apiName}:${groupName}:create',
2836
+ routePath: '/effect/${stem}',
2837
+ method: 'POST',
2838
+ source: 'generated-client',
2839
+ },
2840
+ } satisfies Record<string, OperationContext>;
2841
+
2842
+ export const ${groupName}ApiContract = {
2843
+ basePath: '${servicePrefix}/effect/${stem}',
2844
+ ownerId: '${service.id}',
2845
+ servicePrefix: '${servicePrefix}',
2846
+ } as const;
2847
+ `;
2848
+ }
2849
+ function createEffectSharedApi(service) {
2850
+ if (service) return `${createEffectSharedApiImports()}
2851
+ ${createEffectSharedApiContract(service)}`;
2852
+ return `export const sharedEffectApiPackage = {
2853
+ scope: 'external-effect-service-contracts',
2854
+ } as const;
1297
2855
  `;
1298
2856
  }
1299
- function createEffectServiceEntry() {
2857
+ function createEffectServiceEntry(scope, service = effectService, contractImportPath = ultramodern_workspace_packageName(scope, 'shared-effect-api')) {
2858
+ const apiExport = serviceEffectApiExport(service);
2859
+ const groupName = serviceEffectGroupName(service);
2860
+ const notFoundErrorExport = serviceEffectNotFoundErrorExport(service);
2861
+ const stem = effectApiStem(service);
1300
2862
  return `import {
1301
2863
  defineEffectBff,
1302
2864
  Effect,
1303
2865
  HttpApiBuilder,
1304
2866
  Layer,
1305
- } from '@modern-js/plugin-bff/effect-server';
1306
- import { recommendationsEffectApi } from '../../shared/effect/api';
1307
-
1308
- const recommendationsLayer = HttpApiBuilder.group(
1309
- recommendationsEffectApi,
1310
- 'recommendations',
1311
- (handlers: any) =>
1312
- handlers.handle('list', () =>
1313
- Effect.succeed({
1314
- items: [
1315
- {
1316
- id: 'starter-recommendation',
1317
- title: 'Wire a real recommendation source here',
2867
+ } from '@modern-js/plugin-bff/effect-edge';
2868
+ import { ultramodernApiMarker } from '../../src/ultramodern-build';
2869
+ import {
2870
+ ${apiExport},
2871
+ ${groupName}OperationContexts,
2872
+ ${notFoundErrorExport},
2873
+ type OperationContext,
2874
+ } from '${contractImportPath}';
2875
+
2876
+ const ${groupName}Items = [
2877
+ {
2878
+ id: 'starter-${stem}',
2879
+ marker: ultramodernApiMarker,
2880
+ title: 'Wire a real ${stem} source here',
2881
+ },
2882
+ ];
2883
+
2884
+ const operationAttributes = (operationContext: OperationContext) => {
2885
+ return {
2886
+ 'modernjs.operation.id': operationContext.operationId,
2887
+ 'modernjs.operation.method': operationContext.method,
2888
+ 'modernjs.operation.route': operationContext.routePath,
2889
+ 'modernjs.operation.source': operationContext.source,
2890
+ ...(typeof operationContext.traceId === 'string'
2891
+ ? { 'modernjs.trace.id': operationContext.traceId }
2892
+ : {}),
2893
+ };
2894
+ };
2895
+
2896
+ const ${groupName}Layer = HttpApiBuilder.group(
2897
+ ${apiExport},
2898
+ '${groupName}',
2899
+ (handlers) =>
2900
+ handlers
2901
+ .handle('list', ({ query }) =>
2902
+ Effect.succeed({
2903
+ items:
2904
+ typeof query.limit === 'number'
2905
+ ? ${groupName}Items.slice(0, query.limit)
2906
+ : ${groupName}Items,
2907
+ }).pipe(
2908
+ Effect.withSpan('ultramodern.effect.${groupName}.list', {
2909
+ attributes: operationAttributes(${groupName}OperationContexts.list),
2910
+ kind: 'server',
2911
+ }),
2912
+ ),
2913
+ )
2914
+ .handle('get', ({ params }) => {
2915
+ const item = ${groupName}Items.find(item => item.id === params.id);
2916
+ return (item !== undefined
2917
+ ? Effect.succeed(item)
2918
+ : Effect.fail(new ${notFoundErrorExport}({ id: params.id }))).pipe(
2919
+ Effect.withSpan('ultramodern.effect.${groupName}.get', {
2920
+ attributes: operationAttributes(${groupName}OperationContexts.get),
2921
+ kind: 'server',
2922
+ }),
2923
+ );
2924
+ })
2925
+ .handle('create', ({ payload }) =>
2926
+ Effect.succeed({
2927
+ item: {
2928
+ id: \`generated-${stem}-\${payload.title
2929
+ .toLowerCase()
2930
+ .replaceAll(/[^a-z0-9]+/g, '-')
2931
+ .replaceAll(/^-|-$/g, '')}\`,
2932
+ marker: ultramodernApiMarker,
2933
+ title: payload.title,
1318
2934
  },
1319
- ],
1320
- }),
1321
- ),
2935
+ }).pipe(
2936
+ Effect.withSpan('ultramodern.effect.${groupName}.create', {
2937
+ attributes: operationAttributes(${groupName}OperationContexts.create),
2938
+ kind: 'server',
2939
+ }),
2940
+ ),
2941
+ ),
1322
2942
  );
1323
2943
 
1324
- const layer = HttpApiBuilder.layer(recommendationsEffectApi).pipe(
1325
- Layer.provide(recommendationsLayer),
2944
+ const layer = HttpApiBuilder.layer(${apiExport}).pipe(
2945
+ Layer.provide(${groupName}Layer),
1326
2946
  );
1327
2947
 
1328
2948
  export default defineEffectBff({
1329
- api: recommendationsEffectApi,
2949
+ api: ${apiExport},
1330
2950
  layer,
1331
2951
  });
1332
2952
  `;
1333
2953
  }
2954
+ function createEffectClient(service, contractImportPath) {
2955
+ const apiExport = serviceEffectApiExport(service);
2956
+ const contractExport = serviceEffectGroupName(service);
2957
+ const stem = effectApiStem(service);
2958
+ const groupName = serviceEffectGroupName(service);
2959
+ const singular = serviceEffectErrorStem(service);
2960
+ const clientOptionsName = `${toPascalCase(stem)}ClientOptions`;
2961
+ const createClientName = `create${toPascalCase(stem)}Client`;
2962
+ const listName = `list${toPascalCase(stem)}`;
2963
+ const getName = `get${toPascalCase(singular)}`;
2964
+ const createName = `create${toPascalCase(singular)}`;
2965
+ return `import {
2966
+ makeEffectHttpApiClient,
2967
+ runEffectRequest,
2968
+ } from '@modern-js/plugin-bff/effect-client';
2969
+ import {
2970
+ ${contractExport}ApiContract,
2971
+ ${apiExport},
2972
+ ${groupName}OperationContexts,
2973
+ type OperationContext,
2974
+ } from '${contractImportPath}';
2975
+
2976
+ export type ${clientOptionsName} = {
2977
+ baseUrl?: string | URL;
2978
+ locale?: string;
2979
+ operationContext?: OperationContext;
2980
+ traceparent?: string;
2981
+ };
2982
+
2983
+ export function ${createClientName}(
2984
+ options: ${clientOptionsName} = {},
2985
+ ) {
2986
+ return makeEffectHttpApiClient(${apiExport}, {
2987
+ baseUrl: options.baseUrl ?? ${contractExport}ApiContract.servicePrefix,
2988
+ });
2989
+ }
2990
+
2991
+ export function ${listName}(
2992
+ options: ${clientOptionsName} & { limit?: number } = {},
2993
+ ) {
2994
+ return runEffectRequest(
2995
+ ${createClientName}({
2996
+ ...options,
2997
+ operationContext:
2998
+ options.operationContext ?? ${groupName}OperationContexts.list,
2999
+ }),
3000
+ ).then(client =>
3001
+ runEffectRequest(
3002
+ client.${groupName}.list({ query: { limit: options.limit } }),
3003
+ ),
3004
+ );
3005
+ }
3006
+
3007
+ export function ${getName}(
3008
+ id: string,
3009
+ options: ${clientOptionsName} = {},
3010
+ ) {
3011
+ return runEffectRequest(
3012
+ ${createClientName}({
3013
+ ...options,
3014
+ operationContext:
3015
+ options.operationContext ?? ${groupName}OperationContexts.get,
3016
+ }),
3017
+ ).then(client =>
3018
+ runEffectRequest(client.${groupName}.get({ params: { id } })),
3019
+ );
3020
+ }
3021
+
3022
+ export function ${createName}(
3023
+ title: string,
3024
+ options: ${clientOptionsName} = {},
3025
+ ) {
3026
+ return runEffectRequest(
3027
+ ${createClientName}({
3028
+ ...options,
3029
+ operationContext:
3030
+ options.operationContext ?? ${groupName}OperationContexts.create,
3031
+ }),
3032
+ ).then(client =>
3033
+ runEffectRequest(
3034
+ client.${groupName}.create({ payload: { title } }),
3035
+ ),
3036
+ );
3037
+ }
3038
+ `;
3039
+ }
3040
+ function createShellEffectClient(scope) {
3041
+ return `export {
3042
+ createRecommendation,
3043
+ createRecommendationsClient,
3044
+ getRecommendation,
3045
+ listRecommendations,
3046
+ type RecommendationsClientOptions,
3047
+ } from '${ultramodern_workspace_packageName(scope, 'remote-commerce')}/effect/client';
3048
+ `;
3049
+ }
1334
3050
  function toPascalCase(value) {
1335
3051
  return value.split(/[-_]+/).filter(Boolean).map((part)=>`${part.charAt(0).toUpperCase()}${part.slice(1)}`).join('');
1336
3052
  }
3053
+ function effectApiTopologyMetadata(app) {
3054
+ if (!appHasEffectApi(app)) return;
3055
+ return {
3056
+ effect: {
3057
+ runtime: 'effect',
3058
+ bff: {
3059
+ prefix: app.effectApi.prefix,
3060
+ openapi: '/openapi.json'
3061
+ },
3062
+ contract: {
3063
+ export: './shared/effect/api',
3064
+ path: `${app.directory}/shared/effect/api.ts`
3065
+ },
3066
+ client: {
3067
+ export: './effect/client',
3068
+ path: `${app.directory}/src/effect/${app.effectApi.stem}-client.ts`
3069
+ },
3070
+ serverEntry: `${app.directory}/api/effect/index.ts`,
3071
+ basePath: `${app.effectApi.prefix}/effect/${app.effectApi.stem}`,
3072
+ consumedBy: app.effectApi.consumedBy
3073
+ }
3074
+ };
3075
+ }
1337
3076
  function createTopology(scope) {
1338
3077
  return {
1339
3078
  schemaVersion: 1,
@@ -1373,25 +3112,12 @@ function createTopology(scope) {
1373
3112
  fallbackTelemetryEvent: 'modernjs:mv-runtime-parity',
1374
3113
  sharedContractVersion: 'mf-ssr-contract-v1'
1375
3114
  },
3115
+ ...effectApiTopologyMetadata(remote) ? {
3116
+ api: effectApiTopologyMetadata(remote)
3117
+ } : {},
1376
3118
  ownership: remote.ownership
1377
3119
  })),
1378
- effectServices: [
1379
- {
1380
- id: effectService.id,
1381
- kind: 'effect-service',
1382
- runtime: 'effect',
1383
- package: ultramodern_workspace_packageName(scope, effectService.packageSuffix),
1384
- consumedBy: [
1385
- shellApp.id,
1386
- 'remote-commerce'
1387
- ],
1388
- bff: {
1389
- prefix: '/recommendations-api',
1390
- openapi: '/openapi.json'
1391
- },
1392
- ownership: effectService.ownership
1393
- }
1394
- ],
3120
+ effectServices: [],
1395
3121
  sharedPackages: sharedPackages.map((sharedPackage)=>({
1396
3122
  id: sharedPackage.id,
1397
3123
  package: ultramodern_workspace_packageName(scope, sharedPackage.id),
@@ -1401,7 +3127,7 @@ function createTopology(scope) {
1401
3127
  validation: {
1402
3128
  script: "scripts/validate-ultramodern-workspace.mjs",
1403
3129
  commands: [
1404
- 'pnpm ultramodern:check'
3130
+ 'mise exec -- pnpm ultramodern:check'
1405
3131
  ]
1406
3132
  }
1407
3133
  };
@@ -1413,12 +3139,6 @@ function createOwnership(scope) {
1413
3139
  owners: [
1414
3140
  shellApp,
1415
3141
  ...remoteApps,
1416
- {
1417
- id: effectService.id,
1418
- packageSuffix: effectService.packageSuffix,
1419
- directory: effectService.directory,
1420
- ownership: effectService.ownership
1421
- },
1422
3142
  ...sharedPackages.map((sharedPackage)=>({
1423
3143
  id: sharedPackage.id,
1424
3144
  packageSuffix: sharedPackage.id,
@@ -1456,19 +3176,15 @@ function createDevelopmentOverlay() {
1456
3176
  ].map((app)=>[
1457
3177
  app.id,
1458
3178
  app.port
1459
- ]).concat([
1460
- [
1461
- effectService.id,
1462
- effectService.port
1463
- ]
1464
- ])),
3179
+ ])),
1465
3180
  manifests: Object.fromEntries(remoteApps.map((remote)=>[
1466
3181
  remote.id,
1467
3182
  `http://localhost:${remote.port}/mf-manifest.json`
1468
3183
  ])),
1469
- services: {
1470
- [effectService.id]: `http://localhost:${effectService.port}/recommendations-api`
1471
- }
3184
+ apis: Object.fromEntries(verticalEffectApps().map((app)=>[
3185
+ app.id,
3186
+ `http://localhost:${app.port}${effectApiPrefix(app)}`
3187
+ ]))
1472
3188
  };
1473
3189
  }
1474
3190
  function createPackageSourceMetadata(scope, packageSource) {
@@ -1498,6 +3214,232 @@ function createPackageSourceMetadata(scope, packageSource) {
1498
3214
  }
1499
3215
  };
1500
3216
  }
3217
+ function createEffectOperationContract(target) {
3218
+ const stem = effectApiStem(target);
3219
+ return {
3220
+ group: serviceEffectGroupName(target),
3221
+ notFound: serviceEffectNotFoundErrorExport(target),
3222
+ operations: {
3223
+ list: {
3224
+ method: 'GET',
3225
+ path: `/effect/${stem}`,
3226
+ source: 'generated-client'
3227
+ },
3228
+ get: {
3229
+ method: 'GET',
3230
+ path: `/effect/${stem}/:id`,
3231
+ source: 'generated-client'
3232
+ },
3233
+ create: {
3234
+ method: 'POST',
3235
+ path: `/effect/${stem}`,
3236
+ source: 'generated-client'
3237
+ }
3238
+ }
3239
+ };
3240
+ }
3241
+ function createAppConfigContract(app) {
3242
+ return {
3243
+ preset: 'presetUltramodern',
3244
+ plugins: [
3245
+ 'appTools',
3246
+ 'tanstackRouterPlugin',
3247
+ 'i18nPlugin',
3248
+ ...appHasEffectApi(app) ? [
3249
+ 'bffPlugin'
3250
+ ] : [],
3251
+ 'moduleFederationPlugin',
3252
+ 'zephyrRspackPlugin'
3253
+ ],
3254
+ output: {
3255
+ disableTsChecker: true,
3256
+ distPath: {
3257
+ html: './'
3258
+ },
3259
+ polyfill: 'off',
3260
+ splitRouteChunks: false
3261
+ },
3262
+ performance: {
3263
+ rsdoctor: {
3264
+ enabledByEnv: 'ULTRAMODERN_RSDOCTOR=true',
3265
+ disableClientServer: true
3266
+ }
3267
+ },
3268
+ html: {
3269
+ outputStructure: 'flat'
3270
+ },
3271
+ source: {
3272
+ mainEntryName: 'index',
3273
+ siteUrlGlobal: 'ULTRAMODERN_SITE_URL'
3274
+ },
3275
+ ...appHasEffectApi(app) ? {
3276
+ bff: {
3277
+ runtimeFramework: 'effect',
3278
+ prefix: app.effectApi.prefix,
3279
+ openapi: '/openapi.json'
3280
+ }
3281
+ } : {}
3282
+ };
3283
+ }
3284
+ function createStylingContract(enableTailwind) {
3285
+ return {
3286
+ tailwind: enableTailwind,
3287
+ ...enableTailwind ? {
3288
+ postcssPlugins: [
3289
+ '@tailwindcss/postcss'
3290
+ ],
3291
+ contentGlobs: [
3292
+ './src/**/*.{js,jsx,ts,tsx}'
3293
+ ]
3294
+ } : {}
3295
+ };
3296
+ }
3297
+ function createAppGeneratedContract(scope, app, apps, enableTailwind) {
3298
+ const remoteAppsForShell = apps.filter((candidate)=>'shell' !== candidate.kind && candidate.mfName);
3299
+ return {
3300
+ id: app.id,
3301
+ package: ultramodern_workspace_packageName(scope, app.packageSuffix),
3302
+ path: app.directory,
3303
+ kind: app.kind,
3304
+ config: createAppConfigContract(app),
3305
+ styling: createStylingContract(enableTailwind),
3306
+ deploy: {
3307
+ target: 'cloudflare',
3308
+ worker: {
3309
+ ssr: true
3310
+ },
3311
+ output: {
3312
+ flat: true,
3313
+ htmlDistPath: './'
3314
+ }
3315
+ },
3316
+ ssr: {
3317
+ mode: 'stream',
3318
+ moduleFederationAppSSR: true
3319
+ },
3320
+ i18n: {
3321
+ plugin: '@modern-js/plugin-i18n',
3322
+ backend: true,
3323
+ reactI18next: false,
3324
+ languages: [
3325
+ 'en',
3326
+ 'cs'
3327
+ ],
3328
+ fallbackLanguage: 'en',
3329
+ publicDir: './locales'
3330
+ },
3331
+ moduleFederation: {
3332
+ name: app.mfName,
3333
+ ...'shell' === app.kind ? {
3334
+ remotes: remoteAppsForShell.map((remote)=>({
3335
+ id: remote.id,
3336
+ alias: remoteDependencyAlias(remote),
3337
+ name: remote.mfName,
3338
+ manifestEnv: createRemoteManifestEnv(remote),
3339
+ manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`
3340
+ }))
3341
+ } : {},
3342
+ exposes: Object.keys(app.exposes ?? {}),
3343
+ dts: {
3344
+ displayErrorInTerminal: true,
3345
+ compilerInstance: "--package typescript -- tsc"
3346
+ },
3347
+ browserSafeExposesOnly: true,
3348
+ zephyrRspackPlugin: ZEPHYR_RSPACK_PLUGIN_VERSION
3349
+ },
3350
+ marker: {
3351
+ appId: app.id,
3352
+ packageName: ultramodern_workspace_packageName(scope, app.packageSuffix),
3353
+ version: '0.1.0',
3354
+ build: createBuildMarker(scope, app),
3355
+ deployProfile: 'cloudflare-ssr-mf-effect-v1',
3356
+ uiSurface: 'ui',
3357
+ ...appHasEffectApi(app) ? {
3358
+ apiSurface: 'effect-bff'
3359
+ } : {}
3360
+ },
3361
+ ...'commerce' === app.domain ? {
3362
+ boundaryVisualization: {
3363
+ mode: 'overlay',
3364
+ layoutAffecting: false,
3365
+ toggle: 'user-controlled',
3366
+ boundaries: [
3367
+ {
3368
+ id: 'explore',
3369
+ labelKey: 'commerce.boundaries.explore',
3370
+ owner: 'team-explore',
3371
+ color: '#ff5a57',
3372
+ owns: [
3373
+ 'header',
3374
+ 'footer',
3375
+ 'recommendations',
3376
+ 'catalog'
3377
+ ]
3378
+ },
3379
+ {
3380
+ id: 'decide',
3381
+ labelKey: 'commerce.boundaries.decide',
3382
+ owner: 'team-decide',
3383
+ color: '#24d671',
3384
+ owns: [
3385
+ 'product-detail',
3386
+ 'variant-selection'
3387
+ ]
3388
+ },
3389
+ {
3390
+ id: 'checkout',
3391
+ labelKey: 'commerce.boundaries.checkout',
3392
+ owner: 'team-checkout',
3393
+ color: '#f4d044',
3394
+ owns: [
3395
+ 'add-to-cart',
3396
+ 'cart-link',
3397
+ 'cart-lines'
3398
+ ]
3399
+ }
3400
+ ]
3401
+ }
3402
+ } : {},
3403
+ ...appHasEffectApi(app) ? {
3404
+ effect: {
3405
+ runtime: 'effect',
3406
+ import: '@modern-js/plugin-bff/effect-edge',
3407
+ prefix: app.effectApi.prefix,
3408
+ openapi: '/openapi.json',
3409
+ workerEntry: 'worker/__modern_bff_effect.js',
3410
+ contract: './shared/effect/api',
3411
+ client: './effect/client',
3412
+ ...createEffectOperationContract(app)
3413
+ }
3414
+ } : {}
3415
+ };
3416
+ }
3417
+ function createGeneratedContract(scope, apps = [
3418
+ shellApp,
3419
+ ...remoteApps
3420
+ ], enableTailwind = true) {
3421
+ return {
3422
+ schemaVersion: 1,
3423
+ profile: 'cloudflare-ssr-mf-effect-v1',
3424
+ packageManager: {
3425
+ source: 'package.json',
3426
+ manager: 'pnpm',
3427
+ version: PNPM_VERSION,
3428
+ toolchain: 'mise'
3429
+ },
3430
+ versions: {
3431
+ typescript: TYPESCRIPT_VERSION,
3432
+ typescriptNativePreview: TYPESCRIPT_NATIVE_PREVIEW_VERSION,
3433
+ moduleFederation: MODULE_FEDERATION_VERSION,
3434
+ tanstackRouter: TANSTACK_ROUTER_VERSION,
3435
+ i18next: I18NEXT_VERSION,
3436
+ zephyrRspackPlugin: ZEPHYR_RSPACK_PLUGIN_VERSION,
3437
+ zephyrAgent: ZEPHYR_AGENT_VERSION,
3438
+ wrangler: WRANGLER_VERSION
3439
+ },
3440
+ apps: apps.map((app)=>createAppGeneratedContract(scope, app, apps, enableTailwind))
3441
+ };
3442
+ }
1501
3443
  function createTemplateManifest(modernVersion, packageSource) {
1502
3444
  return {
1503
3445
  schemaVersion: 1,
@@ -1532,11 +3474,18 @@ function createTemplateManifest(modernVersion, packageSource) {
1532
3474
  materialization: {
1533
3475
  targetRoot: 'generated-project-root',
1534
3476
  allowedPaths: [
3477
+ '.agents/**',
3478
+ '.github/**',
3479
+ '.gitignore',
3480
+ '.mise.toml',
1535
3481
  '.modernjs/**',
3482
+ 'AGENTS.md',
1536
3483
  'README.md',
1537
3484
  'apps/**',
1538
3485
  'packages/**',
1539
3486
  'package.json',
3487
+ 'oxfmt.config.ts',
3488
+ 'oxlint.config.ts',
1540
3489
  'pnpm-workspace.yaml',
1541
3490
  "scripts/**",
1542
3491
  'services/**',
@@ -1545,7 +3494,6 @@ function createTemplateManifest(modernVersion, packageSource) {
1545
3494
  ],
1546
3495
  deniedPaths: [
1547
3496
  '.git/**',
1548
- '.github/**',
1549
3497
  '.npmrc',
1550
3498
  '.yarnrc',
1551
3499
  '.env',
@@ -1561,6 +3509,28 @@ function createTemplateManifest(modernVersion, packageSource) {
1561
3509
  modernPackageSpecifier: modernPackageVersion(packageSource),
1562
3510
  generatedWorkspacePackageSpecifier: WORKSPACE_PACKAGE_VERSION
1563
3511
  },
3512
+ agentSkills: {
3513
+ installDir: '.agents/skills',
3514
+ source: {
3515
+ repository: 'https://github.com/rstackjs/agent-skills',
3516
+ commit: RSTACK_AGENT_SKILLS_COMMIT,
3517
+ license: 'MIT',
3518
+ licensePath: '.agents/rstackjs-agent-skills-LICENSE'
3519
+ },
3520
+ baseline: baselineAgentSkills,
3521
+ moduleFederationSource: {
3522
+ repository: 'https://github.com/module-federation/agent-skills',
3523
+ commit: MODULE_FEDERATION_AGENT_SKILLS_COMMIT,
3524
+ install: 'clone',
3525
+ baseline: moduleFederationAgentSkills
3526
+ },
3527
+ privateSource: {
3528
+ repository: 'https://github.com/TechsioCZ/skills',
3529
+ install: 'clone-if-authorized',
3530
+ baseline: privateAgentSkills
3531
+ },
3532
+ lockFile: '.agents/skills-lock.json'
3533
+ },
1564
3534
  validation: {
1565
3535
  schemaValidation: true,
1566
3536
  sourceValidation: [
@@ -1577,48 +3547,82 @@ function createTemplateManifest(modernVersion, packageSource) {
1577
3547
  ],
1578
3548
  postMaterializationValidation: [
1579
3549
  'ultramodern-workspace-contract-check',
3550
+ 'github-workflow-security-enforced',
3551
+ 'pnpm-11-policy-enforced',
1580
3552
  'template-manifest-retained'
1581
3553
  ],
1582
3554
  expectedCommands: [
1583
- "pnpm install --ignore-scripts",
1584
- 'pnpm run ultramodern:check'
3555
+ 'mise install',
3556
+ 'mise exec -- pnpm install',
3557
+ 'mise exec -- pnpm run ultramodern:check'
1585
3558
  ]
1586
3559
  }
1587
3560
  };
1588
3561
  }
1589
- function writeApp(targetDir, scope, app, packageSource) {
1590
- writeJson(targetDir, `${app.directory}/package.json`, createAppPackage(scope, app, packageSource));
1591
- writeJson(targetDir, `${app.directory}/tsconfig.json`, createPackageTsConfig(app.directory));
1592
- writeFile(targetDir, `${app.directory}/src/modern-app-env.d.ts`, "/// <reference types='@modern-js/app-tools/types' />\n");
3562
+ function writeApp(targetDir, scope, app, packageSource, enableTailwind) {
3563
+ writeJson(targetDir, `${app.directory}/package.json`, createAppPackage(scope, app, packageSource, enableTailwind));
3564
+ writeJson(targetDir, `${app.directory}/tsconfig.json`, createPackageTsConfig(app.directory, appHasEffectApi(app)));
3565
+ writeFile(targetDir, `${app.directory}/src/modern-app-env.d.ts`, "/// <reference types='@modern-js/app-tools/types' />\n\ndeclare const ULTRAMODERN_SITE_URL: string;\n");
3566
+ writeFile(targetDir, `${app.directory}/src/ultramodern-build.ts`, createUltramodernBuildModule(scope, app));
1593
3567
  writeFile(targetDir, `${app.directory}/modern.config.ts`, createAppModernConfig(app));
3568
+ writeFile(targetDir, `${app.directory}/src/modern.runtime.ts`, createAppRuntimeConfig(app));
3569
+ writeJson(targetDir, `${app.directory}/locales/en/translation.json`, createAppLocaleMessages(app, 'en'));
3570
+ writeJson(targetDir, `${app.directory}/locales/cs/translation.json`, createAppLocaleMessages(app, 'cs'));
3571
+ writeFile(targetDir, `${app.directory}/src/routes/index.css`, createAppStyles(enableTailwind));
3572
+ if (enableTailwind) {
3573
+ writeFile(targetDir, `${app.directory}/postcss.config.mjs`, createPostcssConfig());
3574
+ writeFile(targetDir, `${app.directory}/tailwind.config.ts`, createTailwindConfig());
3575
+ }
1594
3576
  writeFile(targetDir, `${app.directory}/module-federation.config.ts`, 'shell' === app.kind ? createShellModuleFederationConfig() : createRemoteModuleFederationConfig(app));
1595
3577
  writeFile(targetDir, `${app.directory}/src/routes/layout.tsx`, createLayout(app.id));
1596
- writeFile(targetDir, `${app.directory}/src/routes/page.tsx`, 'shell' === app.kind ? createShellPage() : createRemotePage(app));
1597
- if ('vertical' === app.kind) {
3578
+ writeFile(targetDir, `${app.directory}/src/routes/[lang]/page.tsx`, 'shell' === app.kind ? createShellPage() : createRemotePage(app));
3579
+ if ('shell' === app.kind) writeFile(targetDir, `${app.directory}/src/effect/recommendations-client.ts`, createShellEffectClient(scope));
3580
+ if (appHasEffectApi(app)) {
3581
+ writeFile(targetDir, `${app.directory}/shared/effect/api.ts`, createEffectSharedApi(app));
3582
+ writeFile(targetDir, `${app.directory}/api/effect/index.ts`, createEffectServiceEntry(scope, app, '../../shared/effect/api'));
3583
+ writeFile(targetDir, `${app.directory}/src/effect/${app.effectApi.stem}-client.ts`, createEffectClient(app, '../../shared/effect/api'));
3584
+ }
3585
+ if ('vertical' === app.kind || 'horizontal-remote' === app.kind) {
1598
3586
  writeFile(targetDir, `${app.directory}/src/remote-entry.tsx`, createRemoteEntry(app));
1599
- const widgetFile = 'remote-identity' === app.id ? 'IdentityWidget.tsx' : 'CommerceWidget.tsx';
1600
- writeFile(targetDir, `${app.directory}/src/components/${widgetFile}`, createRemoteWidget(app));
3587
+ writeFile(targetDir, `${app.directory}/src/components/${remoteWidgetFile(app)}.tsx`, createRemoteWidget(app));
1601
3588
  }
1602
3589
  if ('horizontal-design-system' === app.kind) {
1603
- writeFile(targetDir, `${app.directory}/src/components/Button.tsx`, createDesignButton());
3590
+ writeFile(targetDir, `${app.directory}/src/components/button.tsx`, createDesignButton());
1604
3591
  writeFile(targetDir, `${app.directory}/src/tokens.ts`, createDesignTokens());
1605
3592
  }
1606
3593
  }
1607
- function writeEffectService(targetDir, scope, packageSource) {
1608
- writeJson(targetDir, `${effectService.directory}/package.json`, createServicePackage(scope, packageSource));
1609
- writeJson(targetDir, `${effectService.directory}/tsconfig.json`, createPackageTsConfig(effectService.directory, true));
1610
- writeFile(targetDir, `${effectService.directory}/src/modern-app-env.d.ts`, "/// <reference types='@modern-js/app-tools/types' />\n");
1611
- writeFile(targetDir, `${effectService.directory}/src/routes/page.tsx`, `export default function RecommendationsServiceHome() {
1612
- return <main>Recommendations Effect service</main>;
3594
+ function writeEffectService(targetDir, scope, packageSource, enableTailwind, service = effectService) {
3595
+ writeJson(targetDir, `${service.directory}/package.json`, createServicePackage(scope, packageSource, enableTailwind, service));
3596
+ writeJson(targetDir, `${service.directory}/tsconfig.json`, createPackageTsConfig(service.directory, true));
3597
+ writeFile(targetDir, `${service.directory}/src/modern-app-env.d.ts`, "/// <reference types='@modern-js/app-tools/types' />\n");
3598
+ writeFile(targetDir, `${service.directory}/src/ultramodern-build.ts`, createUltramodernBuildModule(scope, service));
3599
+ writeFile(targetDir, `${service.directory}/src/routes/layout.tsx`, createLayout(service.id));
3600
+ writeFile(targetDir, `${service.directory}/src/routes/page.tsx`, `export default function ${toPascalCase(service.id)}Home() {
3601
+ return <main>${service.id} Effect service</main>;
1613
3602
  }
1614
3603
  `);
1615
- writeFile(targetDir, `${effectService.directory}/modern.config.ts`, createServiceModernConfig());
1616
- writeFile(targetDir, `${effectService.directory}/shared/effect/api.ts`, createEffectSharedApi());
1617
- writeFile(targetDir, `${effectService.directory}/api/effect/index.ts`, createEffectServiceEntry());
3604
+ writeFile(targetDir, `${service.directory}/src/routes/index.css`, createAppStyles(enableTailwind));
3605
+ if (enableTailwind) {
3606
+ writeFile(targetDir, `${service.directory}/postcss.config.mjs`, createPostcssConfig());
3607
+ writeFile(targetDir, `${service.directory}/tailwind.config.ts`, createTailwindConfig());
3608
+ }
3609
+ writeFile(targetDir, `${service.directory}/modern.config.ts`, createServiceModernConfigFor(service));
3610
+ writeFile(targetDir, `${service.directory}/api/effect/index.ts`, createEffectServiceEntry(scope, service));
3611
+ }
3612
+ function writeGenericSharedPackage(targetDir, scope, packageSource, sharedPackage) {
3613
+ writeJson(targetDir, `${sharedPackage.directory}/package.json`, createSharedPackage(scope, sharedPackage.id, sharedPackage.description, packageSource));
3614
+ writeJson(targetDir, `${sharedPackage.directory}/tsconfig.json`, {
3615
+ extends: `${relativeRootFor(sharedPackage.directory)}/tsconfig.base.json`,
3616
+ include: [
3617
+ 'src'
3618
+ ]
3619
+ });
3620
+ writeFile(targetDir, `${sharedPackage.directory}/src/index.ts`, `export const packageId = '${sharedPackage.id}';
3621
+ `);
1618
3622
  }
1619
- function writeSharedPackages(targetDir, scope) {
3623
+ function writeSharedPackages(targetDir, scope, packageSource) {
1620
3624
  for (const sharedPackage of sharedPackages){
1621
- writeJson(targetDir, `${sharedPackage.directory}/package.json`, createSharedPackage(scope, sharedPackage.id, sharedPackage.description));
3625
+ writeJson(targetDir, `${sharedPackage.directory}/package.json`, createSharedPackage(scope, sharedPackage.id, sharedPackage.description, packageSource));
1622
3626
  writeJson(targetDir, `${sharedPackage.directory}/tsconfig.json`, {
1623
3627
  extends: `${relativeRootFor(sharedPackage.directory)}/tsconfig.base.json`,
1624
3628
  include: [
@@ -1627,51 +3631,315 @@ function writeSharedPackages(targetDir, scope) {
1627
3631
  });
1628
3632
  }
1629
3633
  writeFile(targetDir, 'packages/shared-contracts/src/index.ts', `export const ultramodernWorkspaceContract = {
3634
+ ownership: 'topology/ownership.json',
1630
3635
  preset: 'presetUltramodern',
1631
3636
  topology: 'topology/reference-topology.json',
1632
- ownership: 'topology/ownership.json',
1633
3637
  } as const;
1634
3638
  `);
1635
3639
  writeFile(targetDir, 'packages/shared-design-tokens/src/index.ts', `export const sharedDesignTokens = {
1636
3640
  color: {
1637
- surface: '#f6fbf7',
1638
- foreground: '#133225',
1639
3641
  accent: '#2f8f68',
3642
+ foreground: '#133225',
3643
+ surface: '#f6fbf7',
1640
3644
  },
1641
3645
  } as const;
1642
3646
  `);
1643
- writeFile(targetDir, 'packages/shared-effect-api/src/index.ts', `export type Recommendation = {
1644
- id: string;
1645
- title: string;
1646
- };
1647
-
1648
- export const recommendationsApiContract = {
1649
- serviceId: '${effectService.id}',
1650
- basePath: '/recommendations-api/effect/recommendations',
1651
- } as const;
1652
- `);
3647
+ writeFile(targetDir, 'packages/shared-effect-api/src/index.ts', createEffectSharedApi());
3648
+ }
3649
+ function readJsonFile(filePath) {
3650
+ return JSON.parse(node_fs.readFileSync(filePath, 'utf-8'));
3651
+ }
3652
+ function writeJsonFile(filePath, value) {
3653
+ node_fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
3654
+ }
3655
+ function appendEffectSharedApiContract(targetDir, service = effectService) {
3656
+ const relativePath = 'packages/shared-effect-api/src/index.ts';
3657
+ assertSafeRelativePath(relativePath);
3658
+ const filePath = node_path.join(targetDir, relativePath);
3659
+ ensureInsideRoot(targetDir, filePath);
3660
+ if (!node_fs.existsSync(filePath)) throw new Error(`Missing generated Effect API package: ${relativePath}`);
3661
+ const current = node_fs.readFileSync(filePath, 'utf-8');
3662
+ const apiExport = serviceEffectApiExport(service);
3663
+ if (current.includes(`export const ${apiExport} =`)) return;
3664
+ const contentWithImports = current.includes('@modern-js/plugin-bff/effect-client') ? current.trimEnd() : `${createEffectSharedApiImports()}\n${current.trimEnd()}`;
3665
+ node_fs.writeFileSync(filePath, `${contentWithImports}\n\n${createEffectSharedApiContract(service)}`, 'utf-8');
3666
+ }
3667
+ function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
3668
+ if (packageSource) return resolvePackageSource({
3669
+ targetDir: workspaceRoot,
3670
+ packageName: node_path.basename(workspaceRoot),
3671
+ modernVersion,
3672
+ packageSource
3673
+ });
3674
+ const metadataPath = node_path.join(workspaceRoot, '.modernjs/ultramodern-package-source.json');
3675
+ if (!node_fs.existsSync(metadataPath)) return resolvePackageSource({
3676
+ targetDir: workspaceRoot,
3677
+ packageName: node_path.basename(workspaceRoot),
3678
+ modernVersion
3679
+ });
3680
+ const metadata = readJsonFile(metadataPath);
3681
+ const aliases = metadata.modernPackages?.aliases ?? {};
3682
+ const firstAlias = Object.values(aliases).find((value)=>'string' == typeof value);
3683
+ const firstPackage = Object.keys(aliases)[0];
3684
+ const aliasScope = firstAlias?.match(/^@([^/]+)\//)?.[1];
3685
+ const unscopedName = firstPackage?.split('/').at(-1) ?? '';
3686
+ const aliasUnscopedName = firstAlias?.split('/').at(-1) ?? '';
3687
+ const aliasPackageNamePrefix = aliasUnscopedName && unscopedName && aliasUnscopedName.endsWith(unscopedName) ? aliasUnscopedName.slice(0, -unscopedName.length) : void 0;
3688
+ return {
3689
+ strategy: 'install' === metadata.strategy ? 'install' : 'workspace',
3690
+ modernPackageVersion: 'string' == typeof metadata.modernPackages?.specifier ? metadata.modernPackages.specifier : modernVersion,
3691
+ registry: metadata.modernPackages?.registry,
3692
+ aliasScope,
3693
+ aliasPackageNamePrefix
3694
+ };
3695
+ }
3696
+ function assertValidMicroVerticalName(name) {
3697
+ const normalized = toKebabCase(name);
3698
+ if (!normalized || normalized !== name) throw new Error(`Invalid MicroVertical name "${name}". Use lowercase kebab-case.`);
3699
+ return normalized;
3700
+ }
3701
+ function nextAvailablePort(ports) {
3702
+ const numericPorts = Object.values(ports).filter((value)=>'number' == typeof value && Number.isFinite(value));
3703
+ return Math.max(3030, ...numericPorts) + 1;
3704
+ }
3705
+ function assertCanCreate(workspaceRoot, relativePath) {
3706
+ if (node_fs.existsSync(node_path.join(workspaceRoot, relativePath))) throw new Error(`Refusing to overwrite existing path: ${relativePath}`);
3707
+ }
3708
+ function addRootDevScript(workspaceRoot, scope, packageSuffix, scriptName) {
3709
+ const packagePath = node_path.join(workspaceRoot, 'package.json');
3710
+ const rootPackage = readJsonFile(packagePath);
3711
+ rootPackage.scripts ??= {};
3712
+ rootPackage.scripts[`dev:${scriptName}`] = `pnpm --filter ${ultramodern_workspace_packageName(scope, packageSuffix)} dev`;
3713
+ if ('string' == typeof rootPackage.scripts.dev && !rootPackage.scripts.dev.includes(ultramodern_workspace_packageName(scope, packageSuffix))) {
3714
+ const packageFilter = `--filter ${ultramodern_workspace_packageName(scope, packageSuffix)}`;
3715
+ rootPackage.scripts.dev = rootPackage.scripts.dev.endsWith(' dev') ? rootPackage.scripts.dev.replace(/ dev$/u, ` ${packageFilter} dev`) : `${rootPackage.scripts.dev} ${packageFilter}`;
3716
+ }
3717
+ writeJsonFile(packagePath, rootPackage);
3718
+ }
3719
+ function addShellZephyrDependency(workspaceRoot, scope, remote) {
3720
+ const packagePath = node_path.join(workspaceRoot, shellApp.directory, 'package.json');
3721
+ const shellPackage = readJsonFile(packagePath);
3722
+ shellPackage['zephyr:dependencies'] ??= {};
3723
+ shellPackage['zephyr:dependencies'][remoteDependencyAlias(remote)] = zephyrRemoteDependency(scope, remote);
3724
+ writeJsonFile(packagePath, shellPackage);
3725
+ }
3726
+ function addShellWorkspaceDependency(workspaceRoot, scope, remote) {
3727
+ if (!appHasEffectApi(remote)) return;
3728
+ const packagePath = node_path.join(workspaceRoot, shellApp.directory, 'package.json');
3729
+ const shellPackage = readJsonFile(packagePath);
3730
+ shellPackage.dependencies ??= {};
3731
+ shellPackage.dependencies[ultramodern_workspace_packageName(scope, remote.packageSuffix)] = WORKSPACE_PACKAGE_VERSION;
3732
+ writeJsonFile(packagePath, shellPackage);
3733
+ }
3734
+ function remoteTopologyEntry(scope, remote) {
3735
+ return {
3736
+ id: remote.id,
3737
+ kind: remote.kind,
3738
+ domain: remote.domain,
3739
+ package: ultramodern_workspace_packageName(scope, remote.packageSuffix),
3740
+ moduleFederation: {
3741
+ role: 'remote',
3742
+ name: remote.mfName,
3743
+ manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`,
3744
+ exposes: Object.keys(remote.exposes ?? {}),
3745
+ ssr: true,
3746
+ fallbackTelemetryEvent: 'modernjs:mv-runtime-parity',
3747
+ sharedContractVersion: 'mf-ssr-contract-v1'
3748
+ },
3749
+ ...effectApiTopologyMetadata(remote) ? {
3750
+ api: effectApiTopologyMetadata(remote)
3751
+ } : {},
3752
+ ownership: remote.ownership
3753
+ };
3754
+ }
3755
+ function ownershipEntry(scope, owner) {
3756
+ return {
3757
+ id: owner.id,
3758
+ package: ultramodern_workspace_packageName(scope, owner.packageSuffix),
3759
+ path: owner.directory,
3760
+ ownership: owner.ownership
3761
+ };
3762
+ }
3763
+ function remotesFromTopology(topology, ports) {
3764
+ return (topology.remotes ?? []).map((remote)=>{
3765
+ const effectApi = remote.api?.effect ? {
3766
+ stem: 'string' == typeof remote.api.effect.basePath ? remote.api.effect.basePath.split('/').filter(Boolean).at(-1) ?? remote.domain ?? String(remote.id).replace(/^remote-/, '') : remote.domain ?? String(remote.id).replace(/^remote-/, ''),
3767
+ prefix: remote.api.effect.bff?.prefix ?? `/${remote.domain ?? String(remote.id).replace(/^remote-/, '')}-api`,
3768
+ consumedBy: Array.isArray(remote.api.effect.consumedBy) ? remote.api.effect.consumedBy : [
3769
+ shellApp.id,
3770
+ remote.id
3771
+ ]
3772
+ } : void 0;
3773
+ return {
3774
+ id: remote.id,
3775
+ directory: '',
3776
+ packageSuffix: remote.package?.split('/').at(-1) ?? remote.id,
3777
+ displayName: remote.id,
3778
+ kind: remote.kind ?? 'vertical',
3779
+ domain: remote.domain ?? String(remote.id).replace(/^remote-/, ''),
3780
+ portEnv: '',
3781
+ port: 'number' == typeof ports[remote.id] ? ports[remote.id] : 0,
3782
+ mfName: remote.moduleFederation?.name ?? `remote${toPascalCase(remote.id)}`,
3783
+ ...effectApi ? {
3784
+ effectApi
3785
+ } : {},
3786
+ ownership: remote.ownership ?? createNeutralOwnership(remote.id)
3787
+ };
3788
+ });
3789
+ }
3790
+ function addUltramodernMicroVertical(options) {
3791
+ const name = assertValidMicroVerticalName(options.name);
3792
+ const rootPackage = readJsonFile(node_path.join(options.workspaceRoot, 'package.json'));
3793
+ const scope = toPackageScope(String(rootPackage.name ?? node_path.basename(options.workspaceRoot)));
3794
+ const topologyPath = node_path.join(options.workspaceRoot, 'topology/reference-topology.json');
3795
+ const ownershipPath = node_path.join(options.workspaceRoot, 'topology/ownership.json');
3796
+ const overlayPath = node_path.join(options.workspaceRoot, 'topology/local-overlays/development.json');
3797
+ for (const requiredPath of [
3798
+ topologyPath,
3799
+ ownershipPath,
3800
+ overlayPath
3801
+ ])if (!node_fs.existsSync(requiredPath)) throw new Error(`Missing UltraModern workspace file: ${requiredPath}`);
3802
+ const topology = readJsonFile(topologyPath);
3803
+ const ownership = readJsonFile(ownershipPath);
3804
+ const overlay = readJsonFile(overlayPath);
3805
+ overlay.ports ??= {};
3806
+ const packageSource = existingPackageSource(options.workspaceRoot, options.modernVersion, options.packageSource);
3807
+ const enableTailwind = false !== options.enableTailwind;
3808
+ const port = nextAvailablePort(overlay.ports);
3809
+ if ('remote' === options.kind || 'horizontal-remote' === options.kind) {
3810
+ const remote = createRemoteDescriptor(name, options.kind, port);
3811
+ assertCanCreate(options.workspaceRoot, remote.directory);
3812
+ if ((topology.remotes ?? []).some((entry)=>entry.id === remote.id)) throw new Error(`Topology already contains ${remote.id}`);
3813
+ if (Object.values(overlay.ports).includes(remote.port)) throw new Error(`Development port ${remote.port} is already in use`);
3814
+ writeApp(options.workspaceRoot, scope, remote, packageSource, enableTailwind);
3815
+ topology.shell ??= {};
3816
+ topology.shell.remoteRefs ??= [];
3817
+ topology.shell.remoteRefs.push(remote.id);
3818
+ topology.shell.moduleFederation ??= {};
3819
+ topology.shell.moduleFederation.remotes ??= [];
3820
+ topology.shell.moduleFederation.remotes.push({
3821
+ id: remote.id,
3822
+ name: remote.mfName,
3823
+ manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`
3824
+ });
3825
+ topology.remotes ??= [];
3826
+ topology.remotes.push(remoteTopologyEntry(scope, remote));
3827
+ ownership.owners ??= [];
3828
+ ownership.owners.push(ownershipEntry(scope, remote));
3829
+ overlay.ports[remote.id] = remote.port;
3830
+ overlay.manifests ??= {};
3831
+ overlay.manifests[remote.id] = `http://localhost:${remote.port}/mf-manifest.json`;
3832
+ if (appHasEffectApi(remote)) {
3833
+ overlay.apis ??= {};
3834
+ overlay.apis[remote.id] = `http://localhost:${remote.port}${effectApiPrefix(remote)}`;
3835
+ }
3836
+ writeJsonFile(topologyPath, topology);
3837
+ writeJsonFile(ownershipPath, ownership);
3838
+ writeJsonFile(overlayPath, overlay);
3839
+ writeJsonFile(node_path.join(options.workspaceRoot, GENERATED_CONTRACT_PATH), createGeneratedContract(scope, [
3840
+ shellApp,
3841
+ ...remotesFromTopology(topology, overlay.ports)
3842
+ ], enableTailwind));
3843
+ const shellConfigPath = node_path.join(options.workspaceRoot, `${shellApp.directory}/module-federation.config.ts`);
3844
+ writeFileReplacing(options.workspaceRoot, `${shellApp.directory}/module-federation.config.ts`, createShellModuleFederationConfig(remotesFromTopology(topology, overlay.ports)));
3845
+ if (!node_fs.existsSync(shellConfigPath)) throw new Error('Shell Module Federation config was not regenerated');
3846
+ addShellZephyrDependency(options.workspaceRoot, scope, remote);
3847
+ addShellWorkspaceDependency(options.workspaceRoot, scope, remote);
3848
+ addRootDevScript(options.workspaceRoot, scope, remote.packageSuffix, name);
3849
+ return;
3850
+ }
3851
+ if ('service' === options.kind) {
3852
+ const service = createServiceDescriptor(name, port);
3853
+ assertCanCreate(options.workspaceRoot, service.directory);
3854
+ if ((topology.effectServices ?? []).some((entry)=>entry.id === service.id)) throw new Error(`Topology already contains ${service.id}`);
3855
+ writeEffectService(options.workspaceRoot, scope, packageSource, enableTailwind, service);
3856
+ appendEffectSharedApiContract(options.workspaceRoot, service);
3857
+ topology.effectServices ??= [];
3858
+ topology.effectServices.push({
3859
+ id: service.id,
3860
+ kind: 'effect-service',
3861
+ runtime: 'effect',
3862
+ package: ultramodern_workspace_packageName(scope, service.packageSuffix),
3863
+ consumedBy: [
3864
+ shellApp.id
3865
+ ],
3866
+ bff: {
3867
+ prefix: serviceApiPrefix(service),
3868
+ openapi: '/openapi.json'
3869
+ },
3870
+ contract: {
3871
+ package: ultramodern_workspace_packageName(scope, 'shared-effect-api'),
3872
+ export: serviceEffectApiExport(service),
3873
+ path: 'packages/shared-effect-api/src/index.ts'
3874
+ },
3875
+ serverEntry: `${service.directory}/api/effect/index.ts`,
3876
+ basePath: `${serviceApiPrefix(service)}/effect/${effectApiStem(service)}`,
3877
+ ...createEffectOperationContract(service),
3878
+ ownership: service.ownership
3879
+ });
3880
+ ownership.owners ??= [];
3881
+ ownership.owners.push(ownershipEntry(scope, service));
3882
+ overlay.ports[service.id] = service.port;
3883
+ overlay.services ??= {};
3884
+ overlay.services[service.id] = `http://localhost:${service.port}${serviceApiPrefix(service)}`;
3885
+ writeJsonFile(topologyPath, topology);
3886
+ writeJsonFile(ownershipPath, ownership);
3887
+ writeJsonFile(overlayPath, overlay);
3888
+ addRootDevScript(options.workspaceRoot, scope, service.packageSuffix, name);
3889
+ return;
3890
+ }
3891
+ if ('shared' === options.kind) {
3892
+ const sharedPackage = createSharedPackageDescriptor(name);
3893
+ assertCanCreate(options.workspaceRoot, sharedPackage.directory);
3894
+ if ((topology.sharedPackages ?? []).some((entry)=>entry.id === sharedPackage.id)) throw new Error(`Topology already contains ${sharedPackage.id}`);
3895
+ writeGenericSharedPackage(options.workspaceRoot, scope, packageSource, sharedPackage);
3896
+ topology.sharedPackages ??= [];
3897
+ topology.sharedPackages.push({
3898
+ id: sharedPackage.id,
3899
+ package: ultramodern_workspace_packageName(scope, sharedPackage.id),
3900
+ path: sharedPackage.directory,
3901
+ description: sharedPackage.description
3902
+ });
3903
+ ownership.owners ??= [];
3904
+ ownership.owners.push(ownershipEntry(scope, {
3905
+ id: sharedPackage.id,
3906
+ packageSuffix: sharedPackage.id,
3907
+ directory: sharedPackage.directory,
3908
+ ownership: createNeutralOwnership(sharedPackage.id, 'tier-1-shared-contract')
3909
+ }));
3910
+ writeJsonFile(topologyPath, topology);
3911
+ writeJsonFile(ownershipPath, ownership);
3912
+ return;
3913
+ }
3914
+ throw new Error(`Unsupported MicroVertical kind: ${options.kind}`);
1653
3915
  }
1654
3916
  function generateUltramodernWorkspace(options) {
1655
3917
  const scope = toPackageScope(options.packageName);
1656
3918
  const packageSource = resolvePackageSource(options);
3919
+ const enableTailwind = false !== options.enableTailwind;
1657
3920
  node_fs.mkdirSync(options.targetDir, {
1658
3921
  recursive: true
1659
3922
  });
1660
3923
  copyRootTemplate(options.targetDir, {
1661
3924
  packageName: options.packageName,
1662
- packageScope: scope
3925
+ packageScope: scope,
3926
+ pnpmVersion: PNPM_VERSION,
3927
+ tailwindEnabled: String(enableTailwind)
1663
3928
  });
1664
3929
  writeJson(options.targetDir, 'package.json', createRootPackageJson(scope, packageSource));
1665
- writeJson(options.targetDir, 'tsconfig.base.json', createTsConfigBase(scope));
3930
+ writeJson(options.targetDir, 'tsconfig.base.json', createTsConfigBase());
1666
3931
  writeJson(options.targetDir, 'topology/reference-topology.json', createTopology(scope));
1667
3932
  writeJson(options.targetDir, 'topology/ownership.json', createOwnership(scope));
1668
3933
  writeJson(options.targetDir, 'topology/local-overlays/development.json', createDevelopmentOverlay());
1669
3934
  writeJson(options.targetDir, '.modernjs/ultramodern-workspace-template-manifest.json', createTemplateManifest(options.modernVersion, packageSource));
1670
3935
  writeJson(options.targetDir, '.modernjs/ultramodern-package-source.json', createPackageSourceMetadata(scope, packageSource));
1671
- writeApp(options.targetDir, scope, shellApp, packageSource);
1672
- for (const remote of remoteApps)writeApp(options.targetDir, scope, remote, packageSource);
1673
- writeEffectService(options.targetDir, scope, packageSource);
1674
- writeSharedPackages(options.targetDir, scope);
3936
+ writeJson(options.targetDir, GENERATED_CONTRACT_PATH, createGeneratedContract(scope, [
3937
+ shellApp,
3938
+ ...remoteApps
3939
+ ], enableTailwind));
3940
+ writeApp(options.targetDir, scope, shellApp, packageSource, enableTailwind);
3941
+ for (const remote of remoteApps)writeApp(options.targetDir, scope, remote, packageSource, enableTailwind);
3942
+ writeSharedPackages(options.targetDir, scope, packageSource);
1675
3943
  }
1676
3944
  const src_dirname = node_path.dirname(fileURLToPath(import.meta.url));
1677
3945
  const templateDir = node_path.resolve(src_dirname, '..', 'template');
@@ -1681,9 +3949,12 @@ const sha1Pattern = /^[0-9a-f]{40}$/;
1681
3949
  const sha256Pattern = /^[0-9a-f]{64}$/;
1682
3950
  const templateIdPattern = /^[a-z0-9][a-z0-9._-]*$/;
1683
3951
  const packageNamePattern = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/;
3952
+ const src_TANSTACK_ROUTER_VERSION = '1.170.8';
3953
+ const src_TAILWIND_VERSION = '4.3.0';
3954
+ const src_TAILWIND_POSTCSS_VERSION = '4.3.0';
3955
+ const src_PNPM_VERSION = '11.4.0';
1684
3956
  const requiredDeniedPaths = [
1685
3957
  '.git/**',
1686
- '.github/**',
1687
3958
  '.npmrc',
1688
3959
  '.yarnrc',
1689
3960
  '.env',
@@ -1726,8 +3997,8 @@ function detectRouterFramework() {
1726
3997
  '--router',
1727
3998
  '-r'
1728
3999
  ]);
1729
- if (!routerValue || 'react-router' === routerValue) return 'react-router';
1730
- if ('tanstack' === routerValue) return 'tanstack';
4000
+ if (!routerValue || 'tanstack' === routerValue) return 'tanstack';
4001
+ if ('react-router' === routerValue) return 'react-router';
1731
4002
  console.error(i18n.t(localeKeys.error.invalidRouter, {
1732
4003
  router: routerValue
1733
4004
  }));
@@ -1746,7 +4017,7 @@ function detectBffRuntime() {
1746
4017
  process.exit(1);
1747
4018
  }
1748
4019
  function src_renderTemplate(template, data) {
1749
- const tagRegex = /\{\{(#if|#unless|\/if|\/unless)(?:\s+(\w+))?\}\}/g;
4020
+ const tagRegex = /\{\{(~?)(#if|#unless|\/if|\/unless)(?:\s+(\w+))?(~?)\}\}/g;
1750
4021
  function renderConditionals(startIndex, expectedClose) {
1751
4022
  let rendered = '';
1752
4023
  let cursor = startIndex;
@@ -1757,7 +4028,7 @@ function src_renderTemplate(template, data) {
1757
4028
  rendered: rendered + template.slice(cursor),
1758
4029
  nextIndex: template.length
1759
4030
  };
1760
- const [raw, tag, condition] = match;
4031
+ const [raw, , tag, condition, rightTrim] = match;
1761
4032
  const tagIndex = match.index;
1762
4033
  rendered += template.slice(cursor, tagIndex);
1763
4034
  cursor = tagIndex + raw.length;
@@ -1773,10 +4044,17 @@ function src_renderTemplate(template, data) {
1773
4044
  }
1774
4045
  if ('/if' === tag || '/unless' === tag) {
1775
4046
  const kind = '/if' === tag ? 'if' : 'unless';
1776
- if (expectedClose === kind) return {
1777
- rendered,
1778
- nextIndex: cursor
1779
- };
4047
+ if (expectedClose === kind) {
4048
+ let nextIndex = cursor;
4049
+ if ('~' === rightTrim) {
4050
+ const trailingWhitespace = /^\s*/u.exec(template.slice(nextIndex));
4051
+ nextIndex += trailingWhitespace?.[0].length ?? 0;
4052
+ }
4053
+ return {
4054
+ rendered,
4055
+ nextIndex
4056
+ };
4057
+ }
1780
4058
  rendered += raw;
1781
4059
  }
1782
4060
  }
@@ -1857,20 +4135,29 @@ function createBuiltinTemplateManifest(version) {
1857
4135
  materialization: {
1858
4136
  targetRoot: 'generated-project-root',
1859
4137
  allowedPaths: [
4138
+ '.agents/**',
1860
4139
  '.browserslistrc',
4140
+ '.github/**',
1861
4141
  '.gitignore',
4142
+ '.mise.toml',
1862
4143
  '.modernjs/**',
1863
4144
  '.nvmrc',
4145
+ 'AGENTS.md',
1864
4146
  'README.md',
1865
4147
  'api/**',
1866
- 'biome.json',
4148
+ 'config/**',
1867
4149
  'modern.config.ts',
4150
+ 'oxfmt.config.ts',
4151
+ 'oxlint.config.ts',
1868
4152
  'package.json',
4153
+ 'pnpm-workspace.yaml',
1869
4154
  'postcss.config.mjs',
4155
+ 'rstest.config.mts',
1870
4156
  "scripts/**",
1871
4157
  'shared/**',
1872
4158
  'src/**',
1873
4159
  'tailwind.config.ts',
4160
+ 'tests/**',
1874
4161
  'tsconfig.json'
1875
4162
  ],
1876
4163
  deniedPaths: requiredDeniedPaths,
@@ -1899,11 +4186,17 @@ function createBuiltinTemplateManifest(version) {
1899
4186
  postMaterializationValidation: [
1900
4187
  'ultramodern-contract-check',
1901
4188
  'dependency-install-with-lifecycle-deny',
4189
+ 'github-workflow-security-enforced',
4190
+ 'package-source-retained',
4191
+ 'pnpm-11-policy-enforced',
4192
+ 'rstest-smoke-tests',
1902
4193
  'template-manifest-retained'
1903
4194
  ],
1904
4195
  expectedCommands: [
1905
- "pnpm install --ignore-scripts",
1906
- 'pnpm run ultramodern:check'
4196
+ 'mise install',
4197
+ 'mise exec -- pnpm install',
4198
+ 'mise exec -- pnpm test',
4199
+ 'mise exec -- pnpm run ultramodern:check'
1907
4200
  ]
1908
4201
  }
1909
4202
  };
@@ -2008,9 +4301,19 @@ function writeTemplateManifestEvidence(targetDir, manifest) {
2008
4301
  });
2009
4302
  node_fs.writeFileSync(evidencePath, `${JSON.stringify(manifest, null, 2)}\n`);
2010
4303
  }
2011
- function showVersion() {
4304
+ function readCreatePackageJson() {
2012
4305
  const createPackageJson = node_path.resolve(src_dirname, '..', 'package.json');
2013
- const createPackage = JSON.parse(node_fs.readFileSync(createPackageJson, 'utf-8'));
4306
+ return JSON.parse(node_fs.readFileSync(createPackageJson, 'utf-8'));
4307
+ }
4308
+ function isBleedingDevCreatePackage(createPackage) {
4309
+ return '@bleedingdev/modern-js-create' === createPackage.name;
4310
+ }
4311
+ function getBleedingDevFrameworkVersion(createPackage, fallbackVersion) {
4312
+ const frameworkVersion = createPackage.ultramodern?.frameworkVersion;
4313
+ return 'string' == typeof frameworkVersion && frameworkVersion.length > 0 ? frameworkVersion : fallbackVersion;
4314
+ }
4315
+ function showVersion() {
4316
+ const createPackage = readCreatePackageJson();
2014
4317
  const version = createPackage.version || 'unknown';
2015
4318
  console.log(i18n.t(localeKeys.version.message, {
2016
4319
  version
@@ -2037,6 +4340,7 @@ function showHelp() {
2037
4340
  if (localeKeys.help.optionUltramodernPackageSource) console.log(i18n.t(localeKeys.help.optionUltramodernPackageSource));
2038
4341
  if (localeKeys.help.optionUltramodernPackageScope) console.log(i18n.t(localeKeys.help.optionUltramodernPackageScope));
2039
4342
  if (localeKeys.help.optionUltramodernPackageNamePrefix) console.log(i18n.t(localeKeys.help.optionUltramodernPackageNamePrefix));
4343
+ if (localeKeys.help.optionMicroVertical) console.log(i18n.t(localeKeys.help.optionMicroVertical));
2040
4344
  console.log(i18n.t(localeKeys.help.optionSub));
2041
4345
  console.log('');
2042
4346
  console.log(i18n.t(localeKeys.help.examples));
@@ -2050,6 +4354,7 @@ function showHelp() {
2050
4354
  if (localeKeys.help.example8) console.log(i18n.t(localeKeys.help.example8));
2051
4355
  if (localeKeys.help.example9) console.log(i18n.t(localeKeys.help.example9));
2052
4356
  if (localeKeys.help.example10) console.log(i18n.t(localeKeys.help.example10));
4357
+ if (localeKeys.help.example11) console.log(i18n.t(localeKeys.help.example11));
2053
4358
  console.log('');
2054
4359
  console.log(i18n.t(localeKeys.help.moreInfo));
2055
4360
  console.log('');
@@ -2075,20 +4380,30 @@ function detectSubprojectFlag() {
2075
4380
  }
2076
4381
  function detectTailwindFlag() {
2077
4382
  const args = process.argv.slice(2);
2078
- return args.includes('--tailwind');
4383
+ return !args.includes('--no-tailwind');
2079
4384
  }
2080
4385
  function detectWorkspaceProtocolFlag() {
2081
4386
  const args = process.argv.slice(2);
2082
4387
  return args.includes('--workspace');
2083
4388
  }
2084
- function detectUltramodernWorkspaceFlag() {
4389
+ function detectMicroVerticalKind() {
4390
+ const kind = getOptionValue(process.argv.slice(2), [
4391
+ '--microvertical'
4392
+ ]);
4393
+ if (!kind) return;
4394
+ if ('remote' === kind || 'horizontal-remote' === kind || 'service' === kind || 'shared' === kind) return kind;
4395
+ console.error('--microvertical must be one of: remote, horizontal-remote, service, shared');
4396
+ process.exit(1);
4397
+ }
4398
+ function detectUltramodernWorkspaceFlag(createPackage) {
2085
4399
  const args = process.argv.slice(2);
2086
- return args.includes(ULTRAMODERN_WORKSPACE_FLAG);
4400
+ return args.includes(ULTRAMODERN_WORKSPACE_FLAG) || isBleedingDevCreatePackage(createPackage);
2087
4401
  }
2088
- function detectUltramodernPackageSource(args, modernVersion) {
4402
+ function detectUltramodernPackageSource(args, defaultPackageVersion, createPackage) {
4403
+ const bleedingDevDefaults = isBleedingDevCreatePackage(createPackage);
2089
4404
  const strategy = getOptionValue(args, [
2090
4405
  '--ultramodern-package-source'
2091
- ]) ?? 'workspace';
4406
+ ]) ?? (bleedingDevDefaults ? 'install' : 'workspace');
2092
4407
  if ('workspace' !== strategy && 'install' !== strategy) {
2093
4408
  console.error('--ultramodern-package-source must be "workspace" or "install"');
2094
4409
  process.exit(1);
@@ -2097,18 +4412,68 @@ function detectUltramodernPackageSource(args, modernVersion) {
2097
4412
  strategy,
2098
4413
  modernPackageVersion: getOptionValue(args, [
2099
4414
  '--ultramodern-package-version'
2100
- ]) ?? modernVersion,
4415
+ ]) ?? defaultPackageVersion,
2101
4416
  registry: getOptionValue(args, [
2102
4417
  '--ultramodern-package-registry'
2103
4418
  ]),
2104
4419
  aliasScope: getOptionValue(args, [
2105
4420
  '--ultramodern-package-scope'
2106
- ]),
4421
+ ]) ?? (bleedingDevDefaults && 'install' === strategy ? 'bleedingdev' : void 0),
2107
4422
  aliasPackageNamePrefix: getOptionValue(args, [
2108
4423
  '--ultramodern-package-name-prefix'
2109
4424
  ]) ?? 'modern-js-'
2110
4425
  };
2111
4426
  }
4427
+ function src_modernAliasPackageName(packageName, packageSource) {
4428
+ if (!packageSource.aliasScope) return packageName;
4429
+ const scope = packageSource.aliasScope.replace(/^@/, '');
4430
+ const unscopedName = packageName.split('/').at(-1);
4431
+ return `@${scope}/${packageSource.aliasPackageNamePrefix ?? ''}${unscopedName}`;
4432
+ }
4433
+ function singleAppModernPackageSpecifier(packageName, packageSource, useWorkspaceProtocol) {
4434
+ if (useWorkspaceProtocol) return 'workspace:*';
4435
+ if ('install' !== packageSource.strategy || !packageSource.aliasScope) return packageSource.modernPackageVersion;
4436
+ return `npm:${src_modernAliasPackageName(packageName, packageSource)}@${packageSource.modernPackageVersion}`;
4437
+ }
4438
+ const singleAppModernPackages = [
4439
+ '@modern-js/runtime',
4440
+ '@modern-js/app-tools',
4441
+ '@modern-js/tsconfig',
4442
+ '@modern-js/plugin-i18n',
4443
+ '@modern-js/plugin-tanstack',
4444
+ '@modern-js/plugin-bff',
4445
+ '@modern-js/adapter-rstest'
4446
+ ];
4447
+ function createSingleAppPackageSourceEvidence(packageSource, useWorkspaceProtocol) {
4448
+ const strategy = useWorkspaceProtocol ? 'workspace' : 'install';
4449
+ const specifier = useWorkspaceProtocol ? 'workspace:*' : packageSource.modernPackageVersion;
4450
+ const aliases = 'install' === strategy && packageSource.aliasScope ? Object.fromEntries(singleAppModernPackages.map((packageName)=>[
4451
+ packageName,
4452
+ src_modernAliasPackageName(packageName, packageSource)
4453
+ ])) : void 0;
4454
+ return {
4455
+ schemaVersion: 1,
4456
+ preset: 'presetUltramodern',
4457
+ strategy,
4458
+ modernPackages: {
4459
+ specifier,
4460
+ packages: singleAppModernPackages,
4461
+ ...packageSource.registry ? {
4462
+ registry: packageSource.registry
4463
+ } : {},
4464
+ ...aliases ? {
4465
+ aliases
4466
+ } : {}
4467
+ }
4468
+ };
4469
+ }
4470
+ function writeSingleAppPackageSourceEvidence(targetDir, packageSource, useWorkspaceProtocol) {
4471
+ const evidencePath = node_path.join(targetDir, '.modernjs', 'ultramodern-package-source.json');
4472
+ node_fs.mkdirSync(node_path.dirname(evidencePath), {
4473
+ recursive: true
4474
+ });
4475
+ node_fs.writeFileSync(evidencePath, `${JSON.stringify(createSingleAppPackageSourceEvidence(packageSource, useWorkspaceProtocol), null, 2)}\n`);
4476
+ }
2112
4477
  function isDirectoryEmpty(dirPath) {
2113
4478
  if (!node_fs.existsSync(dirPath)) return false;
2114
4479
  try {
@@ -2130,7 +4495,8 @@ async function getProjectName() {
2130
4495
  '--ultramodern-package-version',
2131
4496
  '--ultramodern-package-registry',
2132
4497
  '--ultramodern-package-scope',
2133
- '--ultramodern-package-name-prefix'
4498
+ '--ultramodern-package-name-prefix',
4499
+ '--microvertical'
2134
4500
  ]);
2135
4501
  const optionWithoutValue = new Set([
2136
4502
  '--help',
@@ -2143,6 +4509,7 @@ async function getProjectName() {
2143
4509
  '--tanstack',
2144
4510
  '--bff',
2145
4511
  '--tailwind',
4512
+ '--no-tailwind',
2146
4513
  '--workspace',
2147
4514
  ULTRAMODERN_WORKSPACE_FLAG
2148
4515
  ]);
@@ -2154,7 +4521,7 @@ async function getProjectName() {
2154
4521
  i += 1;
2155
4522
  continue;
2156
4523
  }
2157
- if (!(arg.startsWith('--lang=') || arg.startsWith('--router=') || arg.startsWith('--bff-runtime=') || arg.startsWith('--ultramodern-package-source=') || arg.startsWith('--ultramodern-package-version=') || arg.startsWith('--ultramodern-package-registry=') || arg.startsWith('--ultramodern-package-scope=') || arg.startsWith('--ultramodern-package-name-prefix='))) positionalArgs.push(arg);
4524
+ if (!(arg.startsWith('--lang=') || arg.startsWith('--router=') || arg.startsWith('--bff-runtime=') || arg.startsWith('--ultramodern-package-source=') || arg.startsWith('--ultramodern-package-version=') || arg.startsWith('--ultramodern-package-registry=') || arg.startsWith('--ultramodern-package-scope=') || arg.startsWith('--ultramodern-package-name-prefix=') || arg.startsWith('--microvertical='))) positionalArgs.push(arg);
2158
4525
  }
2159
4526
  }
2160
4527
  const projectNameArg = positionalArgs[0];
@@ -2185,6 +4552,27 @@ async function main() {
2185
4552
  const { name: projectName, useCurrentDir } = await getProjectName();
2186
4553
  const targetDir = useCurrentDir ? process.cwd() : node_path.isAbsolute(projectName) ? projectName : node_path.resolve(process.cwd(), projectName);
2187
4554
  const generatedPackageName = useCurrentDir || node_path.isAbsolute(projectName) ? node_path.basename(targetDir) : projectName;
4555
+ const createPackage = readCreatePackageJson();
4556
+ const version = createPackage.version || 'latest';
4557
+ const ultramodernPackageVersion = isBleedingDevCreatePackage(createPackage) ? getBleedingDevFrameworkVersion(createPackage, version) : version;
4558
+ const microVerticalKind = detectMicroVerticalKind();
4559
+ if (microVerticalKind) {
4560
+ const overridePackageSource = args.some((arg)=>arg.startsWith('--ultramodern-package-')) ? detectUltramodernPackageSource(args, ultramodernPackageVersion, createPackage) : void 0;
4561
+ addUltramodernMicroVertical({
4562
+ workspaceRoot: process.cwd(),
4563
+ name: generatedPackageName,
4564
+ kind: microVerticalKind,
4565
+ modernVersion: version,
4566
+ enableTailwind: detectTailwindFlag(),
4567
+ packageSource: overridePackageSource
4568
+ });
4569
+ const dim = '\x1b[2m\x1b[3m';
4570
+ const reset = '\x1b[0m';
4571
+ console.log(`${i18n.t(localeKeys.message.success)}\n`);
4572
+ console.log(`${dim} mise install${reset}`);
4573
+ console.log(`${dim} mise exec -- pnpm ultramodern:check${reset}\n`);
4574
+ return;
4575
+ }
2188
4576
  if (node_fs.existsSync(targetDir)) {
2189
4577
  const files = node_fs.readdirSync(targetDir);
2190
4578
  if (files.length > 0) {
@@ -2194,16 +4582,14 @@ async function main() {
2194
4582
  process.exit(1);
2195
4583
  }
2196
4584
  }
2197
- const createPackageJson = node_path.resolve(src_dirname, '..', 'package.json');
2198
- const createPackage = JSON.parse(node_fs.readFileSync(createPackageJson, 'utf-8'));
2199
- const version = createPackage.version || 'latest';
2200
- const generateWorkspace = detectUltramodernWorkspaceFlag();
4585
+ const generateWorkspace = detectUltramodernWorkspaceFlag(createPackage);
2201
4586
  if (generateWorkspace) {
2202
4587
  generateUltramodernWorkspace({
2203
4588
  targetDir,
2204
4589
  packageName: generatedPackageName,
2205
4590
  modernVersion: version,
2206
- packageSource: detectUltramodernPackageSource(args, version)
4591
+ enableTailwind: detectTailwindFlag(),
4592
+ packageSource: detectUltramodernPackageSource(args, ultramodernPackageVersion, createPackage)
2207
4593
  });
2208
4594
  const dim = '\x1b[2m\x1b[3m';
2209
4595
  const reset = '\x1b[0m';
@@ -2212,8 +4598,9 @@ async function main() {
2212
4598
  if (!useCurrentDir) console.log(`${dim} ${i18n.t(localeKeys.message.step1, {
2213
4599
  projectName
2214
4600
  })}${reset}`);
4601
+ console.log(`${dim} mise install${reset}`);
2215
4602
  console.log(`${dim} ${i18n.t(localeKeys.message.step2)}${reset}`);
2216
- console.log(`${dim} pnpm ultramodern:check${reset}`);
4603
+ console.log(`${dim} mise exec -- pnpm ultramodern:check${reset}`);
2217
4604
  console.log(`${dim} ${i18n.t(localeKeys.message.step3)}${reset}\n`);
2218
4605
  return;
2219
4606
  }
@@ -2223,12 +4610,23 @@ async function main() {
2223
4610
  const bffRuntime = detectBffRuntime();
2224
4611
  const enableTailwind = detectTailwindFlag();
2225
4612
  const useWorkspaceProtocol = detectWorkspaceProtocolFlag();
2226
- const dependencyVersion = useWorkspaceProtocol ? 'workspace:*' : version;
4613
+ const packageSource = detectUltramodernPackageSource(args, ultramodernPackageVersion, createPackage);
2227
4614
  const templateManifest = createBuiltinTemplateManifest(version);
2228
4615
  validateTemplateManifest(templateManifest);
2229
4616
  copyTemplate(templateDir, targetDir, {
2230
4617
  packageName: generatedPackageName,
2231
- version: dependencyVersion,
4618
+ version: useWorkspaceProtocol ? 'workspace:*' : packageSource.modernPackageVersion,
4619
+ runtimeVersion: singleAppModernPackageSpecifier('@modern-js/runtime', packageSource, useWorkspaceProtocol),
4620
+ appToolsVersion: singleAppModernPackageSpecifier('@modern-js/app-tools', packageSource, useWorkspaceProtocol),
4621
+ adapterRstestVersion: singleAppModernPackageSpecifier('@modern-js/adapter-rstest', packageSource, useWorkspaceProtocol),
4622
+ tsconfigVersion: singleAppModernPackageSpecifier('@modern-js/tsconfig', packageSource, useWorkspaceProtocol),
4623
+ pluginTanstackVersion: singleAppModernPackageSpecifier('@modern-js/plugin-tanstack', packageSource, useWorkspaceProtocol),
4624
+ pluginBffVersion: singleAppModernPackageSpecifier('@modern-js/plugin-bff', packageSource, useWorkspaceProtocol),
4625
+ pluginI18nVersion: singleAppModernPackageSpecifier('@modern-js/plugin-i18n', packageSource, useWorkspaceProtocol),
4626
+ tanstackRouterVersion: src_TANSTACK_ROUTER_VERSION,
4627
+ tailwindVersion: src_TAILWIND_VERSION,
4628
+ tailwindPostcssVersion: src_TAILWIND_POSTCSS_VERSION,
4629
+ pnpmVersion: src_PNPM_VERSION,
2232
4630
  isSubproject,
2233
4631
  routerFramework,
2234
4632
  bffRuntime,
@@ -2238,21 +4636,37 @@ async function main() {
2238
4636
  const targetPackageJson = node_path.join(targetDir, 'package.json');
2239
4637
  const packageJson = JSON.parse(node_fs.readFileSync(targetPackageJson, 'utf-8'));
2240
4638
  packageJson.name = generatedPackageName;
4639
+ packageJson.modernjs = {
4640
+ ...packageJson.modernjs ?? {},
4641
+ preset: 'presetUltramodern',
4642
+ packageSource: {
4643
+ strategy: useWorkspaceProtocol ? 'workspace' : 'install',
4644
+ config: './.modernjs/ultramodern-package-source.json'
4645
+ }
4646
+ };
2241
4647
  if (isSubproject) {
2242
4648
  delete packageJson['lint-staged'];
2243
4649
  delete packageJson['simple-git-hooks'];
2244
4650
  if (packageJson.scripts) {
2245
4651
  delete packageJson.scripts.prepare;
4652
+ delete packageJson.scripts.format;
4653
+ delete packageJson.scripts['format:check'];
2246
4654
  delete packageJson.scripts.lint;
4655
+ delete packageJson.scripts['lint:fix'];
4656
+ delete packageJson.scripts['skills:install'];
4657
+ delete packageJson.scripts['skills:check'];
2247
4658
  }
2248
4659
  if (packageJson.devDependencies) {
2249
4660
  delete packageJson.devDependencies['lint-staged'];
2250
4661
  delete packageJson.devDependencies['simple-git-hooks'];
2251
- delete packageJson.devDependencies['@biomejs/biome'];
4662
+ delete packageJson.devDependencies.oxlint;
4663
+ delete packageJson.devDependencies.oxfmt;
4664
+ delete packageJson.devDependencies.ultracite;
2252
4665
  }
2253
4666
  }
2254
4667
  node_fs.writeFileSync(targetPackageJson, `${JSON.stringify(packageJson, null, 2)}\n`);
2255
4668
  writeTemplateManifestEvidence(targetDir, templateManifest);
4669
+ writeSingleAppPackageSourceEvidence(targetDir, packageSource, useWorkspaceProtocol);
2256
4670
  const dim = '\x1b[2m\x1b[3m';
2257
4671
  const reset = '\x1b[0m';
2258
4672
  console.log(`${i18n.t(localeKeys.message.success)}\n`);
@@ -2268,10 +4682,14 @@ function copyTemplate(src, dest, options) {
2268
4682
  recursive: true
2269
4683
  });
2270
4684
  const excludeInSubproject = [
4685
+ '.agents',
4686
+ '.github',
2271
4687
  '.gitignore.handlebars',
2272
- 'biome.json',
4688
+ 'AGENTS.md',
2273
4689
  '.npmrc',
2274
- '.nvmrc'
4690
+ '.nvmrc',
4691
+ 'oxfmt.config.ts',
4692
+ 'oxlint.config.ts'
2275
4693
  ];
2276
4694
  function copyRecursive(srcDir, destDir) {
2277
4695
  const entries = node_fs.readdirSync(srcDir, {
@@ -2293,6 +4711,17 @@ function copyTemplate(src, dest, options) {
2293
4711
  const rendered = src_renderTemplate(templateContent, {
2294
4712
  packageName: options.packageName,
2295
4713
  version: options.version,
4714
+ runtimeVersion: options.runtimeVersion,
4715
+ appToolsVersion: options.appToolsVersion,
4716
+ adapterRstestVersion: options.adapterRstestVersion,
4717
+ tsconfigVersion: options.tsconfigVersion,
4718
+ pluginTanstackVersion: options.pluginTanstackVersion,
4719
+ pluginBffVersion: options.pluginBffVersion,
4720
+ pluginI18nVersion: options.pluginI18nVersion,
4721
+ tanstackRouterVersion: options.tanstackRouterVersion,
4722
+ tailwindVersion: options.tailwindVersion,
4723
+ tailwindPostcssVersion: options.tailwindPostcssVersion,
4724
+ pnpmVersion: options.pnpmVersion,
2296
4725
  isSubproject: options.isSubproject,
2297
4726
  isTanstackRouter: 'tanstack' === options.routerFramework,
2298
4727
  enableBff: 'none' !== options.bffRuntime,
@@ -2300,7 +4729,7 @@ function copyTemplate(src, dest, options) {
2300
4729
  useHonoBff: 'hono' === options.bffRuntime,
2301
4730
  bffRuntime: options.bffRuntime,
2302
4731
  enableTailwind: options.enableTailwind,
2303
- routerImportPath: 'tanstack' === options.routerFramework ? 'tanstack-router' : 'router'
4732
+ routerRuntimeImport: 'tanstack' === options.routerFramework ? '@modern-js/plugin-tanstack/runtime' : '@modern-js/runtime/router'
2304
4733
  });
2305
4734
  if (0 === rendered.trim().length) continue;
2306
4735
  destPath = destPath.replace(/\.handlebars$/, '');