@bleedingdev/modern-js-create 3.2.0-ultramodern.57 → 3.2.0-ultramodern.59
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.
- package/README.md +30 -39
- package/dist/index.js +670 -702
- package/dist/types/locale/en.d.ts +1 -1
- package/dist/types/locale/zh.d.ts +1 -1
- package/dist/types/ultramodern-workspace.d.ts +2 -4
- package/package.json +3 -3
- package/template/.codex/hooks.json +16 -0
- package/template/AGENTS.md +4 -8
- package/template/README.md +40 -43
- package/template/lefthook.yml +15 -0
- package/template/package.json.handlebars +5 -15
- package/template/pnpm-workspace.yaml +1 -1
- package/template/scripts/bootstrap-agent-skills.mjs +39 -7
- package/template/scripts/validate-ultramodern.mjs.handlebars +18 -8
- package/template/src/routes/[lang]/page.tsx.handlebars +1 -1
- package/template-workspace/.codex/hooks.json +16 -0
- package/template-workspace/AGENTS.md +4 -2
- package/template-workspace/README.md.handlebars +10 -11
- package/template-workspace/lefthook.yml +15 -0
- package/template-workspace/pnpm-workspace.yaml +2 -3
- package/template-workspace/scripts/assert-mf-types.mjs +3 -3
- package/template-workspace/scripts/setup-agent-reference-repos.mjs +6 -2
- package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +106 -103
package/dist/index.js
CHANGED
|
@@ -250,8 +250,8 @@ ListCache.prototype.get = _listCacheGet;
|
|
|
250
250
|
ListCache.prototype.has = _listCacheHas;
|
|
251
251
|
ListCache.prototype.set = _listCacheSet;
|
|
252
252
|
const _ListCache = ListCache;
|
|
253
|
-
var
|
|
254
|
-
const _Map =
|
|
253
|
+
var _Map_Map = _getNative(_root, 'Map');
|
|
254
|
+
const _Map = _Map_Map;
|
|
255
255
|
function mapCacheClear() {
|
|
256
256
|
this.size = 0;
|
|
257
257
|
this.__data__ = {
|
|
@@ -453,8 +453,8 @@ const EN_LOCALE = {
|
|
|
453
453
|
success: '✨ Created successfully!',
|
|
454
454
|
nextSteps: '📋 Next steps:',
|
|
455
455
|
step1: 'cd {projectName}',
|
|
456
|
-
step2: '
|
|
457
|
-
step3: '
|
|
456
|
+
step2: 'pnpm install',
|
|
457
|
+
step3: 'pnpm dev'
|
|
458
458
|
},
|
|
459
459
|
help: {
|
|
460
460
|
title: '🚀 Modern.js Project Creator',
|
|
@@ -474,7 +474,7 @@ const EN_LOCALE = {
|
|
|
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
|
-
|
|
477
|
+
optionVertical: ' --vertical Add a full-stack vertical to an existing UltraModern workspace',
|
|
478
478
|
optionSub: ' -s, --sub Mark as a subproject (package in monorepo)',
|
|
479
479
|
examples: '💡 Examples:',
|
|
480
480
|
example1: ' create my-app',
|
|
@@ -487,7 +487,7 @@ const EN_LOCALE = {
|
|
|
487
487
|
example8: ' create my-app --router tanstack --bff-runtime effect',
|
|
488
488
|
example9: ' create my-app --router tanstack --bff-runtime effect --workspace',
|
|
489
489
|
example10: ' pnpm dlx @bleedingdev/modern-js-create my-super-app',
|
|
490
|
-
example11: ' create catalog --
|
|
490
|
+
example11: ' create catalog --vertical',
|
|
491
491
|
moreInfo: '📚 Learn more: https://modernjs.dev'
|
|
492
492
|
},
|
|
493
493
|
version: {
|
|
@@ -510,8 +510,8 @@ const ZH_LOCALE = {
|
|
|
510
510
|
success: '✨ 创建成功!',
|
|
511
511
|
nextSteps: '📋 下一步:',
|
|
512
512
|
step1: 'cd {projectName}',
|
|
513
|
-
step2: '
|
|
514
|
-
step3: '
|
|
513
|
+
step2: 'pnpm install',
|
|
514
|
+
step3: 'pnpm dev'
|
|
515
515
|
},
|
|
516
516
|
help: {
|
|
517
517
|
title: '🚀 Modern.js 项目创建工具',
|
|
@@ -531,7 +531,7 @@ const ZH_LOCALE = {
|
|
|
531
531
|
optionUltramodernPackageSource: ' --ultramodern-package-source 选择 UltraModern 依赖来源(workspace 或 install)',
|
|
532
532
|
optionUltramodernPackageScope: ' --ultramodern-package-scope npm alias 安装使用的发布 scope(例如 bleedingdev)',
|
|
533
533
|
optionUltramodernPackageNamePrefix: ' --ultramodern-package-name-prefix npm alias 包名前缀(默认:modern-js-)',
|
|
534
|
-
|
|
534
|
+
optionVertical: ' --vertical 向现有 UltraModern 工作区添加全栈 Vertical',
|
|
535
535
|
optionSub: ' -s, --sub 标记为子项目(monorepo 中的子包)',
|
|
536
536
|
examples: '💡 示例:',
|
|
537
537
|
example1: ' create my-app',
|
|
@@ -544,7 +544,7 @@ const ZH_LOCALE = {
|
|
|
544
544
|
example8: ' create my-app --router tanstack --bff-runtime effect',
|
|
545
545
|
example9: ' create my-app --router tanstack --bff-runtime effect --workspace',
|
|
546
546
|
example10: ' create my-super-app --ultramodern-workspace --ultramodern-package-source install --ultramodern-package-scope bleedingdev',
|
|
547
|
-
example11: ' create catalog --
|
|
547
|
+
example11: ' create catalog --vertical',
|
|
548
548
|
moreInfo: '📚 更多信息: https://modernjs.dev'
|
|
549
549
|
},
|
|
550
550
|
version: {
|
|
@@ -571,11 +571,12 @@ const TYPESCRIPT_NATIVE_PREVIEW_VERSION = '7.0.0-dev.20260527.2';
|
|
|
571
571
|
const OXLINT_VERSION = '1.66.0';
|
|
572
572
|
const OXFMT_VERSION = '0.51.0';
|
|
573
573
|
const ULTRACITE_VERSION = '7.7.0';
|
|
574
|
+
const LEFTHOOK_VERSION = '^2.1.9';
|
|
574
575
|
const I18NEXT_VERSION = '26.2.0';
|
|
575
576
|
const REACT_VERSION = '^19.2.6';
|
|
576
577
|
const REACT_DOM_VERSION = '^19.2.6';
|
|
577
578
|
const REACT_ROUTER_DOM_VERSION = '7.16.0';
|
|
578
|
-
const PNPM_VERSION = '11.
|
|
579
|
+
const PNPM_VERSION = '11.5.0';
|
|
579
580
|
const WORKSPACE_PACKAGE_VERSION = 'workspace:*';
|
|
580
581
|
const GENERATED_CONTRACT_PATH = '.modernjs/ultramodern-generated-contract.json';
|
|
581
582
|
const RSTACK_AGENT_SKILLS_COMMIT = '61c948b42512e223bad44b83af4080eba48b2677';
|
|
@@ -620,10 +621,10 @@ const shellApp = {
|
|
|
620
621
|
portEnv: 'SHELL_SUPER_APP_PORT',
|
|
621
622
|
port: 3020,
|
|
622
623
|
mfName: 'shellSuperApp',
|
|
623
|
-
|
|
624
|
-
'
|
|
625
|
-
'
|
|
626
|
-
'
|
|
624
|
+
verticalRefs: [
|
|
625
|
+
'explore',
|
|
626
|
+
'decide',
|
|
627
|
+
'checkout'
|
|
627
628
|
],
|
|
628
629
|
ownership: {
|
|
629
630
|
team: 'super-app-platform',
|
|
@@ -640,22 +641,22 @@ const shellApp = {
|
|
|
640
641
|
}
|
|
641
642
|
}
|
|
642
643
|
};
|
|
643
|
-
const
|
|
644
|
+
const verticalApps = [
|
|
644
645
|
{
|
|
645
|
-
id: '
|
|
646
|
-
directory: '
|
|
647
|
-
packageSuffix: '
|
|
648
|
-
displayName: 'Explore
|
|
646
|
+
id: 'explore',
|
|
647
|
+
directory: 'verticals/explore',
|
|
648
|
+
packageSuffix: 'explore',
|
|
649
|
+
displayName: 'Explore Vertical',
|
|
649
650
|
kind: 'vertical',
|
|
650
651
|
domain: 'explore',
|
|
651
|
-
portEnv: '
|
|
652
|
+
portEnv: 'VERTICAL_EXPLORE_PORT',
|
|
652
653
|
port: 3021,
|
|
653
|
-
mfName: '
|
|
654
|
+
mfName: 'verticalExplore',
|
|
654
655
|
exposes: {
|
|
655
656
|
'./Footer': './src/components/footer.tsx',
|
|
656
657
|
'./Header': './src/components/header.tsx',
|
|
657
658
|
'./Recommendations': './src/components/recommendations.tsx',
|
|
658
|
-
'./Route': './src/
|
|
659
|
+
'./Route': './src/federation-entry.tsx',
|
|
659
660
|
'./StorePicker': './src/components/store-picker.tsx'
|
|
660
661
|
},
|
|
661
662
|
effectApi: {
|
|
@@ -663,15 +664,15 @@ const remoteApps = [
|
|
|
663
664
|
prefix: '/explore-api',
|
|
664
665
|
consumedBy: [
|
|
665
666
|
shellApp.id,
|
|
666
|
-
'
|
|
667
|
+
'explore'
|
|
667
668
|
]
|
|
668
669
|
},
|
|
669
670
|
ownership: {
|
|
670
671
|
team: 'tractor-explore',
|
|
671
672
|
slack: '#tractor-explore',
|
|
672
673
|
pagerDuty: 'pd-tractor-explore',
|
|
673
|
-
runbookRef: 'runbooks/wave2/
|
|
674
|
-
adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#
|
|
674
|
+
runbookRef: 'runbooks/wave2/explore.md',
|
|
675
|
+
adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#explore',
|
|
675
676
|
blastRadius: {
|
|
676
677
|
tier: 'tier-1-tractor-discovery',
|
|
677
678
|
references: [
|
|
@@ -682,37 +683,37 @@ const remoteApps = [
|
|
|
682
683
|
}
|
|
683
684
|
},
|
|
684
685
|
{
|
|
685
|
-
id: '
|
|
686
|
-
directory: '
|
|
687
|
-
packageSuffix: '
|
|
688
|
-
displayName: 'Decide
|
|
686
|
+
id: 'decide',
|
|
687
|
+
directory: 'verticals/decide',
|
|
688
|
+
packageSuffix: 'decide',
|
|
689
|
+
displayName: 'Decide Vertical',
|
|
689
690
|
kind: 'vertical',
|
|
690
691
|
domain: 'decide',
|
|
691
|
-
portEnv: '
|
|
692
|
+
portEnv: 'VERTICAL_DECIDE_PORT',
|
|
692
693
|
port: 3022,
|
|
693
|
-
mfName: '
|
|
694
|
-
|
|
695
|
-
'
|
|
696
|
-
'
|
|
694
|
+
mfName: 'verticalDecide',
|
|
695
|
+
verticalRefs: [
|
|
696
|
+
'explore',
|
|
697
|
+
'checkout'
|
|
697
698
|
],
|
|
698
699
|
exposes: {
|
|
699
700
|
'./ProductPage': './src/components/product-page.tsx',
|
|
700
|
-
'./Route': './src/
|
|
701
|
+
'./Route': './src/federation-entry.tsx'
|
|
701
702
|
},
|
|
702
703
|
effectApi: {
|
|
703
704
|
stem: 'decide',
|
|
704
705
|
prefix: '/decide-api',
|
|
705
706
|
consumedBy: [
|
|
706
707
|
shellApp.id,
|
|
707
|
-
'
|
|
708
|
+
'decide'
|
|
708
709
|
]
|
|
709
710
|
},
|
|
710
711
|
ownership: {
|
|
711
712
|
team: 'tractor-decide',
|
|
712
713
|
slack: '#tractor-decide',
|
|
713
714
|
pagerDuty: 'pd-tractor-decide',
|
|
714
|
-
runbookRef: 'runbooks/wave2/
|
|
715
|
-
adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#
|
|
715
|
+
runbookRef: 'runbooks/wave2/decide.md',
|
|
716
|
+
adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#decide',
|
|
716
717
|
blastRadius: {
|
|
717
718
|
tier: 'tier-1-tractor-configuration',
|
|
718
719
|
references: [
|
|
@@ -723,21 +724,21 @@ const remoteApps = [
|
|
|
723
724
|
}
|
|
724
725
|
},
|
|
725
726
|
{
|
|
726
|
-
id: '
|
|
727
|
-
directory: '
|
|
728
|
-
packageSuffix: '
|
|
729
|
-
displayName: 'Checkout
|
|
727
|
+
id: 'checkout',
|
|
728
|
+
directory: 'verticals/checkout',
|
|
729
|
+
packageSuffix: 'checkout',
|
|
730
|
+
displayName: 'Checkout Vertical',
|
|
730
731
|
kind: 'vertical',
|
|
731
732
|
domain: 'checkout',
|
|
732
|
-
portEnv: '
|
|
733
|
+
portEnv: 'VERTICAL_CHECKOUT_PORT',
|
|
733
734
|
port: 3023,
|
|
734
|
-
mfName: '
|
|
735
|
+
mfName: 'verticalCheckout',
|
|
735
736
|
exposes: {
|
|
736
737
|
'./AddToCart': './src/components/add-to-cart.tsx',
|
|
737
738
|
'./CartPage': './src/components/cart-page.tsx',
|
|
738
739
|
'./CheckoutPage': './src/components/checkout-page.tsx',
|
|
739
740
|
'./MiniCart': './src/components/mini-cart.tsx',
|
|
740
|
-
'./Route': './src/
|
|
741
|
+
'./Route': './src/federation-entry.tsx',
|
|
741
742
|
'./ThanksPage': './src/components/thanks-page.tsx'
|
|
742
743
|
},
|
|
743
744
|
effectApi: {
|
|
@@ -745,15 +746,15 @@ const remoteApps = [
|
|
|
745
746
|
prefix: '/checkout-api',
|
|
746
747
|
consumedBy: [
|
|
747
748
|
shellApp.id,
|
|
748
|
-
'
|
|
749
|
+
'checkout'
|
|
749
750
|
]
|
|
750
751
|
},
|
|
751
752
|
ownership: {
|
|
752
753
|
team: 'tractor-checkout',
|
|
753
754
|
slack: '#tractor-checkout',
|
|
754
755
|
pagerDuty: 'pd-tractor-checkout',
|
|
755
|
-
runbookRef: 'runbooks/wave2/
|
|
756
|
-
adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#
|
|
756
|
+
runbookRef: 'runbooks/wave2/checkout.md',
|
|
757
|
+
adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#checkout',
|
|
757
758
|
blastRadius: {
|
|
758
759
|
tier: 'tier-1-tractor-purchase',
|
|
759
760
|
references: [
|
|
@@ -764,27 +765,6 @@ const remoteApps = [
|
|
|
764
765
|
}
|
|
765
766
|
}
|
|
766
767
|
];
|
|
767
|
-
const effectService = {
|
|
768
|
-
id: 'service-recommendations-effect',
|
|
769
|
-
directory: 'services/service-recommendations-effect',
|
|
770
|
-
packageSuffix: 'service-recommendations-effect',
|
|
771
|
-
portEnv: 'SERVICE_RECOMMENDATIONS_PORT',
|
|
772
|
-
port: 3030,
|
|
773
|
-
ownership: {
|
|
774
|
-
team: 'personalization-platform',
|
|
775
|
-
slack: '#personalization-platform',
|
|
776
|
-
pagerDuty: 'pd-personalization-platform',
|
|
777
|
-
runbookRef: 'runbooks/wave2/service-recommendations-effect.md',
|
|
778
|
-
adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#service-recommendations-effect',
|
|
779
|
-
blastRadius: {
|
|
780
|
-
tier: 'tier-2-personalization',
|
|
781
|
-
references: [
|
|
782
|
-
'docs/super-app-rfc-adr/wave2/blast-radius.md#recommendations',
|
|
783
|
-
'docs/super-app-rfc-adr/wave2/rollback.md#effect-service-lkg'
|
|
784
|
-
]
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
};
|
|
788
768
|
const effectDiagnostics = [
|
|
789
769
|
'anyUnknownInErrorContext',
|
|
790
770
|
'classSelfMismatch',
|
|
@@ -869,21 +849,21 @@ const sharedPackages = [
|
|
|
869
849
|
{
|
|
870
850
|
id: 'shared-design-tokens',
|
|
871
851
|
directory: 'packages/shared-design-tokens',
|
|
872
|
-
description: 'Design token placeholders consumed by shell and
|
|
852
|
+
description: 'Design token placeholders consumed by shell and verticals.'
|
|
873
853
|
},
|
|
874
854
|
{
|
|
875
855
|
id: 'shared-effect-api',
|
|
876
856
|
directory: 'packages/shared-effect-api',
|
|
877
|
-
description: 'Shared Effect API type placeholders for
|
|
857
|
+
description: 'Shared Effect API type placeholders for vertical clients.'
|
|
878
858
|
}
|
|
879
859
|
];
|
|
880
|
-
function createNeutralOwnership(id, tier = 'tier-2-
|
|
860
|
+
function createNeutralOwnership(id, tier = 'tier-2-vertical') {
|
|
881
861
|
return {
|
|
882
862
|
team: 'super-app-platform',
|
|
883
863
|
slack: '#super-app-platform',
|
|
884
864
|
pagerDuty: 'pd-super-app-platform',
|
|
885
|
-
runbookRef: `runbooks/
|
|
886
|
-
adrRef: `docs/super-app-rfc-adr/
|
|
865
|
+
runbookRef: `runbooks/verticals/${id}.md`,
|
|
866
|
+
adrRef: `docs/super-app-rfc-adr/verticals.md#${id}`,
|
|
887
867
|
blastRadius: {
|
|
888
868
|
tier,
|
|
889
869
|
references: [
|
|
@@ -892,49 +872,35 @@ function createNeutralOwnership(id, tier = 'tier-2-microvertical') {
|
|
|
892
872
|
}
|
|
893
873
|
};
|
|
894
874
|
}
|
|
895
|
-
function
|
|
875
|
+
function createVerticalDescriptor(name, port) {
|
|
896
876
|
const domain = toKebabCase(name);
|
|
897
|
-
const id =
|
|
877
|
+
const id = domain;
|
|
898
878
|
const displayPrefix = toPascalCase(domain).replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
899
879
|
return {
|
|
900
880
|
id,
|
|
901
|
-
directory: `
|
|
902
|
-
packageSuffix:
|
|
903
|
-
displayName: `${displayPrefix}
|
|
904
|
-
kind: '
|
|
881
|
+
directory: `verticals/${domain}`,
|
|
882
|
+
packageSuffix: domain,
|
|
883
|
+
displayName: `${displayPrefix} Vertical`,
|
|
884
|
+
kind: 'vertical',
|
|
905
885
|
domain,
|
|
906
|
-
portEnv: `
|
|
886
|
+
portEnv: `VERTICAL_${toEnvSegment(domain)}_PORT`,
|
|
907
887
|
port,
|
|
908
|
-
mfName: `
|
|
888
|
+
mfName: `vertical${toPascalCase(domain)}`,
|
|
909
889
|
exposes: {
|
|
910
|
-
'./Route': './src/
|
|
890
|
+
'./Route': './src/federation-entry.tsx',
|
|
911
891
|
'./Widget': `./src/components/${domain}-widget.tsx`
|
|
912
892
|
},
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
}
|
|
922
|
-
} : {},
|
|
893
|
+
effectApi: {
|
|
894
|
+
stem: domain,
|
|
895
|
+
prefix: `/${domain}-api`,
|
|
896
|
+
consumedBy: [
|
|
897
|
+
shellApp.id,
|
|
898
|
+
id
|
|
899
|
+
]
|
|
900
|
+
},
|
|
923
901
|
ownership: createNeutralOwnership(id)
|
|
924
902
|
};
|
|
925
903
|
}
|
|
926
|
-
function createServiceDescriptor(name, port) {
|
|
927
|
-
const normalized = toKebabCase(name);
|
|
928
|
-
const suffix = normalized.endsWith('-effect') ? normalized : `service-${normalized.replace(/^service-/, '')}-effect`;
|
|
929
|
-
return {
|
|
930
|
-
id: suffix,
|
|
931
|
-
directory: `services/${suffix}`,
|
|
932
|
-
packageSuffix: suffix,
|
|
933
|
-
portEnv: `${toEnvSegment(suffix)}_PORT`,
|
|
934
|
-
port,
|
|
935
|
-
ownership: createNeutralOwnership(suffix, 'tier-2-effect-service')
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
904
|
function serviceApiPrefix(service) {
|
|
939
905
|
const name = service.id.replace(/^service-/, '').replace(/-effect$/, '');
|
|
940
906
|
return name.endsWith('-api') ? `/${name}` : `/${name}-api`;
|
|
@@ -948,18 +914,9 @@ function effectApiPrefix(target) {
|
|
|
948
914
|
function effectApiStem(target) {
|
|
949
915
|
return target.effectApi?.stem ?? target.id.replace(/^service-/, '').replace(/-effect$/, '').replace(/-api$/, '');
|
|
950
916
|
}
|
|
951
|
-
function verticalEffectApps(remotes =
|
|
917
|
+
function verticalEffectApps(remotes = verticalApps) {
|
|
952
918
|
return remotes.filter(appHasEffectApi);
|
|
953
919
|
}
|
|
954
|
-
function createSharedPackageDescriptor(name) {
|
|
955
|
-
const normalized = toKebabCase(name);
|
|
956
|
-
const id = normalized.startsWith('shared-') ? normalized : `shared-${normalized}`;
|
|
957
|
-
return {
|
|
958
|
-
id,
|
|
959
|
-
directory: `packages/${id}`,
|
|
960
|
-
description: `Shared ${normalized.replace(/^shared-/, '')} package placeholder.`
|
|
961
|
-
};
|
|
962
|
-
}
|
|
963
920
|
function normalizePath(filePath) {
|
|
964
921
|
return filePath.split(node_path.sep).join('/');
|
|
965
922
|
}
|
|
@@ -1125,19 +1082,19 @@ function createRootPackageJson(scope, packageSource) {
|
|
|
1125
1082
|
type: 'module',
|
|
1126
1083
|
packageManager: `pnpm@${PNPM_VERSION}`,
|
|
1127
1084
|
scripts: {
|
|
1128
|
-
dev: `pnpm --parallel --filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)} --filter ${ultramodern_workspace_packageName(scope, '
|
|
1085
|
+
dev: `pnpm --parallel --filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)} --filter ${ultramodern_workspace_packageName(scope, 'explore')} --filter ${ultramodern_workspace_packageName(scope, 'decide')} --filter ${ultramodern_workspace_packageName(scope, 'checkout')} dev`,
|
|
1129
1086
|
'dev:shell': `pnpm --filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)} dev`,
|
|
1130
|
-
'dev:explore': `pnpm --filter ${ultramodern_workspace_packageName(scope, '
|
|
1131
|
-
'dev:decide': `pnpm --filter ${ultramodern_workspace_packageName(scope, '
|
|
1132
|
-
'dev:checkout': `pnpm --filter ${ultramodern_workspace_packageName(scope, '
|
|
1133
|
-
build: 'pnpm -r --filter "./
|
|
1087
|
+
'dev:explore': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'explore')} dev`,
|
|
1088
|
+
'dev:decide': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'decide')} dev`,
|
|
1089
|
+
'dev:checkout': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'checkout')} dev`,
|
|
1090
|
+
build: 'pnpm -r --filter "./verticals/*" run build && pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types',
|
|
1134
1091
|
format: 'oxfmt .',
|
|
1135
1092
|
'format:check': 'oxfmt --check .',
|
|
1136
1093
|
lint: 'oxlint .',
|
|
1137
1094
|
'lint:fix': 'oxlint . --fix',
|
|
1138
1095
|
typecheck: `pnpm -r --filter "@${scope}/*" typecheck`,
|
|
1139
|
-
'cloudflare:build': 'pnpm -r --filter "./
|
|
1140
|
-
'cloudflare:deploy': 'pnpm -r --filter "./
|
|
1096
|
+
'cloudflare:build': 'pnpm -r --filter "./verticals/*" run cloudflare:build && pnpm --filter "./apps/shell-super-app" run cloudflare:build && pnpm ultramodern:assert-mf-types',
|
|
1097
|
+
'cloudflare:deploy': 'pnpm -r --filter "./verticals/*" run cloudflare:deploy && pnpm --filter "./apps/shell-super-app" run cloudflare:deploy',
|
|
1141
1098
|
'cloudflare:proof': "node ./scripts/proof-cloudflare-version.mjs --out .codex/reports/cloudflare-version-proof/public-url-proof.json",
|
|
1142
1099
|
'skills:install': "node ./scripts/bootstrap-agent-skills.mjs",
|
|
1143
1100
|
'skills:check': "node ./scripts/bootstrap-agent-skills.mjs --check",
|
|
@@ -1145,17 +1102,16 @@ function createRootPackageJson(scope, packageSource) {
|
|
|
1145
1102
|
'agents:refs:check': "node ./scripts/setup-agent-reference-repos.mjs --check",
|
|
1146
1103
|
'ultramodern:assert-mf-types': "node ./scripts/assert-mf-types.mjs",
|
|
1147
1104
|
'ultramodern:check': "node ./scripts/validate-ultramodern-workspace.mjs",
|
|
1148
|
-
postinstall: "node ./scripts/
|
|
1105
|
+
postinstall: "node ./scripts/bootstrap-agent-skills.mjs && (git rev-parse --is-inside-work-tree >/dev/null 2>&1 && lefthook install || true) && node ./scripts/setup-agent-reference-repos.mjs",
|
|
1149
1106
|
check: 'pnpm format:check && pnpm lint && pnpm typecheck && pnpm skills:check && pnpm ultramodern:check'
|
|
1150
1107
|
},
|
|
1151
1108
|
engines: {
|
|
1152
1109
|
node: '>=20',
|
|
1153
|
-
pnpm: `>=${PNPM_VERSION} <11.
|
|
1110
|
+
pnpm: `>=${PNPM_VERSION} <11.6.0`
|
|
1154
1111
|
},
|
|
1155
1112
|
workspaces: [
|
|
1156
1113
|
'apps/*',
|
|
1157
|
-
'
|
|
1158
|
-
'services/*',
|
|
1114
|
+
'verticals/*',
|
|
1159
1115
|
'packages/*'
|
|
1160
1116
|
],
|
|
1161
1117
|
modernjs: {
|
|
@@ -1171,6 +1127,7 @@ function createRootPackageJson(scope, packageSource) {
|
|
|
1171
1127
|
devDependencies: {
|
|
1172
1128
|
'@effect/tsgo': EFFECT_TSGO_VERSION,
|
|
1173
1129
|
"@typescript/native-preview": TYPESCRIPT_NATIVE_PREVIEW_VERSION,
|
|
1130
|
+
lefthook: LEFTHOOK_VERSION,
|
|
1174
1131
|
oxlint: OXLINT_VERSION,
|
|
1175
1132
|
oxfmt: OXFMT_VERSION,
|
|
1176
1133
|
ultracite: ULTRACITE_VERSION,
|
|
@@ -1185,11 +1142,11 @@ function remoteDependencyAlias(remote) {
|
|
|
1185
1142
|
function zephyrRemoteDependency(scope, remote) {
|
|
1186
1143
|
return `${ultramodern_workspace_packageName(scope, remote.packageSuffix)}@workspace:*`;
|
|
1187
1144
|
}
|
|
1188
|
-
function resolveRemoteRefs(app, remotes =
|
|
1189
|
-
const
|
|
1190
|
-
return
|
|
1145
|
+
function resolveRemoteRefs(app, remotes = verticalApps) {
|
|
1146
|
+
const verticalRefs = app.verticalRefs ?? [];
|
|
1147
|
+
return verticalRefs.map((remoteRef)=>remotes.find((remote)=>remote.id === remoteRef)).filter((remote)=>void 0 !== remote);
|
|
1191
1148
|
}
|
|
1192
|
-
function createModuleFederationRemoteContracts(app, remotes =
|
|
1149
|
+
function createModuleFederationRemoteContracts(app, remotes = verticalApps) {
|
|
1193
1150
|
return resolveRemoteRefs(app, remotes).map((remote)=>({
|
|
1194
1151
|
id: remote.id,
|
|
1195
1152
|
alias: remoteDependencyAlias(remote),
|
|
@@ -1198,8 +1155,8 @@ function createModuleFederationRemoteContracts(app, remotes = remoteApps) {
|
|
|
1198
1155
|
manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`
|
|
1199
1156
|
}));
|
|
1200
1157
|
}
|
|
1201
|
-
function createZephyrDependencies(scope, app, remotes =
|
|
1202
|
-
if (!app.
|
|
1158
|
+
function createZephyrDependencies(scope, app, remotes = verticalApps) {
|
|
1159
|
+
if (!app.verticalRefs?.length) return {};
|
|
1203
1160
|
return Object.fromEntries(resolveRemoteRefs(app, remotes).map((remote)=>[
|
|
1204
1161
|
remoteDependencyAlias(remote),
|
|
1205
1162
|
zephyrRemoteDependency(scope, remote)
|
|
@@ -1317,9 +1274,9 @@ function createAppPackage(scope, app, packageSource, enableTailwind) {
|
|
|
1317
1274
|
scripts: {
|
|
1318
1275
|
dev: 'modern dev',
|
|
1319
1276
|
build: app.exposes ? `modern build && node ${relativeRootFor(app.directory)}/scripts/assert-mf-types.mjs` : 'modern build',
|
|
1320
|
-
'cloudflare:build': 'MODERNJS_DEPLOY=cloudflare modern build && MODERNJS_DEPLOY=cloudflare modern deploy',
|
|
1321
|
-
'cloudflare:deploy': '
|
|
1322
|
-
'cloudflare:preview': '
|
|
1277
|
+
'cloudflare:build': 'ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern build && ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy',
|
|
1278
|
+
'cloudflare:deploy': 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true pnpm run cloudflare:build && wrangler deploy --config .output/wrangler.json',
|
|
1279
|
+
'cloudflare:preview': 'pnpm run cloudflare:build && wrangler dev --config .output/wrangler.json',
|
|
1323
1280
|
'cloudflare:proof': `node ${relativeRootFor(app.directory)}/scripts/proof-cloudflare-version.mjs --app ${app.id}`,
|
|
1324
1281
|
serve: 'modern serve',
|
|
1325
1282
|
typecheck: effectTsgoTypecheckCommand
|
|
@@ -1347,46 +1304,6 @@ function createAppPackage(scope, app, packageSource, enableTailwind) {
|
|
|
1347
1304
|
if (Object.keys(packageExports).length > 0) packageJson.exports = packageExports;
|
|
1348
1305
|
return packageJson;
|
|
1349
1306
|
}
|
|
1350
|
-
function createServicePackage(scope, packageSource, enableTailwind, service = effectService) {
|
|
1351
|
-
return {
|
|
1352
|
-
private: true,
|
|
1353
|
-
name: ultramodern_workspace_packageName(scope, service.packageSuffix),
|
|
1354
|
-
version: '0.1.0',
|
|
1355
|
-
scripts: {
|
|
1356
|
-
dev: 'modern dev',
|
|
1357
|
-
build: 'modern build',
|
|
1358
|
-
serve: 'modern serve',
|
|
1359
|
-
typecheck: effectTsgoTypecheckCommand
|
|
1360
|
-
},
|
|
1361
|
-
modernjs: {
|
|
1362
|
-
preset: 'presetUltramodern',
|
|
1363
|
-
role: 'effect-service',
|
|
1364
|
-
appId: service.id,
|
|
1365
|
-
topology: `${relativeRootFor(service.directory)}/topology/reference-topology.json`
|
|
1366
|
-
},
|
|
1367
|
-
dependencies: {
|
|
1368
|
-
'@modern-js/runtime': modernPackageSpecifier('@modern-js/runtime', packageSource),
|
|
1369
|
-
[ultramodern_workspace_packageName(scope, 'shared-effect-api')]: WORKSPACE_PACKAGE_VERSION,
|
|
1370
|
-
react: REACT_VERSION,
|
|
1371
|
-
'react-dom': REACT_DOM_VERSION
|
|
1372
|
-
},
|
|
1373
|
-
devDependencies: {
|
|
1374
|
-
'@modern-js/app-tools': modernPackageSpecifier('@modern-js/app-tools', packageSource),
|
|
1375
|
-
'@modern-js/plugin-bff': modernPackageSpecifier('@modern-js/plugin-bff', packageSource),
|
|
1376
|
-
'@effect/tsgo': EFFECT_TSGO_VERSION,
|
|
1377
|
-
...enableTailwind ? {
|
|
1378
|
-
'@tailwindcss/postcss': `^${TAILWIND_POSTCSS_VERSION}`,
|
|
1379
|
-
postcss: '^8.5.6',
|
|
1380
|
-
tailwindcss: `^${TAILWIND_VERSION}`
|
|
1381
|
-
} : {},
|
|
1382
|
-
"@typescript/native-preview": TYPESCRIPT_NATIVE_PREVIEW_VERSION,
|
|
1383
|
-
'@types/node': '^20',
|
|
1384
|
-
'@types/react': '^19.1.8',
|
|
1385
|
-
'@types/react-dom': '^19.1.6',
|
|
1386
|
-
typescript: TYPESCRIPT_VERSION
|
|
1387
|
-
}
|
|
1388
|
-
};
|
|
1389
|
-
}
|
|
1390
1307
|
function createSharedPackage(scope, id, description, packageSource) {
|
|
1391
1308
|
const packageJson = {
|
|
1392
1309
|
private: true,
|
|
@@ -1465,11 +1382,36 @@ const zephyrRspackPlugin = () => ({
|
|
|
1465
1382
|
const appId = '${app.id}';
|
|
1466
1383
|
const cloudflareWorkerName = '${createCloudflareWorkerName(scope, app)}';
|
|
1467
1384
|
const port = Number(process.env['${app.portEnv}'] ?? ${app.port});
|
|
1468
|
-
const
|
|
1469
|
-
const
|
|
1470
|
-
|
|
1385
|
+
const envValue = (name: string) => {
|
|
1386
|
+
const value = process.env[name]?.trim();
|
|
1387
|
+
return value !== undefined && value.length > 0 ? value : undefined;
|
|
1388
|
+
};
|
|
1389
|
+
const configuredSiteUrl = envValue('MODERN_PUBLIC_SITE_URL');
|
|
1390
|
+
const configuredCloudflareUrl = envValue('${createCloudflarePublicUrlEnv(app)}');
|
|
1391
|
+
const cloudflareWorkersDevSubdomain = envValue(
|
|
1392
|
+
'ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN',
|
|
1393
|
+
);
|
|
1394
|
+
const inferredCloudflareUrl =
|
|
1395
|
+
cloudflareDeployEnabled && cloudflareWorkersDevSubdomain !== undefined
|
|
1396
|
+
? \`https://\${cloudflareWorkerName}.\${cloudflareWorkersDevSubdomain}.workers.dev\`
|
|
1397
|
+
: undefined;
|
|
1471
1398
|
const siteUrl =
|
|
1472
|
-
|
|
1399
|
+
configuredCloudflareUrl ||
|
|
1400
|
+
configuredSiteUrl ||
|
|
1401
|
+
inferredCloudflareUrl ||
|
|
1402
|
+
\`http://localhost:\${port}\`;
|
|
1403
|
+
|
|
1404
|
+
if (
|
|
1405
|
+
cloudflareDeployEnabled &&
|
|
1406
|
+
process.env['ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS'] === 'true' &&
|
|
1407
|
+
configuredCloudflareUrl === undefined &&
|
|
1408
|
+
configuredSiteUrl === undefined &&
|
|
1409
|
+
inferredCloudflareUrl === undefined
|
|
1410
|
+
) {
|
|
1411
|
+
throw new Error(
|
|
1412
|
+
\`Cloudflare deploy for \${appId} needs ${createCloudflarePublicUrlEnv(app)}, MODERN_PUBLIC_SITE_URL, or ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN.\`,
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1473
1415
|
|
|
1474
1416
|
export default defineConfig(
|
|
1475
1417
|
presetUltramodern(
|
|
@@ -1619,14 +1561,59 @@ ${entries.map(([key, entryValue])=>` '${key}': '${entryValue}',`).join('\n')}
|
|
|
1619
1561
|
}`;
|
|
1620
1562
|
}
|
|
1621
1563
|
function createRemoteManifestEnv(remote) {
|
|
1622
|
-
return `
|
|
1564
|
+
return `VERTICAL_${toEnvSegment(remote.domain ?? remote.id)}_MF_MANIFEST`;
|
|
1565
|
+
}
|
|
1566
|
+
function createModuleFederationRemoteUrlHelpers(app, remotes = verticalApps) {
|
|
1567
|
+
if (0 === resolveRemoteRefs(app, remotes).length) return '';
|
|
1568
|
+
return `const cloudflareDeployEnabled =
|
|
1569
|
+
process.env['MODERNJS_DEPLOY'] === 'cloudflare';
|
|
1570
|
+
const cloudflareWorkersDevSubdomain =
|
|
1571
|
+
process.env['ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN']?.trim();
|
|
1572
|
+
const requireCloudflarePublicUrls =
|
|
1573
|
+
process.env['ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS'] === 'true';
|
|
1574
|
+
|
|
1575
|
+
function createRemoteManifestUrl(options: {
|
|
1576
|
+
manifestEnv: string;
|
|
1577
|
+
publicUrlEnv: string;
|
|
1578
|
+
workerName: string;
|
|
1579
|
+
mfName: string;
|
|
1580
|
+
port: number;
|
|
1581
|
+
}) {
|
|
1582
|
+
const configuredManifest = process.env[options.manifestEnv]?.trim();
|
|
1583
|
+
if (configuredManifest !== undefined && configuredManifest.length > 0) {
|
|
1584
|
+
return configuredManifest;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
const configuredPublicUrl = process.env[options.publicUrlEnv]?.trim();
|
|
1588
|
+
if (configuredPublicUrl !== undefined && configuredPublicUrl.length > 0) {
|
|
1589
|
+
return \`\${options.mfName}@\${configuredPublicUrl.replace(/\\/+$/u, '')}/mf-manifest.json\`;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
if (cloudflareDeployEnabled && cloudflareWorkersDevSubdomain !== undefined) {
|
|
1593
|
+
return \`\${options.mfName}@https://\${options.workerName}.\${cloudflareWorkersDevSubdomain}.workers.dev/mf-manifest.json\`;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
if (cloudflareDeployEnabled && requireCloudflarePublicUrls) {
|
|
1597
|
+
throw new Error(
|
|
1598
|
+
\`Cloudflare deploy needs \${options.publicUrlEnv}, \${options.manifestEnv}, or ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN for remote \${options.mfName}.\`,
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
return \`\${options.mfName}@http://localhost:\${options.port}/mf-manifest.json\`;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
`;
|
|
1623
1606
|
}
|
|
1624
|
-
function createModuleFederationRemotesConfig(app, remotes =
|
|
1607
|
+
function createModuleFederationRemotesConfig(scope, app, remotes = verticalApps) {
|
|
1625
1608
|
const remoteEntries = resolveRemoteRefs(app, remotes).map((remote)=>{
|
|
1626
1609
|
const key = remoteDependencyAlias(remote);
|
|
1627
|
-
return ` ${key}:
|
|
1628
|
-
|
|
1629
|
-
'${remote
|
|
1610
|
+
return ` ${key}: createRemoteManifestUrl({
|
|
1611
|
+
manifestEnv: '${createRemoteManifestEnv(remote)}',
|
|
1612
|
+
publicUrlEnv: '${createCloudflarePublicUrlEnv(remote)}',
|
|
1613
|
+
workerName: '${createCloudflareWorkerName(scope, remote)}',
|
|
1614
|
+
mfName: '${remote.mfName}',
|
|
1615
|
+
port: ${remote.port},
|
|
1616
|
+
}),`;
|
|
1630
1617
|
}).join('\n');
|
|
1631
1618
|
if (!remoteEntries) return '';
|
|
1632
1619
|
return ` remotes: {
|
|
@@ -1634,10 +1621,10 @@ ${remoteEntries}
|
|
|
1634
1621
|
},
|
|
1635
1622
|
`;
|
|
1636
1623
|
}
|
|
1637
|
-
function createShellModuleFederationConfig(remotes =
|
|
1624
|
+
function createShellModuleFederationConfig(scope, remotes = verticalApps) {
|
|
1638
1625
|
const shellHost = {
|
|
1639
1626
|
...shellApp,
|
|
1640
|
-
|
|
1627
|
+
verticalRefs: remotes.map((remote)=>remote.id)
|
|
1641
1628
|
};
|
|
1642
1629
|
return `// @effect-diagnostics nodeBuiltinImport:off processEnv:off
|
|
1643
1630
|
import { createRequire } from 'node:module';
|
|
@@ -1651,6 +1638,7 @@ const runtimeVersion = (require('@modern-js/runtime/package.json') as { version:
|
|
|
1651
1638
|
const reactVersion = (require('react/package.json') as { version: string }).version;
|
|
1652
1639
|
const reactDomVersion = (require('react-dom/package.json') as { version: string }).version;
|
|
1653
1640
|
|
|
1641
|
+
${createModuleFederationRemoteUrlHelpers(shellHost, remotes)}
|
|
1654
1642
|
export default createModuleFederationConfig({
|
|
1655
1643
|
treeShakingSharedExcludePlugins: ['RspackModuleFederationPlugin'],
|
|
1656
1644
|
dev: {
|
|
@@ -1664,7 +1652,7 @@ export default createModuleFederationConfig({
|
|
|
1664
1652
|
},
|
|
1665
1653
|
filename: 'remoteEntry.js',
|
|
1666
1654
|
name: '${shellApp.mfName}',
|
|
1667
|
-
${createModuleFederationRemotesConfig(shellHost, remotes)}${createSharedModuleFederationConfig()},
|
|
1655
|
+
${createModuleFederationRemotesConfig(scope, shellHost, remotes)}${createSharedModuleFederationConfig()},
|
|
1668
1656
|
});
|
|
1669
1657
|
`;
|
|
1670
1658
|
}
|
|
@@ -1691,7 +1679,7 @@ export const ultramodernApiMarker = {
|
|
|
1691
1679
|
} as const;
|
|
1692
1680
|
`;
|
|
1693
1681
|
}
|
|
1694
|
-
function createRemoteModuleFederationConfig(app, remotes =
|
|
1682
|
+
function createRemoteModuleFederationConfig(scope, app, remotes = verticalApps) {
|
|
1695
1683
|
const exposes = formatTsObjectLiteral(app.exposes ?? {});
|
|
1696
1684
|
return `// @effect-diagnostics nodeBuiltinImport:off
|
|
1697
1685
|
import { createRequire } from 'node:module';
|
|
@@ -1705,6 +1693,7 @@ const runtimeVersion = (require('@modern-js/runtime/package.json') as { version:
|
|
|
1705
1693
|
const reactVersion = (require('react/package.json') as { version: string }).version;
|
|
1706
1694
|
const reactDomVersion = (require('react-dom/package.json') as { version: string }).version;
|
|
1707
1695
|
|
|
1696
|
+
${createModuleFederationRemoteUrlHelpers(app, remotes)}
|
|
1708
1697
|
export default createModuleFederationConfig({
|
|
1709
1698
|
treeShakingSharedExcludePlugins: ['RspackModuleFederationPlugin'],
|
|
1710
1699
|
dev: {
|
|
@@ -1719,7 +1708,7 @@ export default createModuleFederationConfig({
|
|
|
1719
1708
|
exposes: ${exposes},
|
|
1720
1709
|
filename: 'remoteEntry.js',
|
|
1721
1710
|
name: '${app.mfName}',
|
|
1722
|
-
${createModuleFederationRemotesConfig(app, remotes)}${createSharedModuleFederationConfig()},
|
|
1711
|
+
${createModuleFederationRemotesConfig(scope, app, remotes)}${createSharedModuleFederationConfig()},
|
|
1723
1712
|
});
|
|
1724
1713
|
`;
|
|
1725
1714
|
}
|
|
@@ -1754,6 +1743,16 @@ function createRouteOwnedI18nPaths(app) {
|
|
|
1754
1743
|
},
|
|
1755
1744
|
titleKey: 'shell.routes.listing'
|
|
1756
1745
|
},
|
|
1746
|
+
{
|
|
1747
|
+
...base,
|
|
1748
|
+
canonicalPath: '/stores',
|
|
1749
|
+
id: 'shell-stores',
|
|
1750
|
+
localisedPaths: {
|
|
1751
|
+
cs: '/prodejci',
|
|
1752
|
+
en: '/stores'
|
|
1753
|
+
},
|
|
1754
|
+
titleKey: 'shell.routes.storePicker'
|
|
1755
|
+
},
|
|
1757
1756
|
{
|
|
1758
1757
|
...base,
|
|
1759
1758
|
canonicalPath: '/tractors/:slug',
|
|
@@ -1984,7 +1983,7 @@ function createRouteAliasPage(canonicalPath) {
|
|
|
1984
1983
|
return `export { default } from '${rootPageImport}';
|
|
1985
1984
|
`;
|
|
1986
1985
|
}
|
|
1987
|
-
function createAppEnvDts(app, remotes =
|
|
1986
|
+
function createAppEnvDts(app, remotes = verticalApps) {
|
|
1988
1987
|
const remoteModuleDeclarations = resolveRemoteRefs(app, remotes).flatMap((remote)=>Object.keys(remote.exposes ?? {}).filter((expose)=>'./Route' !== expose).map((expose)=>{
|
|
1989
1988
|
const moduleName = `${remoteDependencyAlias(remote)}/${expose.replace(/^\.\//u, '')}`;
|
|
1990
1989
|
return `declare module '${moduleName}' {
|
|
@@ -2002,46 +2001,11 @@ declare module '*.svg' {
|
|
|
2002
2001
|
}
|
|
2003
2002
|
${remoteModuleDeclarations ? `\n${remoteModuleDeclarations}` : ''}`;
|
|
2004
2003
|
}
|
|
2005
|
-
function createServiceModernConfigFor(service = effectService) {
|
|
2006
|
-
return `// @effect-diagnostics processEnv:off
|
|
2007
|
-
import { appTools, defineConfig, presetUltramodern } from '@modern-js/app-tools';
|
|
2008
|
-
import { bffPlugin } from '@modern-js/plugin-bff';
|
|
2009
|
-
|
|
2010
|
-
const appId = '${service.id}';
|
|
2011
|
-
const port = Number(process.env['${service.portEnv}'] ?? ${service.port});
|
|
2012
|
-
|
|
2013
|
-
export default defineConfig(
|
|
2014
|
-
presetUltramodern(
|
|
2015
|
-
{
|
|
2016
|
-
bff: {
|
|
2017
|
-
effect: {
|
|
2018
|
-
openapi: {
|
|
2019
|
-
path: '/openapi.json',
|
|
2020
|
-
},
|
|
2021
|
-
},
|
|
2022
|
-
prefix: '${serviceApiPrefix(service)}',
|
|
2023
|
-
runtimeFramework: 'effect',
|
|
2024
|
-
},
|
|
2025
|
-
plugins: [appTools(), bffPlugin()],
|
|
2026
|
-
server: {
|
|
2027
|
-
port,
|
|
2028
|
-
},
|
|
2029
|
-
},
|
|
2030
|
-
{
|
|
2031
|
-
appId,
|
|
2032
|
-
enableBffRequestId: true,
|
|
2033
|
-
enableTelemetryExporters: true,
|
|
2034
|
-
telemetryFailLoudStartup: false,
|
|
2035
|
-
},
|
|
2036
|
-
),
|
|
2037
|
-
);
|
|
2038
|
-
`;
|
|
2039
|
-
}
|
|
2040
2004
|
function createAppRuntimeConfig(app) {
|
|
2041
2005
|
const namespace = appI18nNamespace(app);
|
|
2042
2006
|
const localeMessages = (language)=>{
|
|
2043
2007
|
if ('shell' !== app.kind) return createAppLocaleMessages(app, language);
|
|
2044
|
-
return Object.assign({}, createAppLocaleMessages(app, language), ...
|
|
2008
|
+
return Object.assign({}, createAppLocaleMessages(app, language), ...verticalApps.map((remote)=>createAppLocaleMessages(remote, language)));
|
|
2045
2009
|
};
|
|
2046
2010
|
const resources = {
|
|
2047
2011
|
cs: {
|
|
@@ -2082,14 +2046,44 @@ export default defineRuntimeConfig({
|
|
|
2082
2046
|
function createCssTokenImport(scope) {
|
|
2083
2047
|
return `@import '${ultramodern_workspace_packageName(scope, 'shared-design-tokens')}/tokens.css';\n`;
|
|
2084
2048
|
}
|
|
2085
|
-
function
|
|
2086
|
-
|
|
2049
|
+
function createTailwindPrefix(raw) {
|
|
2050
|
+
const prefix = raw.toLowerCase().replace(/[^a-z]/gu, '');
|
|
2051
|
+
if (!prefix) throw new Error(`Cannot derive a Tailwind prefix from ${raw}`);
|
|
2052
|
+
return prefix;
|
|
2053
|
+
}
|
|
2054
|
+
function tailwindPrefixForApp(app) {
|
|
2055
|
+
if ('shell' === app.kind) return 'shell';
|
|
2056
|
+
return createTailwindPrefix(app.domain ?? app.id);
|
|
2057
|
+
}
|
|
2058
|
+
function tailwindPrefixForService(service) {
|
|
2059
|
+
return createTailwindPrefix(service.id);
|
|
2060
|
+
}
|
|
2061
|
+
function assertUniqueTailwindPrefixes(apps, services = []) {
|
|
2062
|
+
const seen = new Map();
|
|
2063
|
+
const entries = [
|
|
2064
|
+
...apps.map((app)=>[
|
|
2065
|
+
app.id,
|
|
2066
|
+
tailwindPrefixForApp(app)
|
|
2067
|
+
]),
|
|
2068
|
+
...services.map((service)=>[
|
|
2069
|
+
service.id,
|
|
2070
|
+
tailwindPrefixForService(service)
|
|
2071
|
+
])
|
|
2072
|
+
];
|
|
2073
|
+
for (const [id, prefix] of entries){
|
|
2074
|
+
const previous = seen.get(prefix);
|
|
2075
|
+
if (previous) throw new Error(`Tailwind prefix ${prefix} for ${id} collides with ${previous}`);
|
|
2076
|
+
seen.set(prefix, id);
|
|
2077
|
+
}
|
|
2087
2078
|
}
|
|
2088
|
-
function
|
|
2089
|
-
return
|
|
2079
|
+
function createTailwindImport(prefix) {
|
|
2080
|
+
return `@import 'tailwindcss' prefix(${prefix}) source(none);\n@source '..';\n`;
|
|
2090
2081
|
}
|
|
2091
|
-
function
|
|
2092
|
-
return `${enableTailwind ?
|
|
2082
|
+
function createShellStyles(enableTailwind, scope) {
|
|
2083
|
+
return `${enableTailwind ? createTailwindImport(tailwindPrefixForApp(shellApp)) : ''}${createCssTokenImport(scope)}`;
|
|
2084
|
+
}
|
|
2085
|
+
function createRemoteStyles(enableTailwind, scope, app) {
|
|
2086
|
+
return `${enableTailwind ? createTailwindImport(tailwindPrefixForApp(app)) : ''}${createCssTokenImport(scope)}`;
|
|
2093
2087
|
}
|
|
2094
2088
|
function createAppStyles(enableTailwind, scope, app) {
|
|
2095
2089
|
return 'shell' === app.kind ? createShellStyles(enableTailwind, scope) : createRemoteStyles(enableTailwind, scope, app);
|
|
@@ -2106,10 +2100,12 @@ function createTailwindConfig() {
|
|
|
2106
2100
|
return `import type { Config } from 'tailwindcss';
|
|
2107
2101
|
|
|
2108
2102
|
export default {
|
|
2109
|
-
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
|
2110
2103
|
} satisfies Config;
|
|
2111
2104
|
`;
|
|
2112
2105
|
}
|
|
2106
|
+
function createTw(prefix) {
|
|
2107
|
+
return (classList)=>classList.split(/\s+/u).filter(Boolean).map((candidate)=>`${prefix}:${candidate.replace(/\[&&\]:/gu, '')}`).join(' ');
|
|
2108
|
+
}
|
|
2113
2109
|
function createCommerceAssetSvg(title, palette) {
|
|
2114
2110
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="900" viewBox="0 0 1440 900" role="img" aria-label="${title}">
|
|
2115
2111
|
<defs>
|
|
@@ -2185,7 +2181,7 @@ function commerceAssetsForApp(app) {
|
|
|
2185
2181
|
tractor: '#914d76'
|
|
2186
2182
|
})
|
|
2187
2183
|
};
|
|
2188
|
-
if ('
|
|
2184
|
+
if ('explore' === app.id) return {
|
|
2189
2185
|
[commerceAssetPublicPath('autonomy.svg')]: createCommerceAssetSvg('Autonomous tractor concept', {
|
|
2190
2186
|
accent: '#c26a2e',
|
|
2191
2187
|
ground: '#668f55',
|
|
@@ -2211,7 +2207,7 @@ function commerceAssetsForApp(app) {
|
|
|
2211
2207
|
tractor: '#914d76'
|
|
2212
2208
|
})
|
|
2213
2209
|
};
|
|
2214
|
-
if ('
|
|
2210
|
+
if ('decide' === app.id) return {
|
|
2215
2211
|
[commerceAssetPublicPath('field-loader.svg')]: createCommerceAssetSvg('Field Loader 112 tractor detail', {
|
|
2216
2212
|
accent: '#d6b85d',
|
|
2217
2213
|
ground: '#84ad58',
|
|
@@ -2372,11 +2368,12 @@ const LocalizedHead = () => {
|
|
|
2372
2368
|
`;
|
|
2373
2369
|
}
|
|
2374
2370
|
function createShellPage() {
|
|
2371
|
+
const tw = createTw(tailwindPrefixForApp(shellApp));
|
|
2375
2372
|
return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
2376
2373
|
import { Helmet } from '@modern-js/runtime/head';
|
|
2377
2374
|
import { useLocation } from '@modern-js/plugin-tanstack/runtime';
|
|
2378
2375
|
import ShellFrame from '../shell-frame';
|
|
2379
|
-
import { StorePicker } from '../
|
|
2376
|
+
import { StorePicker } from '../vertical-components';
|
|
2380
2377
|
import { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';
|
|
2381
2378
|
import { ultramodernUiMarker } from '../../ultramodern-build';
|
|
2382
2379
|
|
|
@@ -2390,25 +2387,25 @@ export default function ShellHome() {
|
|
|
2390
2387
|
return (
|
|
2391
2388
|
<ShellFrame>
|
|
2392
2389
|
<LocalizedHead />
|
|
2393
|
-
<section className="mx-auto grid max-w-7xl items-center gap-8 py-8 md:grid-cols-[0.9fr_1.1fr] lg:gap-14">
|
|
2394
|
-
<div className="min-w-0">
|
|
2395
|
-
<p className="text-xs font-black uppercase tracking-[0.18em] text-emerald-800">{t('shell.hero.eyebrow')}</p>
|
|
2396
|
-
<h1 className="mt-3 max-w-3xl text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl">{t('shell.title')}</h1>
|
|
2397
|
-
<p className="mt-5 max-w-2xl text-lg leading-8 text-stone-600">{t('shell.hero.lede')}</p>
|
|
2398
|
-
<div className="mt-7 flex flex-wrap gap-3">
|
|
2399
|
-
<a className="inline-flex min-h-11 items-center justify-center rounded-full bg-emerald-800 px-5 font-bold text-white shadow-lg shadow-stone-900/10" href={\`/\${language}/tractors/field-loader-112\`}>
|
|
2390
|
+
<section className="${tw('mx-auto grid max-w-7xl items-center gap-8 py-8 md:grid-cols-[0.9fr_1.1fr] lg:gap-14')}">
|
|
2391
|
+
<div className="${tw('min-w-0')}">
|
|
2392
|
+
<p className="${tw('text-xs font-black uppercase tracking-[0.18em] text-emerald-800')}">{t('shell.hero.eyebrow')}</p>
|
|
2393
|
+
<h1 className="${tw('mt-3 max-w-3xl text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl')}">{t('shell.title')}</h1>
|
|
2394
|
+
<p className="${tw('mt-5 max-w-2xl text-lg leading-8 text-stone-600')}">{t('shell.hero.lede')}</p>
|
|
2395
|
+
<div className="${tw('mt-7 flex flex-wrap gap-3')}">
|
|
2396
|
+
<a className="${tw('inline-flex min-h-11 items-center justify-center rounded-full bg-emerald-800 px-5 font-bold text-white shadow-lg shadow-stone-900/10')}" href={\`/\${language}/tractors/field-loader-112\`}>
|
|
2400
2397
|
{t('shell.hero.primary')}
|
|
2401
2398
|
</a>
|
|
2402
|
-
<a className="inline-flex min-h-11 items-center justify-center rounded-full border border-stone-900/15 bg-white/90 px-5 font-bold text-stone-950 shadow-lg shadow-stone-900/10" href={\`/\${language}/tractors\`}>
|
|
2399
|
+
<a className="${tw('inline-flex min-h-11 items-center justify-center rounded-full border border-stone-900/15 bg-white/90 px-5 font-bold text-stone-950 shadow-lg shadow-stone-900/10')}" href={\`/\${language}/tractors\`}>
|
|
2403
2400
|
{t('shell.hero.secondary')}
|
|
2404
2401
|
</a>
|
|
2405
2402
|
</div>
|
|
2406
2403
|
</div>
|
|
2407
|
-
<img alt="" className="aspect-[16/10] w-full rounded-3xl bg-stone-200 object-cover shadow-2xl shadow-stone-900/20" src={heroField} />
|
|
2404
|
+
<img alt="" className="${tw('aspect-[16/10] w-full rounded-3xl bg-stone-200 object-cover shadow-2xl shadow-stone-900/20')}" src={heroField} />
|
|
2408
2405
|
</section>
|
|
2409
2406
|
<StorePicker />
|
|
2410
|
-
<p className="sr-only" data-testid="ultramodern-preset">presetUltramodern workspace</p>
|
|
2411
|
-
<p className="sr-only" data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
|
|
2407
|
+
<p className="${tw('sr-only')}" data-testid="ultramodern-preset">presetUltramodern workspace</p>
|
|
2408
|
+
<p className="${tw('sr-only')}" data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
|
|
2412
2409
|
{ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
|
|
2413
2410
|
</p>
|
|
2414
2411
|
</ShellFrame>
|
|
@@ -2420,7 +2417,7 @@ function createShellTractorsPage() {
|
|
|
2420
2417
|
return `import { Helmet } from '@modern-js/runtime/head';
|
|
2421
2418
|
import { useLocation } from '@modern-js/plugin-tanstack/runtime';
|
|
2422
2419
|
import ShellFrame from '../../shell-frame';
|
|
2423
|
-
import { Recommendations } from '../../
|
|
2420
|
+
import { Recommendations } from '../../vertical-components';
|
|
2424
2421
|
import { ultramodernLocalisedUrls } from '../../ultramodern-route-metadata';
|
|
2425
2422
|
|
|
2426
2423
|
${createLocalizedHeadComponent()}
|
|
@@ -2434,17 +2431,35 @@ export default function ShellTractorsPage() {
|
|
|
2434
2431
|
}
|
|
2435
2432
|
`;
|
|
2436
2433
|
}
|
|
2434
|
+
function createShellStoresPage() {
|
|
2435
|
+
return `import { Helmet } from '@modern-js/runtime/head';
|
|
2436
|
+
import { useLocation } from '@modern-js/plugin-tanstack/runtime';
|
|
2437
|
+
import ShellFrame from '../../shell-frame';
|
|
2438
|
+
import { StorePicker } from '../../vertical-components';
|
|
2439
|
+
import { ultramodernLocalisedUrls } from '../../ultramodern-route-metadata';
|
|
2440
|
+
|
|
2441
|
+
${createLocalizedHeadComponent()}
|
|
2442
|
+
export default function ShellStoresPage() {
|
|
2443
|
+
return (
|
|
2444
|
+
<ShellFrame>
|
|
2445
|
+
<LocalizedHead />
|
|
2446
|
+
<StorePicker />
|
|
2447
|
+
</ShellFrame>
|
|
2448
|
+
);
|
|
2449
|
+
}
|
|
2450
|
+
`;
|
|
2451
|
+
}
|
|
2437
2452
|
function createShellProductPage() {
|
|
2438
2453
|
return `import { Helmet } from '@modern-js/runtime/head';
|
|
2439
2454
|
import { useLocation } from '@modern-js/plugin-tanstack/runtime';
|
|
2440
2455
|
import ShellFrame from '../../../shell-frame';
|
|
2441
|
-
import { ProductPage } from '../../../
|
|
2456
|
+
import { ProductPage } from '../../../vertical-components';
|
|
2442
2457
|
import { ultramodernLocalisedUrls } from '../../../ultramodern-route-metadata';
|
|
2443
2458
|
|
|
2444
2459
|
${createLocalizedHeadComponent()}
|
|
2445
2460
|
export default function ShellProductPage() {
|
|
2446
2461
|
return (
|
|
2447
|
-
<ShellFrame>
|
|
2462
|
+
<ShellFrame boundary="decide">
|
|
2448
2463
|
<LocalizedHead />
|
|
2449
2464
|
<ProductPage />
|
|
2450
2465
|
</ShellFrame>
|
|
@@ -2456,13 +2471,13 @@ function createShellCartPage() {
|
|
|
2456
2471
|
return `import { Helmet } from '@modern-js/runtime/head';
|
|
2457
2472
|
import { useLocation } from '@modern-js/plugin-tanstack/runtime';
|
|
2458
2473
|
import ShellFrame from '../../shell-frame';
|
|
2459
|
-
import { CartPage } from '../../
|
|
2474
|
+
import { CartPage } from '../../vertical-components';
|
|
2460
2475
|
import { ultramodernLocalisedUrls } from '../../ultramodern-route-metadata';
|
|
2461
2476
|
|
|
2462
2477
|
${createLocalizedHeadComponent()}
|
|
2463
2478
|
export default function ShellCartPage() {
|
|
2464
2479
|
return (
|
|
2465
|
-
<ShellFrame showCart={false}>
|
|
2480
|
+
<ShellFrame boundary="checkout" showCart={false}>
|
|
2466
2481
|
<LocalizedHead />
|
|
2467
2482
|
<CartPage />
|
|
2468
2483
|
</ShellFrame>
|
|
@@ -2471,17 +2486,19 @@ export default function ShellCartPage() {
|
|
|
2471
2486
|
`;
|
|
2472
2487
|
}
|
|
2473
2488
|
function createShellFrameComponent() {
|
|
2489
|
+
const tw = createTw(tailwindPrefixForApp(shellApp));
|
|
2474
2490
|
return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
2475
2491
|
import { useLocation } from '@modern-js/plugin-tanstack/runtime';
|
|
2476
2492
|
import type { ReactNode } from 'react';
|
|
2477
2493
|
import BoundaryOverlay from './boundary-overlay';
|
|
2478
|
-
import { Header, MiniCart } from './
|
|
2494
|
+
import { Header, MiniCart } from './vertical-components';
|
|
2479
2495
|
import { ultramodernLocalisedUrls } from './ultramodern-route-metadata';
|
|
2480
2496
|
|
|
2481
2497
|
const supportedLanguages = ['en', 'cs'] as const;
|
|
2482
2498
|
type SupportedLanguage = (typeof supportedLanguages)[number];
|
|
2483
2499
|
|
|
2484
2500
|
type ShellFrameProps = {
|
|
2501
|
+
boundary?: 'checkout' | 'decide' | 'explore';
|
|
2485
2502
|
children: ReactNode;
|
|
2486
2503
|
showCart?: boolean;
|
|
2487
2504
|
};
|
|
@@ -2601,24 +2618,25 @@ const locationSuffix = (location: {
|
|
|
2601
2618
|
return \`\${locationSearch}\${locationHash}\`;
|
|
2602
2619
|
};
|
|
2603
2620
|
|
|
2604
|
-
export default function ShellFrame({ children, showCart = true }: ShellFrameProps) {
|
|
2621
|
+
export default function ShellFrame({ boundary = 'explore', children, showCart = true }: ShellFrameProps) {
|
|
2605
2622
|
const { i18nInstance, language } = useModernI18n();
|
|
2606
2623
|
const t = i18nInstance['t'].bind(i18nInstance);
|
|
2607
2624
|
const location = useLocation();
|
|
2608
2625
|
const suffix = locationSuffix(location);
|
|
2609
2626
|
|
|
2610
2627
|
return (
|
|
2611
|
-
<main className="min-h-screen bg-um-canvas px-4 py-5 text-um-foreground sm:px-6 lg:px-12">
|
|
2612
|
-
<
|
|
2613
|
-
<div className="mx-auto flex min-h-20 max-w-7xl flex-wrap items-center justify-between gap-3 bg-white/90 px-4 py-3 shadow-xl shadow-stone-900/10 sm:px-6">
|
|
2628
|
+
<main className="${tw('min-h-screen bg-um-canvas px-4 py-5 text-um-foreground sm:px-6 lg:px-12')}" data-mf-page-boundary={boundary}>
|
|
2629
|
+
<div className="${tw('mx-auto flex min-h-20 max-w-7xl flex-col items-start gap-3 bg-white/90 px-4 py-3 shadow-xl shadow-stone-900/10 sm:px-6 md:flex-row md:flex-wrap md:items-center md:justify-between')}">
|
|
2614
2630
|
<Header />
|
|
2615
|
-
<div className="
|
|
2616
|
-
<label className="sr-only" htmlFor="ultramodern-language">
|
|
2631
|
+
<div className="${tw('flex min-w-0 flex-wrap items-center gap-2 md:ml-auto')}">
|
|
2632
|
+
<label className="${tw('sr-only')}" htmlFor="ultramodern-language">
|
|
2617
2633
|
{t('shell.language.switcher')}
|
|
2618
2634
|
</label>
|
|
2619
2635
|
<select
|
|
2620
|
-
|
|
2636
|
+
aria-label={t('shell.language.switcher')}
|
|
2637
|
+
className="${tw('h-10 w-10 cursor-pointer appearance-none border-0 bg-transparent p-0 text-center text-3xl font-black leading-none text-stone-950 shadow-none [appearance:none] [text-align-last:center] focus-visible:rounded-md focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-emerald-700/40 [&::-ms-expand]:hidden [&::picker-icon]:hidden [&_option]:text-xl')}"
|
|
2621
2638
|
id="ultramodern-language"
|
|
2639
|
+
name="language"
|
|
2622
2640
|
onChange={event => {
|
|
2623
2641
|
const nextLanguage = event.currentTarget.value;
|
|
2624
2642
|
if (isSupportedLanguage(nextLanguage)) {
|
|
@@ -2629,15 +2647,17 @@ export default function ShellFrame({ children, showCart = true }: ShellFrameProp
|
|
|
2629
2647
|
}}
|
|
2630
2648
|
value={language}
|
|
2631
2649
|
>
|
|
2632
|
-
{
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2650
|
+
<option aria-label={t('shell.language.en')} value="en">
|
|
2651
|
+
🇬🇧
|
|
2652
|
+
</option>
|
|
2653
|
+
<option aria-label={t('shell.language.cs')} value="cs">
|
|
2654
|
+
🇨🇿
|
|
2655
|
+
</option>
|
|
2637
2656
|
</select>
|
|
2638
2657
|
{showCart ? <MiniCart /> : null}
|
|
2639
2658
|
</div>
|
|
2640
2659
|
</div>
|
|
2660
|
+
<BoundaryOverlay />
|
|
2641
2661
|
{children}
|
|
2642
2662
|
</main>
|
|
2643
2663
|
);
|
|
@@ -2645,6 +2665,7 @@ export default function ShellFrame({ children, showCart = true }: ShellFrameProp
|
|
|
2645
2665
|
`;
|
|
2646
2666
|
}
|
|
2647
2667
|
function createShellBoundaryOverlay() {
|
|
2668
|
+
const tw = createTw(tailwindPrefixForApp(shellApp));
|
|
2648
2669
|
return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
2649
2670
|
import { useEffect, useMemo, useState, type CSSProperties } from 'react';
|
|
2650
2671
|
|
|
@@ -2656,7 +2677,7 @@ type BoundaryConfig = {
|
|
|
2656
2677
|
type BoundaryBox = BoundaryConfig & {
|
|
2657
2678
|
height: number;
|
|
2658
2679
|
id: string;
|
|
2659
|
-
labelPlacement: 'above' | 'inside';
|
|
2680
|
+
labelPlacement: 'above' | 'edge' | 'inside';
|
|
2660
2681
|
left: number;
|
|
2661
2682
|
top: number;
|
|
2662
2683
|
width: number;
|
|
@@ -2678,6 +2699,7 @@ const boundaryIds = ['explore', 'decide', 'checkout'] as const;
|
|
|
2678
2699
|
|
|
2679
2700
|
export default function BoundaryOverlay() {
|
|
2680
2701
|
const { i18nInstance, language } = useModernI18n();
|
|
2702
|
+
const [mounted, setMounted] = useState(false);
|
|
2681
2703
|
const [enabled, setEnabled] = useState(false);
|
|
2682
2704
|
const [boxes, setBoxes] = useState<BoundaryBox[]>([]);
|
|
2683
2705
|
const boundaryConfig = useMemo(() => {
|
|
@@ -2701,6 +2723,10 @@ export default function BoundaryOverlay() {
|
|
|
2701
2723
|
'shell.boundaries.toggle',
|
|
2702
2724
|
);
|
|
2703
2725
|
|
|
2726
|
+
useEffect(() => {
|
|
2727
|
+
setMounted(true);
|
|
2728
|
+
}, []);
|
|
2729
|
+
|
|
2704
2730
|
useEffect(() => {
|
|
2705
2731
|
if (!enabled) {
|
|
2706
2732
|
setBoxes([]);
|
|
@@ -2709,10 +2735,13 @@ export default function BoundaryOverlay() {
|
|
|
2709
2735
|
|
|
2710
2736
|
const readBoxes = () => {
|
|
2711
2737
|
const nextBoxes = Array.from(
|
|
2712
|
-
document.querySelectorAll<HTMLElement>(
|
|
2738
|
+
document.querySelectorAll<HTMLElement>(
|
|
2739
|
+
'[data-mf-page-boundary], [data-mf-boundary]',
|
|
2740
|
+
),
|
|
2713
2741
|
)
|
|
2714
2742
|
.map((element, index) => {
|
|
2715
|
-
const
|
|
2743
|
+
const pageBoundary = element.dataset.mfPageBoundary;
|
|
2744
|
+
const id = pageBoundary ?? element.dataset.mfBoundary ?? 'unknown';
|
|
2716
2745
|
const rect = element.getBoundingClientRect();
|
|
2717
2746
|
if (rect.width <= 0 || rect.height <= 0) {
|
|
2718
2747
|
return undefined;
|
|
@@ -2727,7 +2756,7 @@ export default function BoundaryOverlay() {
|
|
|
2727
2756
|
...config,
|
|
2728
2757
|
height: rect.height,
|
|
2729
2758
|
id: \`\${id}-\${index}\`,
|
|
2730
|
-
labelPlacement: rect.height < 48 ? 'above' : 'inside',
|
|
2759
|
+
labelPlacement: pageBoundary ? 'edge' : rect.height < 48 ? 'above' : 'inside',
|
|
2731
2760
|
left: rect.left,
|
|
2732
2761
|
top: rect.top,
|
|
2733
2762
|
width: rect.width,
|
|
@@ -2742,7 +2771,7 @@ export default function BoundaryOverlay() {
|
|
|
2742
2771
|
|
|
2743
2772
|
const resizeObserver = new ResizeObserver(readBoxes);
|
|
2744
2773
|
for (const element of document.querySelectorAll<HTMLElement>(
|
|
2745
|
-
'[data-mf-boundary]',
|
|
2774
|
+
'[data-mf-page-boundary], [data-mf-boundary]',
|
|
2746
2775
|
)) {
|
|
2747
2776
|
resizeObserver.observe(element);
|
|
2748
2777
|
}
|
|
@@ -2764,11 +2793,15 @@ export default function BoundaryOverlay() {
|
|
|
2764
2793
|
};
|
|
2765
2794
|
}, [boundaryConfig, enabled]);
|
|
2766
2795
|
|
|
2796
|
+
if (!mounted) {
|
|
2797
|
+
return null;
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2767
2800
|
return (
|
|
2768
2801
|
<>
|
|
2769
|
-
<label className="fixed bottom-5 left-5 z-[80] flex items-center gap-2 rounded-xl border border-stone-900/10 bg-white/95 px-4 py-3 text-sm font-semibold text-stone-950 shadow-2xl shadow-stone-900/15">
|
|
2802
|
+
<label className="${tw('fixed bottom-5 left-5 z-[80] flex items-center gap-2 rounded-xl border border-stone-900/10 bg-white/95 px-4 py-3 text-sm font-semibold text-stone-950 shadow-2xl shadow-stone-900/15')}">
|
|
2770
2803
|
<input
|
|
2771
|
-
className="size-4 accent-emerald-800"
|
|
2804
|
+
className="${tw('size-4 accent-emerald-800')}"
|
|
2772
2805
|
checked={enabled}
|
|
2773
2806
|
onChange={event => setEnabled(event.currentTarget.checked)}
|
|
2774
2807
|
type="checkbox"
|
|
@@ -2776,10 +2809,10 @@ export default function BoundaryOverlay() {
|
|
|
2776
2809
|
<span>{toggleLabel}</span>
|
|
2777
2810
|
</label>
|
|
2778
2811
|
{enabled ? (
|
|
2779
|
-
<div aria-hidden="true" className="pointer-events-none fixed inset-0 z-[70]">
|
|
2812
|
+
<div aria-hidden="true" className="${tw('pointer-events-none fixed inset-0 z-[70]')}">
|
|
2780
2813
|
{boxes.map(box => (
|
|
2781
2814
|
<div
|
|
2782
|
-
className="fixed rounded-lg border"
|
|
2815
|
+
className="${tw('fixed rounded-lg border-2')}"
|
|
2783
2816
|
data-label-placement={box.labelPlacement}
|
|
2784
2817
|
key={box.id}
|
|
2785
2818
|
style={
|
|
@@ -2794,7 +2827,7 @@ export default function BoundaryOverlay() {
|
|
|
2794
2827
|
}
|
|
2795
2828
|
>
|
|
2796
2829
|
<span
|
|
2797
|
-
className={
|
|
2830
|
+
className={\`${tw('absolute whitespace-nowrap rounded-full px-2 py-1 text-[0.7rem] font-black leading-none text-stone-950')} \${box.labelPlacement === 'above' ? '${tw('bottom-[calc(100%+0.25rem)] right-1 top-auto')}' : box.labelPlacement === 'edge' ? '${tw('left-0 top-28 -translate-x-[calc(100%-1px)] -rotate-90 rounded-b-none')}' : '${tw('right-1 top-1')}'}\`}
|
|
2798
2831
|
style={{ backgroundColor: box.color }}
|
|
2799
2832
|
>
|
|
2800
2833
|
{box.label}
|
|
@@ -2809,15 +2842,16 @@ export default function BoundaryOverlay() {
|
|
|
2809
2842
|
`;
|
|
2810
2843
|
}
|
|
2811
2844
|
function createShellRemoteComponents(scope) {
|
|
2845
|
+
const tw = createTw(tailwindPrefixForApp(shellApp));
|
|
2812
2846
|
return `import { createLazyComponent } from '@module-federation/modern-js-v3/react';
|
|
2813
2847
|
import { getInstance, loadRemote } from '@module-federation/modern-js-v3/runtime';
|
|
2814
2848
|
import { Suspense, useEffect, useMemo, useState, type ComponentType } from 'react';
|
|
2815
|
-
import HeaderServer from '${ultramodern_workspace_packageName(scope, '
|
|
2816
|
-
import StorePickerServer from '${ultramodern_workspace_packageName(scope, '
|
|
2817
|
-
import RecommendationsServer from '${ultramodern_workspace_packageName(scope, '
|
|
2818
|
-
import ProductPageServer from '${ultramodern_workspace_packageName(scope, '
|
|
2819
|
-
import MiniCartServer from '${ultramodern_workspace_packageName(scope, '
|
|
2820
|
-
import CartPageServer from '${ultramodern_workspace_packageName(scope, '
|
|
2849
|
+
import HeaderServer from '${ultramodern_workspace_packageName(scope, 'explore')}/Header';
|
|
2850
|
+
import StorePickerServer from '${ultramodern_workspace_packageName(scope, 'explore')}/StorePicker';
|
|
2851
|
+
import RecommendationsServer from '${ultramodern_workspace_packageName(scope, 'explore')}/Recommendations';
|
|
2852
|
+
import ProductPageServer from '${ultramodern_workspace_packageName(scope, 'decide')}/ProductPage';
|
|
2853
|
+
import MiniCartServer from '${ultramodern_workspace_packageName(scope, 'checkout')}/MiniCart';
|
|
2854
|
+
import CartPageServer from '${ultramodern_workspace_packageName(scope, 'checkout')}/CartPage';
|
|
2821
2855
|
|
|
2822
2856
|
type RemoteComponentModule = {
|
|
2823
2857
|
default: ComponentType;
|
|
@@ -2833,7 +2867,7 @@ const loadRemoteComponent = async (specifier: string) => {
|
|
|
2833
2867
|
|
|
2834
2868
|
const remoteFallback =
|
|
2835
2869
|
({ error }: { error: Error }) =>
|
|
2836
|
-
<div className="rounded-xl border border-red-900/20 bg-red-50 px-4 py-3 text-sm font-semibold text-red-900" data-remote-error={error.name}>
|
|
2870
|
+
<div className="${tw('rounded-xl border border-red-900/20 bg-red-50 px-4 py-3 text-sm font-semibold text-red-900')}" data-remote-error={error.name}>Vertical unavailable</div>;
|
|
2837
2871
|
|
|
2838
2872
|
const createHydratedRemote = (
|
|
2839
2873
|
ServerComponent: ComponentType,
|
|
@@ -2884,6 +2918,7 @@ export const CartPage = createHydratedRemote(CartPageServer, 'checkout/CartPage'
|
|
|
2884
2918
|
`;
|
|
2885
2919
|
}
|
|
2886
2920
|
function createRemotePage(app) {
|
|
2921
|
+
const tw = createTw(tailwindPrefixForApp(app));
|
|
2887
2922
|
const effectBffImport = appHasEffectApi(app) ? `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
2888
2923
|
import { Helmet } from '@modern-js/runtime/head';
|
|
2889
2924
|
import { useLocation } from '@modern-js/plugin-tanstack/runtime';
|
|
@@ -2925,13 +2960,13 @@ export default function ${toPascalCase(app.id)}Home() {
|
|
|
2925
2960
|
const location = useLocation();
|
|
2926
2961
|
const suffix = locationSuffix(location);
|
|
2927
2962
|
${effectBffState} return (
|
|
2928
|
-
<main className="min-h-screen bg-um-canvas px-4 py-6 text-um-foreground sm:px-8">
|
|
2963
|
+
<main className="${tw('min-h-screen bg-um-canvas px-4 py-6 text-um-foreground sm:px-8')}">
|
|
2929
2964
|
<LocalizedHead />
|
|
2930
|
-
<nav aria-label={t('${app.domain}.language.switcher')} className="flex gap-3">
|
|
2965
|
+
<nav aria-label={t('${app.domain}.language.switcher')} className="${tw('flex gap-3')}">
|
|
2931
2966
|
{supportedLanguages.map(code => (
|
|
2932
2967
|
<a
|
|
2933
2968
|
aria-current={language === code ? 'page' : undefined}
|
|
2934
|
-
className="rounded-full border border-stone-900/15 bg-white px-4 py-2 text-sm font-bold text-stone-950 no-underline"
|
|
2969
|
+
className="${tw('rounded-full border border-stone-900/15 bg-white px-4 py-2 text-sm font-bold text-stone-950 no-underline')}"
|
|
2935
2970
|
href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
|
|
2936
2971
|
key={code}
|
|
2937
2972
|
>
|
|
@@ -2939,9 +2974,9 @@ ${effectBffState} return (
|
|
|
2939
2974
|
</a>
|
|
2940
2975
|
))}
|
|
2941
2976
|
</nav>
|
|
2942
|
-
<h1 className="mt-10 text-5xl font-black">{t('${app.domain}.title')}</h1>
|
|
2943
|
-
<p className="mt-3 text-lg text-stone-600" data-mf-role="${app.kind}">{t('${app.domain}.role')}</p>
|
|
2944
|
-
<p className="sr-only" data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
|
|
2977
|
+
<h1 className="${tw('mt-10 text-5xl font-black')}">{t('${app.domain}.title')}</h1>
|
|
2978
|
+
<p className="${tw('mt-3 text-lg text-stone-600')}" data-mf-role="${app.kind}">{t('${app.domain}.role')}</p>
|
|
2979
|
+
<p className="${tw('sr-only')}" data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
|
|
2945
2980
|
{ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
|
|
2946
2981
|
</p>
|
|
2947
2982
|
${effectBffMarkup} </main>
|
|
@@ -2963,52 +2998,55 @@ export default function Layout() {
|
|
|
2963
2998
|
`;
|
|
2964
2999
|
}
|
|
2965
3000
|
function createRemoteEntry(app) {
|
|
3001
|
+
const tw = createTw(tailwindPrefixForApp(app));
|
|
2966
3002
|
if (app.exposes?.['./ProductPage']) return `export { default } from './components/product-page';
|
|
2967
3003
|
`;
|
|
2968
3004
|
if (app.exposes?.['./CartPage']) return `export { default } from './components/cart-page';
|
|
2969
3005
|
`;
|
|
2970
3006
|
return `export default function ${toPascalCase(app.domain ?? app.id)}Route() {
|
|
2971
3007
|
return (
|
|
2972
|
-
<section className="rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10" data-mf-remote="${app.id}" data-mf-expose="./Route">
|
|
2973
|
-
<h2 className="text-2xl font-black">${app.displayName}</h2>
|
|
2974
|
-
<p className="mt-2 text-stone-600">Route surface for ${app.domain ?? app.id}.</p>
|
|
3008
|
+
<section className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}" data-mf-remote="${app.id}" data-mf-expose="./Route">
|
|
3009
|
+
<h2 className="${tw('text-2xl font-black')}">${app.displayName}</h2>
|
|
3010
|
+
<p className="${tw('mt-2 text-stone-600')}">Route surface for ${app.domain ?? app.id}.</p>
|
|
2975
3011
|
</section>
|
|
2976
3012
|
);
|
|
2977
3013
|
}
|
|
2978
3014
|
`;
|
|
2979
3015
|
}
|
|
2980
3016
|
function createRemoteWidget(app) {
|
|
3017
|
+
const tw = createTw(tailwindPrefixForApp(app));
|
|
2981
3018
|
const componentName = `${toPascalCase(app.domain ?? app.id)}Widget`;
|
|
2982
3019
|
const body = 'vertical' === app.kind ? `Owns the ${app.domain} vertical route surface.` : 'Provides shared UI primitives for the workspace.';
|
|
2983
3020
|
return `export default function ${componentName}() {
|
|
2984
3021
|
return (
|
|
2985
|
-
<section className="rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10" data-mf-remote="${app.id}">
|
|
2986
|
-
<h2 className="text-2xl font-black">${app.displayName}</h2>
|
|
2987
|
-
<p className="mt-2 text-stone-600">${body}</p>
|
|
3022
|
+
<section className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}" data-mf-remote="${app.id}">
|
|
3023
|
+
<h2 className="${tw('text-2xl font-black')}">${app.displayName}</h2>
|
|
3024
|
+
<p className="${tw('mt-2 text-stone-600')}">${body}</p>
|
|
2988
3025
|
</section>
|
|
2989
3026
|
);
|
|
2990
3027
|
}
|
|
2991
3028
|
`;
|
|
2992
3029
|
}
|
|
2993
3030
|
function createRemoteExposeComponent(app, expose) {
|
|
2994
|
-
|
|
3031
|
+
const tw = createTw(tailwindPrefixForApp(app));
|
|
3032
|
+
if ('explore' === app.id && './Header' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
2995
3033
|
|
|
2996
3034
|
export default function Header() {
|
|
2997
3035
|
const { i18nInstance, language } = useModernI18n();
|
|
2998
3036
|
const t = i18nInstance['t'].bind(i18nInstance);
|
|
2999
3037
|
|
|
3000
3038
|
return (
|
|
3001
|
-
<header className="flex min-w-0 flex-
|
|
3002
|
-
<a className="whitespace-nowrap text-xl font-black tracking-normal text-stone-950 no-underline" href={\`/\${language}\`}>Acre & Iron</a>
|
|
3003
|
-
<nav aria-label={t('explore.header.navigation')} className="flex items-center gap-5">
|
|
3004
|
-
<a className="text-sm font-extrabold text-stone-900 no-underline" href={\`/\${language}/tractors\`}>{t('explore.header.machines')}</a>
|
|
3005
|
-
<a className="text-sm font-extrabold text-stone-900 no-underline" href={\`/\${language}/stores\`}>{t('explore.header.stores')}</a>
|
|
3039
|
+
<header className="${tw('flex min-w-0 flex-wrap items-center gap-x-8 gap-y-2 md:flex-1')}" data-mf-boundary="explore">
|
|
3040
|
+
<a className="${tw('whitespace-nowrap text-xl font-black tracking-normal text-stone-950 no-underline')}" href={\`/\${language}\`}>Acre & Iron</a>
|
|
3041
|
+
<nav aria-label={t('explore.header.navigation')} className="${tw('flex items-center gap-5')}">
|
|
3042
|
+
<a className="${tw('text-sm font-extrabold text-stone-900 no-underline')}" href={\`/\${language}/tractors\`}>{t('explore.header.machines')}</a>
|
|
3043
|
+
<a className="${tw('text-sm font-extrabold text-stone-900 no-underline')}" href={\`/\${language}/stores\`}>{t('explore.header.stores')}</a>
|
|
3006
3044
|
</nav>
|
|
3007
3045
|
</header>
|
|
3008
3046
|
);
|
|
3009
3047
|
}
|
|
3010
3048
|
`;
|
|
3011
|
-
if ('
|
|
3049
|
+
if ('explore' === app.id && './Recommendations' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
3012
3050
|
|
|
3013
3051
|
const tractors = [
|
|
3014
3052
|
{ badge: 'explore.recommendations.bestRows', image: '${commerceAssetUrl('orchard.svg')}', name: 'Orchard Tractor', slug: 'orchard-tractor' },
|
|
@@ -3022,14 +3060,14 @@ export default function Recommendations() {
|
|
|
3022
3060
|
const t = i18nInstance['t'].bind(i18nInstance);
|
|
3023
3061
|
|
|
3024
3062
|
return (
|
|
3025
|
-
<section className="mx-auto mt-12 max-w-7xl" data-mf-boundary="explore">
|
|
3026
|
-
<h2 className="text-3xl font-black tracking-normal text-stone-950">{t('explore.recommendations.title')}</h2>
|
|
3027
|
-
<div className="mt-5 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
|
3063
|
+
<section className="${tw('mx-auto mt-12 max-w-7xl')}" data-mf-boundary="explore">
|
|
3064
|
+
<h2 className="${tw('text-3xl font-black tracking-normal text-stone-950')}">{t('explore.recommendations.title')}</h2>
|
|
3065
|
+
<div className="${tw('mt-5 grid gap-4 md:grid-cols-2 xl:grid-cols-4')}">
|
|
3028
3066
|
{tractors.map(tractor => (
|
|
3029
|
-
<a className="block rounded-2xl bg-white/90 p-4 text-stone-950 no-underline shadow-xl shadow-stone-900/10 transition hover:-translate-y-0.5 hover:shadow-2xl" href={\`/\${language}/tractors/\${tractor.slug}\`} key={tractor.slug}>
|
|
3030
|
-
<img alt="" className="aspect-video w-full rounded-xl bg-stone-200 object-cover" src={tractor.image} />
|
|
3031
|
-
<span className="mt-4 block text-xs font-black uppercase tracking-[0.16em] text-amber-700">{t(tractor.badge)}</span>
|
|
3032
|
-
<strong className="mt-2 block text-xl font-black leading-tight">{tractor.name}</strong>
|
|
3067
|
+
<a className="${tw('block rounded-2xl bg-white/90 p-4 text-stone-950 no-underline shadow-xl shadow-stone-900/10 transition hover:-translate-y-0.5 hover:shadow-2xl')}" href={\`/\${language}/tractors/\${tractor.slug}\`} key={tractor.slug}>
|
|
3068
|
+
<img alt="" className="${tw('aspect-video w-full rounded-xl bg-stone-200 object-cover')}" src={tractor.image} />
|
|
3069
|
+
<span className="${tw('mt-4 block text-xs font-black uppercase tracking-[0.16em] text-amber-700')}">{t(tractor.badge)}</span>
|
|
3070
|
+
<strong className="${tw('mt-2 block text-xl font-black leading-tight')}">{tractor.name}</strong>
|
|
3033
3071
|
</a>
|
|
3034
3072
|
))}
|
|
3035
3073
|
</div>
|
|
@@ -3037,7 +3075,7 @@ export default function Recommendations() {
|
|
|
3037
3075
|
);
|
|
3038
3076
|
}
|
|
3039
3077
|
`;
|
|
3040
|
-
if ('
|
|
3078
|
+
if ('explore' === app.id && './StorePicker' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
3041
3079
|
|
|
3042
3080
|
const fieldLoaderImage = '${commerceAssetUrl('field-loader.svg')}';
|
|
3043
3081
|
const vineyardImage = '${commerceAssetUrl('vineyard.svg')}';
|
|
@@ -3047,32 +3085,32 @@ export default function StorePicker() {
|
|
|
3047
3085
|
const t = i18nInstance['t'].bind(i18nInstance);
|
|
3048
3086
|
|
|
3049
3087
|
return (
|
|
3050
|
-
<section className="mx-auto mt-12 max-w-7xl" data-mf-boundary="explore">
|
|
3051
|
-
<h2 className="text-3xl font-black tracking-normal text-stone-950">{t('explore.stores.title')}</h2>
|
|
3052
|
-
<div className="mt-5 grid gap-4 md:grid-cols-2">
|
|
3053
|
-
<article className="rounded-2xl bg-white/90 p-4 shadow-xl shadow-stone-900/10">
|
|
3054
|
-
<img alt="" className="aspect-video w-full rounded-xl bg-stone-200 object-cover" src={fieldLoaderImage} />
|
|
3055
|
-
<span className="mt-4 block text-xs font-black uppercase tracking-[0.16em] text-emerald-800">{t('explore.stores.northRegion')}</span>
|
|
3056
|
-
<strong className="mt-2 block text-2xl font-black">Bohemia Field Supply</strong>
|
|
3088
|
+
<section className="${tw('mx-auto mt-12 max-w-7xl')}" data-mf-boundary="explore">
|
|
3089
|
+
<h2 className="${tw('text-3xl font-black tracking-normal text-stone-950')}">{t('explore.stores.title')}</h2>
|
|
3090
|
+
<div className="${tw('mt-5 grid gap-4 md:grid-cols-2')}">
|
|
3091
|
+
<article className="${tw('rounded-2xl bg-white/90 p-4 shadow-xl shadow-stone-900/10')}">
|
|
3092
|
+
<img alt="" className="${tw('aspect-video w-full rounded-xl bg-stone-200 object-cover')}" src={fieldLoaderImage} />
|
|
3093
|
+
<span className="${tw('mt-4 block text-xs font-black uppercase tracking-[0.16em] text-emerald-800')}">{t('explore.stores.northRegion')}</span>
|
|
3094
|
+
<strong className="${tw('mt-2 block text-2xl font-black')}">Bohemia Field Supply</strong>
|
|
3057
3095
|
</article>
|
|
3058
|
-
<article className="rounded-2xl bg-white/90 p-4 shadow-xl shadow-stone-900/10">
|
|
3059
|
-
<img alt="" className="aspect-video w-full rounded-xl bg-stone-200 object-cover" src={vineyardImage} />
|
|
3060
|
-
<span className="mt-4 block text-xs font-black uppercase tracking-[0.16em] text-emerald-800">{t('explore.stores.southRegion')}</span>
|
|
3061
|
-
<strong className="mt-2 block text-2xl font-black">Moravia Iron Works</strong>
|
|
3096
|
+
<article className="${tw('rounded-2xl bg-white/90 p-4 shadow-xl shadow-stone-900/10')}">
|
|
3097
|
+
<img alt="" className="${tw('aspect-video w-full rounded-xl bg-stone-200 object-cover')}" src={vineyardImage} />
|
|
3098
|
+
<span className="${tw('mt-4 block text-xs font-black uppercase tracking-[0.16em] text-emerald-800')}">{t('explore.stores.southRegion')}</span>
|
|
3099
|
+
<strong className="${tw('mt-2 block text-2xl font-black')}">Moravia Iron Works</strong>
|
|
3062
3100
|
</article>
|
|
3063
3101
|
</div>
|
|
3064
3102
|
</section>
|
|
3065
3103
|
);
|
|
3066
3104
|
}
|
|
3067
3105
|
`;
|
|
3068
|
-
if ('
|
|
3069
|
-
return <footer className="mx-auto mt-12 max-w-7xl text-sm font-bold text-stone-600" data-mf-boundary="explore">Acre & Iron</footer>;
|
|
3106
|
+
if ('explore' === app.id && './Footer' === expose) return `export default function Footer() {
|
|
3107
|
+
return <footer className="${tw('mx-auto mt-12 max-w-7xl text-sm font-bold text-stone-600')}" data-mf-boundary="explore">Acre & Iron</footer>;
|
|
3070
3108
|
}
|
|
3071
3109
|
`;
|
|
3072
3110
|
if ('./Widget' === expose) return createRemoteWidget(app);
|
|
3073
3111
|
const componentName = `${toPascalCase(app.domain ?? app.id)}${toPascalCase(expose.replace(/^\.\//u, ''))}`;
|
|
3074
|
-
if ('
|
|
3075
|
-
import { AddToCart, Recommendations } from './
|
|
3112
|
+
if ('decide' === app.id && './ProductPage' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
3113
|
+
import { AddToCart, Recommendations } from './vertical-components';
|
|
3076
3114
|
|
|
3077
3115
|
const fieldLoaderImage = '${commerceAssetUrl('field-loader.svg')}';
|
|
3078
3116
|
|
|
@@ -3082,16 +3120,16 @@ export default function ${componentName}() {
|
|
|
3082
3120
|
|
|
3083
3121
|
return (
|
|
3084
3122
|
<>
|
|
3085
|
-
<section className="mx-auto mt-10 grid max-w-7xl items-center gap-8 md:grid-cols-[1fr_0.95fr] lg:gap-14" data-mf-boundary="decide" data-mf-remote="${app.id}" data-mf-expose="${expose}">
|
|
3086
|
-
<img alt="" className="aspect-[1/0.9] w-full rounded-3xl border-[18px] border-amber-200 bg-stone-200 object-cover shadow-2xl shadow-stone-900/20" src={fieldLoaderImage} />
|
|
3123
|
+
<section className="${tw('mx-auto mt-10 grid max-w-7xl items-center gap-8 md:grid-cols-[1fr_0.95fr] lg:gap-14')}" data-mf-boundary="decide" data-mf-remote="${app.id}" data-mf-expose="${expose}">
|
|
3124
|
+
<img alt="" className="${tw('aspect-[1/0.9] w-full rounded-3xl border-[18px] border-amber-200 bg-stone-200 object-cover shadow-2xl shadow-stone-900/20')}" src={fieldLoaderImage} />
|
|
3087
3125
|
<div>
|
|
3088
|
-
<p className="text-xs font-black uppercase tracking-[0.18em] text-emerald-800">{t('decide.product.eyebrow')}</p>
|
|
3089
|
-
<h1 className="mt-3 text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl">Field Loader 112</h1>
|
|
3090
|
-
<p className="mt-5 max-w-2xl text-lg leading-8 text-stone-600">{t('decide.product.lede')}</p>
|
|
3091
|
-
<div className="mt-8 grid gap-4 sm:grid-cols-3">
|
|
3092
|
-
<article className="rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10"><span className="block text-sm font-bold text-stone-500">{t('decide.product.price')}</span><strong className="mt-2 block text-lg font-black">EUR 42,500</strong></article>
|
|
3093
|
-
<article className="rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10"><span className="block text-sm font-bold text-stone-500">{t('decide.product.power')}</span><strong className="mt-2 block text-lg font-black">112 hp</strong></article>
|
|
3094
|
-
<article className="rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10"><span className="block text-sm font-bold text-stone-500">{t('decide.product.availability')}</span><strong className="mt-2 block text-lg font-black">{t('decide.product.inStock')}</strong></article>
|
|
3126
|
+
<p className="${tw('text-xs font-black uppercase tracking-[0.18em] text-emerald-800')}">{t('decide.product.eyebrow')}</p>
|
|
3127
|
+
<h1 className="${tw('mt-3 text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl')}">Field Loader 112</h1>
|
|
3128
|
+
<p className="${tw('mt-5 max-w-2xl text-lg leading-8 text-stone-600')}">{t('decide.product.lede')}</p>
|
|
3129
|
+
<div className="${tw('mt-8 grid gap-4 sm:grid-cols-3')}">
|
|
3130
|
+
<article className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}"><span className="${tw('block text-sm font-bold text-stone-500')}">{t('decide.product.price')}</span><strong className="${tw('mt-2 block text-lg font-black')}">EUR 42,500</strong></article>
|
|
3131
|
+
<article className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}"><span className="${tw('block text-sm font-bold text-stone-500')}">{t('decide.product.power')}</span><strong className="${tw('mt-2 block text-lg font-black')}">112 hp</strong></article>
|
|
3132
|
+
<article className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}"><span className="${tw('block text-sm font-bold text-stone-500')}">{t('decide.product.availability')}</span><strong className="${tw('mt-2 block text-lg font-black')}">{t('decide.product.inStock')}</strong></article>
|
|
3095
3133
|
</div>
|
|
3096
3134
|
<AddToCart />
|
|
3097
3135
|
</div>
|
|
@@ -3101,7 +3139,7 @@ export default function ${componentName}() {
|
|
|
3101
3139
|
);
|
|
3102
3140
|
}
|
|
3103
3141
|
`;
|
|
3104
|
-
if ('
|
|
3142
|
+
if ('checkout' === app.id && './AddToCart' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
3105
3143
|
import { useCartLines } from '../cart-store';
|
|
3106
3144
|
|
|
3107
3145
|
export default function ${componentName}() {
|
|
@@ -3110,18 +3148,18 @@ export default function ${componentName}() {
|
|
|
3110
3148
|
const cart = useCartLines();
|
|
3111
3149
|
|
|
3112
3150
|
return (
|
|
3113
|
-
<div className="mt-8 flex flex-wrap gap-3" data-mf-boundary="checkout">
|
|
3114
|
-
<button className="inline-flex min-h-11 items-center justify-center rounded-full bg-emerald-800 px-5 font-bold text-white shadow-lg shadow-stone-900/10" onClick={cart.addFieldLoader} type="button">
|
|
3151
|
+
<div className="${tw('mt-8 flex flex-wrap gap-3')}" data-mf-boundary="checkout">
|
|
3152
|
+
<button className="${tw('inline-flex min-h-11 items-center justify-center rounded-full bg-emerald-800 px-5 font-bold text-white shadow-lg shadow-stone-900/10')}" onClick={cart.addFieldLoader} type="button">
|
|
3115
3153
|
{t('checkout.actions.addToCart')}
|
|
3116
3154
|
</button>
|
|
3117
|
-
<a className="inline-flex min-h-11 items-center justify-center rounded-full border border-stone-900/15 bg-white/90 px-5 font-bold text-stone-950 shadow-lg shadow-stone-900/10" href={\`/\${language}/cart\`}>
|
|
3155
|
+
<a className="${tw('inline-flex min-h-11 items-center justify-center rounded-full border border-stone-900/15 bg-white/90 px-5 font-bold text-stone-950 shadow-lg shadow-stone-900/10')}" href={\`/\${language}/cart\`}>
|
|
3118
3156
|
{t('checkout.actions.viewCart')}
|
|
3119
3157
|
</a>
|
|
3120
3158
|
</div>
|
|
3121
3159
|
);
|
|
3122
3160
|
}
|
|
3123
3161
|
`;
|
|
3124
|
-
if ('
|
|
3162
|
+
if ('checkout' === app.id && './MiniCart' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
3125
3163
|
import { useCartLines } from '../cart-store';
|
|
3126
3164
|
|
|
3127
3165
|
export default function ${componentName}() {
|
|
@@ -3131,13 +3169,13 @@ export default function ${componentName}() {
|
|
|
3131
3169
|
const count = cart.lines.reduce((sum, line) => sum + line.quantity, 0);
|
|
3132
3170
|
|
|
3133
3171
|
return (
|
|
3134
|
-
<a className="inline-flex h-10 shrink-0 items-center justify-center rounded-full border border-stone-900/15 bg-white px-4 text-sm font-extrabold text-stone-950 no-underline shadow-lg shadow-stone-900/5" data-mf-boundary="checkout" href={\`/\${language}/cart\`}>
|
|
3172
|
+
<a className="${tw('inline-flex h-10 shrink-0 items-center justify-center rounded-full border border-stone-900/15 bg-white px-4 text-sm font-extrabold text-stone-950 no-underline shadow-lg shadow-stone-900/5')}" data-mf-boundary="checkout" href={\`/\${language}/cart\`}>
|
|
3135
3173
|
{t('checkout.cart.title')} ({count})
|
|
3136
3174
|
</a>
|
|
3137
3175
|
);
|
|
3138
3176
|
}
|
|
3139
3177
|
`;
|
|
3140
|
-
if ('
|
|
3178
|
+
if ('checkout' === app.id && './CartPage' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
3141
3179
|
import { useCartLines } from '../cart-store';
|
|
3142
3180
|
|
|
3143
3181
|
export default function ${componentName}() {
|
|
@@ -3146,24 +3184,24 @@ export default function ${componentName}() {
|
|
|
3146
3184
|
const cart = useCartLines();
|
|
3147
3185
|
|
|
3148
3186
|
return (
|
|
3149
|
-
<section className="mx-auto mt-10 max-w-7xl" data-mf-boundary="checkout" data-mf-remote="${app.id}" data-mf-expose="${expose}">
|
|
3150
|
-
<h1 className="text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl">{t('checkout.cart.title')}</h1>
|
|
3151
|
-
<div className="mt-8 rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10">
|
|
3187
|
+
<section className="${tw('mx-auto mt-10 max-w-7xl')}" data-mf-boundary="checkout" data-mf-remote="${app.id}" data-mf-expose="${expose}">
|
|
3188
|
+
<h1 className="${tw('text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl')}">{t('checkout.cart.title')}</h1>
|
|
3189
|
+
<div className="${tw('mt-8 rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}">
|
|
3152
3190
|
{cart.lines.length === 0 ? (
|
|
3153
3191
|
<p>{t('checkout.cart.empty')}</p>
|
|
3154
3192
|
) : (
|
|
3155
3193
|
<>
|
|
3156
3194
|
{cart.lines.map(line => (
|
|
3157
|
-
<article className="grid gap-4 border-t border-stone-900/10 py-4 first:border-t-0 sm:grid-cols-[1fr_auto] sm:items-center" key={line.id}>
|
|
3195
|
+
<article className="${tw('grid gap-4 border-t border-stone-900/10 py-4 first:border-t-0 sm:grid-cols-[1fr_auto] sm:items-center')}" key={line.id}>
|
|
3158
3196
|
<div>
|
|
3159
|
-
<strong className="text-lg font-black">{line.name}</strong>
|
|
3160
|
-
<p className="text-stone-600">EUR {line.price.toLocaleString('en-US')}</p>
|
|
3197
|
+
<strong className="${tw('text-lg font-black')}">{line.name}</strong>
|
|
3198
|
+
<p className="${tw('text-stone-600')}">EUR {line.price.toLocaleString('en-US')}</p>
|
|
3161
3199
|
</div>
|
|
3162
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
3163
|
-
<button className="inline-flex size-9 items-center justify-center rounded-full border border-stone-900/15 bg-white font-black" onClick={() => cart.decrement(line.id)} type="button">-</button>
|
|
3164
|
-
<span className="min-w-6 text-center font-black">{line.quantity}</span>
|
|
3165
|
-
<button className="inline-flex size-9 items-center justify-center rounded-full border border-stone-900/15 bg-white font-black" onClick={() => cart.increment(line.id)} type="button">+</button>
|
|
3166
|
-
<button className="inline-flex min-h-10 items-center justify-center rounded-full border border-stone-900/15 bg-white px-4 font-bold text-stone-950" onClick={() => cart.remove(line.id)} type="button">
|
|
3200
|
+
<div className="${tw('flex flex-wrap items-center gap-2')}">
|
|
3201
|
+
<button className="${tw('inline-flex size-9 items-center justify-center rounded-full border border-stone-900/15 bg-white font-black')}" onClick={() => cart.decrement(line.id)} type="button">-</button>
|
|
3202
|
+
<span className="${tw('min-w-6 text-center font-black')}">{line.quantity}</span>
|
|
3203
|
+
<button className="${tw('inline-flex size-9 items-center justify-center rounded-full border border-stone-900/15 bg-white font-black')}" onClick={() => cart.increment(line.id)} type="button">+</button>
|
|
3204
|
+
<button className="${tw('inline-flex min-h-10 items-center justify-center rounded-full border border-stone-900/15 bg-white px-4 font-bold text-stone-950')}" onClick={() => cart.remove(line.id)} type="button">
|
|
3167
3205
|
{t('checkout.actions.remove')}
|
|
3168
3206
|
</button>
|
|
3169
3207
|
</div>
|
|
@@ -3179,20 +3217,21 @@ export default function ${componentName}() {
|
|
|
3179
3217
|
`;
|
|
3180
3218
|
return `export default function ${componentName}() {
|
|
3181
3219
|
return (
|
|
3182
|
-
<section className="rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10" data-mf-remote="${app.id}" data-mf-expose="${expose}">
|
|
3183
|
-
<h2 className="text-2xl font-black">${app.displayName} ${expose.replace(/^\.\//u, '')}</h2>
|
|
3184
|
-
<p className="mt-2 text-stone-600">Module Federation surface owned by ${app.ownership.team}.</p>
|
|
3220
|
+
<section className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}" data-mf-remote="${app.id}" data-mf-expose="${expose}">
|
|
3221
|
+
<h2 className="${tw('text-2xl font-black')}">${app.displayName} ${expose.replace(/^\.\//u, '')}</h2>
|
|
3222
|
+
<p className="${tw('mt-2 text-stone-600')}">Module Federation surface owned by ${app.ownership.team}.</p>
|
|
3185
3223
|
</section>
|
|
3186
3224
|
);
|
|
3187
3225
|
}
|
|
3188
3226
|
`;
|
|
3189
3227
|
}
|
|
3190
|
-
function createDecideRemoteComponents(scope) {
|
|
3228
|
+
function createDecideRemoteComponents(scope, app) {
|
|
3229
|
+
const tw = createTw(tailwindPrefixForApp(app));
|
|
3191
3230
|
return `import { createLazyComponent } from '@module-federation/modern-js-v3/react';
|
|
3192
3231
|
import { getInstance, loadRemote } from '@module-federation/modern-js-v3/runtime';
|
|
3193
3232
|
import { Suspense, useEffect, useMemo, useState, type ComponentType } from 'react';
|
|
3194
|
-
import RecommendationsServer from '${ultramodern_workspace_packageName(scope, '
|
|
3195
|
-
import AddToCartServer from '${ultramodern_workspace_packageName(scope, '
|
|
3233
|
+
import RecommendationsServer from '${ultramodern_workspace_packageName(scope, 'explore')}/Recommendations';
|
|
3234
|
+
import AddToCartServer from '${ultramodern_workspace_packageName(scope, 'checkout')}/AddToCart';
|
|
3196
3235
|
|
|
3197
3236
|
type RemoteComponentModule = {
|
|
3198
3237
|
default: ComponentType;
|
|
@@ -3208,7 +3247,7 @@ const loadRemoteComponent = async (specifier: string) => {
|
|
|
3208
3247
|
|
|
3209
3248
|
const remoteFallback =
|
|
3210
3249
|
({ error }: { error: Error }) =>
|
|
3211
|
-
<div className="rounded-xl border border-red-900/20 bg-red-50 px-4 py-3 text-sm font-semibold text-red-900" data-remote-error={error.name}>
|
|
3250
|
+
<div className="${tw('rounded-xl border border-red-900/20 bg-red-50 px-4 py-3 text-sm font-semibold text-red-900')}" data-remote-error={error.name}>Vertical unavailable</div>;
|
|
3212
3251
|
|
|
3213
3252
|
const createHydratedRemote = (
|
|
3214
3253
|
ServerComponent: ComponentType,
|
|
@@ -3296,9 +3335,9 @@ function createAppLocaleMessages(app, language) {
|
|
|
3296
3335
|
switcher: 'en' === language ? 'Language' : 'Jazyk'
|
|
3297
3336
|
},
|
|
3298
3337
|
remotes: {
|
|
3299
|
-
checkout: 'en' === language ? 'Checkout
|
|
3300
|
-
decide: 'en' === language ? 'Decide
|
|
3301
|
-
explore: 'en' === language ? 'Explore
|
|
3338
|
+
checkout: 'en' === language ? 'Checkout Vertical' : 'Checkout remote',
|
|
3339
|
+
decide: 'en' === language ? 'Decide Vertical' : 'Decide remote',
|
|
3340
|
+
explore: 'en' === language ? 'Explore Vertical' : 'Explore remote'
|
|
3302
3341
|
},
|
|
3303
3342
|
boundaries: {
|
|
3304
3343
|
checkout: 'en' === language ? 'checkout' : 'pokladna',
|
|
@@ -3310,7 +3349,8 @@ function createAppLocaleMessages(app, language) {
|
|
|
3310
3349
|
cart: 'en' === language ? 'Cart' : 'Košík',
|
|
3311
3350
|
home: 'en' === language ? 'Home' : 'Domů',
|
|
3312
3351
|
listing: 'en' === language ? 'Tractors' : 'Traktory',
|
|
3313
|
-
productDetail: 'en' === language ? 'Tractor detail' : 'Detail traktoru'
|
|
3352
|
+
productDetail: 'en' === language ? 'Tractor detail' : 'Detail traktoru',
|
|
3353
|
+
storePicker: 'en' === language ? 'Stores' : 'Prodejci'
|
|
3314
3354
|
},
|
|
3315
3355
|
title: 'en' === language ? 'Acre & Iron' : 'Acre & Iron'
|
|
3316
3356
|
}
|
|
@@ -3383,10 +3423,11 @@ function createAppLocaleMessages(app, language) {
|
|
|
3383
3423
|
}
|
|
3384
3424
|
};
|
|
3385
3425
|
}
|
|
3386
|
-
function createDesignButton() {
|
|
3426
|
+
function createDesignButton(app) {
|
|
3427
|
+
const tw = createTw(tailwindPrefixForApp(app));
|
|
3387
3428
|
return `export default function Button({ label }: { label: string }) {
|
|
3388
3429
|
return (
|
|
3389
|
-
<button className="rounded-full text-um-foreground" type="button">
|
|
3430
|
+
<button className="${tw('rounded-full text-um-foreground')}" type="button">
|
|
3390
3431
|
{label}
|
|
3391
3432
|
</button>
|
|
3392
3433
|
);
|
|
@@ -3511,35 +3552,35 @@ function createSharedDesignTokensCss() {
|
|
|
3511
3552
|
}
|
|
3512
3553
|
`;
|
|
3513
3554
|
}
|
|
3514
|
-
function serviceEffectApiExport(service
|
|
3555
|
+
function serviceEffectApiExport(service) {
|
|
3515
3556
|
return `${toCamelCase(effectApiStem(service))}EffectApi`;
|
|
3516
3557
|
}
|
|
3517
|
-
function serviceEffectGroupName(service
|
|
3558
|
+
function serviceEffectGroupName(service) {
|
|
3518
3559
|
return toCamelCase(effectApiStem(service));
|
|
3519
3560
|
}
|
|
3520
|
-
function serviceEffectApiName(service
|
|
3561
|
+
function serviceEffectApiName(service) {
|
|
3521
3562
|
return `${toPascalCase(effectApiStem(service))}EffectApi`;
|
|
3522
3563
|
}
|
|
3523
|
-
function serviceEffectSchemaExport(service
|
|
3564
|
+
function serviceEffectSchemaExport(service) {
|
|
3524
3565
|
return `${toCamelCase(effectApiStem(service))}ItemSchema`;
|
|
3525
3566
|
}
|
|
3526
|
-
function serviceEffectMarkerSchemaExport(service
|
|
3567
|
+
function serviceEffectMarkerSchemaExport(service) {
|
|
3527
3568
|
return `${toCamelCase(effectApiStem(service))}MarkerSchema`;
|
|
3528
3569
|
}
|
|
3529
|
-
function serviceEffectReadinessSchemaExport(service
|
|
3570
|
+
function serviceEffectReadinessSchemaExport(service) {
|
|
3530
3571
|
return `${toCamelCase(effectApiStem(service))}ReadinessSchema`;
|
|
3531
3572
|
}
|
|
3532
|
-
function serviceEffectErrorStem(service
|
|
3573
|
+
function serviceEffectErrorStem(service) {
|
|
3533
3574
|
const stem = effectApiStem(service);
|
|
3534
3575
|
return 'recommendations' === stem ? 'recommendation' : stem;
|
|
3535
3576
|
}
|
|
3536
|
-
function serviceEffectCreatePayloadSchemaExport(service
|
|
3577
|
+
function serviceEffectCreatePayloadSchemaExport(service) {
|
|
3537
3578
|
return `${toCamelCase(effectApiStem(service))}CreatePayloadSchema`;
|
|
3538
3579
|
}
|
|
3539
|
-
function serviceEffectNotFoundErrorExport(service
|
|
3580
|
+
function serviceEffectNotFoundErrorExport(service) {
|
|
3540
3581
|
return `${toPascalCase(serviceEffectErrorStem(service))}NotFound`;
|
|
3541
3582
|
}
|
|
3542
|
-
function serviceEffectNotFoundSchemaExport(service
|
|
3583
|
+
function serviceEffectNotFoundSchemaExport(service) {
|
|
3543
3584
|
return `${toCamelCase(serviceEffectErrorStem(service))}NotFoundSchema`;
|
|
3544
3585
|
}
|
|
3545
3586
|
function createEffectSharedApiImports() {
|
|
@@ -3552,7 +3593,7 @@ function createEffectSharedApiImports() {
|
|
|
3552
3593
|
} from '@modern-js/plugin-bff/effect-client';
|
|
3553
3594
|
`;
|
|
3554
3595
|
}
|
|
3555
|
-
function createEffectSharedApiContract(service
|
|
3596
|
+
function createEffectSharedApiContract(service) {
|
|
3556
3597
|
const schemaExport = serviceEffectSchemaExport(service);
|
|
3557
3598
|
const markerSchemaExport = serviceEffectMarkerSchemaExport(service);
|
|
3558
3599
|
const readinessSchemaExport = serviceEffectReadinessSchemaExport(service);
|
|
@@ -3693,7 +3734,7 @@ ${createEffectSharedApiContract(service)}`;
|
|
|
3693
3734
|
} as const;
|
|
3694
3735
|
`;
|
|
3695
3736
|
}
|
|
3696
|
-
function createEffectServiceEntry(scope, service
|
|
3737
|
+
function createEffectServiceEntry(scope, service, contractImportPath = ultramodern_workspace_packageName(scope, 'shared-effect-api')) {
|
|
3697
3738
|
const apiExport = serviceEffectApiExport(service);
|
|
3698
3739
|
const groupName = serviceEffectGroupName(service);
|
|
3699
3740
|
const notFoundErrorExport = serviceEffectNotFoundErrorExport(service);
|
|
@@ -3917,7 +3958,7 @@ function createShellEffectClient(scope) {
|
|
|
3917
3958
|
getCheckoutReadiness,
|
|
3918
3959
|
listCheckout,
|
|
3919
3960
|
type CheckoutClientOptions,
|
|
3920
|
-
} from '${ultramodern_workspace_packageName(scope, '
|
|
3961
|
+
} from '${ultramodern_workspace_packageName(scope, 'checkout')}/effect/client';
|
|
3921
3962
|
|
|
3922
3963
|
export {
|
|
3923
3964
|
createDecide,
|
|
@@ -3926,7 +3967,7 @@ export {
|
|
|
3926
3967
|
getDecideReadiness,
|
|
3927
3968
|
listDecide,
|
|
3928
3969
|
type DecideClientOptions,
|
|
3929
|
-
} from '${ultramodern_workspace_packageName(scope, '
|
|
3970
|
+
} from '${ultramodern_workspace_packageName(scope, 'decide')}/effect/client';
|
|
3930
3971
|
|
|
3931
3972
|
export {
|
|
3932
3973
|
createExplore,
|
|
@@ -3935,7 +3976,7 @@ export {
|
|
|
3935
3976
|
getExploreReadiness,
|
|
3936
3977
|
listExplore,
|
|
3937
3978
|
type ExploreClientOptions,
|
|
3938
|
-
} from '${ultramodern_workspace_packageName(scope, '
|
|
3979
|
+
} from '${ultramodern_workspace_packageName(scope, 'explore')}/effect/client';
|
|
3939
3980
|
`;
|
|
3940
3981
|
}
|
|
3941
3982
|
function toPascalCase(value) {
|
|
@@ -4076,14 +4117,13 @@ function createTopology(scope) {
|
|
|
4076
4117
|
return {
|
|
4077
4118
|
schemaVersion: 1,
|
|
4078
4119
|
id: 'ultramodern-superapp-workspace-reference-topology',
|
|
4079
|
-
description: 'Generated UltraModern workspace skeleton
|
|
4120
|
+
description: 'Generated UltraModern workspace skeleton with full-stack vertical ownership.',
|
|
4080
4121
|
preset: 'presetUltramodern',
|
|
4081
|
-
sourceFixture: "scripts/mv-integration-pilot/__fixtures__/reference-topology.json",
|
|
4082
4122
|
shell: {
|
|
4083
4123
|
id: shellApp.id,
|
|
4084
4124
|
kind: 'shell',
|
|
4085
4125
|
package: ultramodern_workspace_packageName(scope, shellApp.packageSuffix),
|
|
4086
|
-
|
|
4126
|
+
verticalRefs: shellApp.verticalRefs,
|
|
4087
4127
|
moduleFederation: {
|
|
4088
4128
|
role: 'host',
|
|
4089
4129
|
name: shellApp.mfName,
|
|
@@ -4094,31 +4134,31 @@ function createTopology(scope) {
|
|
|
4094
4134
|
cloudflare: createCloudflareDeployContract(scope, shellApp),
|
|
4095
4135
|
ownership: shellApp.ownership
|
|
4096
4136
|
},
|
|
4097
|
-
|
|
4098
|
-
id:
|
|
4099
|
-
kind:
|
|
4100
|
-
domain:
|
|
4101
|
-
package: ultramodern_workspace_packageName(scope,
|
|
4137
|
+
verticals: verticalApps.map((vertical)=>({
|
|
4138
|
+
id: vertical.id,
|
|
4139
|
+
kind: vertical.kind,
|
|
4140
|
+
domain: vertical.domain,
|
|
4141
|
+
package: ultramodern_workspace_packageName(scope, vertical.packageSuffix),
|
|
4142
|
+
path: vertical.directory,
|
|
4102
4143
|
moduleFederation: {
|
|
4103
4144
|
role: 'remote',
|
|
4104
|
-
name:
|
|
4105
|
-
manifestUrl: `http://localhost:${
|
|
4106
|
-
exposes: Object.keys(
|
|
4107
|
-
...
|
|
4108
|
-
|
|
4109
|
-
remotes: createModuleFederationRemoteContracts(
|
|
4145
|
+
name: vertical.mfName,
|
|
4146
|
+
manifestUrl: `http://localhost:${vertical.port}/mf-manifest.json`,
|
|
4147
|
+
exposes: Object.keys(vertical.exposes ?? {}),
|
|
4148
|
+
...vertical.verticalRefs?.length ? {
|
|
4149
|
+
verticalRefs: vertical.verticalRefs,
|
|
4150
|
+
remotes: createModuleFederationRemoteContracts(vertical)
|
|
4110
4151
|
} : {},
|
|
4111
4152
|
ssr: true,
|
|
4112
4153
|
fallbackTelemetryEvent: 'modernjs:mv-runtime-parity',
|
|
4113
4154
|
sharedContractVersion: 'mf-ssr-contract-v1'
|
|
4114
4155
|
},
|
|
4115
|
-
...effectApiTopologyMetadata(
|
|
4116
|
-
api: effectApiTopologyMetadata(
|
|
4156
|
+
...effectApiTopologyMetadata(vertical) ? {
|
|
4157
|
+
api: effectApiTopologyMetadata(vertical)
|
|
4117
4158
|
} : {},
|
|
4118
|
-
cloudflare: createCloudflareDeployContract(scope,
|
|
4119
|
-
ownership:
|
|
4159
|
+
cloudflare: createCloudflareDeployContract(scope, vertical),
|
|
4160
|
+
ownership: vertical.ownership
|
|
4120
4161
|
})),
|
|
4121
|
-
effectServices: [],
|
|
4122
4162
|
sharedPackages: sharedPackages.map((sharedPackage)=>({
|
|
4123
4163
|
id: sharedPackage.id,
|
|
4124
4164
|
package: ultramodern_workspace_packageName(scope, sharedPackage.id),
|
|
@@ -4128,7 +4168,7 @@ function createTopology(scope) {
|
|
|
4128
4168
|
validation: {
|
|
4129
4169
|
script: "scripts/validate-ultramodern-workspace.mjs",
|
|
4130
4170
|
commands: [
|
|
4131
|
-
'
|
|
4171
|
+
'pnpm ultramodern:check'
|
|
4132
4172
|
]
|
|
4133
4173
|
}
|
|
4134
4174
|
};
|
|
@@ -4139,7 +4179,7 @@ function createOwnership(scope) {
|
|
|
4139
4179
|
preset: 'presetUltramodern',
|
|
4140
4180
|
owners: [
|
|
4141
4181
|
shellApp,
|
|
4142
|
-
...
|
|
4182
|
+
...verticalApps,
|
|
4143
4183
|
...sharedPackages.map((sharedPackage)=>({
|
|
4144
4184
|
id: sharedPackage.id,
|
|
4145
4185
|
packageSuffix: sharedPackage.id,
|
|
@@ -4173,12 +4213,12 @@ function createDevelopmentOverlay() {
|
|
|
4173
4213
|
preset: 'presetUltramodern',
|
|
4174
4214
|
ports: Object.fromEntries([
|
|
4175
4215
|
shellApp,
|
|
4176
|
-
...
|
|
4216
|
+
...verticalApps
|
|
4177
4217
|
].map((app)=>[
|
|
4178
4218
|
app.id,
|
|
4179
4219
|
app.port
|
|
4180
4220
|
])),
|
|
4181
|
-
manifests: Object.fromEntries(
|
|
4221
|
+
manifests: Object.fromEntries(verticalApps.map((remote)=>[
|
|
4182
4222
|
remote.id,
|
|
4183
4223
|
`http://localhost:${remote.port}/mf-manifest.json`
|
|
4184
4224
|
])),
|
|
@@ -4260,8 +4300,9 @@ function createAppConfigContract(app) {
|
|
|
4260
4300
|
output: {
|
|
4261
4301
|
assetPrefix: {
|
|
4262
4302
|
envFallbackOrder: [
|
|
4263
|
-
'MODERN_PUBLIC_SITE_URL',
|
|
4264
4303
|
createCloudflarePublicUrlEnv(app),
|
|
4304
|
+
'MODERN_PUBLIC_SITE_URL',
|
|
4305
|
+
'ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN',
|
|
4265
4306
|
app.portEnv
|
|
4266
4307
|
],
|
|
4267
4308
|
defaultLocalhostPort: app.port
|
|
@@ -4297,15 +4338,14 @@ function createAppConfigContract(app) {
|
|
|
4297
4338
|
}
|
|
4298
4339
|
function cssLayerName(app) {
|
|
4299
4340
|
if ('shell' === app.kind) return 'ultramodern-shell-base';
|
|
4300
|
-
return `ultramodern-
|
|
4341
|
+
return `ultramodern-vertical-${app.domain ?? app.id}`;
|
|
4301
4342
|
}
|
|
4302
4343
|
function cssRole(app) {
|
|
4303
4344
|
if ('shell' === app.kind) return 'shell-base-overlay';
|
|
4304
|
-
return '
|
|
4345
|
+
return 'vertical-css';
|
|
4305
4346
|
}
|
|
4306
4347
|
function cssClassPrefix(app) {
|
|
4307
|
-
|
|
4308
|
-
return `${app.domain ?? app.id.replace(/^remote-/, '')}-`;
|
|
4348
|
+
return `${tailwindPrefixForApp(app)}:`;
|
|
4309
4349
|
}
|
|
4310
4350
|
function createCssDedupeContract(scope) {
|
|
4311
4351
|
return {
|
|
@@ -4323,7 +4363,7 @@ function createCssSsrContract(app) {
|
|
|
4323
4363
|
cloudflare: true,
|
|
4324
4364
|
firstPaintRequired: true,
|
|
4325
4365
|
linkEmission: 'modern-ssr-css-assets',
|
|
4326
|
-
|
|
4366
|
+
verticalCss: 'shell' === app.kind ? 'host-preloads-shell-and-shared-css' : 'federated-manifest-owned-css'
|
|
4327
4367
|
};
|
|
4328
4368
|
}
|
|
4329
4369
|
function createAppCssFederationContract(scope, app) {
|
|
@@ -4359,7 +4399,7 @@ function createAppCssFederationContract(scope, app) {
|
|
|
4359
4399
|
'src/routes/index.css'
|
|
4360
4400
|
],
|
|
4361
4401
|
...'shell' !== app.kind ? {
|
|
4362
|
-
|
|
4402
|
+
federationEntry: 'src/federation-entry.tsx'
|
|
4363
4403
|
} : {}
|
|
4364
4404
|
},
|
|
4365
4405
|
assets: {
|
|
@@ -4421,10 +4461,10 @@ function createCssFederationContract(scope) {
|
|
|
4421
4461
|
'base',
|
|
4422
4462
|
'overlay'
|
|
4423
4463
|
],
|
|
4424
|
-
|
|
4464
|
+
verticals: [
|
|
4425
4465
|
'vertical-css'
|
|
4426
4466
|
],
|
|
4427
|
-
|
|
4467
|
+
forbiddenVerticalLayers: [
|
|
4428
4468
|
'ultramodern-shell-base',
|
|
4429
4469
|
'ultramodern-shell-overlay'
|
|
4430
4470
|
]
|
|
@@ -4438,9 +4478,9 @@ function createStylingContract(scope, app, enableTailwind) {
|
|
|
4438
4478
|
postcssPlugins: [
|
|
4439
4479
|
'@tailwindcss/postcss'
|
|
4440
4480
|
],
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4481
|
+
prefix: tailwindPrefixForApp(app),
|
|
4482
|
+
source: '..',
|
|
4483
|
+
sourceMode: 'source(none)'
|
|
4444
4484
|
} : {},
|
|
4445
4485
|
federation: createAppCssFederationContract(scope, app)
|
|
4446
4486
|
};
|
|
@@ -4448,7 +4488,7 @@ function createStylingContract(scope, app, enableTailwind) {
|
|
|
4448
4488
|
function createAppGeneratedContract(scope, app, apps, enableTailwind) {
|
|
4449
4489
|
const appWithResolvedRefs = 'shell' === app.kind ? {
|
|
4450
4490
|
...app,
|
|
4451
|
-
|
|
4491
|
+
verticalRefs: apps.filter((candidate)=>'shell' !== candidate.kind).map((candidate)=>candidate.id)
|
|
4452
4492
|
} : app;
|
|
4453
4493
|
const consumedRemotes = createModuleFederationRemoteContracts(appWithResolvedRefs, apps);
|
|
4454
4494
|
return {
|
|
@@ -4512,8 +4552,8 @@ function createAppGeneratedContract(scope, app, apps, enableTailwind) {
|
|
|
4512
4552
|
},
|
|
4513
4553
|
moduleFederation: {
|
|
4514
4554
|
name: app.mfName,
|
|
4515
|
-
...appWithResolvedRefs.
|
|
4516
|
-
|
|
4555
|
+
...appWithResolvedRefs.verticalRefs?.length ? {
|
|
4556
|
+
verticalRefs: appWithResolvedRefs.verticalRefs,
|
|
4517
4557
|
remotes: consumedRemotes
|
|
4518
4558
|
} : {},
|
|
4519
4559
|
exposes: Object.keys(app.exposes ?? {}),
|
|
@@ -4554,7 +4594,7 @@ function createAppGeneratedContract(scope, app, apps, enableTailwind) {
|
|
|
4554
4594
|
}
|
|
4555
4595
|
function createGeneratedContract(scope, apps = [
|
|
4556
4596
|
shellApp,
|
|
4557
|
-
...
|
|
4597
|
+
...verticalApps
|
|
4558
4598
|
], enableTailwind = true) {
|
|
4559
4599
|
return {
|
|
4560
4600
|
schemaVersion: 1,
|
|
@@ -4586,7 +4626,7 @@ function createTemplateManifest(modernVersion, packageSource) {
|
|
|
4586
4626
|
id: 'modernjs-ultramodern-superapp-workspace',
|
|
4587
4627
|
version: modernVersion,
|
|
4588
4628
|
displayName: 'Modern.js UltraModern SuperApp Workspace',
|
|
4589
|
-
description: 'Canonical shell,
|
|
4629
|
+
description: 'Canonical shell, full-stack verticals, shared packages, and topology skeleton.',
|
|
4590
4630
|
compatibilityLane: 'ultramodern-mv',
|
|
4591
4631
|
minimumModernVersion: modernVersion
|
|
4592
4632
|
},
|
|
@@ -4614,6 +4654,7 @@ function createTemplateManifest(modernVersion, packageSource) {
|
|
|
4614
4654
|
targetRoot: 'generated-project-root',
|
|
4615
4655
|
allowedPaths: [
|
|
4616
4656
|
'.agents/**',
|
|
4657
|
+
'.codex/**',
|
|
4617
4658
|
'.github/**',
|
|
4618
4659
|
'.gitignore',
|
|
4619
4660
|
'.mise.toml',
|
|
@@ -4622,6 +4663,7 @@ function createTemplateManifest(modernVersion, packageSource) {
|
|
|
4622
4663
|
'README.md',
|
|
4623
4664
|
'apps/**',
|
|
4624
4665
|
'packages/**',
|
|
4666
|
+
'lefthook.yml',
|
|
4625
4667
|
'package.json',
|
|
4626
4668
|
'oxfmt.config.ts',
|
|
4627
4669
|
'oxlint.config.ts',
|
|
@@ -4692,13 +4734,13 @@ function createTemplateManifest(modernVersion, packageSource) {
|
|
|
4692
4734
|
],
|
|
4693
4735
|
expectedCommands: [
|
|
4694
4736
|
'mise install',
|
|
4695
|
-
'
|
|
4696
|
-
'
|
|
4737
|
+
'pnpm install',
|
|
4738
|
+
'pnpm run ultramodern:check'
|
|
4697
4739
|
]
|
|
4698
4740
|
}
|
|
4699
4741
|
};
|
|
4700
4742
|
}
|
|
4701
|
-
function createAssertMfTypesScript(remotes =
|
|
4743
|
+
function createAssertMfTypesScript(remotes = verticalApps) {
|
|
4702
4744
|
return `import fs from 'node:fs';
|
|
4703
4745
|
import path from 'node:path';
|
|
4704
4746
|
|
|
@@ -4770,7 +4812,7 @@ for (const appDir of appDirs) {
|
|
|
4770
4812
|
}
|
|
4771
4813
|
`;
|
|
4772
4814
|
}
|
|
4773
|
-
function createWorkspaceValidationScript(scope, enableTailwind, remotes =
|
|
4815
|
+
function createWorkspaceValidationScript(scope, enableTailwind, remotes = verticalApps) {
|
|
4774
4816
|
const verticals = remotes.filter(appHasEffectApi).map((remote)=>({
|
|
4775
4817
|
id: remote.id,
|
|
4776
4818
|
domain: remote.domain,
|
|
@@ -4779,19 +4821,18 @@ function createWorkspaceValidationScript(scope, enableTailwind, remotes = remote
|
|
|
4779
4821
|
path: remote.directory,
|
|
4780
4822
|
mfName: remote.mfName,
|
|
4781
4823
|
apiPrefix: remote.effectApi.prefix,
|
|
4824
|
+
tailwindPrefix: tailwindPrefixForApp(remote),
|
|
4782
4825
|
packageName: ultramodern_workspace_packageName(scope, remote.packageSuffix),
|
|
4783
4826
|
exposes: Object.keys(remote.exposes ?? {}),
|
|
4784
4827
|
componentPaths: Object.keys(remote.exposes ?? {}).map((expose)=>remoteComponentOutputPath(remote, expose)).filter((componentPath)=>Boolean(componentPath)),
|
|
4785
4828
|
namespace: appI18nNamespace(remote),
|
|
4786
4829
|
routePagePaths: createRouteOwnedI18nPaths(remote).filter((route)=>'/' !== route.canonicalPath).map((route)=>createRoutePageFilePath(remote, route.canonicalPath)),
|
|
4787
4830
|
localisedUrls: createLocalisedUrlsMap(remote),
|
|
4788
|
-
|
|
4831
|
+
verticalRefs: remote.verticalRefs ?? []
|
|
4789
4832
|
}));
|
|
4790
4833
|
const shellNamespace = appI18nNamespace(shellApp);
|
|
4791
4834
|
const oldRemotePaths = [
|
|
4792
|
-
'apps/remotes
|
|
4793
|
-
'apps/remotes/remote-identity',
|
|
4794
|
-
'apps/remotes/remote-design-system'
|
|
4835
|
+
'apps/remotes'
|
|
4795
4836
|
];
|
|
4796
4837
|
return `import { execFileSync } from 'node:child_process';
|
|
4797
4838
|
import fs from 'node:fs';
|
|
@@ -4828,7 +4869,7 @@ const activePnpmVersion = execFileSync('pnpm', ['--version'], {
|
|
|
4828
4869
|
|
|
4829
4870
|
assert(
|
|
4830
4871
|
activePnpmVersion === expectedPnpmVersion,
|
|
4831
|
-
\`Generated workspace requires pnpm \${expectedPnpmVersion}; active pnpm is \${activePnpmVersion}. Run mise install, then rerun
|
|
4872
|
+
\`Generated workspace requires pnpm \${expectedPnpmVersion}; active pnpm is \${activePnpmVersion}. Run mise install, then rerun pnpm from the activated shell.\`,
|
|
4832
4873
|
);
|
|
4833
4874
|
|
|
4834
4875
|
const requiredPaths = [
|
|
@@ -4884,7 +4925,7 @@ for (const vertical of fullStackVerticals) {
|
|
|
4884
4925
|
\`\${vertical.path}/src/effect/\${vertical.stem}-client.ts\`,
|
|
4885
4926
|
\`\${vertical.path}/src/modern-app-env.d.ts\`,
|
|
4886
4927
|
\`\${vertical.path}/src/modern.runtime.ts\`,
|
|
4887
|
-
\`\${vertical.path}/src/
|
|
4928
|
+
\`\${vertical.path}/src/federation-entry.tsx\`,
|
|
4888
4929
|
...vertical.componentPaths,
|
|
4889
4930
|
\`\${vertical.path}/locales/en/translation.json\`,
|
|
4890
4931
|
\`\${vertical.path}/locales/en/\${vertical.namespace}.json\`,
|
|
@@ -4933,18 +4974,21 @@ assert(packageSource.strategy === 'workspace' || packageSource.strategy === 'ins
|
|
|
4933
4974
|
assert(packageSource.generatedWorkspacePackages?.specifier === 'workspace:*', 'Generated workspace packages must keep workspace:* links');
|
|
4934
4975
|
assert(
|
|
4935
4976
|
rootPackage.scripts?.build ===
|
|
4936
|
-
'pnpm -r --filter "./
|
|
4937
|
-
'Root build script must build
|
|
4977
|
+
'pnpm -r --filter "./verticals/*" run build && pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types',
|
|
4978
|
+
'Root build script must build verticals before shell',
|
|
4938
4979
|
);
|
|
4939
4980
|
assert(rootPackage.scripts?.['ultramodern:check'] === 'node ./scripts/validate-ultramodern-workspace.mjs', 'Root must expose ultramodern:check');
|
|
4940
4981
|
assert(rootPackage.scripts?.['ultramodern:assert-mf-types'] === 'node ./scripts/assert-mf-types.mjs', 'Root must expose ultramodern:assert-mf-types');
|
|
4941
4982
|
assert(rootPackage.scripts?.['cloudflare:deploy']?.includes('run cloudflare:deploy'), 'Root must expose cloudflare:deploy');
|
|
4942
4983
|
assert(rootPackage.scripts?.['cloudflare:proof'] === 'node ./scripts/proof-cloudflare-version.mjs --out .codex/reports/cloudflare-version-proof/public-url-proof.json', 'Root must expose cloudflare:proof');
|
|
4984
|
+
assert(rootPackage.scripts?.['skills:install'] === 'node ./scripts/bootstrap-agent-skills.mjs', 'Root must expose skills:install');
|
|
4985
|
+
assert(rootPackage.scripts?.['skills:check'] === 'node ./scripts/bootstrap-agent-skills.mjs --check', 'Root must expose skills:check');
|
|
4986
|
+
assert(rootPackage.scripts?.postinstall === 'node ./scripts/bootstrap-agent-skills.mjs && (git rev-parse --is-inside-work-tree >/dev/null 2>&1 && lefthook install || true) && node ./scripts/setup-agent-reference-repos.mjs', 'Root postinstall must bootstrap agent skills and hooks before reference repositories');
|
|
4943
4987
|
|
|
4944
4988
|
const expectedAppIds = ['shell-super-app', ...fullStackVerticals.map(vertical => vertical.id)];
|
|
4945
4989
|
assert(
|
|
4946
4990
|
JSON.stringify(generatedContract.apps?.map(app => app.id)) === JSON.stringify(expectedAppIds),
|
|
4947
|
-
'Generated contract must contain shell plus the Tractor full-stack
|
|
4991
|
+
'Generated contract must contain shell plus the Tractor full-stack verticals',
|
|
4948
4992
|
);
|
|
4949
4993
|
assert(generatedContract.cssFederation?.sharedDesignTokens?.owner?.id === 'shared-design-tokens', 'CSS federation must declare shared design token ownership');
|
|
4950
4994
|
assert(generatedContract.cssFederation?.sharedDesignTokens?.role === 'shared-design-tokens', 'CSS federation must mark shared-design-tokens as token owner');
|
|
@@ -4966,7 +5010,7 @@ const expectedZephyrDependencies = Object.fromEntries(
|
|
|
4966
5010
|
assert(
|
|
4967
5011
|
JSON.stringify(shellPackage['zephyr:dependencies']) ===
|
|
4968
5012
|
JSON.stringify(expectedZephyrDependencies),
|
|
4969
|
-
'Shell Zephyr dependencies must reference every Tractor
|
|
5013
|
+
'Shell Zephyr dependencies must reference every Tractor vertical package',
|
|
4970
5014
|
);
|
|
4971
5015
|
const shellContract = generatedContract.apps?.find(app => app.id === 'shell-super-app');
|
|
4972
5016
|
assert(shellContract?.deploy?.cloudflare?.workerName === expectedWorkerName('shell-super-app'), 'Shell Cloudflare workerName is incorrect');
|
|
@@ -4975,7 +5019,7 @@ assert(topology.shell?.cloudflare?.workerName === expectedWorkerName('shell-supe
|
|
|
4975
5019
|
assert(shellContract?.styling?.federation?.owner?.id === 'shell-super-app', 'Shell CSS federation owner is missing');
|
|
4976
5020
|
assert(shellContract?.styling?.federation?.role === 'shell-base-overlay', 'Shell must own base and overlay CSS');
|
|
4977
5021
|
assert(shellContract?.styling?.federation?.rootSelector === '[data-app-id="shell-super-app"]', 'Shell CSS root selector is incorrect');
|
|
4978
|
-
assert(shellContract?.styling?.federation?.classPrefix === 'shell
|
|
5022
|
+
assert(shellContract?.styling?.federation?.classPrefix === 'shell:', 'Shell CSS class prefix is incorrect');
|
|
4979
5023
|
assert(shellContract?.styling?.federation?.layers?.owned?.includes('ultramodern-shell-base'), 'Shell must own the base CSS layer');
|
|
4980
5024
|
assert(shellContract?.styling?.federation?.layers?.owned?.includes('ultramodern-shell-overlay'), 'Shell must own the overlay CSS layer');
|
|
4981
5025
|
assert(shellContract?.styling?.federation?.entrypoints?.css?.includes('src/routes/index.css'), 'Shell CSS entrypoint is missing');
|
|
@@ -4983,23 +5027,24 @@ assert(shellContract?.styling?.federation?.assets?.shared?.some(asset => asset.e
|
|
|
4983
5027
|
assert(shellContract?.styling?.federation?.dedupe?.duplicateBaseStylesAllowed === false, 'Shell CSS contract must forbid duplicated base styles');
|
|
4984
5028
|
assert(shellContract?.styling?.federation?.ssr?.firstPaintRequired === true, 'Shell CSS must be required for SSR first paint');
|
|
4985
5029
|
assert(
|
|
4986
|
-
topology.shell?.
|
|
4987
|
-
'Topology shell
|
|
5030
|
+
topology.shell?.verticalRefs?.join(',') === fullStackVerticals.map(vertical => vertical.id).join(','),
|
|
5031
|
+
'Topology shell verticalRefs must match Tractor verticals',
|
|
4988
5032
|
);
|
|
4989
|
-
assert(topology.
|
|
4990
|
-
assert((
|
|
5033
|
+
assert(topology.verticals?.length === fullStackVerticals.length, 'Topology must contain only Tractor verticals');
|
|
5034
|
+
assert(!('remotes' in topology), 'Topology must not expose legacy remotes; use verticals');
|
|
5035
|
+
assert(!('effectServices' in topology), 'Default APIs must be vertical-owned, not effectServices');
|
|
4991
5036
|
|
|
4992
5037
|
for (const vertical of fullStackVerticals) {
|
|
4993
5038
|
const packageJson = readJson(\`\${vertical.path}/package.json\`);
|
|
4994
5039
|
assert(packageJson.name === vertical.packageName, \`\${vertical.id} package name is incorrect\`);
|
|
4995
|
-
assert(packageJson.scripts?.['cloudflare:deploy'] === '
|
|
5040
|
+
assert(packageJson.scripts?.['cloudflare:deploy'] === 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true pnpm run cloudflare:build && wrangler deploy --config .output/wrangler.json', \`\${vertical.id} must expose cloudflare:deploy\`);
|
|
4996
5041
|
assert(packageJson.scripts?.['cloudflare:proof']?.includes(\`--app \${vertical.id}\`), \`\${vertical.id} must expose cloudflare:proof\`);
|
|
4997
5042
|
assert(packageJson.dependencies?.['@modern-js/plugin-bff'], \`\${vertical.id} must depend on plugin-bff\`);
|
|
4998
5043
|
assert(packageJson.exports?.['./effect/client'] === \`./src/effect/\${vertical.stem}-client.ts\`, \`\${vertical.id} must export its Effect client\`);
|
|
4999
5044
|
assert(packageJson.exports?.['./shared/effect/api'] === './shared/effect/api.ts', \`\${vertical.id} must export its Effect API contract\`);
|
|
5000
5045
|
const expectedVerticalZephyrDependencies = Object.fromEntries(
|
|
5001
5046
|
fullStackVerticals
|
|
5002
|
-
.filter(candidate => vertical.
|
|
5047
|
+
.filter(candidate => vertical.verticalRefs.includes(candidate.id))
|
|
5003
5048
|
.map(candidate => [
|
|
5004
5049
|
candidate.domain,
|
|
5005
5050
|
\`\${candidate.packageName}@workspace:*\`,
|
|
@@ -5008,7 +5053,7 @@ for (const vertical of fullStackVerticals) {
|
|
|
5008
5053
|
assert(
|
|
5009
5054
|
JSON.stringify(packageJson['zephyr:dependencies']) ===
|
|
5010
5055
|
JSON.stringify(expectedVerticalZephyrDependencies),
|
|
5011
|
-
\`\${vertical.id} Zephyr dependencies must match declared
|
|
5056
|
+
\`\${vertical.id} Zephyr dependencies must match declared vertical refs\`,
|
|
5012
5057
|
);
|
|
5013
5058
|
|
|
5014
5059
|
const contractEntry = generatedContract.apps?.find(app => app.id === vertical.id);
|
|
@@ -5020,11 +5065,11 @@ for (const vertical of fullStackVerticals) {
|
|
|
5020
5065
|
assert(contractEntry?.moduleFederation?.name === vertical.mfName, \`\${vertical.id} MF name is incorrect\`);
|
|
5021
5066
|
assert(JSON.stringify(contractEntry?.moduleFederation?.exposes) === JSON.stringify(vertical.exposes), \`\${vertical.id} MF exposes are incorrect\`);
|
|
5022
5067
|
assert(contractEntry?.moduleFederation?.dts?.compilerInstance === '--package typescript -- tsc', \`\${vertical.id} must keep mandatory DTS compiler\`);
|
|
5023
|
-
assert(JSON.stringify(contractEntry?.moduleFederation?.
|
|
5068
|
+
assert(JSON.stringify(contractEntry?.moduleFederation?.verticalRefs ?? []) === JSON.stringify(vertical.verticalRefs), \`\${vertical.id} MF verticalRefs are incorrect\`);
|
|
5024
5069
|
assert(
|
|
5025
5070
|
JSON.stringify((contractEntry?.moduleFederation?.remotes ?? []).map(remote => remote.id)) ===
|
|
5026
|
-
JSON.stringify(vertical.
|
|
5027
|
-
\`\${vertical.id} MF consumed
|
|
5071
|
+
JSON.stringify(vertical.verticalRefs),
|
|
5072
|
+
\`\${vertical.id} MF consumed verticals are incorrect\`,
|
|
5028
5073
|
);
|
|
5029
5074
|
assert(contractEntry?.effect?.prefix === vertical.apiPrefix, \`\${vertical.id} Effect API prefix is incorrect\`);
|
|
5030
5075
|
assert(contractEntry?.effect?.group === vertical.group, \`\${vertical.id} Effect group is incorrect\`);
|
|
@@ -5041,23 +5086,23 @@ for (const vertical of fullStackVerticals) {
|
|
|
5041
5086
|
assert(contractEntry?.routes?.source === 'route-owned', \`\${vertical.id} routes must be route-owned\`);
|
|
5042
5087
|
assert(contractEntry?.routes?.metadataExport === './src/routes/ultramodern-route-metadata', \`\${vertical.id} route metadata export is incorrect\`);
|
|
5043
5088
|
assert(contractEntry?.styling?.federation?.owner?.id === vertical.id, \`\${vertical.id} CSS federation owner is missing\`);
|
|
5044
|
-
assert(contractEntry?.styling?.federation?.role === 'vertical-
|
|
5089
|
+
assert(contractEntry?.styling?.federation?.role === 'vertical-css', \`\${vertical.id} must own only vertical CSS\`);
|
|
5045
5090
|
assert(contractEntry?.styling?.federation?.rootSelector === \`[data-app-id="\${vertical.id}"]\`, \`\${vertical.id} CSS root selector is incorrect\`);
|
|
5046
|
-
assert(contractEntry?.styling?.federation?.classPrefix === \`\${vertical.domain}
|
|
5047
|
-
assert(contractEntry?.styling?.federation?.layers?.owned?.includes(\`ultramodern-
|
|
5091
|
+
assert(contractEntry?.styling?.federation?.classPrefix === \`\${vertical.domain}:\`, \`\${vertical.id} CSS class prefix is incorrect\`);
|
|
5092
|
+
assert(contractEntry?.styling?.federation?.layers?.owned?.includes(\`ultramodern-vertical-\${vertical.domain}\`), \`\${vertical.id} vertical CSS layer is missing\`);
|
|
5048
5093
|
assert(!contractEntry?.styling?.federation?.layers?.owned?.includes('ultramodern-shell-base'), \`\${vertical.id} must not own shell base CSS\`);
|
|
5049
|
-
assert(contractEntry?.styling?.federation?.entrypoints?.
|
|
5094
|
+
assert(contractEntry?.styling?.federation?.entrypoints?.federationEntry === 'src/federation-entry.tsx', \`\${vertical.id} CSS contract must include federation entry\`);
|
|
5050
5095
|
assert(contractEntry?.styling?.federation?.assets?.shared?.some(asset => asset.endsWith('/shared-design-tokens/tokens.css')), \`\${vertical.id} must import shared design token CSS\`);
|
|
5051
5096
|
assert(contractEntry?.styling?.federation?.dedupe?.runtimeLoad === 'once-per-content-hash', \`\${vertical.id} CSS dedupe strategy is incorrect\`);
|
|
5052
|
-
assert(contractEntry?.styling?.federation?.ssr?.
|
|
5097
|
+
assert(contractEntry?.styling?.federation?.ssr?.verticalCss === 'federated-manifest-owned-css', \`\${vertical.id} SSR CSS loading contract is incorrect\`);
|
|
5053
5098
|
|
|
5054
|
-
const topologyEntry = topology.
|
|
5099
|
+
const topologyEntry = topology.verticals?.find(verticalEntry => verticalEntry.id === vertical.id);
|
|
5055
5100
|
assert(topologyEntry?.kind === 'vertical', \`\${vertical.id} topology kind is incorrect\`);
|
|
5056
5101
|
assert(topologyEntry?.package === vertical.packageName, \`\${vertical.id} topology package is incorrect\`);
|
|
5057
5102
|
assert(topologyEntry?.cloudflare?.workerName === expectedWorkerName(vertical.id), \`\${vertical.id} topology Cloudflare workerName is incorrect\`);
|
|
5058
5103
|
assert(topologyEntry?.moduleFederation?.name === vertical.mfName, \`\${vertical.id} topology MF name is incorrect\`);
|
|
5059
5104
|
assert(JSON.stringify(topologyEntry?.moduleFederation?.exposes) === JSON.stringify(vertical.exposes), \`\${vertical.id} topology exposes are incorrect\`);
|
|
5060
|
-
assert(JSON.stringify(topologyEntry?.moduleFederation?.
|
|
5105
|
+
assert(JSON.stringify(topologyEntry?.moduleFederation?.verticalRefs ?? []) === JSON.stringify(vertical.verticalRefs), \`\${vertical.id} topology verticalRefs are incorrect\`);
|
|
5061
5106
|
assert(topologyEntry?.api?.effect?.bff?.prefix === vertical.apiPrefix, \`\${vertical.id} topology API prefix is incorrect\`);
|
|
5062
5107
|
assert(topologyEntry?.api?.effect?.serverEntry === \`\${vertical.path}/api/effect/index.ts\`, \`\${vertical.id} topology server entry is incorrect\`);
|
|
5063
5108
|
assert(topologyEntry?.api?.effect?.readiness?.endpoint === \`/effect/\${vertical.stem}/readiness\`, \`\${vertical.id} topology readiness endpoint is incorrect\`);
|
|
@@ -5124,10 +5169,10 @@ function parseArgs(argv) {
|
|
|
5124
5169
|
|
|
5125
5170
|
function printHelp() {
|
|
5126
5171
|
process.stdout.write(\`Usage:
|
|
5127
|
-
node scripts/proof-cloudflare-version.mjs [--app
|
|
5172
|
+
node scripts/proof-cloudflare-version.mjs [--app explore] [--out evidence.json] [--require-public-urls]
|
|
5128
5173
|
|
|
5129
5174
|
Set each app's public URL using the contract env key, for example:
|
|
5130
|
-
|
|
5175
|
+
ULTRAMODERN_PUBLIC_URL_EXPLORE=https://explore.example.workers.dev
|
|
5131
5176
|
\`);
|
|
5132
5177
|
}
|
|
5133
5178
|
|
|
@@ -5392,12 +5437,15 @@ main().then(
|
|
|
5392
5437
|
);
|
|
5393
5438
|
`;
|
|
5394
5439
|
}
|
|
5395
|
-
function writeGeneratedWorkspaceScripts(targetDir, scope, enableTailwind, remotes =
|
|
5440
|
+
function writeGeneratedWorkspaceScripts(targetDir, scope, enableTailwind, remotes = verticalApps) {
|
|
5396
5441
|
writeFileReplacing(targetDir, "scripts/assert-mf-types.mjs", createAssertMfTypesScript());
|
|
5397
5442
|
writeFileReplacing(targetDir, "scripts/validate-ultramodern-workspace.mjs", createWorkspaceValidationScript(scope, enableTailwind, remotes));
|
|
5398
5443
|
writeFileReplacing(targetDir, "scripts/proof-cloudflare-version.mjs", createCloudflareVersionProofScript());
|
|
5399
5444
|
}
|
|
5400
5445
|
function writeApp(targetDir, scope, app, packageSource, enableTailwind) {
|
|
5446
|
+
const writeAppFile = (relativePath, content)=>{
|
|
5447
|
+
writeFile(targetDir, `${app.directory}/${relativePath}`, content);
|
|
5448
|
+
};
|
|
5401
5449
|
writeJson(targetDir, `${app.directory}/package.json`, createAppPackage(scope, app, packageSource, enableTailwind));
|
|
5402
5450
|
writeJson(targetDir, `${app.directory}/tsconfig.json`, createPackageTsConfig(app.directory, appHasEffectApi(app)));
|
|
5403
5451
|
writeFile(targetDir, `${app.directory}/src/modern-app-env.d.ts`, createAppEnvDts(app));
|
|
@@ -5414,75 +5462,40 @@ function writeApp(targetDir, scope, app, packageSource, enableTailwind) {
|
|
|
5414
5462
|
writeFile(targetDir, `${app.directory}/postcss.config.mjs`, createPostcssConfig());
|
|
5415
5463
|
writeFile(targetDir, `${app.directory}/tailwind.config.ts`, createTailwindConfig());
|
|
5416
5464
|
}
|
|
5417
|
-
writeFile(targetDir, `${app.directory}/module-federation.config.ts`, 'shell' === app.kind ? createShellModuleFederationConfig() : createRemoteModuleFederationConfig(app));
|
|
5418
|
-
|
|
5465
|
+
writeFile(targetDir, `${app.directory}/module-federation.config.ts`, 'shell' === app.kind ? createShellModuleFederationConfig(scope) : createRemoteModuleFederationConfig(scope, app));
|
|
5466
|
+
writeAppFile('src/routes/layout.tsx', createLayout(app.id));
|
|
5419
5467
|
for (const [relativePath, content] of Object.entries(commerceAssetsForApp(app)))writeFile(targetDir, `${app.directory}/${relativePath}`, content);
|
|
5420
|
-
|
|
5468
|
+
writeAppFile('src/routes/[lang]/page.tsx', 'shell' === app.kind ? createShellPage() : createRemotePage(app));
|
|
5421
5469
|
for (const route of createRouteOwnedI18nPaths(app))if ('/' !== route.canonicalPath && 'shell' !== app.kind) writeFile(targetDir, createRoutePageFilePath(app, route.canonicalPath), createRouteAliasPage(route.canonicalPath));
|
|
5422
5470
|
if ('shell' === app.kind) {
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5471
|
+
writeAppFile('src/routes/vertical-components.tsx', createShellRemoteComponents(scope));
|
|
5472
|
+
writeAppFile('src/routes/shell-frame.tsx', createShellFrameComponent());
|
|
5473
|
+
writeAppFile('src/routes/boundary-overlay.tsx', createShellBoundaryOverlay());
|
|
5426
5474
|
writeFile(targetDir, `${app.directory}/src/effect/recommendations-client.ts`, createShellEffectClient(scope));
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5475
|
+
writeAppFile('src/routes/[lang]/tractors/page.tsx', createShellTractorsPage());
|
|
5476
|
+
writeAppFile('src/routes/[lang]/stores/page.tsx', createShellStoresPage());
|
|
5477
|
+
writeAppFile('src/routes/[lang]/tractors/[slug]/page.tsx', createShellProductPage());
|
|
5478
|
+
writeAppFile('src/routes/[lang]/cart/page.tsx', createShellCartPage());
|
|
5430
5479
|
}
|
|
5431
5480
|
if (appHasEffectApi(app)) {
|
|
5432
5481
|
writeFile(targetDir, `${app.directory}/shared/effect/api.ts`, createEffectSharedApi(app));
|
|
5433
5482
|
writeFile(targetDir, `${app.directory}/api/effect/index.ts`, createEffectServiceEntry(scope, app, '../../shared/effect/api'));
|
|
5434
5483
|
writeFile(targetDir, `${app.directory}/src/effect/${app.effectApi.stem}-client.ts`, createEffectClient(app, '../../shared/effect/api'));
|
|
5435
5484
|
}
|
|
5436
|
-
if ('vertical' === app.kind
|
|
5437
|
-
|
|
5438
|
-
if ('
|
|
5439
|
-
if ('
|
|
5485
|
+
if ('vertical' === app.kind) {
|
|
5486
|
+
writeAppFile('src/federation-entry.tsx', createRemoteEntry(app));
|
|
5487
|
+
if ('decide' === app.id) writeAppFile('src/components/vertical-components.tsx', createDecideRemoteComponents(scope, app));
|
|
5488
|
+
if ('checkout' === app.id) writeFile(targetDir, `${app.directory}/src/cart-store.ts`, createCheckoutCartStore());
|
|
5440
5489
|
for (const expose of Object.keys(app.exposes ?? {})){
|
|
5441
5490
|
const outputPath = remoteComponentOutputPath(app, expose);
|
|
5442
|
-
if (outputPath)
|
|
5491
|
+
if (outputPath) writeAppFile(outputPath.slice(app.directory.length + 1), createRemoteExposeComponent(app, expose));
|
|
5443
5492
|
}
|
|
5444
5493
|
}
|
|
5445
5494
|
if ('horizontal-design-system' === app.kind) {
|
|
5446
|
-
|
|
5495
|
+
writeAppFile('src/components/button.tsx', createDesignButton(app));
|
|
5447
5496
|
writeFile(targetDir, `${app.directory}/src/tokens.ts`, createDesignTokens());
|
|
5448
5497
|
}
|
|
5449
5498
|
}
|
|
5450
|
-
function writeEffectService(targetDir, scope, packageSource, enableTailwind, service = effectService) {
|
|
5451
|
-
writeJson(targetDir, `${service.directory}/package.json`, createServicePackage(scope, packageSource, enableTailwind, service));
|
|
5452
|
-
writeJson(targetDir, `${service.directory}/tsconfig.json`, createPackageTsConfig(service.directory, true));
|
|
5453
|
-
writeFile(targetDir, `${service.directory}/src/modern-app-env.d.ts`, "/// <reference types='@modern-js/app-tools/types' />\n");
|
|
5454
|
-
writeFile(targetDir, `${service.directory}/src/ultramodern-build.ts`, createUltramodernBuildModule(scope, service));
|
|
5455
|
-
writeFile(targetDir, `${service.directory}/src/routes/layout.tsx`, createLayout(service.id));
|
|
5456
|
-
writeFile(targetDir, `${service.directory}/src/routes/page.tsx`, `export default function ${toPascalCase(service.id)}Home() {
|
|
5457
|
-
return (
|
|
5458
|
-
<main className="min-h-screen bg-um-canvas px-4 py-6 text-um-foreground sm:px-8">
|
|
5459
|
-
<section className="rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10">
|
|
5460
|
-
<h1 className="text-3xl font-black">${service.id} Effect service</h1>
|
|
5461
|
-
</section>
|
|
5462
|
-
</main>
|
|
5463
|
-
);
|
|
5464
|
-
}
|
|
5465
|
-
`);
|
|
5466
|
-
writeFile(targetDir, `${service.directory}/src/routes/index.css`, createServiceStyles(enableTailwind, scope, service));
|
|
5467
|
-
if (enableTailwind) {
|
|
5468
|
-
writeFile(targetDir, `${service.directory}/postcss.config.mjs`, createPostcssConfig());
|
|
5469
|
-
writeFile(targetDir, `${service.directory}/tailwind.config.ts`, createTailwindConfig());
|
|
5470
|
-
}
|
|
5471
|
-
writeFile(targetDir, `${service.directory}/modern.config.ts`, createServiceModernConfigFor(service));
|
|
5472
|
-
writeFile(targetDir, `${service.directory}/api/effect/index.ts`, createEffectServiceEntry(scope, service));
|
|
5473
|
-
}
|
|
5474
|
-
function writeGenericSharedPackage(targetDir, scope, packageSource, sharedPackage) {
|
|
5475
|
-
writeJson(targetDir, `${sharedPackage.directory}/package.json`, createSharedPackage(scope, sharedPackage.id, sharedPackage.description, packageSource));
|
|
5476
|
-
writeJson(targetDir, `${sharedPackage.directory}/tsconfig.json`, {
|
|
5477
|
-
extends: `${relativeRootFor(sharedPackage.directory)}/tsconfig.base.json`,
|
|
5478
|
-
include: [
|
|
5479
|
-
'src'
|
|
5480
|
-
]
|
|
5481
|
-
});
|
|
5482
|
-
writeFile(targetDir, `${sharedPackage.directory}/src/index.ts`, `export const packageId = '${sharedPackage.id}';
|
|
5483
|
-
`);
|
|
5484
|
-
if ('shared-design-tokens' === sharedPackage.id) writeFile(targetDir, `${sharedPackage.directory}/src/tokens.css`, createSharedDesignTokensCss());
|
|
5485
|
-
}
|
|
5486
5499
|
function writeSharedPackages(targetDir, scope, packageSource) {
|
|
5487
5500
|
for (const sharedPackage of sharedPackages){
|
|
5488
5501
|
writeJson(targetDir, `${sharedPackage.directory}/package.json`, createSharedPackage(scope, sharedPackage.id, sharedPackage.description, packageSource));
|
|
@@ -5516,18 +5529,6 @@ function readJsonFile(filePath) {
|
|
|
5516
5529
|
function writeJsonFile(filePath, value) {
|
|
5517
5530
|
node_fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
|
|
5518
5531
|
}
|
|
5519
|
-
function appendEffectSharedApiContract(targetDir, service = effectService) {
|
|
5520
|
-
const relativePath = 'packages/shared-effect-api/src/index.ts';
|
|
5521
|
-
assertSafeRelativePath(relativePath);
|
|
5522
|
-
const filePath = node_path.join(targetDir, relativePath);
|
|
5523
|
-
ensureInsideRoot(targetDir, filePath);
|
|
5524
|
-
if (!node_fs.existsSync(filePath)) throw new Error(`Missing generated Effect API package: ${relativePath}`);
|
|
5525
|
-
const current = node_fs.readFileSync(filePath, 'utf-8');
|
|
5526
|
-
const apiExport = serviceEffectApiExport(service);
|
|
5527
|
-
if (current.includes(`export const ${apiExport} =`)) return;
|
|
5528
|
-
const contentWithImports = current.includes('@modern-js/plugin-bff/effect-client') ? current.trimEnd() : `${createEffectSharedApiImports()}\n${current.trimEnd()}`;
|
|
5529
|
-
node_fs.writeFileSync(filePath, `${contentWithImports}\n\n${createEffectSharedApiContract(service)}`, 'utf-8');
|
|
5530
|
-
}
|
|
5531
5532
|
function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
|
|
5532
5533
|
if (packageSource) return resolvePackageSource({
|
|
5533
5534
|
targetDir: workspaceRoot,
|
|
@@ -5557,9 +5558,17 @@ function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
|
|
|
5557
5558
|
aliasPackageNamePrefix
|
|
5558
5559
|
};
|
|
5559
5560
|
}
|
|
5560
|
-
function
|
|
5561
|
+
function existingTailwindEnabled(workspaceRoot) {
|
|
5562
|
+
const contractPath = node_path.join(workspaceRoot, GENERATED_CONTRACT_PATH);
|
|
5563
|
+
if (!node_fs.existsSync(contractPath)) return true;
|
|
5564
|
+
const contract = readJsonFile(contractPath);
|
|
5565
|
+
const apps = isRecord(contract) && Array.isArray(contract.apps) ? contract.apps : [];
|
|
5566
|
+
const shell = apps.find((app)=>isRecord(app) && app.id === shellApp.id);
|
|
5567
|
+
return shell?.styling && isRecord(shell.styling) ? false !== shell.styling.tailwind : true;
|
|
5568
|
+
}
|
|
5569
|
+
function assertValidVerticalName(name) {
|
|
5561
5570
|
const normalized = toKebabCase(name);
|
|
5562
|
-
if (!normalized || normalized !== name) throw new Error(`Invalid
|
|
5571
|
+
if (!normalized || normalized !== name) throw new Error(`Invalid Vertical name "${name}". Use lowercase kebab-case.`);
|
|
5563
5572
|
return normalized;
|
|
5564
5573
|
}
|
|
5565
5574
|
function nextAvailablePort(ports) {
|
|
@@ -5595,30 +5604,31 @@ function addShellWorkspaceDependency(workspaceRoot, scope, remote) {
|
|
|
5595
5604
|
shellPackage.dependencies[ultramodern_workspace_packageName(scope, remote.packageSuffix)] = WORKSPACE_PACKAGE_VERSION;
|
|
5596
5605
|
writeJsonFile(packagePath, shellPackage);
|
|
5597
5606
|
}
|
|
5598
|
-
function
|
|
5607
|
+
function verticalTopologyEntry(scope, vertical) {
|
|
5599
5608
|
return {
|
|
5600
|
-
id:
|
|
5601
|
-
kind:
|
|
5602
|
-
domain:
|
|
5603
|
-
package: ultramodern_workspace_packageName(scope,
|
|
5609
|
+
id: vertical.id,
|
|
5610
|
+
kind: vertical.kind,
|
|
5611
|
+
domain: vertical.domain,
|
|
5612
|
+
package: ultramodern_workspace_packageName(scope, vertical.packageSuffix),
|
|
5613
|
+
path: vertical.directory,
|
|
5604
5614
|
moduleFederation: {
|
|
5605
5615
|
role: 'remote',
|
|
5606
|
-
name:
|
|
5607
|
-
manifestUrl: `http://localhost:${
|
|
5608
|
-
exposes: Object.keys(
|
|
5609
|
-
...
|
|
5610
|
-
|
|
5611
|
-
remotes: createModuleFederationRemoteContracts(
|
|
5616
|
+
name: vertical.mfName,
|
|
5617
|
+
manifestUrl: `http://localhost:${vertical.port}/mf-manifest.json`,
|
|
5618
|
+
exposes: Object.keys(vertical.exposes ?? {}),
|
|
5619
|
+
...vertical.verticalRefs?.length ? {
|
|
5620
|
+
verticalRefs: vertical.verticalRefs,
|
|
5621
|
+
remotes: createModuleFederationRemoteContracts(vertical)
|
|
5612
5622
|
} : {},
|
|
5613
5623
|
ssr: true,
|
|
5614
5624
|
fallbackTelemetryEvent: 'modernjs:mv-runtime-parity',
|
|
5615
5625
|
sharedContractVersion: 'mf-ssr-contract-v1'
|
|
5616
5626
|
},
|
|
5617
|
-
...effectApiTopologyMetadata(
|
|
5618
|
-
api: effectApiTopologyMetadata(
|
|
5627
|
+
...effectApiTopologyMetadata(vertical) ? {
|
|
5628
|
+
api: effectApiTopologyMetadata(vertical)
|
|
5619
5629
|
} : {},
|
|
5620
|
-
cloudflare: createCloudflareDeployContract(scope,
|
|
5621
|
-
ownership:
|
|
5630
|
+
cloudflare: createCloudflareDeployContract(scope, vertical),
|
|
5631
|
+
ownership: vertical.ownership
|
|
5622
5632
|
};
|
|
5623
5633
|
}
|
|
5624
5634
|
function ownershipEntry(scope, owner) {
|
|
@@ -5629,47 +5639,48 @@ function ownershipEntry(scope, owner) {
|
|
|
5629
5639
|
ownership: owner.ownership
|
|
5630
5640
|
};
|
|
5631
5641
|
}
|
|
5632
|
-
function
|
|
5633
|
-
return (topology.
|
|
5634
|
-
const
|
|
5635
|
-
const
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5642
|
+
function verticalsFromTopology(topology, ports) {
|
|
5643
|
+
return (topology.verticals ?? []).map((vertical)=>{
|
|
5644
|
+
const domain = vertical.domain ?? String(vertical.id);
|
|
5645
|
+
const packageSuffix = vertical.package?.split('/').at(-1) ?? domain;
|
|
5646
|
+
const effectApi = vertical.api?.effect ? {
|
|
5647
|
+
stem: 'string' == typeof vertical.api.effect.basePath ? vertical.api.effect.basePath.split('/').filter(Boolean).at(-1) ?? domain : domain,
|
|
5648
|
+
prefix: vertical.api.effect.bff?.prefix ?? `/${domain}-api`,
|
|
5649
|
+
consumedBy: Array.isArray(vertical.api.effect.consumedBy) ? vertical.api.effect.consumedBy : [
|
|
5639
5650
|
shellApp.id,
|
|
5640
|
-
|
|
5651
|
+
vertical.id
|
|
5641
5652
|
]
|
|
5642
5653
|
} : void 0;
|
|
5643
5654
|
return {
|
|
5644
|
-
id:
|
|
5645
|
-
directory: 'string' == typeof
|
|
5655
|
+
id: vertical.id,
|
|
5656
|
+
directory: 'string' == typeof vertical.path ? vertical.path : `verticals/${domain}`,
|
|
5646
5657
|
packageSuffix,
|
|
5647
|
-
displayName:
|
|
5648
|
-
kind:
|
|
5649
|
-
domain
|
|
5658
|
+
displayName: vertical.displayName ?? `${toPascalCase(domain)} Vertical`,
|
|
5659
|
+
kind: 'vertical',
|
|
5660
|
+
domain,
|
|
5650
5661
|
portEnv: '',
|
|
5651
|
-
port: 'number' == typeof ports[
|
|
5652
|
-
mfName:
|
|
5653
|
-
...Array.isArray(
|
|
5654
|
-
exposes: Object.fromEntries(
|
|
5662
|
+
port: 'number' == typeof ports[vertical.id] ? ports[vertical.id] : 0,
|
|
5663
|
+
mfName: vertical.moduleFederation?.name ?? `vertical${toPascalCase(domain)}`,
|
|
5664
|
+
...Array.isArray(vertical.moduleFederation?.exposes) ? {
|
|
5665
|
+
exposes: Object.fromEntries(vertical.moduleFederation.exposes.map((expose)=>[
|
|
5655
5666
|
expose,
|
|
5656
5667
|
''
|
|
5657
5668
|
]))
|
|
5658
5669
|
} : {},
|
|
5659
|
-
...Array.isArray(
|
|
5660
|
-
|
|
5661
|
-
} : Array.isArray(
|
|
5662
|
-
|
|
5670
|
+
...Array.isArray(vertical.moduleFederation?.verticalRefs) ? {
|
|
5671
|
+
verticalRefs: vertical.moduleFederation.verticalRefs
|
|
5672
|
+
} : Array.isArray(vertical.moduleFederation?.remotes) ? {
|
|
5673
|
+
verticalRefs: vertical.moduleFederation.remotes.map((entry)=>entry.id).filter((id)=>'string' == typeof id)
|
|
5663
5674
|
} : {},
|
|
5664
5675
|
...effectApi ? {
|
|
5665
5676
|
effectApi
|
|
5666
5677
|
} : {},
|
|
5667
|
-
ownership:
|
|
5678
|
+
ownership: vertical.ownership ?? createNeutralOwnership(vertical.id)
|
|
5668
5679
|
};
|
|
5669
5680
|
});
|
|
5670
5681
|
}
|
|
5671
|
-
function
|
|
5672
|
-
const name =
|
|
5682
|
+
function addUltramodernVertical(options) {
|
|
5683
|
+
const name = assertValidVerticalName(options.name);
|
|
5673
5684
|
const rootPackage = readJsonFile(node_path.join(options.workspaceRoot, 'package.json'));
|
|
5674
5685
|
const scope = toPackageScope(String(rootPackage.name ?? node_path.basename(options.workspaceRoot)));
|
|
5675
5686
|
const topologyPath = node_path.join(options.workspaceRoot, 'topology/reference-topology.json');
|
|
@@ -5685,124 +5696,63 @@ function addUltramodernMicroVertical(options) {
|
|
|
5685
5696
|
const overlay = readJsonFile(overlayPath);
|
|
5686
5697
|
overlay.ports ??= {};
|
|
5687
5698
|
const packageSource = existingPackageSource(options.workspaceRoot, options.modernVersion, options.packageSource);
|
|
5688
|
-
const enableTailwind =
|
|
5699
|
+
const enableTailwind = options.enableTailwind ?? existingTailwindEnabled(options.workspaceRoot);
|
|
5689
5700
|
const port = nextAvailablePort(overlay.ports);
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
}
|
|
5737
|
-
if ('service' === options.kind) {
|
|
5738
|
-
const service = createServiceDescriptor(name, port);
|
|
5739
|
-
assertCanCreate(options.workspaceRoot, service.directory);
|
|
5740
|
-
if ((topology.effectServices ?? []).some((entry)=>entry.id === service.id)) throw new Error(`Topology already contains ${service.id}`);
|
|
5741
|
-
writeEffectService(options.workspaceRoot, scope, packageSource, enableTailwind, service);
|
|
5742
|
-
appendEffectSharedApiContract(options.workspaceRoot, service);
|
|
5743
|
-
topology.effectServices ??= [];
|
|
5744
|
-
topology.effectServices.push({
|
|
5745
|
-
id: service.id,
|
|
5746
|
-
kind: 'effect-service',
|
|
5747
|
-
runtime: 'effect',
|
|
5748
|
-
package: ultramodern_workspace_packageName(scope, service.packageSuffix),
|
|
5749
|
-
consumedBy: [
|
|
5750
|
-
shellApp.id
|
|
5751
|
-
],
|
|
5752
|
-
bff: {
|
|
5753
|
-
prefix: serviceApiPrefix(service),
|
|
5754
|
-
openapi: '/openapi.json'
|
|
5755
|
-
},
|
|
5756
|
-
contract: {
|
|
5757
|
-
package: ultramodern_workspace_packageName(scope, 'shared-effect-api'),
|
|
5758
|
-
export: serviceEffectApiExport(service),
|
|
5759
|
-
path: 'packages/shared-effect-api/src/index.ts'
|
|
5760
|
-
},
|
|
5761
|
-
serverEntry: `${service.directory}/api/effect/index.ts`,
|
|
5762
|
-
basePath: `${serviceApiPrefix(service)}/effect/${effectApiStem(service)}`,
|
|
5763
|
-
...createEffectOperationContract(service),
|
|
5764
|
-
ownership: service.ownership
|
|
5765
|
-
});
|
|
5766
|
-
ownership.owners ??= [];
|
|
5767
|
-
ownership.owners.push(ownershipEntry(scope, service));
|
|
5768
|
-
overlay.ports[service.id] = service.port;
|
|
5769
|
-
overlay.services ??= {};
|
|
5770
|
-
overlay.services[service.id] = `http://localhost:${service.port}${serviceApiPrefix(service)}`;
|
|
5771
|
-
writeJsonFile(topologyPath, topology);
|
|
5772
|
-
writeJsonFile(ownershipPath, ownership);
|
|
5773
|
-
writeJsonFile(overlayPath, overlay);
|
|
5774
|
-
addRootDevScript(options.workspaceRoot, scope, service.packageSuffix, name);
|
|
5775
|
-
return;
|
|
5776
|
-
}
|
|
5777
|
-
if ('shared' === options.kind) {
|
|
5778
|
-
const sharedPackage = createSharedPackageDescriptor(name);
|
|
5779
|
-
assertCanCreate(options.workspaceRoot, sharedPackage.directory);
|
|
5780
|
-
if ((topology.sharedPackages ?? []).some((entry)=>entry.id === sharedPackage.id)) throw new Error(`Topology already contains ${sharedPackage.id}`);
|
|
5781
|
-
writeGenericSharedPackage(options.workspaceRoot, scope, packageSource, sharedPackage);
|
|
5782
|
-
topology.sharedPackages ??= [];
|
|
5783
|
-
topology.sharedPackages.push({
|
|
5784
|
-
id: sharedPackage.id,
|
|
5785
|
-
package: ultramodern_workspace_packageName(scope, sharedPackage.id),
|
|
5786
|
-
path: sharedPackage.directory,
|
|
5787
|
-
description: sharedPackage.description
|
|
5788
|
-
});
|
|
5789
|
-
ownership.owners ??= [];
|
|
5790
|
-
ownership.owners.push(ownershipEntry(scope, {
|
|
5791
|
-
id: sharedPackage.id,
|
|
5792
|
-
packageSuffix: sharedPackage.id,
|
|
5793
|
-
directory: sharedPackage.directory,
|
|
5794
|
-
ownership: createNeutralOwnership(sharedPackage.id, 'tier-1-shared-contract')
|
|
5795
|
-
}));
|
|
5796
|
-
writeJsonFile(topologyPath, topology);
|
|
5797
|
-
writeJsonFile(ownershipPath, ownership);
|
|
5798
|
-
return;
|
|
5799
|
-
}
|
|
5800
|
-
throw new Error(`Unsupported MicroVertical kind: ${options.kind}`);
|
|
5701
|
+
const vertical = createVerticalDescriptor(name, port);
|
|
5702
|
+
assertCanCreate(options.workspaceRoot, vertical.directory);
|
|
5703
|
+
if ((topology.verticals ?? []).some((entry)=>entry.id === vertical.id)) throw new Error(`Topology already contains ${vertical.id}`);
|
|
5704
|
+
if (Object.values(overlay.ports).includes(vertical.port)) throw new Error(`Development port ${vertical.port} is already in use`);
|
|
5705
|
+
writeApp(options.workspaceRoot, scope, vertical, packageSource, enableTailwind);
|
|
5706
|
+
topology.shell ??= {};
|
|
5707
|
+
topology.shell.verticalRefs ??= [];
|
|
5708
|
+
topology.shell.verticalRefs.push(vertical.id);
|
|
5709
|
+
topology.shell.moduleFederation ??= {};
|
|
5710
|
+
topology.shell.moduleFederation.remotes ??= [];
|
|
5711
|
+
topology.shell.moduleFederation.remotes.push({
|
|
5712
|
+
id: vertical.id,
|
|
5713
|
+
name: vertical.mfName,
|
|
5714
|
+
manifestUrl: `http://localhost:${vertical.port}/mf-manifest.json`
|
|
5715
|
+
});
|
|
5716
|
+
topology.verticals ??= [];
|
|
5717
|
+
topology.verticals.push(verticalTopologyEntry(scope, vertical));
|
|
5718
|
+
ownership.owners ??= [];
|
|
5719
|
+
ownership.owners.push(ownershipEntry(scope, vertical));
|
|
5720
|
+
overlay.ports[vertical.id] = vertical.port;
|
|
5721
|
+
overlay.manifests ??= {};
|
|
5722
|
+
overlay.manifests[vertical.id] = `http://localhost:${vertical.port}/mf-manifest.json`;
|
|
5723
|
+
overlay.apis ??= {};
|
|
5724
|
+
overlay.apis[vertical.id] = `http://localhost:${vertical.port}${effectApiPrefix(vertical)}`;
|
|
5725
|
+
writeJsonFile(topologyPath, topology);
|
|
5726
|
+
writeJsonFile(ownershipPath, ownership);
|
|
5727
|
+
writeJsonFile(overlayPath, overlay);
|
|
5728
|
+
const updatedVerticals = verticalsFromTopology(topology, overlay.ports);
|
|
5729
|
+
assertUniqueTailwindPrefixes([
|
|
5730
|
+
shellApp,
|
|
5731
|
+
...updatedVerticals
|
|
5732
|
+
]);
|
|
5733
|
+
writeJsonFile(node_path.join(options.workspaceRoot, GENERATED_CONTRACT_PATH), createGeneratedContract(scope, [
|
|
5734
|
+
{
|
|
5735
|
+
...shellApp,
|
|
5736
|
+
verticalRefs: updatedVerticals.map((vertical)=>vertical.id)
|
|
5737
|
+
},
|
|
5738
|
+
...updatedVerticals
|
|
5739
|
+
], enableTailwind));
|
|
5740
|
+
const shellConfigPath = node_path.join(options.workspaceRoot, `${shellApp.directory}/module-federation.config.ts`);
|
|
5741
|
+
writeFileReplacing(options.workspaceRoot, `${shellApp.directory}/module-federation.config.ts`, createShellModuleFederationConfig(scope, updatedVerticals));
|
|
5742
|
+
if (!node_fs.existsSync(shellConfigPath)) throw new Error('Shell Module Federation config was not regenerated');
|
|
5743
|
+
writeGeneratedWorkspaceScripts(options.workspaceRoot, scope, enableTailwind, updatedVerticals);
|
|
5744
|
+
addShellZephyrDependency(options.workspaceRoot, scope, vertical);
|
|
5745
|
+
addShellWorkspaceDependency(options.workspaceRoot, scope, vertical);
|
|
5746
|
+
addRootDevScript(options.workspaceRoot, scope, vertical.packageSuffix, name);
|
|
5801
5747
|
}
|
|
5802
5748
|
function generateUltramodernWorkspace(options) {
|
|
5803
5749
|
const scope = toPackageScope(options.packageName);
|
|
5804
5750
|
const packageSource = resolvePackageSource(options);
|
|
5805
5751
|
const enableTailwind = false !== options.enableTailwind;
|
|
5752
|
+
assertUniqueTailwindPrefixes([
|
|
5753
|
+
shellApp,
|
|
5754
|
+
...verticalApps
|
|
5755
|
+
]);
|
|
5806
5756
|
node_fs.mkdirSync(options.targetDir, {
|
|
5807
5757
|
recursive: true
|
|
5808
5758
|
});
|
|
@@ -5821,10 +5771,10 @@ function generateUltramodernWorkspace(options) {
|
|
|
5821
5771
|
writeJson(options.targetDir, '.modernjs/ultramodern-package-source.json', createPackageSourceMetadata(scope, packageSource));
|
|
5822
5772
|
writeJson(options.targetDir, GENERATED_CONTRACT_PATH, createGeneratedContract(scope, [
|
|
5823
5773
|
shellApp,
|
|
5824
|
-
...
|
|
5774
|
+
...verticalApps
|
|
5825
5775
|
], enableTailwind));
|
|
5826
5776
|
writeApp(options.targetDir, scope, shellApp, packageSource, enableTailwind);
|
|
5827
|
-
for (const remote of
|
|
5777
|
+
for (const remote of verticalApps)writeApp(options.targetDir, scope, remote, packageSource, enableTailwind);
|
|
5828
5778
|
writeSharedPackages(options.targetDir, scope, packageSource);
|
|
5829
5779
|
writeGeneratedWorkspaceScripts(options.targetDir, scope, enableTailwind);
|
|
5830
5780
|
}
|
|
@@ -5839,7 +5789,7 @@ const packageNamePattern = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/;
|
|
|
5839
5789
|
const src_TANSTACK_ROUTER_VERSION = '1.170.8';
|
|
5840
5790
|
const src_TAILWIND_VERSION = '4.3.0';
|
|
5841
5791
|
const src_TAILWIND_POSTCSS_VERSION = '4.3.0';
|
|
5842
|
-
const src_PNPM_VERSION = '11.
|
|
5792
|
+
const src_PNPM_VERSION = '11.5.0';
|
|
5843
5793
|
const requiredDeniedPaths = [
|
|
5844
5794
|
'.git/**',
|
|
5845
5795
|
'.npmrc',
|
|
@@ -5852,9 +5802,11 @@ const requiredDeniedPaths = [
|
|
|
5852
5802
|
const requiredLifecycleDeniedScripts = [
|
|
5853
5803
|
'preinstall',
|
|
5854
5804
|
'install',
|
|
5855
|
-
'postinstall',
|
|
5856
5805
|
'prepare'
|
|
5857
5806
|
];
|
|
5807
|
+
const requiredLifecycleAllowedScripts = [
|
|
5808
|
+
'postinstall'
|
|
5809
|
+
];
|
|
5858
5810
|
function getOptionValue(args, names) {
|
|
5859
5811
|
for (const name of names){
|
|
5860
5812
|
const prefix = `${name}=`;
|
|
@@ -6024,6 +5976,7 @@ function createBuiltinTemplateManifest(version) {
|
|
|
6024
5976
|
allowedPaths: [
|
|
6025
5977
|
'.agents/**',
|
|
6026
5978
|
'.browserslistrc',
|
|
5979
|
+
'.codex/**',
|
|
6027
5980
|
'.github/**',
|
|
6028
5981
|
'.gitignore',
|
|
6029
5982
|
'.mise.toml',
|
|
@@ -6033,6 +5986,7 @@ function createBuiltinTemplateManifest(version) {
|
|
|
6033
5986
|
'README.md',
|
|
6034
5987
|
'api/**',
|
|
6035
5988
|
'config/**',
|
|
5989
|
+
'lefthook.yml',
|
|
6036
5990
|
'modern.config.ts',
|
|
6037
5991
|
'oxfmt.config.ts',
|
|
6038
5992
|
'oxlint.config.ts',
|
|
@@ -6053,7 +6007,7 @@ function createBuiltinTemplateManifest(version) {
|
|
|
6053
6007
|
lifecyclePolicy: {
|
|
6054
6008
|
denyByDefault: true,
|
|
6055
6009
|
deniedScripts: requiredLifecycleDeniedScripts,
|
|
6056
|
-
allowedScripts:
|
|
6010
|
+
allowedScripts: requiredLifecycleAllowedScripts,
|
|
6057
6011
|
requiresExplicitOptIn: true
|
|
6058
6012
|
},
|
|
6059
6013
|
validation: {
|
|
@@ -6072,7 +6026,7 @@ function createBuiltinTemplateManifest(version) {
|
|
|
6072
6026
|
],
|
|
6073
6027
|
postMaterializationValidation: [
|
|
6074
6028
|
'ultramodern-contract-check',
|
|
6075
|
-
'
|
|
6029
|
+
'agent-skill-postinstall-allowed',
|
|
6076
6030
|
'github-workflow-security-enforced',
|
|
6077
6031
|
'package-source-retained',
|
|
6078
6032
|
'pnpm-11-policy-enforced',
|
|
@@ -6081,9 +6035,9 @@ function createBuiltinTemplateManifest(version) {
|
|
|
6081
6035
|
],
|
|
6082
6036
|
expectedCommands: [
|
|
6083
6037
|
'mise install',
|
|
6084
|
-
'
|
|
6085
|
-
'
|
|
6086
|
-
'
|
|
6038
|
+
'pnpm install',
|
|
6039
|
+
'pnpm test',
|
|
6040
|
+
'pnpm run ultramodern:check'
|
|
6087
6041
|
]
|
|
6088
6042
|
}
|
|
6089
6043
|
};
|
|
@@ -6146,7 +6100,7 @@ function validateTemplateManifest(manifest) {
|
|
|
6146
6100
|
assertTemplateManifest(!manifest.materialization.overwritePolicy || 'deny-existing' === manifest.materialization.overwritePolicy || 'allow-generated-only' === manifest.materialization.overwritePolicy, 'materialization.overwritePolicy is unsupported');
|
|
6147
6101
|
assertTemplateManifest(true === manifest.lifecyclePolicy.denyByDefault, 'lifecyclePolicy.denyByDefault must be true');
|
|
6148
6102
|
for (const scriptName of requiredLifecycleDeniedScripts)assertTemplateManifest(manifest.lifecyclePolicy.deniedScripts.includes(scriptName), `lifecyclePolicy.deniedScripts must include ${scriptName}`);
|
|
6149
|
-
assertTemplateManifest(
|
|
6103
|
+
assertTemplateManifest(JSON.stringify(manifest.lifecyclePolicy.allowedScripts) === JSON.stringify(requiredLifecycleAllowedScripts), 'lifecyclePolicy.allowedScripts must only allow generated postinstall');
|
|
6150
6104
|
assertTemplateManifest(true === manifest.validation.schemaValidation, 'validation.schemaValidation must be true');
|
|
6151
6105
|
for (const token of [
|
|
6152
6106
|
'source-type-supported',
|
|
@@ -6227,7 +6181,7 @@ function showHelp() {
|
|
|
6227
6181
|
if (localeKeys.help.optionUltramodernPackageSource) console.log(i18n.t(localeKeys.help.optionUltramodernPackageSource));
|
|
6228
6182
|
if (localeKeys.help.optionUltramodernPackageScope) console.log(i18n.t(localeKeys.help.optionUltramodernPackageScope));
|
|
6229
6183
|
if (localeKeys.help.optionUltramodernPackageNamePrefix) console.log(i18n.t(localeKeys.help.optionUltramodernPackageNamePrefix));
|
|
6230
|
-
if (localeKeys.help.
|
|
6184
|
+
if (localeKeys.help.optionVertical) console.log(i18n.t(localeKeys.help.optionVertical));
|
|
6231
6185
|
console.log(i18n.t(localeKeys.help.optionSub));
|
|
6232
6186
|
console.log('');
|
|
6233
6187
|
console.log(i18n.t(localeKeys.help.examples));
|
|
@@ -6269,18 +6223,22 @@ function detectTailwindFlag() {
|
|
|
6269
6223
|
const args = process.argv.slice(2);
|
|
6270
6224
|
return !args.includes('--no-tailwind');
|
|
6271
6225
|
}
|
|
6226
|
+
function detectExplicitTailwindFlag() {
|
|
6227
|
+
const args = process.argv.slice(2);
|
|
6228
|
+
if (args.includes('--no-tailwind')) return false;
|
|
6229
|
+
if (args.includes('--tailwind')) return true;
|
|
6230
|
+
}
|
|
6272
6231
|
function detectWorkspaceProtocolFlag() {
|
|
6273
6232
|
const args = process.argv.slice(2);
|
|
6274
6233
|
return args.includes('--workspace');
|
|
6275
6234
|
}
|
|
6276
|
-
function
|
|
6277
|
-
const
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
process.exit(1);
|
|
6235
|
+
function detectVerticalFlag() {
|
|
6236
|
+
const args = process.argv.slice(2);
|
|
6237
|
+
if (args.some((arg)=>arg.startsWith('--vertical='))) {
|
|
6238
|
+
console.error('--vertical does not accept a value. Use: create <name> --vertical');
|
|
6239
|
+
process.exit(1);
|
|
6240
|
+
}
|
|
6241
|
+
return args.includes('--vertical');
|
|
6284
6242
|
}
|
|
6285
6243
|
function detectUltramodernWorkspaceFlag(createPackage) {
|
|
6286
6244
|
const args = process.argv.slice(2);
|
|
@@ -6382,8 +6340,7 @@ async function getProjectName() {
|
|
|
6382
6340
|
'--ultramodern-package-version',
|
|
6383
6341
|
'--ultramodern-package-registry',
|
|
6384
6342
|
'--ultramodern-package-scope',
|
|
6385
|
-
'--ultramodern-package-name-prefix'
|
|
6386
|
-
'--microvertical'
|
|
6343
|
+
'--ultramodern-package-name-prefix'
|
|
6387
6344
|
]);
|
|
6388
6345
|
const optionWithoutValue = new Set([
|
|
6389
6346
|
'--help',
|
|
@@ -6398,6 +6355,7 @@ async function getProjectName() {
|
|
|
6398
6355
|
'--tailwind',
|
|
6399
6356
|
'--no-tailwind',
|
|
6400
6357
|
'--workspace',
|
|
6358
|
+
'--vertical',
|
|
6401
6359
|
ULTRAMODERN_WORKSPACE_FLAG
|
|
6402
6360
|
]);
|
|
6403
6361
|
const positionalArgs = [];
|
|
@@ -6408,9 +6366,13 @@ async function getProjectName() {
|
|
|
6408
6366
|
i += 1;
|
|
6409
6367
|
continue;
|
|
6410
6368
|
}
|
|
6411
|
-
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=')
|
|
6369
|
+
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);
|
|
6412
6370
|
}
|
|
6413
6371
|
}
|
|
6372
|
+
if (positionalArgs.length > 1) {
|
|
6373
|
+
console.error(`Unexpected positional argument: ${positionalArgs[1]}`);
|
|
6374
|
+
process.exit(1);
|
|
6375
|
+
}
|
|
6414
6376
|
const projectNameArg = positionalArgs[0];
|
|
6415
6377
|
if (projectNameArg) return {
|
|
6416
6378
|
name: projectNameArg,
|
|
@@ -6442,22 +6404,20 @@ async function main() {
|
|
|
6442
6404
|
const createPackage = readCreatePackageJson();
|
|
6443
6405
|
const version = createPackage.version || 'latest';
|
|
6444
6406
|
const ultramodernPackageVersion = isBleedingDevCreatePackage(createPackage) ? getBleedingDevFrameworkVersion(createPackage, version) : version;
|
|
6445
|
-
const
|
|
6446
|
-
if (
|
|
6407
|
+
const addVertical = detectVerticalFlag();
|
|
6408
|
+
if (addVertical) {
|
|
6447
6409
|
const overridePackageSource = args.some((arg)=>arg.startsWith('--ultramodern-package-')) ? detectUltramodernPackageSource(args, ultramodernPackageVersion, createPackage) : void 0;
|
|
6448
|
-
|
|
6410
|
+
addUltramodernVertical({
|
|
6449
6411
|
workspaceRoot: process.cwd(),
|
|
6450
6412
|
name: generatedPackageName,
|
|
6451
|
-
kind: microVerticalKind,
|
|
6452
6413
|
modernVersion: version,
|
|
6453
|
-
enableTailwind:
|
|
6414
|
+
enableTailwind: detectExplicitTailwindFlag(),
|
|
6454
6415
|
packageSource: overridePackageSource
|
|
6455
6416
|
});
|
|
6456
6417
|
const dim = '\x1b[2m\x1b[3m';
|
|
6457
6418
|
const reset = '\x1b[0m';
|
|
6458
6419
|
console.log(`${i18n.t(localeKeys.message.success)}\n`);
|
|
6459
|
-
console.log(`${dim}
|
|
6460
|
-
console.log(`${dim} mise exec -- pnpm ultramodern:check${reset}\n`);
|
|
6420
|
+
console.log(`${dim} pnpm ultramodern:check${reset}\n`);
|
|
6461
6421
|
return;
|
|
6462
6422
|
}
|
|
6463
6423
|
if (node_fs.existsSync(targetDir)) {
|
|
@@ -6485,9 +6445,8 @@ async function main() {
|
|
|
6485
6445
|
if (!useCurrentDir) console.log(`${dim} ${i18n.t(localeKeys.message.step1, {
|
|
6486
6446
|
projectName
|
|
6487
6447
|
})}${reset}`);
|
|
6488
|
-
console.log(`${dim} mise install${reset}`);
|
|
6489
6448
|
console.log(`${dim} ${i18n.t(localeKeys.message.step2)}${reset}`);
|
|
6490
|
-
console.log(`${dim}
|
|
6449
|
+
console.log(`${dim} pnpm ultramodern:check${reset}`);
|
|
6491
6450
|
console.log(`${dim} ${i18n.t(localeKeys.message.step3)}${reset}\n`);
|
|
6492
6451
|
return;
|
|
6493
6452
|
}
|
|
@@ -6542,14 +6501,23 @@ async function main() {
|
|
|
6542
6501
|
delete packageJson.scripts['lint:fix'];
|
|
6543
6502
|
delete packageJson.scripts['skills:install'];
|
|
6544
6503
|
delete packageJson.scripts['skills:check'];
|
|
6504
|
+
delete packageJson.scripts.postinstall;
|
|
6545
6505
|
}
|
|
6546
6506
|
if (packageJson.devDependencies) {
|
|
6547
6507
|
delete packageJson.devDependencies['lint-staged'];
|
|
6508
|
+
delete packageJson.devDependencies.lefthook;
|
|
6548
6509
|
delete packageJson.devDependencies['simple-git-hooks'];
|
|
6549
6510
|
delete packageJson.devDependencies.oxlint;
|
|
6550
6511
|
delete packageJson.devDependencies.oxfmt;
|
|
6551
6512
|
delete packageJson.devDependencies.ultracite;
|
|
6552
6513
|
}
|
|
6514
|
+
node_fs.rmSync(node_path.join(targetDir, '.codex'), {
|
|
6515
|
+
recursive: true,
|
|
6516
|
+
force: true
|
|
6517
|
+
});
|
|
6518
|
+
node_fs.rmSync(node_path.join(targetDir, 'lefthook.yml'), {
|
|
6519
|
+
force: true
|
|
6520
|
+
});
|
|
6553
6521
|
}
|
|
6554
6522
|
node_fs.writeFileSync(targetPackageJson, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
6555
6523
|
writeTemplateManifestEvidence(targetDir, templateManifest);
|