@bleedingdev/modern-js-create 3.2.0-ultramodern.21 → 3.2.0-ultramodern.22
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 +20 -14
- package/dist/index.js +1460 -321
- package/dist/types/locale/en.d.ts +2 -0
- package/dist/types/locale/zh.d.ts +2 -0
- package/dist/types/ultramodern-workspace.d.ts +13 -0
- package/package.json +6 -6
- package/template/.mise.toml +2 -0
- package/template/README.md +7 -6
- package/template/package.json.handlebars +16 -16
- package/template/pnpm-workspace.yaml +2 -0
- package/template/scripts/validate-ultramodern.mjs.handlebars +138 -26
- package/template/tests/{ultramodern.contract.test.ts → ultramodern.contract.test.ts.handlebars} +28 -0
- package/template-workspace/.agents/skills-lock.json +19 -0
- package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +1 -1
- package/template-workspace/.mise.toml +2 -0
- package/template-workspace/AGENTS.md +7 -7
- package/template-workspace/README.md.handlebars +12 -3
- package/template-workspace/pnpm-workspace.yaml +15 -0
- package/template-workspace/scripts/assert-mf-types.mjs +44 -0
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +73 -13
- package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +449 -121
- package/template-workspace/scripts/check-i18n-strings.mjs +0 -83
package/dist/index.js
CHANGED
|
@@ -468,12 +468,13 @@ const EN_LOCALE = {
|
|
|
468
468
|
optionRouter: ' -r, --router Select router framework (react-router or tanstack)',
|
|
469
469
|
optionBff: ' --bff Enable BFF scaffold (default runtime: effect)',
|
|
470
470
|
optionBffRuntime: ' --bff-runtime Select BFF runtime (hono or effect)',
|
|
471
|
-
optionTailwind: ' --tailwind
|
|
471
|
+
optionTailwind: ' --no-tailwind Disable default Tailwind CSS v4 scaffold',
|
|
472
472
|
optionWorkspace: ' --workspace Use workspace protocol for @modern-js dependencies (for local monorepo testing)',
|
|
473
473
|
optionUltramodernWorkspace: ' --ultramodern-workspace Generate the canonical UltraModern SuperApp workspace',
|
|
474
474
|
optionUltramodernPackageSource: ' --ultramodern-package-source Select UltraModern package source (workspace or install)',
|
|
475
475
|
optionUltramodernPackageScope: ' --ultramodern-package-scope Publish scope for npm alias installs (for example bleedingdev)',
|
|
476
476
|
optionUltramodernPackageNamePrefix: ' --ultramodern-package-name-prefix Prefix for npm alias package names (default: modern-js-)',
|
|
477
|
+
optionMicroVertical: ' --microvertical Add a MicroVertical package to an existing UltraModern workspace (remote, horizontal-remote, service, shared)',
|
|
477
478
|
optionSub: ' -s, --sub Mark as a subproject (package in monorepo)',
|
|
478
479
|
examples: '💡 Examples:',
|
|
479
480
|
example1: ' create my-app',
|
|
@@ -481,11 +482,12 @@ const EN_LOCALE = {
|
|
|
481
482
|
example3: ' create my-app --sub',
|
|
482
483
|
example4: ' create --help',
|
|
483
484
|
example5: ' create my-app --router tanstack',
|
|
484
|
-
example6: ' create my-app --router tanstack --tailwind',
|
|
485
|
+
example6: ' create my-app --router tanstack --no-tailwind',
|
|
485
486
|
example7: ' create my-app --bff',
|
|
486
487
|
example8: ' create my-app --router tanstack --bff-runtime effect',
|
|
487
488
|
example9: ' create my-app --router tanstack --bff-runtime effect --workspace',
|
|
488
489
|
example10: ' pnpm dlx @bleedingdev/modern-js-create my-super-app',
|
|
490
|
+
example11: ' create catalog --microvertical remote',
|
|
489
491
|
moreInfo: '📚 Learn more: https://modernjs.dev'
|
|
490
492
|
},
|
|
491
493
|
version: {
|
|
@@ -523,12 +525,13 @@ const ZH_LOCALE = {
|
|
|
523
525
|
optionRouter: ' -r, --router 选择路由框架 (react-router 或 tanstack)',
|
|
524
526
|
optionBff: ' --bff 启用 BFF 模板(默认运行时:effect)',
|
|
525
527
|
optionBffRuntime: ' --bff-runtime 选择 BFF 运行时(hono 或 effect)',
|
|
526
|
-
optionTailwind: ' --tailwind
|
|
528
|
+
optionTailwind: ' --no-tailwind 禁用默认 Tailwind CSS v4 模板',
|
|
527
529
|
optionWorkspace: ' --workspace 对 @modern-js 依赖使用 workspace 协议(用于本地 monorepo 联调)',
|
|
528
530
|
optionUltramodernWorkspace: ' --ultramodern-workspace 生成标准 UltraModern SuperApp 工作区',
|
|
529
531
|
optionUltramodernPackageSource: ' --ultramodern-package-source 选择 UltraModern 依赖来源(workspace 或 install)',
|
|
530
532
|
optionUltramodernPackageScope: ' --ultramodern-package-scope npm alias 安装使用的发布 scope(例如 bleedingdev)',
|
|
531
533
|
optionUltramodernPackageNamePrefix: ' --ultramodern-package-name-prefix npm alias 包名前缀(默认:modern-js-)',
|
|
534
|
+
optionMicroVertical: ' --microvertical 向现有 UltraModern 工作区添加 MicroVertical 包(remote、horizontal-remote、service、shared)',
|
|
532
535
|
optionSub: ' -s, --sub 标记为子项目(monorepo 中的子包)',
|
|
533
536
|
examples: '💡 示例:',
|
|
534
537
|
example1: ' create my-app',
|
|
@@ -536,11 +539,12 @@ const ZH_LOCALE = {
|
|
|
536
539
|
example3: ' create my-app --sub',
|
|
537
540
|
example4: ' create --help',
|
|
538
541
|
example5: ' create my-app --router tanstack',
|
|
539
|
-
example6: ' create my-app --router tanstack --tailwind',
|
|
542
|
+
example6: ' create my-app --router tanstack --no-tailwind',
|
|
540
543
|
example7: ' create my-app --bff',
|
|
541
544
|
example8: ' create my-app --router tanstack --bff-runtime effect',
|
|
542
545
|
example9: ' create my-app --router tanstack --bff-runtime effect --workspace',
|
|
543
546
|
example10: ' create my-super-app --ultramodern-workspace --ultramodern-package-source install --ultramodern-package-scope bleedingdev',
|
|
547
|
+
example11: ' create catalog --microvertical remote',
|
|
544
548
|
moreInfo: '📚 更多信息: https://modernjs.dev'
|
|
545
549
|
},
|
|
546
550
|
version: {
|
|
@@ -554,20 +558,26 @@ const localeKeys = i18n.init('en', {
|
|
|
554
558
|
});
|
|
555
559
|
const ultramodern_workspace_dirname = node_path.dirname(fileURLToPath(import.meta.url));
|
|
556
560
|
const workspaceTemplateDir = node_path.resolve(ultramodern_workspace_dirname, '..', 'template-workspace');
|
|
557
|
-
const TANSTACK_ROUTER_VERSION = '1.170.
|
|
558
|
-
const MODULE_FEDERATION_VERSION = '2.
|
|
559
|
-
const
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
const
|
|
563
|
-
const
|
|
561
|
+
const TANSTACK_ROUTER_VERSION = '1.170.8';
|
|
562
|
+
const MODULE_FEDERATION_VERSION = '2.5.0';
|
|
563
|
+
const ZEPHYR_RSPACK_PLUGIN_VERSION = '1.1.1';
|
|
564
|
+
const ZEPHYR_AGENT_VERSION = '1.1.1';
|
|
565
|
+
const WRANGLER_VERSION = '4.95.0';
|
|
566
|
+
const TAILWIND_VERSION = '4.3.0';
|
|
567
|
+
const TAILWIND_POSTCSS_VERSION = '4.3.0';
|
|
568
|
+
const EFFECT_TSGO_VERSION = '0.11.0';
|
|
569
|
+
const TYPESCRIPT_VERSION = '6.0.3';
|
|
570
|
+
const TYPESCRIPT_NATIVE_PREVIEW_VERSION = '7.0.0-dev.20260526.1';
|
|
571
|
+
const OXLINT_VERSION = '1.66.0';
|
|
572
|
+
const OXFMT_VERSION = '0.51.0';
|
|
564
573
|
const ULTRACITE_VERSION = '7.7.0';
|
|
565
574
|
const I18NEXT_VERSION = '26.2.0';
|
|
566
575
|
const REACT_VERSION = '^19.2.6';
|
|
567
576
|
const REACT_DOM_VERSION = '^19.2.6';
|
|
568
|
-
const REACT_I18NEXT_VERSION = '17.0.8';
|
|
569
577
|
const WORKSPACE_PACKAGE_VERSION = 'workspace:*';
|
|
578
|
+
const GENERATED_CONTRACT_PATH = '.modernjs/ultramodern-generated-contract.json';
|
|
570
579
|
const RSTACK_AGENT_SKILLS_COMMIT = '61c948b42512e223bad44b83af4080eba48b2677';
|
|
580
|
+
const MODULE_FEDERATION_AGENT_SKILLS_COMMIT = '07bb5b6c43ad457609e00c081b72d4c42508ec76';
|
|
571
581
|
const baselineAgentSkills = [
|
|
572
582
|
'rsbuild-best-practices',
|
|
573
583
|
'rspack-best-practices',
|
|
@@ -577,6 +587,9 @@ const baselineAgentSkills = [
|
|
|
577
587
|
'rslib-modern-package',
|
|
578
588
|
'rstest-best-practices'
|
|
579
589
|
];
|
|
590
|
+
const moduleFederationAgentSkills = [
|
|
591
|
+
'mf'
|
|
592
|
+
];
|
|
580
593
|
const privateAgentSkills = [
|
|
581
594
|
'plan-graph',
|
|
582
595
|
'dag',
|
|
@@ -637,6 +650,14 @@ const remoteApps = [
|
|
|
637
650
|
'./Route': './src/remote-entry.tsx',
|
|
638
651
|
'./Widget': './src/components/commerce-widget.tsx'
|
|
639
652
|
},
|
|
653
|
+
effectApi: {
|
|
654
|
+
stem: 'recommendations',
|
|
655
|
+
prefix: '/commerce-api',
|
|
656
|
+
consumedBy: [
|
|
657
|
+
shellApp.id,
|
|
658
|
+
'remote-commerce'
|
|
659
|
+
]
|
|
660
|
+
},
|
|
640
661
|
ownership: {
|
|
641
662
|
team: 'commerce-experience',
|
|
642
663
|
slack: '#commerce-experience',
|
|
@@ -666,6 +687,14 @@ const remoteApps = [
|
|
|
666
687
|
'./Route': './src/remote-entry.tsx',
|
|
667
688
|
'./Widget': './src/components/identity-widget.tsx'
|
|
668
689
|
},
|
|
690
|
+
effectApi: {
|
|
691
|
+
stem: 'identity',
|
|
692
|
+
prefix: '/identity-api',
|
|
693
|
+
consumedBy: [
|
|
694
|
+
shellApp.id,
|
|
695
|
+
'remote-identity'
|
|
696
|
+
]
|
|
697
|
+
},
|
|
669
698
|
ownership: {
|
|
670
699
|
team: 'identity-platform',
|
|
671
700
|
slack: '#identity-platform',
|
|
@@ -824,6 +853,89 @@ const sharedPackages = [
|
|
|
824
853
|
description: 'Shared Effect API type placeholders for services and clients.'
|
|
825
854
|
}
|
|
826
855
|
];
|
|
856
|
+
function createNeutralOwnership(id, tier = 'tier-2-microvertical') {
|
|
857
|
+
return {
|
|
858
|
+
team: 'super-app-platform',
|
|
859
|
+
slack: '#super-app-platform',
|
|
860
|
+
pagerDuty: 'pd-super-app-platform',
|
|
861
|
+
runbookRef: `runbooks/microverticals/${id}.md`,
|
|
862
|
+
adrRef: `docs/super-app-rfc-adr/microverticals.md#${id}`,
|
|
863
|
+
blastRadius: {
|
|
864
|
+
tier,
|
|
865
|
+
references: [
|
|
866
|
+
`docs/super-app-rfc-adr/blast-radius.md#${id}`
|
|
867
|
+
]
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
function createRemoteDescriptor(name, kind, port) {
|
|
872
|
+
const domain = toKebabCase(name);
|
|
873
|
+
const id = `remote-${domain}`;
|
|
874
|
+
const displayPrefix = toPascalCase(domain).replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
875
|
+
return {
|
|
876
|
+
id,
|
|
877
|
+
directory: `apps/remotes/${id}`,
|
|
878
|
+
packageSuffix: id,
|
|
879
|
+
displayName: `${displayPrefix} Remote`,
|
|
880
|
+
kind: 'horizontal-remote' === kind ? 'horizontal-remote' : 'vertical',
|
|
881
|
+
domain,
|
|
882
|
+
portEnv: `REMOTE_${toEnvSegment(domain)}_PORT`,
|
|
883
|
+
port,
|
|
884
|
+
mfName: `remote${toPascalCase(domain)}`,
|
|
885
|
+
exposes: {
|
|
886
|
+
'./Route': './src/remote-entry.tsx',
|
|
887
|
+
'./Widget': `./src/components/${domain}-widget.tsx`
|
|
888
|
+
},
|
|
889
|
+
...'remote' === kind ? {
|
|
890
|
+
effectApi: {
|
|
891
|
+
stem: domain,
|
|
892
|
+
prefix: `/${domain}-api`,
|
|
893
|
+
consumedBy: [
|
|
894
|
+
shellApp.id,
|
|
895
|
+
id
|
|
896
|
+
]
|
|
897
|
+
}
|
|
898
|
+
} : {},
|
|
899
|
+
ownership: createNeutralOwnership(id)
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
function createServiceDescriptor(name, port) {
|
|
903
|
+
const normalized = toKebabCase(name);
|
|
904
|
+
const suffix = normalized.endsWith('-effect') ? normalized : `service-${normalized.replace(/^service-/, '')}-effect`;
|
|
905
|
+
return {
|
|
906
|
+
id: suffix,
|
|
907
|
+
directory: `services/${suffix}`,
|
|
908
|
+
packageSuffix: suffix,
|
|
909
|
+
portEnv: `${toEnvSegment(suffix)}_PORT`,
|
|
910
|
+
port,
|
|
911
|
+
ownership: createNeutralOwnership(suffix, 'tier-2-effect-service')
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
function serviceApiPrefix(service) {
|
|
915
|
+
const name = service.id.replace(/^service-/, '').replace(/-effect$/, '');
|
|
916
|
+
return name.endsWith('-api') ? `/${name}` : `/${name}-api`;
|
|
917
|
+
}
|
|
918
|
+
function appHasEffectApi(app) {
|
|
919
|
+
return void 0 !== app.effectApi;
|
|
920
|
+
}
|
|
921
|
+
function effectApiPrefix(target) {
|
|
922
|
+
return target.effectApi?.prefix ?? serviceApiPrefix(target);
|
|
923
|
+
}
|
|
924
|
+
function effectApiStem(target) {
|
|
925
|
+
return target.effectApi?.stem ?? target.id.replace(/^service-/, '').replace(/-effect$/, '').replace(/-api$/, '');
|
|
926
|
+
}
|
|
927
|
+
function verticalEffectApps(remotes = remoteApps) {
|
|
928
|
+
return remotes.filter(appHasEffectApi);
|
|
929
|
+
}
|
|
930
|
+
function createSharedPackageDescriptor(name) {
|
|
931
|
+
const normalized = toKebabCase(name);
|
|
932
|
+
const id = normalized.startsWith('shared-') ? normalized : `shared-${normalized}`;
|
|
933
|
+
return {
|
|
934
|
+
id,
|
|
935
|
+
directory: `packages/${id}`,
|
|
936
|
+
description: `Shared ${normalized.replace(/^shared-/, '')} package placeholder.`
|
|
937
|
+
};
|
|
938
|
+
}
|
|
827
939
|
function normalizePath(filePath) {
|
|
828
940
|
return filePath.split(node_path.sep).join('/');
|
|
829
941
|
}
|
|
@@ -844,6 +956,15 @@ function writeFile(targetDir, relativePath, content) {
|
|
|
844
956
|
});
|
|
845
957
|
node_fs.writeFileSync(filePath, content, 'utf-8');
|
|
846
958
|
}
|
|
959
|
+
function writeFileReplacing(targetDir, relativePath, content) {
|
|
960
|
+
assertSafeRelativePath(relativePath);
|
|
961
|
+
const filePath = node_path.join(targetDir, relativePath);
|
|
962
|
+
ensureInsideRoot(targetDir, filePath);
|
|
963
|
+
node_fs.mkdirSync(node_path.dirname(filePath), {
|
|
964
|
+
recursive: true
|
|
965
|
+
});
|
|
966
|
+
node_fs.writeFileSync(filePath, content, 'utf-8');
|
|
967
|
+
}
|
|
847
968
|
function writeJson(targetDir, relativePath, value) {
|
|
848
969
|
writeFile(targetDir, relativePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
849
970
|
}
|
|
@@ -889,6 +1010,17 @@ function toPackageScope(packageName) {
|
|
|
889
1010
|
const normalized = packageName.replace(/^@/, '').replace(/[\\/]+/g, '-').toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^[._-]+|[._-]+$/g, '').replace(/-{2,}/g, '-');
|
|
890
1011
|
return normalized || 'ultramodern-superapp';
|
|
891
1012
|
}
|
|
1013
|
+
function toKebabCase(value) {
|
|
1014
|
+
const normalized = value.trim().replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/[._]+/g, '-').toLowerCase().replace(/-+/g, '-').replace(/^-+|-+$/g, '');
|
|
1015
|
+
return normalized;
|
|
1016
|
+
}
|
|
1017
|
+
function toCamelCase(value) {
|
|
1018
|
+
const pascal = toPascalCase(value);
|
|
1019
|
+
return `${pascal.charAt(0).toLowerCase()}${pascal.slice(1)}`;
|
|
1020
|
+
}
|
|
1021
|
+
function toEnvSegment(value) {
|
|
1022
|
+
return toKebabCase(value).replace(/-/g, '_').toUpperCase();
|
|
1023
|
+
}
|
|
892
1024
|
function ultramodern_workspace_packageName(scope, suffix) {
|
|
893
1025
|
return `@${scope}/${suffix}`;
|
|
894
1026
|
}
|
|
@@ -919,31 +1051,44 @@ function modernPackageSpecifier(packageName, packageSource) {
|
|
|
919
1051
|
if (!packageSource.aliasScope) return packageSource.modernPackageVersion;
|
|
920
1052
|
return `npm:${modernAliasPackageName(packageName, packageSource)}@${packageSource.modernPackageVersion}`;
|
|
921
1053
|
}
|
|
922
|
-
function appDependencies(scope, packageSource) {
|
|
923
|
-
|
|
924
|
-
'@modern-js/plugin-i18n': modernPackageSpecifier('@modern-js/plugin-i18n', packageSource),
|
|
1054
|
+
function appDependencies(scope, packageSource, app) {
|
|
1055
|
+
const dependencies = {
|
|
925
1056
|
'@modern-js/plugin-tanstack': modernPackageSpecifier('@modern-js/plugin-tanstack', packageSource),
|
|
1057
|
+
'@modern-js/plugin-i18n': modernPackageSpecifier('@modern-js/plugin-i18n', packageSource),
|
|
926
1058
|
'@modern-js/runtime': modernPackageSpecifier('@modern-js/runtime', packageSource),
|
|
927
1059
|
'@module-federation/modern-js-v3': MODULE_FEDERATION_VERSION,
|
|
928
1060
|
'@module-federation/runtime': MODULE_FEDERATION_VERSION,
|
|
929
1061
|
'@tanstack/react-router': TANSTACK_ROUTER_VERSION,
|
|
930
|
-
|
|
1062
|
+
i18next: I18NEXT_VERSION,
|
|
1063
|
+
'node-fetch': '^3.3.2',
|
|
931
1064
|
[ultramodern_workspace_packageName(scope, 'shared-contracts')]: WORKSPACE_PACKAGE_VERSION,
|
|
932
1065
|
[ultramodern_workspace_packageName(scope, 'shared-design-tokens')]: WORKSPACE_PACKAGE_VERSION,
|
|
933
|
-
i18next: I18NEXT_VERSION,
|
|
934
1066
|
react: REACT_VERSION,
|
|
935
|
-
'react-dom': REACT_DOM_VERSION
|
|
936
|
-
'react-i18next': REACT_I18NEXT_VERSION
|
|
1067
|
+
'react-dom': REACT_DOM_VERSION
|
|
937
1068
|
};
|
|
1069
|
+
if ('shell' === app.kind) {
|
|
1070
|
+
dependencies['@modern-js/plugin-bff'] = modernPackageSpecifier('@modern-js/plugin-bff', packageSource);
|
|
1071
|
+
for (const remote of verticalEffectApps())dependencies[ultramodern_workspace_packageName(scope, remote.packageSuffix)] = WORKSPACE_PACKAGE_VERSION;
|
|
1072
|
+
}
|
|
1073
|
+
if (appHasEffectApi(app)) dependencies['@modern-js/plugin-bff'] = modernPackageSpecifier('@modern-js/plugin-bff', packageSource);
|
|
1074
|
+
return dependencies;
|
|
938
1075
|
}
|
|
939
|
-
function appDevDependencies(packageSource) {
|
|
1076
|
+
function appDevDependencies(packageSource, enableTailwind) {
|
|
940
1077
|
return {
|
|
941
1078
|
'@modern-js/app-tools': modernPackageSpecifier('@modern-js/app-tools', packageSource),
|
|
942
1079
|
'@effect/tsgo': EFFECT_TSGO_VERSION,
|
|
1080
|
+
...enableTailwind ? {
|
|
1081
|
+
'@tailwindcss/postcss': `^${TAILWIND_POSTCSS_VERSION}`,
|
|
1082
|
+
postcss: '^8.5.6',
|
|
1083
|
+
tailwindcss: `^${TAILWIND_VERSION}`
|
|
1084
|
+
} : {},
|
|
943
1085
|
"@typescript/native-preview": TYPESCRIPT_NATIVE_PREVIEW_VERSION,
|
|
944
1086
|
'@types/node': '^20',
|
|
945
1087
|
'@types/react': '^19.1.8',
|
|
946
|
-
'@types/react-dom': '^19.1.6'
|
|
1088
|
+
'@types/react-dom': '^19.1.6',
|
|
1089
|
+
typescript: TYPESCRIPT_VERSION,
|
|
1090
|
+
'zephyr-rspack-plugin': ZEPHYR_RSPACK_PLUGIN_VERSION,
|
|
1091
|
+
wrangler: WRANGLER_VERSION
|
|
947
1092
|
};
|
|
948
1093
|
}
|
|
949
1094
|
function createRootPackageJson(scope, packageSource) {
|
|
@@ -952,32 +1097,32 @@ function createRootPackageJson(scope, packageSource) {
|
|
|
952
1097
|
name: scope,
|
|
953
1098
|
version: '0.1.0',
|
|
954
1099
|
type: 'module',
|
|
955
|
-
packageManager: 'pnpm@11.
|
|
1100
|
+
packageManager: 'pnpm@11.3.0',
|
|
956
1101
|
scripts: {
|
|
957
1102
|
dev: `pnpm --parallel --filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)} --filter ${ultramodern_workspace_packageName(scope, 'remote-commerce')} --filter ${ultramodern_workspace_packageName(scope, 'remote-identity')} --filter ${ultramodern_workspace_packageName(scope, 'remote-design-system')} dev`,
|
|
958
1103
|
'dev:shell': `pnpm --filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)} dev`,
|
|
959
1104
|
'dev:commerce': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'remote-commerce')} dev`,
|
|
960
1105
|
'dev:identity': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'remote-identity')} dev`,
|
|
961
1106
|
'dev:design-system': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'remote-design-system')} dev`,
|
|
962
|
-
|
|
963
|
-
build: 'pnpm -r --filter ./apps/** --filter ./services/** build',
|
|
1107
|
+
build: 'pnpm -r --filter "./apps/remotes/**" run build && pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types',
|
|
964
1108
|
format: 'oxfmt .',
|
|
965
1109
|
'format:check': 'oxfmt --check .',
|
|
966
|
-
'i18n:check': "node ./scripts/check-i18n-strings.mjs",
|
|
967
1110
|
lint: 'oxlint .',
|
|
968
1111
|
'lint:fix': 'oxlint . --fix',
|
|
969
1112
|
typecheck: `pnpm -r --filter "@${scope}/*" typecheck`,
|
|
1113
|
+
'cloudflare:build': 'pnpm -r --filter "./apps/remotes/**" run cloudflare:build && pnpm --filter "./apps/shell-super-app" run cloudflare:build && pnpm ultramodern:assert-mf-types',
|
|
970
1114
|
'skills:install': "node ./scripts/bootstrap-agent-skills.mjs",
|
|
971
1115
|
'skills:check': "node ./scripts/bootstrap-agent-skills.mjs --check",
|
|
972
1116
|
'agents:refs:install': "node ./scripts/setup-agent-reference-repos.mjs",
|
|
973
1117
|
'agents:refs:check': "node ./scripts/setup-agent-reference-repos.mjs --check",
|
|
1118
|
+
'ultramodern:assert-mf-types': "node ./scripts/assert-mf-types.mjs",
|
|
974
1119
|
'ultramodern:check': "node ./scripts/validate-ultramodern-workspace.mjs",
|
|
975
|
-
postinstall: "node ./scripts/setup-agent-reference-repos.mjs",
|
|
976
|
-
check: 'pnpm format:check && pnpm lint && pnpm typecheck && pnpm
|
|
1120
|
+
postinstall: "node ./scripts/setup-agent-reference-repos.mjs && node ./scripts/bootstrap-agent-skills.mjs",
|
|
1121
|
+
check: 'pnpm format:check && pnpm lint && pnpm typecheck && pnpm skills:check && pnpm ultramodern:check'
|
|
977
1122
|
},
|
|
978
1123
|
engines: {
|
|
979
1124
|
node: '>=20',
|
|
980
|
-
pnpm: '>=11.0.0'
|
|
1125
|
+
pnpm: '>=11.3.0 <11.4.0'
|
|
981
1126
|
},
|
|
982
1127
|
workspaces: [
|
|
983
1128
|
'apps/*',
|
|
@@ -1000,10 +1145,25 @@ function createRootPackageJson(scope, packageSource) {
|
|
|
1000
1145
|
"@typescript/native-preview": TYPESCRIPT_NATIVE_PREVIEW_VERSION,
|
|
1001
1146
|
oxlint: OXLINT_VERSION,
|
|
1002
1147
|
oxfmt: OXFMT_VERSION,
|
|
1003
|
-
ultracite: ULTRACITE_VERSION
|
|
1148
|
+
ultracite: ULTRACITE_VERSION,
|
|
1149
|
+
wrangler: WRANGLER_VERSION,
|
|
1150
|
+
'zephyr-agent': ZEPHYR_AGENT_VERSION
|
|
1004
1151
|
}
|
|
1005
1152
|
};
|
|
1006
1153
|
}
|
|
1154
|
+
function remoteDependencyAlias(remote) {
|
|
1155
|
+
return toCamelCase(remote.domain ?? remote.id.replace(/^remote-/, ''));
|
|
1156
|
+
}
|
|
1157
|
+
function zephyrRemoteDependency(scope, remote) {
|
|
1158
|
+
return `${ultramodern_workspace_packageName(scope, remote.packageSuffix)}@workspace:*`;
|
|
1159
|
+
}
|
|
1160
|
+
function createZephyrDependencies(scope, app, remotes = remoteApps) {
|
|
1161
|
+
if ('shell' !== app.kind) return {};
|
|
1162
|
+
return Object.fromEntries(remotes.map((remote)=>[
|
|
1163
|
+
remoteDependencyAlias(remote),
|
|
1164
|
+
zephyrRemoteDependency(scope, remote)
|
|
1165
|
+
]));
|
|
1166
|
+
}
|
|
1007
1167
|
function createTsConfigBase() {
|
|
1008
1168
|
return {
|
|
1009
1169
|
compilerOptions: {
|
|
@@ -1058,17 +1218,22 @@ function createPackageTsConfig(packageDir, includeApi = false) {
|
|
|
1058
1218
|
if (includeApi) include.push('api', 'shared');
|
|
1059
1219
|
return {
|
|
1060
1220
|
extends: `${relativeRootFor(packageDir)}/tsconfig.base.json`,
|
|
1061
|
-
include
|
|
1221
|
+
include,
|
|
1222
|
+
exclude: [
|
|
1223
|
+
'src/modern-tanstack'
|
|
1224
|
+
]
|
|
1062
1225
|
};
|
|
1063
1226
|
}
|
|
1064
|
-
function createAppPackage(scope, app, packageSource) {
|
|
1065
|
-
|
|
1227
|
+
function createAppPackage(scope, app, packageSource, enableTailwind) {
|
|
1228
|
+
const packageJson = {
|
|
1066
1229
|
private: true,
|
|
1067
1230
|
name: ultramodern_workspace_packageName(scope, app.packageSuffix),
|
|
1068
1231
|
version: '0.1.0',
|
|
1069
1232
|
scripts: {
|
|
1070
1233
|
dev: 'modern dev',
|
|
1071
|
-
build: 'modern build',
|
|
1234
|
+
build: app.exposes ? `modern build && node ${relativeRootFor(app.directory)}/scripts/assert-mf-types.mjs` : 'modern build',
|
|
1235
|
+
'cloudflare:build': 'MODERNJS_DEPLOY=cloudflare modern build && MODERNJS_DEPLOY=cloudflare modern deploy',
|
|
1236
|
+
'cloudflare:preview': 'MODERNJS_DEPLOY=cloudflare modern build && MODERNJS_DEPLOY=cloudflare modern deploy && wrangler dev --config .output/wrangler.json',
|
|
1072
1237
|
serve: 'modern serve',
|
|
1073
1238
|
typecheck: effectTsgoTypecheckCommand
|
|
1074
1239
|
},
|
|
@@ -1076,16 +1241,25 @@ function createAppPackage(scope, app, packageSource) {
|
|
|
1076
1241
|
preset: 'presetUltramodern',
|
|
1077
1242
|
role: 'shell' === app.kind ? 'shell' : 'module-federation-remote',
|
|
1078
1243
|
appId: app.id,
|
|
1079
|
-
topology: `${relativeRootFor(app.directory)}/topology/reference-topology.json
|
|
1244
|
+
topology: `${relativeRootFor(app.directory)}/topology/reference-topology.json`,
|
|
1245
|
+
...appHasEffectApi(app) ? {
|
|
1246
|
+
apiRuntime: 'effect-bff'
|
|
1247
|
+
} : {}
|
|
1080
1248
|
},
|
|
1081
|
-
dependencies:
|
|
1082
|
-
|
|
1249
|
+
'zephyr:dependencies': createZephyrDependencies(scope, app),
|
|
1250
|
+
dependencies: appDependencies(scope, packageSource, app),
|
|
1251
|
+
devDependencies: appDevDependencies(packageSource, enableTailwind)
|
|
1083
1252
|
};
|
|
1253
|
+
if (appHasEffectApi(app)) packageJson.exports = {
|
|
1254
|
+
'./effect/client': `./src/effect/${app.effectApi.stem}-client.ts`,
|
|
1255
|
+
'./shared/effect/api': './shared/effect/api.ts'
|
|
1256
|
+
};
|
|
1257
|
+
return packageJson;
|
|
1084
1258
|
}
|
|
1085
|
-
function createServicePackage(scope, packageSource) {
|
|
1259
|
+
function createServicePackage(scope, packageSource, enableTailwind, service = effectService) {
|
|
1086
1260
|
return {
|
|
1087
1261
|
private: true,
|
|
1088
|
-
name: ultramodern_workspace_packageName(scope,
|
|
1262
|
+
name: ultramodern_workspace_packageName(scope, service.packageSuffix),
|
|
1089
1263
|
version: '0.1.0',
|
|
1090
1264
|
scripts: {
|
|
1091
1265
|
dev: 'modern dev',
|
|
@@ -1096,8 +1270,8 @@ function createServicePackage(scope, packageSource) {
|
|
|
1096
1270
|
modernjs: {
|
|
1097
1271
|
preset: 'presetUltramodern',
|
|
1098
1272
|
role: 'effect-service',
|
|
1099
|
-
appId:
|
|
1100
|
-
topology: `${relativeRootFor(
|
|
1273
|
+
appId: service.id,
|
|
1274
|
+
topology: `${relativeRootFor(service.directory)}/topology/reference-topology.json`
|
|
1101
1275
|
},
|
|
1102
1276
|
dependencies: {
|
|
1103
1277
|
'@modern-js/runtime': modernPackageSpecifier('@modern-js/runtime', packageSource),
|
|
@@ -1109,15 +1283,21 @@ function createServicePackage(scope, packageSource) {
|
|
|
1109
1283
|
'@modern-js/app-tools': modernPackageSpecifier('@modern-js/app-tools', packageSource),
|
|
1110
1284
|
'@modern-js/plugin-bff': modernPackageSpecifier('@modern-js/plugin-bff', packageSource),
|
|
1111
1285
|
'@effect/tsgo': EFFECT_TSGO_VERSION,
|
|
1286
|
+
...enableTailwind ? {
|
|
1287
|
+
'@tailwindcss/postcss': `^${TAILWIND_POSTCSS_VERSION}`,
|
|
1288
|
+
postcss: '^8.5.6',
|
|
1289
|
+
tailwindcss: `^${TAILWIND_VERSION}`
|
|
1290
|
+
} : {},
|
|
1112
1291
|
"@typescript/native-preview": TYPESCRIPT_NATIVE_PREVIEW_VERSION,
|
|
1113
1292
|
'@types/node': '^20',
|
|
1114
1293
|
'@types/react': '^19.1.8',
|
|
1115
|
-
'@types/react-dom': '^19.1.6'
|
|
1294
|
+
'@types/react-dom': '^19.1.6',
|
|
1295
|
+
typescript: TYPESCRIPT_VERSION
|
|
1116
1296
|
}
|
|
1117
1297
|
};
|
|
1118
1298
|
}
|
|
1119
|
-
function createSharedPackage(scope, id, description) {
|
|
1120
|
-
|
|
1299
|
+
function createSharedPackage(scope, id, description, packageSource) {
|
|
1300
|
+
const packageJson = {
|
|
1121
1301
|
private: true,
|
|
1122
1302
|
name: ultramodern_workspace_packageName(scope, id),
|
|
1123
1303
|
version: '0.1.0',
|
|
@@ -1134,19 +1314,56 @@ function createSharedPackage(scope, id, description) {
|
|
|
1134
1314
|
"@typescript/native-preview": TYPESCRIPT_NATIVE_PREVIEW_VERSION
|
|
1135
1315
|
}
|
|
1136
1316
|
};
|
|
1317
|
+
if ('shared-effect-api' === id) packageJson.dependencies = {
|
|
1318
|
+
'@modern-js/plugin-bff': modernPackageSpecifier('@modern-js/plugin-bff', packageSource)
|
|
1319
|
+
};
|
|
1320
|
+
return packageJson;
|
|
1137
1321
|
}
|
|
1138
1322
|
function createAppModernConfig(app) {
|
|
1323
|
+
const bffImport = appHasEffectApi(app) ? "import { bffPlugin } from '@modern-js/plugin-bff';\n" : '';
|
|
1324
|
+
const bffConfig = appHasEffectApi(app) ? ` bff: {
|
|
1325
|
+
effect: {
|
|
1326
|
+
openapi: {
|
|
1327
|
+
path: '/openapi.json',
|
|
1328
|
+
},
|
|
1329
|
+
},
|
|
1330
|
+
prefix: '${effectApiPrefix(app)}',
|
|
1331
|
+
runtimeFramework: 'effect',
|
|
1332
|
+
},
|
|
1333
|
+
` : '';
|
|
1334
|
+
const bffPluginEntry = appHasEffectApi(app) ? ' bffPlugin(),\n' : '';
|
|
1139
1335
|
return `// @effect-diagnostics processEnv:off
|
|
1140
|
-
import {
|
|
1141
|
-
|
|
1336
|
+
import {
|
|
1337
|
+
appTools,
|
|
1338
|
+
defineConfig,
|
|
1339
|
+
presetUltramodern,
|
|
1340
|
+
} from '@modern-js/app-tools';
|
|
1341
|
+
${bffImport}import { i18nPlugin } from '@modern-js/plugin-i18n';
|
|
1142
1342
|
import { tanstackRouterPlugin } from '@modern-js/plugin-tanstack';
|
|
1143
1343
|
import { moduleFederationPlugin } from '@module-federation/modern-js-v3';
|
|
1144
|
-
import { withZephyr } from 'zephyr-
|
|
1344
|
+
import { withZephyr as withZephyrRspack } from 'zephyr-rspack-plugin';
|
|
1345
|
+
|
|
1346
|
+
type ZephyrRspackConfig = Parameters<ReturnType<typeof withZephyrRspack>>[0];
|
|
1347
|
+
|
|
1348
|
+
const zephyrRspackPlugin = () => ({
|
|
1349
|
+
name: 'ultramodern-zephyr-rspack-plugin',
|
|
1350
|
+
pre: ['@modern-js/plugin-module-federation-config'],
|
|
1351
|
+
setup(api: {
|
|
1352
|
+
modifyRspackConfig: (
|
|
1353
|
+
handler: (
|
|
1354
|
+
config: ZephyrRspackConfig,
|
|
1355
|
+
) => ZephyrRspackConfig | Promise<ZephyrRspackConfig>,
|
|
1356
|
+
) => void;
|
|
1357
|
+
}) {
|
|
1358
|
+
api.modifyRspackConfig(config => withZephyrRspack()(config));
|
|
1359
|
+
},
|
|
1360
|
+
});
|
|
1145
1361
|
|
|
1146
1362
|
const appId = '${app.id}';
|
|
1147
1363
|
const port = Number(process.env['${app.portEnv}'] ?? ${app.port});
|
|
1148
1364
|
const configuredSiteUrl = process.env['MODERN_PUBLIC_SITE_URL'];
|
|
1149
|
-
const hasConfiguredSiteUrl =
|
|
1365
|
+
const hasConfiguredSiteUrl =
|
|
1366
|
+
typeof configuredSiteUrl === 'string' && configuredSiteUrl.length > 0;
|
|
1150
1367
|
const isProductionBuild =
|
|
1151
1368
|
process.env['NODE_ENV'] === 'production' || process.argv.includes('build');
|
|
1152
1369
|
|
|
@@ -1156,12 +1373,14 @@ if (isProductionBuild && !hasConfiguredSiteUrl) {
|
|
|
1156
1373
|
);
|
|
1157
1374
|
}
|
|
1158
1375
|
|
|
1159
|
-
const siteUrl = hasConfiguredSiteUrl
|
|
1376
|
+
const siteUrl = hasConfiguredSiteUrl
|
|
1377
|
+
? configuredSiteUrl
|
|
1378
|
+
: \`http://localhost:\${port}\`;
|
|
1160
1379
|
|
|
1161
1380
|
export default defineConfig(
|
|
1162
1381
|
presetUltramodern(
|
|
1163
1382
|
{
|
|
1164
|
-
output: {
|
|
1383
|
+
${bffConfig} output: {
|
|
1165
1384
|
disableTsChecker: true,
|
|
1166
1385
|
distPath: {
|
|
1167
1386
|
html: './',
|
|
@@ -1173,24 +1392,44 @@ export default defineConfig(
|
|
|
1173
1392
|
outputStructure: 'flat',
|
|
1174
1393
|
},
|
|
1175
1394
|
plugins: [
|
|
1176
|
-
appTools(
|
|
1177
|
-
|
|
1178
|
-
}),
|
|
1395
|
+
appTools(),
|
|
1396
|
+
tanstackRouterPlugin(),
|
|
1179
1397
|
i18nPlugin({
|
|
1398
|
+
backend: {
|
|
1399
|
+
enabled: true,
|
|
1400
|
+
},
|
|
1401
|
+
reactI18next: false,
|
|
1180
1402
|
localeDetection: {
|
|
1181
1403
|
fallbackLanguage: 'en',
|
|
1182
1404
|
languages: ['en', 'cs'],
|
|
1183
1405
|
localePathRedirect: true,
|
|
1406
|
+
ignoreRedirectRoutes: [
|
|
1407
|
+
'/@mf-types',
|
|
1408
|
+
'/bundles',
|
|
1409
|
+
'${effectApiPrefix(app)}',
|
|
1410
|
+
'/locales',
|
|
1411
|
+
'/mf-manifest.json',
|
|
1412
|
+
'/mf-stats.json',
|
|
1413
|
+
'/remoteEntry.js',
|
|
1414
|
+
'/static',
|
|
1415
|
+
'/zephyr-manifest.json',
|
|
1416
|
+
],
|
|
1184
1417
|
},
|
|
1185
1418
|
}),
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
withZephyr(),
|
|
1419
|
+
${bffPluginEntry} moduleFederationPlugin(),
|
|
1420
|
+
zephyrRspackPlugin(),
|
|
1189
1421
|
],
|
|
1422
|
+
deploy: {
|
|
1423
|
+
target: 'cloudflare',
|
|
1424
|
+
worker: {
|
|
1425
|
+
ssr: true,
|
|
1426
|
+
},
|
|
1427
|
+
},
|
|
1190
1428
|
server: {
|
|
1191
1429
|
port,
|
|
1430
|
+
publicDir: './locales',
|
|
1192
1431
|
ssr: {
|
|
1193
|
-
mode: '
|
|
1432
|
+
mode: 'stream',
|
|
1194
1433
|
moduleFederationAppSSR: true,
|
|
1195
1434
|
},
|
|
1196
1435
|
},
|
|
@@ -1224,11 +1463,6 @@ function createSharedModuleFederationConfig() {
|
|
|
1224
1463
|
singleton: true,
|
|
1225
1464
|
treeShaking: false,
|
|
1226
1465
|
},
|
|
1227
|
-
i18next: {
|
|
1228
|
-
requiredVersion: dependencies.i18next,
|
|
1229
|
-
singleton: true,
|
|
1230
|
-
treeShaking: false,
|
|
1231
|
-
},
|
|
1232
1466
|
react: {
|
|
1233
1467
|
requiredVersion: reactVersion,
|
|
1234
1468
|
singleton: true,
|
|
@@ -1239,8 +1473,8 @@ function createSharedModuleFederationConfig() {
|
|
|
1239
1473
|
singleton: true,
|
|
1240
1474
|
treeShaking: false,
|
|
1241
1475
|
},
|
|
1242
|
-
'react-
|
|
1243
|
-
requiredVersion:
|
|
1476
|
+
'react-dom/client': {
|
|
1477
|
+
requiredVersion: reactDomVersion,
|
|
1244
1478
|
singleton: true,
|
|
1245
1479
|
treeShaking: false,
|
|
1246
1480
|
},
|
|
@@ -1253,7 +1487,16 @@ function formatTsObjectLiteral(value) {
|
|
|
1253
1487
|
${entries.map(([key, entryValue])=>` '${key}': '${entryValue}',`).join('\n')}
|
|
1254
1488
|
}`;
|
|
1255
1489
|
}
|
|
1256
|
-
function
|
|
1490
|
+
function createRemoteManifestEnv(remote) {
|
|
1491
|
+
return `REMOTE_${toEnvSegment(remote.domain ?? remote.id)}_MF_MANIFEST`;
|
|
1492
|
+
}
|
|
1493
|
+
function createShellModuleFederationConfig(remotes = remoteApps) {
|
|
1494
|
+
const remoteEntries = remotes.map((remote)=>{
|
|
1495
|
+
const key = remoteDependencyAlias(remote);
|
|
1496
|
+
return ` ${key}:
|
|
1497
|
+
process.env['${createRemoteManifestEnv(remote)}'] ??
|
|
1498
|
+
'${remote.mfName}@http://localhost:${remote.port}/mf-manifest.json',`;
|
|
1499
|
+
}).join('\n');
|
|
1257
1500
|
return `// @effect-diagnostics nodeBuiltinImport:off processEnv:off
|
|
1258
1501
|
import { createRequire } from 'node:module';
|
|
1259
1502
|
import { createModuleFederationConfig } from '@module-federation/modern-js-v3';
|
|
@@ -1265,24 +1508,48 @@ const reactVersion = (require('react/package.json') as { version: string }).vers
|
|
|
1265
1508
|
const reactDomVersion = (require('react-dom/package.json') as { version: string }).version;
|
|
1266
1509
|
|
|
1267
1510
|
export default createModuleFederationConfig({
|
|
1268
|
-
|
|
1511
|
+
treeShakingSharedExcludePlugins: ['RspackModuleFederationPlugin'],
|
|
1512
|
+
dev: {
|
|
1513
|
+
disableDynamicRemoteTypeHints: true,
|
|
1514
|
+
},
|
|
1515
|
+
dts: {
|
|
1516
|
+
displayErrorInTerminal: true,
|
|
1517
|
+
generateTypes: {
|
|
1518
|
+
compilerInstance: '--package typescript -- tsc',
|
|
1519
|
+
},
|
|
1520
|
+
},
|
|
1269
1521
|
filename: 'remoteEntry.js',
|
|
1270
1522
|
name: '${shellApp.mfName}',
|
|
1271
1523
|
remotes: {
|
|
1272
|
-
|
|
1273
|
-
process.env['REMOTE_COMMERCE_MF_MANIFEST'] ??
|
|
1274
|
-
'remoteCommerce@http://localhost:3021/mf-manifest.json',
|
|
1275
|
-
designSystem:
|
|
1276
|
-
process.env['REMOTE_DESIGN_SYSTEM_MF_MANIFEST'] ??
|
|
1277
|
-
'remoteDesignSystem@http://localhost:3023/mf-manifest.json',
|
|
1278
|
-
identity:
|
|
1279
|
-
process.env['REMOTE_IDENTITY_MF_MANIFEST'] ??
|
|
1280
|
-
'remoteIdentity@http://localhost:3022/mf-manifest.json',
|
|
1524
|
+
${remoteEntries}
|
|
1281
1525
|
},
|
|
1282
1526
|
${createSharedModuleFederationConfig()},
|
|
1283
1527
|
});
|
|
1284
1528
|
`;
|
|
1285
1529
|
}
|
|
1530
|
+
function createBuildMarker(scope, app) {
|
|
1531
|
+
return node_crypto.createHash('sha256').update(`${scope}:${app.packageSuffix}:${app.id}:0.1.0`).digest('hex').slice(0, 16);
|
|
1532
|
+
}
|
|
1533
|
+
function createUltramodernBuildModule(scope, app) {
|
|
1534
|
+
return `export const ultramodernVerticalIdentity = {
|
|
1535
|
+
appId: '${app.id}',
|
|
1536
|
+
packageName: '${ultramodern_workspace_packageName(scope, app.packageSuffix)}',
|
|
1537
|
+
version: '0.1.0',
|
|
1538
|
+
build: '${createBuildMarker(scope, app)}',
|
|
1539
|
+
deployProfile: 'cloudflare-ssr-mf-effect-v1',
|
|
1540
|
+
} as const;
|
|
1541
|
+
|
|
1542
|
+
export const ultramodernUiMarker = {
|
|
1543
|
+
...ultramodernVerticalIdentity,
|
|
1544
|
+
surface: 'ui',
|
|
1545
|
+
} as const;
|
|
1546
|
+
|
|
1547
|
+
export const ultramodernApiMarker = {
|
|
1548
|
+
...ultramodernVerticalIdentity,
|
|
1549
|
+
surface: 'effect-bff',
|
|
1550
|
+
} as const;
|
|
1551
|
+
`;
|
|
1552
|
+
}
|
|
1286
1553
|
function createRemoteModuleFederationConfig(app) {
|
|
1287
1554
|
const exposes = formatTsObjectLiteral(app.exposes ?? {});
|
|
1288
1555
|
return `// @effect-diagnostics nodeBuiltinImport:off
|
|
@@ -1296,7 +1563,16 @@ const reactVersion = (require('react/package.json') as { version: string }).vers
|
|
|
1296
1563
|
const reactDomVersion = (require('react-dom/package.json') as { version: string }).version;
|
|
1297
1564
|
|
|
1298
1565
|
export default createModuleFederationConfig({
|
|
1299
|
-
|
|
1566
|
+
treeShakingSharedExcludePlugins: ['RspackModuleFederationPlugin'],
|
|
1567
|
+
dev: {
|
|
1568
|
+
disableDynamicRemoteTypeHints: true,
|
|
1569
|
+
},
|
|
1570
|
+
dts: {
|
|
1571
|
+
displayErrorInTerminal: true,
|
|
1572
|
+
generateTypes: {
|
|
1573
|
+
compilerInstance: '--package typescript -- tsc',
|
|
1574
|
+
},
|
|
1575
|
+
},
|
|
1300
1576
|
exposes: ${exposes},
|
|
1301
1577
|
filename: 'remoteEntry.js',
|
|
1302
1578
|
name: '${app.mfName}',
|
|
@@ -1304,13 +1580,16 @@ ${createSharedModuleFederationConfig()},
|
|
|
1304
1580
|
});
|
|
1305
1581
|
`;
|
|
1306
1582
|
}
|
|
1307
|
-
function
|
|
1583
|
+
function remoteWidgetFile(app) {
|
|
1584
|
+
return `${app.domain ?? app.id.replace(/^remote-/, '')}-widget`;
|
|
1585
|
+
}
|
|
1586
|
+
function createServiceModernConfigFor(service = effectService) {
|
|
1308
1587
|
return `// @effect-diagnostics processEnv:off
|
|
1309
1588
|
import { appTools, defineConfig, presetUltramodern } from '@modern-js/app-tools';
|
|
1310
1589
|
import { bffPlugin } from '@modern-js/plugin-bff';
|
|
1311
1590
|
|
|
1312
|
-
const appId = '${
|
|
1313
|
-
const port = Number(process.env['${
|
|
1591
|
+
const appId = '${service.id}';
|
|
1592
|
+
const port = Number(process.env['${service.portEnv}'] ?? ${service.port});
|
|
1314
1593
|
|
|
1315
1594
|
export default defineConfig(
|
|
1316
1595
|
presetUltramodern(
|
|
@@ -1321,7 +1600,7 @@ export default defineConfig(
|
|
|
1321
1600
|
path: '/openapi.json',
|
|
1322
1601
|
},
|
|
1323
1602
|
},
|
|
1324
|
-
prefix: '
|
|
1603
|
+
prefix: '${serviceApiPrefix(service)}',
|
|
1325
1604
|
runtimeFramework: 'effect',
|
|
1326
1605
|
},
|
|
1327
1606
|
plugins: [appTools(), bffPlugin()],
|
|
@@ -1339,7 +1618,15 @@ export default defineConfig(
|
|
|
1339
1618
|
);
|
|
1340
1619
|
`;
|
|
1341
1620
|
}
|
|
1342
|
-
function createAppRuntimeConfig() {
|
|
1621
|
+
function createAppRuntimeConfig(app) {
|
|
1622
|
+
const resources = {
|
|
1623
|
+
cs: {
|
|
1624
|
+
translation: createAppLocaleMessages(app, 'cs')
|
|
1625
|
+
},
|
|
1626
|
+
en: {
|
|
1627
|
+
translation: createAppLocaleMessages(app, 'en')
|
|
1628
|
+
}
|
|
1629
|
+
};
|
|
1343
1630
|
return `import { defineRuntimeConfig } from '@modern-js/runtime';
|
|
1344
1631
|
import { createInstance } from 'i18next';
|
|
1345
1632
|
|
|
@@ -1355,6 +1642,7 @@ export default defineRuntimeConfig({
|
|
|
1355
1642
|
escapeValue: false,
|
|
1356
1643
|
},
|
|
1357
1644
|
ns: ['translation'],
|
|
1645
|
+
resources: ${JSON.stringify(resources, null, 8).split('\n').join('\n ')},
|
|
1358
1646
|
supportedLngs: ['en', 'cs'],
|
|
1359
1647
|
},
|
|
1360
1648
|
},
|
|
@@ -1364,109 +1652,126 @@ export default defineRuntimeConfig({
|
|
|
1364
1652
|
});
|
|
1365
1653
|
`;
|
|
1366
1654
|
}
|
|
1367
|
-
function
|
|
1368
|
-
return
|
|
1369
|
-
|
|
1370
|
-
|
|
1655
|
+
function createAppStyles(enableTailwind) {
|
|
1656
|
+
return `${enableTailwind ? "@import 'tailwindcss';\n\n" : ''}:root {
|
|
1657
|
+
color: #10231c;
|
|
1658
|
+
background: #f6f8f7;
|
|
1659
|
+
font-family:
|
|
1660
|
+
Inter,
|
|
1661
|
+
ui-sans-serif,
|
|
1662
|
+
system-ui,
|
|
1663
|
+
-apple-system,
|
|
1664
|
+
BlinkMacSystemFont,
|
|
1665
|
+
"Segoe UI",
|
|
1666
|
+
sans-serif;
|
|
1667
|
+
}
|
|
1371
1668
|
|
|
1372
|
-
|
|
1373
|
-
|
|
1669
|
+
body {
|
|
1670
|
+
margin: 0;
|
|
1671
|
+
}
|
|
1374
1672
|
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1673
|
+
main {
|
|
1674
|
+
min-height: 100vh;
|
|
1675
|
+
padding: 2rem;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
nav {
|
|
1679
|
+
display: flex;
|
|
1680
|
+
gap: 0.75rem;
|
|
1681
|
+
margin-bottom: 2rem;
|
|
1682
|
+
}
|
|
1382
1683
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1684
|
+
a {
|
|
1685
|
+
color: #166b4b;
|
|
1686
|
+
}
|
|
1687
|
+
`;
|
|
1688
|
+
}
|
|
1689
|
+
function createPostcssConfig() {
|
|
1690
|
+
return `export default {
|
|
1691
|
+
plugins: {
|
|
1692
|
+
'@tailwindcss/postcss': {},
|
|
1693
|
+
},
|
|
1386
1694
|
};
|
|
1695
|
+
`;
|
|
1696
|
+
}
|
|
1697
|
+
function createTailwindConfig() {
|
|
1698
|
+
return `import type { Config } from 'tailwindcss';
|
|
1699
|
+
|
|
1700
|
+
export default {
|
|
1701
|
+
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
|
1702
|
+
} satisfies Config;
|
|
1703
|
+
`;
|
|
1704
|
+
}
|
|
1705
|
+
function createLocalizedHeadComponent() {
|
|
1706
|
+
return `const fallbackLanguage = 'en';
|
|
1707
|
+
const supportedLanguages = ['en', 'cs'] as const;
|
|
1708
|
+
type SupportedLanguage = (typeof supportedLanguages)[number];
|
|
1709
|
+
|
|
1710
|
+
const localizedPath = (language: SupportedLanguage) => \`/\${language}\`;
|
|
1387
1711
|
|
|
1388
1712
|
const absoluteUrl = (pathname: string) => {
|
|
1389
1713
|
const origin = ULTRAMODERN_SITE_URL.replace(/\\/+$/u, '');
|
|
1390
1714
|
return \`\${origin}\${pathname}\`;
|
|
1391
1715
|
};
|
|
1392
|
-
${includeLocationSuffix ? `
|
|
1393
|
-
const locationSuffix = (location: { hash?: unknown; search?: unknown; searchStr?: unknown }) => {
|
|
1394
|
-
const { hash, search, searchStr } = location;
|
|
1395
|
-
let locationSearch = '';
|
|
1396
|
-
if (typeof searchStr === 'string') {
|
|
1397
|
-
locationSearch = searchStr;
|
|
1398
|
-
} else if (typeof search === 'string') {
|
|
1399
|
-
locationSearch = search;
|
|
1400
|
-
}
|
|
1401
|
-
const locationHash = typeof hash === 'string' ? hash : '';
|
|
1402
|
-
return \`\${locationSearch}\${locationHash}\`;
|
|
1403
|
-
};
|
|
1404
|
-
` : ''}
|
|
1405
1716
|
const LocalizedHead = () => {
|
|
1406
|
-
const
|
|
1407
|
-
const location = useLocation();
|
|
1408
|
-
const currentLanguage = isSupportedLanguage(language) ? language : fallbackLanguage;
|
|
1409
|
-
const canonicalPath = localizedPath(location.pathname, currentLanguage);
|
|
1717
|
+
const canonicalPath = localizedPath(fallbackLanguage);
|
|
1410
1718
|
|
|
1411
1719
|
return (
|
|
1412
|
-
|
|
1720
|
+
<>
|
|
1413
1721
|
<link rel="canonical" href={absoluteUrl(canonicalPath)} />
|
|
1414
|
-
{supportedLanguages.map(
|
|
1722
|
+
{supportedLanguages.map(code => (
|
|
1415
1723
|
<link
|
|
1416
|
-
href={absoluteUrl(localizedPath(
|
|
1724
|
+
href={absoluteUrl(localizedPath(code))}
|
|
1417
1725
|
hrefLang={code}
|
|
1418
1726
|
key={code}
|
|
1419
1727
|
rel="alternate"
|
|
1420
1728
|
/>
|
|
1421
1729
|
))}
|
|
1422
1730
|
<link
|
|
1423
|
-
href={absoluteUrl(localizedPath(
|
|
1731
|
+
href={absoluteUrl(localizedPath(fallbackLanguage))}
|
|
1424
1732
|
hrefLang="x-default"
|
|
1425
1733
|
rel="alternate"
|
|
1426
1734
|
/>
|
|
1427
|
-
|
|
1735
|
+
</>
|
|
1428
1736
|
);
|
|
1429
1737
|
};
|
|
1430
1738
|
`;
|
|
1431
1739
|
}
|
|
1432
1740
|
function createShellPage() {
|
|
1433
|
-
return `import {
|
|
1434
|
-
import {
|
|
1435
|
-
import
|
|
1436
|
-
|
|
1741
|
+
return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
1742
|
+
import { ultramodernUiMarker } from '../../ultramodern-build';
|
|
1743
|
+
import '../index.css';
|
|
1744
|
+
|
|
1745
|
+
const languageCodes = ['en', 'cs'] as const;
|
|
1437
1746
|
|
|
1438
|
-
const
|
|
1747
|
+
const remoteKeys = ['commerce', 'identity', 'designSystem'] as const;
|
|
1439
1748
|
|
|
1440
|
-
${createLocalizedHeadComponent(
|
|
1749
|
+
${createLocalizedHeadComponent()}
|
|
1441
1750
|
export default function ShellHome() {
|
|
1442
|
-
const {
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1445
|
-
const currentLanguage = isSupportedLanguage(language) ? language : fallbackLanguage;
|
|
1446
|
-
const suffix = locationSuffix(location);
|
|
1447
|
-
const languageOptions = supportedLanguages.map((code) => ({
|
|
1448
|
-
code,
|
|
1449
|
-
href: \`\${localizedPath(location.pathname, code)}\${suffix}\`,
|
|
1450
|
-
label: t(\`language.\${code}\`),
|
|
1451
|
-
}));
|
|
1751
|
+
const { i18nInstance, language } = useModernI18n();
|
|
1752
|
+
const t = i18nInstance.t.bind(i18nInstance);
|
|
1753
|
+
|
|
1452
1754
|
return (
|
|
1453
1755
|
<main>
|
|
1454
1756
|
<LocalizedHead />
|
|
1455
|
-
<nav aria-label={t('language.switcher')}>
|
|
1456
|
-
{
|
|
1757
|
+
<nav aria-label={t('shell.language.switcher')}>
|
|
1758
|
+
{languageCodes.map(code => (
|
|
1457
1759
|
<a
|
|
1458
|
-
aria-current={
|
|
1459
|
-
href={
|
|
1460
|
-
key={
|
|
1760
|
+
aria-current={language === code ? 'page' : undefined}
|
|
1761
|
+
href={\`/\${code}\`}
|
|
1762
|
+
key={code}
|
|
1461
1763
|
>
|
|
1462
|
-
{
|
|
1764
|
+
{t(\`shell.language.\${code}\`)}
|
|
1463
1765
|
</a>
|
|
1464
1766
|
))}
|
|
1465
1767
|
</nav>
|
|
1466
1768
|
<h1>{t('shell.title')}</h1>
|
|
1467
|
-
<p data-testid="ultramodern-preset">
|
|
1769
|
+
<p data-testid="ultramodern-preset">presetUltramodern workspace</p>
|
|
1770
|
+
<p data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
|
|
1771
|
+
{ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
|
|
1772
|
+
</p>
|
|
1468
1773
|
<ul>
|
|
1469
|
-
{
|
|
1774
|
+
{remoteKeys.map(remote => (
|
|
1470
1775
|
<li key={remote}>{t(\`shell.remotes.\${remote}\`)}</li>
|
|
1471
1776
|
))}
|
|
1472
1777
|
</ul>
|
|
@@ -1476,54 +1781,143 @@ export default function ShellHome() {
|
|
|
1476
1781
|
`;
|
|
1477
1782
|
}
|
|
1478
1783
|
function createRemotePage(app) {
|
|
1479
|
-
|
|
1480
|
-
import {
|
|
1481
|
-
import {
|
|
1482
|
-
import {
|
|
1784
|
+
const effectBffImport = appHasEffectApi(app) ? `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
1785
|
+
import { useEffect, useState } from 'react';
|
|
1786
|
+
import { ultramodernUiMarker } from '../../ultramodern-build';
|
|
1787
|
+
` : "import { useModernI18n } from '@modern-js/plugin-i18n/runtime';\nimport { ultramodernUiMarker } from '../../ultramodern-build';\n";
|
|
1788
|
+
const effectBffState = appHasEffectApi(app) ? ` const [effectApiStatus, setEffectApiStatus] = useState('pending');
|
|
1789
|
+
|
|
1790
|
+
useEffect(() => {
|
|
1791
|
+
void fetch('${effectApiPrefix(app)}/effect/${effectApiStem(app)}?limit=1', {
|
|
1792
|
+
headers: {
|
|
1793
|
+
accept: 'application/json',
|
|
1794
|
+
},
|
|
1795
|
+
})
|
|
1796
|
+
.then(response => {
|
|
1797
|
+
if (!response.ok) {
|
|
1798
|
+
throw new Error(\`Effect BFF request failed: \${response.status}\`);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
return response.json() as Promise<{ items?: Array<{ title?: string }> }>;
|
|
1802
|
+
})
|
|
1803
|
+
.then(data => {
|
|
1804
|
+
setEffectApiStatus(data.items[0]?.title ?? 'empty');
|
|
1805
|
+
})
|
|
1806
|
+
.catch(() => {
|
|
1807
|
+
setEffectApiStatus('unavailable');
|
|
1808
|
+
});
|
|
1809
|
+
}, []);
|
|
1810
|
+
|
|
1811
|
+
` : '';
|
|
1812
|
+
const effectBffMarkup = appHasEffectApi(app) ? ` <p data-testid="effect-bff-status">{effectApiStatus}</p>
|
|
1813
|
+
` : '';
|
|
1814
|
+
return `${effectBffImport}import '../index.css';
|
|
1483
1815
|
|
|
1484
1816
|
${createLocalizedHeadComponent()}
|
|
1485
1817
|
export default function ${toPascalCase(app.id)}Home() {
|
|
1486
|
-
const {
|
|
1487
|
-
|
|
1488
|
-
return (
|
|
1818
|
+
const { i18nInstance, language } = useModernI18n();
|
|
1819
|
+
const t = i18nInstance.t.bind(i18nInstance);
|
|
1820
|
+
${effectBffState} return (
|
|
1489
1821
|
<main>
|
|
1490
1822
|
<LocalizedHead />
|
|
1491
|
-
<
|
|
1492
|
-
|
|
1493
|
-
|
|
1823
|
+
<nav aria-label={t('${app.domain}.language.switcher')}>
|
|
1824
|
+
{supportedLanguages.map(code => (
|
|
1825
|
+
<a
|
|
1826
|
+
aria-current={language === code ? 'page' : undefined}
|
|
1827
|
+
href={\`/\${code}\`}
|
|
1828
|
+
key={code}
|
|
1829
|
+
>
|
|
1830
|
+
{t(\`${app.domain}.language.\${code}\`)}
|
|
1831
|
+
</a>
|
|
1832
|
+
))}
|
|
1833
|
+
</nav>
|
|
1834
|
+
<h1>{t('${app.domain}.title')}</h1>
|
|
1835
|
+
<p data-mf-role="${app.kind}">{t('${app.domain}.role')}</p>
|
|
1836
|
+
<p data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
|
|
1837
|
+
{ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
|
|
1838
|
+
</p>
|
|
1839
|
+
${effectBffMarkup} </main>
|
|
1494
1840
|
);
|
|
1495
1841
|
}
|
|
1496
1842
|
`;
|
|
1497
1843
|
}
|
|
1498
1844
|
function createLayout(appId) {
|
|
1499
|
-
return `import
|
|
1845
|
+
return `import { Outlet } from '@modern-js/plugin-tanstack/runtime';
|
|
1846
|
+
import './index.css';
|
|
1500
1847
|
|
|
1501
|
-
export default function Layout(
|
|
1502
|
-
return
|
|
1848
|
+
export default function Layout() {
|
|
1849
|
+
return (
|
|
1850
|
+
<div data-app-id="${appId}">
|
|
1851
|
+
<Outlet />
|
|
1852
|
+
</div>
|
|
1853
|
+
);
|
|
1503
1854
|
}
|
|
1504
1855
|
`;
|
|
1505
1856
|
}
|
|
1506
1857
|
function createRemoteEntry(app) {
|
|
1507
|
-
|
|
1508
|
-
return `export { default } from './components/${componentFile}';
|
|
1858
|
+
return `export { default } from './components/${remoteWidgetFile(app)}';
|
|
1509
1859
|
`;
|
|
1510
1860
|
}
|
|
1511
1861
|
function createRemoteWidget(app) {
|
|
1512
|
-
const componentName =
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
export default function ${componentName}() {
|
|
1516
|
-
const { t } = useTranslation();
|
|
1517
|
-
|
|
1862
|
+
const componentName = `${toPascalCase(app.domain ?? app.id)}Widget`;
|
|
1863
|
+
const body = 'vertical' === app.kind ? `Owns the ${app.domain} vertical route surface.` : 'Provides shared UI primitives for the workspace.';
|
|
1864
|
+
return `export default function ${componentName}() {
|
|
1518
1865
|
return (
|
|
1519
1866
|
<section data-mf-remote="${app.id}">
|
|
1520
|
-
<h2
|
|
1521
|
-
<p
|
|
1867
|
+
<h2>${app.displayName}</h2>
|
|
1868
|
+
<p>${body}</p>
|
|
1522
1869
|
</section>
|
|
1523
1870
|
);
|
|
1524
1871
|
}
|
|
1525
1872
|
`;
|
|
1526
1873
|
}
|
|
1874
|
+
function createAppLocaleMessages(app, language) {
|
|
1875
|
+
const czechLabels = {
|
|
1876
|
+
commerce: {
|
|
1877
|
+
role: 'obchod',
|
|
1878
|
+
title: 'Obchodni remote'
|
|
1879
|
+
},
|
|
1880
|
+
'design-system': {
|
|
1881
|
+
role: 'design system',
|
|
1882
|
+
title: 'Design system remote'
|
|
1883
|
+
},
|
|
1884
|
+
identity: {
|
|
1885
|
+
role: 'identita',
|
|
1886
|
+
title: 'Identitni remote'
|
|
1887
|
+
}
|
|
1888
|
+
};
|
|
1889
|
+
if ('shell' === app.kind) return {
|
|
1890
|
+
shell: {
|
|
1891
|
+
language: {
|
|
1892
|
+
cs: 'en' === language ? 'Czech' : 'Cestina',
|
|
1893
|
+
en: 'en' === language ? 'English' : 'Anglictina',
|
|
1894
|
+
switcher: 'en' === language ? 'Language' : 'Jazyk'
|
|
1895
|
+
},
|
|
1896
|
+
remotes: {
|
|
1897
|
+
commerce: 'en' === language ? 'Commerce Remote' : 'Obchodni remote',
|
|
1898
|
+
designSystem: 'en' === language ? 'Design System Remote' : 'Design system remote',
|
|
1899
|
+
identity: 'en' === language ? 'Identity Remote' : 'Identitni remote'
|
|
1900
|
+
},
|
|
1901
|
+
title: 'en' === language ? 'UltraModern SuperApp Shell' : 'UltraModern SuperApp shell'
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
const domain = app.domain ?? app.id;
|
|
1905
|
+
const czechLabel = czechLabels[domain] ?? {
|
|
1906
|
+
role: domain,
|
|
1907
|
+
title: `${app.displayName} CZ`
|
|
1908
|
+
};
|
|
1909
|
+
return {
|
|
1910
|
+
[domain]: {
|
|
1911
|
+
language: {
|
|
1912
|
+
cs: 'en' === language ? 'Czech' : 'Cestina',
|
|
1913
|
+
en: 'en' === language ? 'English' : 'Anglictina',
|
|
1914
|
+
switcher: 'en' === language ? 'Language' : 'Jazyk'
|
|
1915
|
+
},
|
|
1916
|
+
role: 'en' === language ? app.domain ?? app.kind : czechLabel.role,
|
|
1917
|
+
title: 'en' === language ? app.displayName : czechLabel.title
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1527
1921
|
function createDesignButton() {
|
|
1528
1922
|
return `import { designTokens } from '../tokens';
|
|
1529
1923
|
|
|
@@ -1554,124 +1948,373 @@ function createDesignTokens() {
|
|
|
1554
1948
|
} as const;
|
|
1555
1949
|
`;
|
|
1556
1950
|
}
|
|
1557
|
-
function
|
|
1558
|
-
|
|
1559
|
-
language: {
|
|
1560
|
-
cs: 'Czech',
|
|
1561
|
-
en: 'English',
|
|
1562
|
-
switcher: 'Language'
|
|
1563
|
-
},
|
|
1564
|
-
shell: {
|
|
1565
|
-
preset: 'presetUltramodern workspace',
|
|
1566
|
-
remotes: {
|
|
1567
|
-
'remote-commerce': 'Commerce Remote',
|
|
1568
|
-
'remote-design-system': 'Design System Remote',
|
|
1569
|
-
'remote-identity': 'Identity Remote'
|
|
1570
|
-
},
|
|
1571
|
-
title: 'UltraModern SuperApp Shell'
|
|
1572
|
-
}
|
|
1573
|
-
};
|
|
1574
|
-
return {
|
|
1575
|
-
remote: {
|
|
1576
|
-
domain: app.domain ?? app.kind,
|
|
1577
|
-
title: app.displayName,
|
|
1578
|
-
widget: {
|
|
1579
|
-
body: 'vertical' === app.kind ? `Owns the ${app.domain} vertical route surface.` : 'Provides shared UI primitives for the workspace.',
|
|
1580
|
-
title: app.displayName
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
};
|
|
1951
|
+
function serviceEffectApiExport(service = effectService) {
|
|
1952
|
+
return `${toCamelCase(effectApiStem(service))}EffectApi`;
|
|
1584
1953
|
}
|
|
1585
|
-
function
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
return {
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
widget: {
|
|
1607
|
-
body: 'vertical' === app.kind ? `Vlastni ${app.domain} vertical route surface.` : 'Poskytuje sdilene UI prvky pro workspace.',
|
|
1608
|
-
title: app.displayName
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
};
|
|
1954
|
+
function serviceEffectGroupName(service = effectService) {
|
|
1955
|
+
return toCamelCase(effectApiStem(service));
|
|
1956
|
+
}
|
|
1957
|
+
function serviceEffectApiName(service = effectService) {
|
|
1958
|
+
return `${toPascalCase(effectApiStem(service))}EffectApi`;
|
|
1959
|
+
}
|
|
1960
|
+
function serviceEffectSchemaExport(service = effectService) {
|
|
1961
|
+
return `${toCamelCase(effectApiStem(service))}ItemSchema`;
|
|
1962
|
+
}
|
|
1963
|
+
function serviceEffectErrorStem(service = effectService) {
|
|
1964
|
+
const stem = effectApiStem(service);
|
|
1965
|
+
return 'recommendations' === stem ? 'recommendation' : stem;
|
|
1966
|
+
}
|
|
1967
|
+
function serviceEffectCreatePayloadSchemaExport(service = effectService) {
|
|
1968
|
+
return `${toCamelCase(effectApiStem(service))}CreatePayloadSchema`;
|
|
1969
|
+
}
|
|
1970
|
+
function serviceEffectNotFoundErrorExport(service = effectService) {
|
|
1971
|
+
return `${toPascalCase(serviceEffectErrorStem(service))}NotFound`;
|
|
1972
|
+
}
|
|
1973
|
+
function serviceEffectNotFoundSchemaExport(service = effectService) {
|
|
1974
|
+
return `${toCamelCase(serviceEffectErrorStem(service))}NotFoundSchema`;
|
|
1612
1975
|
}
|
|
1613
|
-
function
|
|
1976
|
+
function createEffectSharedApiImports() {
|
|
1614
1977
|
return `import {
|
|
1615
1978
|
HttpApi,
|
|
1616
1979
|
HttpApiEndpoint,
|
|
1617
1980
|
HttpApiGroup,
|
|
1981
|
+
HttpApiSchema,
|
|
1618
1982
|
Schema,
|
|
1619
1983
|
} from '@modern-js/plugin-bff/effect-client';
|
|
1620
|
-
|
|
1621
|
-
|
|
1984
|
+
`;
|
|
1985
|
+
}
|
|
1986
|
+
function createEffectSharedApiContract(service = effectService) {
|
|
1987
|
+
const schemaExport = serviceEffectSchemaExport(service);
|
|
1988
|
+
const createPayloadSchemaExport = serviceEffectCreatePayloadSchemaExport(service);
|
|
1989
|
+
const notFoundErrorExport = serviceEffectNotFoundErrorExport(service);
|
|
1990
|
+
const notFoundSchemaExport = serviceEffectNotFoundSchemaExport(service);
|
|
1991
|
+
const apiExport = serviceEffectApiExport(service);
|
|
1992
|
+
const apiName = serviceEffectApiName(service);
|
|
1993
|
+
const groupName = serviceEffectGroupName(service);
|
|
1994
|
+
const stem = effectApiStem(service);
|
|
1995
|
+
const servicePrefix = effectApiPrefix(service);
|
|
1996
|
+
return `export const ${schemaExport} = Schema.Struct({
|
|
1622
1997
|
id: Schema.String,
|
|
1998
|
+
marker: Schema.Struct({
|
|
1999
|
+
appId: Schema.String,
|
|
2000
|
+
packageName: Schema.String,
|
|
2001
|
+
version: Schema.String,
|
|
2002
|
+
build: Schema.String,
|
|
2003
|
+
deployProfile: Schema.String,
|
|
2004
|
+
surface: Schema.String,
|
|
2005
|
+
}),
|
|
1623
2006
|
title: Schema.String,
|
|
1624
2007
|
});
|
|
1625
2008
|
|
|
1626
|
-
export const
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
2009
|
+
export const ${createPayloadSchemaExport} = Schema.Struct({
|
|
2010
|
+
title: Schema.String,
|
|
2011
|
+
});
|
|
2012
|
+
|
|
2013
|
+
export class ${notFoundErrorExport} extends Schema.TaggedErrorClass<${notFoundErrorExport}>()(
|
|
2014
|
+
'${notFoundErrorExport}',
|
|
2015
|
+
{
|
|
2016
|
+
id: Schema.String,
|
|
2017
|
+
},
|
|
2018
|
+
) {}
|
|
2019
|
+
|
|
2020
|
+
export const ${notFoundSchemaExport} = ${notFoundErrorExport}.pipe(
|
|
2021
|
+
HttpApiSchema.status(404),
|
|
2022
|
+
);
|
|
2023
|
+
|
|
2024
|
+
export type OperationContext = {
|
|
2025
|
+
operationId: string;
|
|
2026
|
+
routePath: string;
|
|
2027
|
+
method: string;
|
|
2028
|
+
source: string;
|
|
2029
|
+
traceId?: string;
|
|
2030
|
+
};
|
|
2031
|
+
|
|
2032
|
+
export const ${apiExport} = HttpApi.make('${apiName}').add(
|
|
2033
|
+
HttpApiGroup.make('${groupName}')
|
|
2034
|
+
.add(
|
|
2035
|
+
HttpApiEndpoint.get('list', '/effect/${stem}', {
|
|
2036
|
+
query: {
|
|
2037
|
+
limit: Schema.optional(Schema.NumberFromString),
|
|
2038
|
+
},
|
|
2039
|
+
success: Schema.Struct({
|
|
2040
|
+
items: Schema.Array(${schemaExport}),
|
|
2041
|
+
}),
|
|
1631
2042
|
}),
|
|
1632
|
-
|
|
1633
|
-
|
|
2043
|
+
)
|
|
2044
|
+
.add(
|
|
2045
|
+
HttpApiEndpoint.get('get', '/effect/${stem}/:id', {
|
|
2046
|
+
params: {
|
|
2047
|
+
id: Schema.String,
|
|
2048
|
+
},
|
|
2049
|
+
success: ${schemaExport},
|
|
2050
|
+
error: ${notFoundSchemaExport},
|
|
2051
|
+
}),
|
|
2052
|
+
)
|
|
2053
|
+
.add(
|
|
2054
|
+
HttpApiEndpoint.post('create', '/effect/${stem}', {
|
|
2055
|
+
payload: ${createPayloadSchemaExport},
|
|
2056
|
+
success: Schema.Struct({
|
|
2057
|
+
item: ${schemaExport},
|
|
2058
|
+
}),
|
|
2059
|
+
}),
|
|
2060
|
+
),
|
|
1634
2061
|
);
|
|
2062
|
+
|
|
2063
|
+
export const ${groupName}OperationContexts = {
|
|
2064
|
+
list: {
|
|
2065
|
+
operationId: '${apiName}:${groupName}:list',
|
|
2066
|
+
routePath: '/effect/${stem}',
|
|
2067
|
+
method: 'GET',
|
|
2068
|
+
source: 'generated-client',
|
|
2069
|
+
},
|
|
2070
|
+
get: {
|
|
2071
|
+
operationId: '${apiName}:${groupName}:get',
|
|
2072
|
+
routePath: '/effect/${stem}/:id',
|
|
2073
|
+
method: 'GET',
|
|
2074
|
+
source: 'generated-client',
|
|
2075
|
+
},
|
|
2076
|
+
create: {
|
|
2077
|
+
operationId: '${apiName}:${groupName}:create',
|
|
2078
|
+
routePath: '/effect/${stem}',
|
|
2079
|
+
method: 'POST',
|
|
2080
|
+
source: 'generated-client',
|
|
2081
|
+
},
|
|
2082
|
+
} satisfies Record<string, OperationContext>;
|
|
2083
|
+
|
|
2084
|
+
export const ${groupName}ApiContract = {
|
|
2085
|
+
basePath: '${servicePrefix}/effect/${stem}',
|
|
2086
|
+
ownerId: '${service.id}',
|
|
2087
|
+
servicePrefix: '${servicePrefix}',
|
|
2088
|
+
} as const;
|
|
2089
|
+
`;
|
|
2090
|
+
}
|
|
2091
|
+
function createEffectSharedApi(service) {
|
|
2092
|
+
if (service) return `${createEffectSharedApiImports()}
|
|
2093
|
+
${createEffectSharedApiContract(service)}`;
|
|
2094
|
+
return `export const sharedEffectApiPackage = {
|
|
2095
|
+
scope: 'external-effect-service-contracts',
|
|
2096
|
+
} as const;
|
|
1635
2097
|
`;
|
|
1636
2098
|
}
|
|
1637
|
-
function createEffectServiceEntry() {
|
|
2099
|
+
function createEffectServiceEntry(scope, service = effectService, contractImportPath = ultramodern_workspace_packageName(scope, 'shared-effect-api')) {
|
|
2100
|
+
const apiExport = serviceEffectApiExport(service);
|
|
2101
|
+
const groupName = serviceEffectGroupName(service);
|
|
2102
|
+
const notFoundErrorExport = serviceEffectNotFoundErrorExport(service);
|
|
2103
|
+
const stem = effectApiStem(service);
|
|
1638
2104
|
return `import {
|
|
1639
2105
|
defineEffectBff,
|
|
1640
2106
|
Effect,
|
|
1641
2107
|
HttpApiBuilder,
|
|
1642
2108
|
Layer,
|
|
1643
|
-
} from '@modern-js/plugin-bff/effect-
|
|
1644
|
-
import {
|
|
2109
|
+
} from '@modern-js/plugin-bff/effect-edge';
|
|
2110
|
+
import { ultramodernApiMarker } from '../../src/ultramodern-build';
|
|
2111
|
+
import {
|
|
2112
|
+
${apiExport},
|
|
2113
|
+
${groupName}OperationContexts,
|
|
2114
|
+
${notFoundErrorExport},
|
|
2115
|
+
type OperationContext,
|
|
2116
|
+
} from '${contractImportPath}';
|
|
2117
|
+
|
|
2118
|
+
const ${groupName}Items = [
|
|
2119
|
+
{
|
|
2120
|
+
id: 'starter-${stem}',
|
|
2121
|
+
marker: ultramodernApiMarker,
|
|
2122
|
+
title: 'Wire a real ${stem} source here',
|
|
2123
|
+
},
|
|
2124
|
+
];
|
|
2125
|
+
|
|
2126
|
+
const operationAttributes = (operationContext: OperationContext) => {
|
|
2127
|
+
return {
|
|
2128
|
+
'modernjs.operation.id': operationContext.operationId,
|
|
2129
|
+
'modernjs.operation.method': operationContext.method,
|
|
2130
|
+
'modernjs.operation.route': operationContext.routePath,
|
|
2131
|
+
'modernjs.operation.source': operationContext.source,
|
|
2132
|
+
...(typeof operationContext.traceId === 'string'
|
|
2133
|
+
? { 'modernjs.trace.id': operationContext.traceId }
|
|
2134
|
+
: {}),
|
|
2135
|
+
};
|
|
2136
|
+
};
|
|
1645
2137
|
|
|
1646
|
-
const
|
|
1647
|
-
|
|
1648
|
-
'
|
|
2138
|
+
const ${groupName}Layer = HttpApiBuilder.group(
|
|
2139
|
+
${apiExport},
|
|
2140
|
+
'${groupName}',
|
|
1649
2141
|
(handlers) =>
|
|
1650
|
-
handlers
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
2142
|
+
handlers
|
|
2143
|
+
.handle('list', ({ query }) =>
|
|
2144
|
+
Effect.succeed({
|
|
2145
|
+
items:
|
|
2146
|
+
typeof query.limit === 'number'
|
|
2147
|
+
? ${groupName}Items.slice(0, query.limit)
|
|
2148
|
+
: ${groupName}Items,
|
|
2149
|
+
}).pipe(
|
|
2150
|
+
Effect.withSpan('ultramodern.effect.${groupName}.list', {
|
|
2151
|
+
attributes: operationAttributes(${groupName}OperationContexts.list),
|
|
2152
|
+
kind: 'server',
|
|
2153
|
+
}),
|
|
2154
|
+
),
|
|
2155
|
+
)
|
|
2156
|
+
.handle('get', ({ params }) => {
|
|
2157
|
+
const item = ${groupName}Items.find(item => item.id === params.id);
|
|
2158
|
+
return (item !== undefined
|
|
2159
|
+
? Effect.succeed(item)
|
|
2160
|
+
: Effect.fail(new ${notFoundErrorExport}({ id: params.id }))).pipe(
|
|
2161
|
+
Effect.withSpan('ultramodern.effect.${groupName}.get', {
|
|
2162
|
+
attributes: operationAttributes(${groupName}OperationContexts.get),
|
|
2163
|
+
kind: 'server',
|
|
2164
|
+
}),
|
|
2165
|
+
);
|
|
2166
|
+
})
|
|
2167
|
+
.handle('create', ({ payload }) =>
|
|
2168
|
+
Effect.succeed({
|
|
2169
|
+
item: {
|
|
2170
|
+
id: \`generated-${stem}-\${payload.title
|
|
2171
|
+
.toLowerCase()
|
|
2172
|
+
.replaceAll(/[^a-z0-9]+/g, '-')
|
|
2173
|
+
.replaceAll(/^-|-$/g, '')}\`,
|
|
2174
|
+
marker: ultramodernApiMarker,
|
|
2175
|
+
title: payload.title,
|
|
1656
2176
|
},
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
2177
|
+
}).pipe(
|
|
2178
|
+
Effect.withSpan('ultramodern.effect.${groupName}.create', {
|
|
2179
|
+
attributes: operationAttributes(${groupName}OperationContexts.create),
|
|
2180
|
+
kind: 'server',
|
|
2181
|
+
}),
|
|
2182
|
+
),
|
|
2183
|
+
),
|
|
1660
2184
|
);
|
|
1661
2185
|
|
|
1662
|
-
const layer = HttpApiBuilder.layer(
|
|
1663
|
-
Layer.provide(
|
|
2186
|
+
const layer = HttpApiBuilder.layer(${apiExport}).pipe(
|
|
2187
|
+
Layer.provide(${groupName}Layer),
|
|
1664
2188
|
);
|
|
1665
2189
|
|
|
1666
2190
|
export default defineEffectBff({
|
|
1667
|
-
api:
|
|
2191
|
+
api: ${apiExport},
|
|
1668
2192
|
layer,
|
|
1669
2193
|
});
|
|
1670
2194
|
`;
|
|
1671
2195
|
}
|
|
2196
|
+
function createEffectClient(service, contractImportPath) {
|
|
2197
|
+
const apiExport = serviceEffectApiExport(service);
|
|
2198
|
+
const contractExport = serviceEffectGroupName(service);
|
|
2199
|
+
const stem = effectApiStem(service);
|
|
2200
|
+
const groupName = serviceEffectGroupName(service);
|
|
2201
|
+
const singular = serviceEffectErrorStem(service);
|
|
2202
|
+
const clientOptionsName = `${toPascalCase(stem)}ClientOptions`;
|
|
2203
|
+
const createClientName = `create${toPascalCase(stem)}Client`;
|
|
2204
|
+
const listName = `list${toPascalCase(stem)}`;
|
|
2205
|
+
const getName = `get${toPascalCase(singular)}`;
|
|
2206
|
+
const createName = `create${toPascalCase(singular)}`;
|
|
2207
|
+
return `import {
|
|
2208
|
+
makeEffectHttpApiClient,
|
|
2209
|
+
runEffectRequest,
|
|
2210
|
+
} from '@modern-js/plugin-bff/effect-client';
|
|
2211
|
+
import {
|
|
2212
|
+
${contractExport}ApiContract,
|
|
2213
|
+
${apiExport},
|
|
2214
|
+
${groupName}OperationContexts,
|
|
2215
|
+
type OperationContext,
|
|
2216
|
+
} from '${contractImportPath}';
|
|
2217
|
+
|
|
2218
|
+
export type ${clientOptionsName} = {
|
|
2219
|
+
baseUrl?: string | URL;
|
|
2220
|
+
locale?: string;
|
|
2221
|
+
operationContext?: OperationContext;
|
|
2222
|
+
traceparent?: string;
|
|
2223
|
+
};
|
|
2224
|
+
|
|
2225
|
+
export function ${createClientName}(
|
|
2226
|
+
options: ${clientOptionsName} = {},
|
|
2227
|
+
) {
|
|
2228
|
+
return makeEffectHttpApiClient(${apiExport}, {
|
|
2229
|
+
baseUrl: options.baseUrl ?? ${contractExport}ApiContract.servicePrefix,
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
export function ${listName}(
|
|
2234
|
+
options: ${clientOptionsName} & { limit?: number } = {},
|
|
2235
|
+
) {
|
|
2236
|
+
return runEffectRequest(
|
|
2237
|
+
${createClientName}({
|
|
2238
|
+
...options,
|
|
2239
|
+
operationContext:
|
|
2240
|
+
options.operationContext ?? ${groupName}OperationContexts.list,
|
|
2241
|
+
}),
|
|
2242
|
+
).then(client =>
|
|
2243
|
+
runEffectRequest(
|
|
2244
|
+
client.${groupName}.list({ query: { limit: options.limit } }),
|
|
2245
|
+
),
|
|
2246
|
+
);
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
export function ${getName}(
|
|
2250
|
+
id: string,
|
|
2251
|
+
options: ${clientOptionsName} = {},
|
|
2252
|
+
) {
|
|
2253
|
+
return runEffectRequest(
|
|
2254
|
+
${createClientName}({
|
|
2255
|
+
...options,
|
|
2256
|
+
operationContext:
|
|
2257
|
+
options.operationContext ?? ${groupName}OperationContexts.get,
|
|
2258
|
+
}),
|
|
2259
|
+
).then(client =>
|
|
2260
|
+
runEffectRequest(client.${groupName}.get({ params: { id } })),
|
|
2261
|
+
);
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
export function ${createName}(
|
|
2265
|
+
title: string,
|
|
2266
|
+
options: ${clientOptionsName} = {},
|
|
2267
|
+
) {
|
|
2268
|
+
return runEffectRequest(
|
|
2269
|
+
${createClientName}({
|
|
2270
|
+
...options,
|
|
2271
|
+
operationContext:
|
|
2272
|
+
options.operationContext ?? ${groupName}OperationContexts.create,
|
|
2273
|
+
}),
|
|
2274
|
+
).then(client =>
|
|
2275
|
+
runEffectRequest(
|
|
2276
|
+
client.${groupName}.create({ payload: { title } }),
|
|
2277
|
+
),
|
|
2278
|
+
);
|
|
2279
|
+
}
|
|
2280
|
+
`;
|
|
2281
|
+
}
|
|
2282
|
+
function createShellEffectClient(scope) {
|
|
2283
|
+
return `export {
|
|
2284
|
+
createRecommendation,
|
|
2285
|
+
createRecommendationsClient,
|
|
2286
|
+
getRecommendation,
|
|
2287
|
+
listRecommendations,
|
|
2288
|
+
type RecommendationsClientOptions,
|
|
2289
|
+
} from '${ultramodern_workspace_packageName(scope, 'remote-commerce')}/effect/client';
|
|
2290
|
+
`;
|
|
2291
|
+
}
|
|
1672
2292
|
function toPascalCase(value) {
|
|
1673
2293
|
return value.split(/[-_]+/).filter(Boolean).map((part)=>`${part.charAt(0).toUpperCase()}${part.slice(1)}`).join('');
|
|
1674
2294
|
}
|
|
2295
|
+
function effectApiTopologyMetadata(app) {
|
|
2296
|
+
if (!appHasEffectApi(app)) return;
|
|
2297
|
+
return {
|
|
2298
|
+
effect: {
|
|
2299
|
+
runtime: 'effect',
|
|
2300
|
+
bff: {
|
|
2301
|
+
prefix: app.effectApi.prefix,
|
|
2302
|
+
openapi: '/openapi.json'
|
|
2303
|
+
},
|
|
2304
|
+
contract: {
|
|
2305
|
+
export: './shared/effect/api',
|
|
2306
|
+
path: `${app.directory}/shared/effect/api.ts`
|
|
2307
|
+
},
|
|
2308
|
+
client: {
|
|
2309
|
+
export: './effect/client',
|
|
2310
|
+
path: `${app.directory}/src/effect/${app.effectApi.stem}-client.ts`
|
|
2311
|
+
},
|
|
2312
|
+
serverEntry: `${app.directory}/api/effect/index.ts`,
|
|
2313
|
+
basePath: `${app.effectApi.prefix}/effect/${app.effectApi.stem}`,
|
|
2314
|
+
consumedBy: app.effectApi.consumedBy
|
|
2315
|
+
}
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
1675
2318
|
function createTopology(scope) {
|
|
1676
2319
|
return {
|
|
1677
2320
|
schemaVersion: 1,
|
|
@@ -1711,25 +2354,12 @@ function createTopology(scope) {
|
|
|
1711
2354
|
fallbackTelemetryEvent: 'modernjs:mv-runtime-parity',
|
|
1712
2355
|
sharedContractVersion: 'mf-ssr-contract-v1'
|
|
1713
2356
|
},
|
|
2357
|
+
...effectApiTopologyMetadata(remote) ? {
|
|
2358
|
+
api: effectApiTopologyMetadata(remote)
|
|
2359
|
+
} : {},
|
|
1714
2360
|
ownership: remote.ownership
|
|
1715
2361
|
})),
|
|
1716
|
-
effectServices: [
|
|
1717
|
-
{
|
|
1718
|
-
id: effectService.id,
|
|
1719
|
-
kind: 'effect-service',
|
|
1720
|
-
runtime: 'effect',
|
|
1721
|
-
package: ultramodern_workspace_packageName(scope, effectService.packageSuffix),
|
|
1722
|
-
consumedBy: [
|
|
1723
|
-
shellApp.id,
|
|
1724
|
-
'remote-commerce'
|
|
1725
|
-
],
|
|
1726
|
-
bff: {
|
|
1727
|
-
prefix: '/recommendations-api',
|
|
1728
|
-
openapi: '/openapi.json'
|
|
1729
|
-
},
|
|
1730
|
-
ownership: effectService.ownership
|
|
1731
|
-
}
|
|
1732
|
-
],
|
|
2362
|
+
effectServices: [],
|
|
1733
2363
|
sharedPackages: sharedPackages.map((sharedPackage)=>({
|
|
1734
2364
|
id: sharedPackage.id,
|
|
1735
2365
|
package: ultramodern_workspace_packageName(scope, sharedPackage.id),
|
|
@@ -1751,12 +2381,6 @@ function createOwnership(scope) {
|
|
|
1751
2381
|
owners: [
|
|
1752
2382
|
shellApp,
|
|
1753
2383
|
...remoteApps,
|
|
1754
|
-
{
|
|
1755
|
-
id: effectService.id,
|
|
1756
|
-
packageSuffix: effectService.packageSuffix,
|
|
1757
|
-
directory: effectService.directory,
|
|
1758
|
-
ownership: effectService.ownership
|
|
1759
|
-
},
|
|
1760
2384
|
...sharedPackages.map((sharedPackage)=>({
|
|
1761
2385
|
id: sharedPackage.id,
|
|
1762
2386
|
packageSuffix: sharedPackage.id,
|
|
@@ -1794,19 +2418,15 @@ function createDevelopmentOverlay() {
|
|
|
1794
2418
|
].map((app)=>[
|
|
1795
2419
|
app.id,
|
|
1796
2420
|
app.port
|
|
1797
|
-
])
|
|
1798
|
-
[
|
|
1799
|
-
effectService.id,
|
|
1800
|
-
effectService.port
|
|
1801
|
-
]
|
|
1802
|
-
])),
|
|
2421
|
+
])),
|
|
1803
2422
|
manifests: Object.fromEntries(remoteApps.map((remote)=>[
|
|
1804
2423
|
remote.id,
|
|
1805
2424
|
`http://localhost:${remote.port}/mf-manifest.json`
|
|
1806
2425
|
])),
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
2426
|
+
apis: Object.fromEntries(verticalEffectApps().map((app)=>[
|
|
2427
|
+
app.id,
|
|
2428
|
+
`http://localhost:${app.port}${effectApiPrefix(app)}`
|
|
2429
|
+
]))
|
|
1810
2430
|
};
|
|
1811
2431
|
}
|
|
1812
2432
|
function createPackageSourceMetadata(scope, packageSource) {
|
|
@@ -1836,6 +2456,185 @@ function createPackageSourceMetadata(scope, packageSource) {
|
|
|
1836
2456
|
}
|
|
1837
2457
|
};
|
|
1838
2458
|
}
|
|
2459
|
+
function createEffectOperationContract(target) {
|
|
2460
|
+
const stem = effectApiStem(target);
|
|
2461
|
+
return {
|
|
2462
|
+
group: serviceEffectGroupName(target),
|
|
2463
|
+
notFound: serviceEffectNotFoundErrorExport(target),
|
|
2464
|
+
operations: {
|
|
2465
|
+
list: {
|
|
2466
|
+
method: 'GET',
|
|
2467
|
+
path: `/effect/${stem}`,
|
|
2468
|
+
source: 'generated-client'
|
|
2469
|
+
},
|
|
2470
|
+
get: {
|
|
2471
|
+
method: 'GET',
|
|
2472
|
+
path: `/effect/${stem}/:id`,
|
|
2473
|
+
source: 'generated-client'
|
|
2474
|
+
},
|
|
2475
|
+
create: {
|
|
2476
|
+
method: 'POST',
|
|
2477
|
+
path: `/effect/${stem}`,
|
|
2478
|
+
source: 'generated-client'
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
function createAppConfigContract(app) {
|
|
2484
|
+
return {
|
|
2485
|
+
preset: 'presetUltramodern',
|
|
2486
|
+
plugins: [
|
|
2487
|
+
'appTools',
|
|
2488
|
+
'tanstackRouterPlugin',
|
|
2489
|
+
'i18nPlugin',
|
|
2490
|
+
...appHasEffectApi(app) ? [
|
|
2491
|
+
'bffPlugin'
|
|
2492
|
+
] : [],
|
|
2493
|
+
'moduleFederationPlugin',
|
|
2494
|
+
'zephyrRspackPlugin'
|
|
2495
|
+
],
|
|
2496
|
+
output: {
|
|
2497
|
+
disableTsChecker: true,
|
|
2498
|
+
distPath: {
|
|
2499
|
+
html: './'
|
|
2500
|
+
},
|
|
2501
|
+
polyfill: 'off',
|
|
2502
|
+
splitRouteChunks: false
|
|
2503
|
+
},
|
|
2504
|
+
html: {
|
|
2505
|
+
outputStructure: 'flat'
|
|
2506
|
+
},
|
|
2507
|
+
source: {
|
|
2508
|
+
mainEntryName: 'index',
|
|
2509
|
+
siteUrlGlobal: 'ULTRAMODERN_SITE_URL'
|
|
2510
|
+
},
|
|
2511
|
+
...appHasEffectApi(app) ? {
|
|
2512
|
+
bff: {
|
|
2513
|
+
runtimeFramework: 'effect',
|
|
2514
|
+
prefix: app.effectApi.prefix,
|
|
2515
|
+
openapi: '/openapi.json'
|
|
2516
|
+
}
|
|
2517
|
+
} : {}
|
|
2518
|
+
};
|
|
2519
|
+
}
|
|
2520
|
+
function createStylingContract(enableTailwind) {
|
|
2521
|
+
return {
|
|
2522
|
+
tailwind: enableTailwind,
|
|
2523
|
+
...enableTailwind ? {
|
|
2524
|
+
postcssPlugins: [
|
|
2525
|
+
'@tailwindcss/postcss'
|
|
2526
|
+
],
|
|
2527
|
+
contentGlobs: [
|
|
2528
|
+
'./src/**/*.{js,jsx,ts,tsx}'
|
|
2529
|
+
]
|
|
2530
|
+
} : {}
|
|
2531
|
+
};
|
|
2532
|
+
}
|
|
2533
|
+
function createAppGeneratedContract(scope, app, apps, enableTailwind) {
|
|
2534
|
+
const remoteAppsForShell = apps.filter((candidate)=>'shell' !== candidate.kind && candidate.mfName);
|
|
2535
|
+
return {
|
|
2536
|
+
id: app.id,
|
|
2537
|
+
package: ultramodern_workspace_packageName(scope, app.packageSuffix),
|
|
2538
|
+
path: app.directory,
|
|
2539
|
+
kind: app.kind,
|
|
2540
|
+
config: createAppConfigContract(app),
|
|
2541
|
+
styling: createStylingContract(enableTailwind),
|
|
2542
|
+
deploy: {
|
|
2543
|
+
target: 'cloudflare',
|
|
2544
|
+
worker: {
|
|
2545
|
+
ssr: true
|
|
2546
|
+
},
|
|
2547
|
+
output: {
|
|
2548
|
+
flat: true,
|
|
2549
|
+
htmlDistPath: './'
|
|
2550
|
+
}
|
|
2551
|
+
},
|
|
2552
|
+
ssr: {
|
|
2553
|
+
mode: 'stream',
|
|
2554
|
+
moduleFederationAppSSR: true
|
|
2555
|
+
},
|
|
2556
|
+
i18n: {
|
|
2557
|
+
plugin: '@modern-js/plugin-i18n',
|
|
2558
|
+
backend: true,
|
|
2559
|
+
reactI18next: false,
|
|
2560
|
+
languages: [
|
|
2561
|
+
'en',
|
|
2562
|
+
'cs'
|
|
2563
|
+
],
|
|
2564
|
+
fallbackLanguage: 'en',
|
|
2565
|
+
publicDir: './locales'
|
|
2566
|
+
},
|
|
2567
|
+
moduleFederation: {
|
|
2568
|
+
name: app.mfName,
|
|
2569
|
+
...'shell' === app.kind ? {
|
|
2570
|
+
remotes: remoteAppsForShell.map((remote)=>({
|
|
2571
|
+
id: remote.id,
|
|
2572
|
+
alias: remoteDependencyAlias(remote),
|
|
2573
|
+
name: remote.mfName,
|
|
2574
|
+
manifestEnv: createRemoteManifestEnv(remote),
|
|
2575
|
+
manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`
|
|
2576
|
+
}))
|
|
2577
|
+
} : {},
|
|
2578
|
+
exposes: Object.keys(app.exposes ?? {}),
|
|
2579
|
+
dts: {
|
|
2580
|
+
displayErrorInTerminal: true,
|
|
2581
|
+
compilerInstance: "--package typescript -- tsc"
|
|
2582
|
+
},
|
|
2583
|
+
browserSafeExposesOnly: true,
|
|
2584
|
+
zephyrRspackPlugin: ZEPHYR_RSPACK_PLUGIN_VERSION
|
|
2585
|
+
},
|
|
2586
|
+
marker: {
|
|
2587
|
+
appId: app.id,
|
|
2588
|
+
packageName: ultramodern_workspace_packageName(scope, app.packageSuffix),
|
|
2589
|
+
version: '0.1.0',
|
|
2590
|
+
build: createBuildMarker(scope, app),
|
|
2591
|
+
deployProfile: 'cloudflare-ssr-mf-effect-v1',
|
|
2592
|
+
uiSurface: 'ui',
|
|
2593
|
+
...appHasEffectApi(app) ? {
|
|
2594
|
+
apiSurface: 'effect-bff'
|
|
2595
|
+
} : {}
|
|
2596
|
+
},
|
|
2597
|
+
...appHasEffectApi(app) ? {
|
|
2598
|
+
effect: {
|
|
2599
|
+
runtime: 'effect',
|
|
2600
|
+
import: '@modern-js/plugin-bff/effect-edge',
|
|
2601
|
+
prefix: app.effectApi.prefix,
|
|
2602
|
+
openapi: '/openapi.json',
|
|
2603
|
+
workerEntry: 'worker/__modern_bff_effect.js',
|
|
2604
|
+
contract: './shared/effect/api',
|
|
2605
|
+
client: './effect/client',
|
|
2606
|
+
...createEffectOperationContract(app)
|
|
2607
|
+
}
|
|
2608
|
+
} : {}
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
function createGeneratedContract(scope, apps = [
|
|
2612
|
+
shellApp,
|
|
2613
|
+
...remoteApps
|
|
2614
|
+
], enableTailwind = true) {
|
|
2615
|
+
return {
|
|
2616
|
+
schemaVersion: 1,
|
|
2617
|
+
profile: 'cloudflare-ssr-mf-effect-v1',
|
|
2618
|
+
packageManager: {
|
|
2619
|
+
source: 'package.json',
|
|
2620
|
+
manager: 'pnpm',
|
|
2621
|
+
version: '11.3.0',
|
|
2622
|
+
toolchain: 'mise',
|
|
2623
|
+
corepack: false
|
|
2624
|
+
},
|
|
2625
|
+
versions: {
|
|
2626
|
+
typescript: TYPESCRIPT_VERSION,
|
|
2627
|
+
typescriptNativePreview: TYPESCRIPT_NATIVE_PREVIEW_VERSION,
|
|
2628
|
+
moduleFederation: MODULE_FEDERATION_VERSION,
|
|
2629
|
+
tanstackRouter: TANSTACK_ROUTER_VERSION,
|
|
2630
|
+
i18next: I18NEXT_VERSION,
|
|
2631
|
+
zephyrRspackPlugin: ZEPHYR_RSPACK_PLUGIN_VERSION,
|
|
2632
|
+
zephyrAgent: ZEPHYR_AGENT_VERSION,
|
|
2633
|
+
wrangler: WRANGLER_VERSION
|
|
2634
|
+
},
|
|
2635
|
+
apps: apps.map((app)=>createAppGeneratedContract(scope, app, apps, enableTailwind))
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
1839
2638
|
function createTemplateManifest(modernVersion, packageSource) {
|
|
1840
2639
|
return {
|
|
1841
2640
|
schemaVersion: 1,
|
|
@@ -1913,6 +2712,12 @@ function createTemplateManifest(modernVersion, packageSource) {
|
|
|
1913
2712
|
licensePath: '.agents/rstackjs-agent-skills-LICENSE'
|
|
1914
2713
|
},
|
|
1915
2714
|
baseline: baselineAgentSkills,
|
|
2715
|
+
moduleFederationSource: {
|
|
2716
|
+
repository: 'https://github.com/module-federation/agent-skills',
|
|
2717
|
+
commit: MODULE_FEDERATION_AGENT_SKILLS_COMMIT,
|
|
2718
|
+
install: 'clone',
|
|
2719
|
+
baseline: moduleFederationAgentSkills
|
|
2720
|
+
},
|
|
1916
2721
|
privateSource: {
|
|
1917
2722
|
repository: 'https://github.com/TechsioCZ/skills',
|
|
1918
2723
|
install: 'clone-if-authorized',
|
|
@@ -1947,42 +2752,72 @@ function createTemplateManifest(modernVersion, packageSource) {
|
|
|
1947
2752
|
}
|
|
1948
2753
|
};
|
|
1949
2754
|
}
|
|
1950
|
-
function writeApp(targetDir, scope, app, packageSource) {
|
|
1951
|
-
writeJson(targetDir, `${app.directory}/package.json`, createAppPackage(scope, app, packageSource));
|
|
1952
|
-
writeJson(targetDir, `${app.directory}/tsconfig.json`, createPackageTsConfig(app.directory));
|
|
2755
|
+
function writeApp(targetDir, scope, app, packageSource, enableTailwind) {
|
|
2756
|
+
writeJson(targetDir, `${app.directory}/package.json`, createAppPackage(scope, app, packageSource, enableTailwind));
|
|
2757
|
+
writeJson(targetDir, `${app.directory}/tsconfig.json`, createPackageTsConfig(app.directory, appHasEffectApi(app)));
|
|
1953
2758
|
writeFile(targetDir, `${app.directory}/src/modern-app-env.d.ts`, "/// <reference types='@modern-js/app-tools/types' />\n\ndeclare const ULTRAMODERN_SITE_URL: string;\n");
|
|
2759
|
+
writeFile(targetDir, `${app.directory}/src/ultramodern-build.ts`, createUltramodernBuildModule(scope, app));
|
|
1954
2760
|
writeFile(targetDir, `${app.directory}/modern.config.ts`, createAppModernConfig(app));
|
|
1955
|
-
writeFile(targetDir, `${app.directory}/src/modern.runtime.ts`, createAppRuntimeConfig());
|
|
1956
|
-
writeJson(targetDir, `${app.directory}/
|
|
1957
|
-
writeJson(targetDir, `${app.directory}/
|
|
2761
|
+
writeFile(targetDir, `${app.directory}/src/modern.runtime.ts`, createAppRuntimeConfig(app));
|
|
2762
|
+
writeJson(targetDir, `${app.directory}/locales/en/translation.json`, createAppLocaleMessages(app, 'en'));
|
|
2763
|
+
writeJson(targetDir, `${app.directory}/locales/cs/translation.json`, createAppLocaleMessages(app, 'cs'));
|
|
2764
|
+
writeFile(targetDir, `${app.directory}/src/routes/index.css`, createAppStyles(enableTailwind));
|
|
2765
|
+
if (enableTailwind) {
|
|
2766
|
+
writeFile(targetDir, `${app.directory}/postcss.config.mjs`, createPostcssConfig());
|
|
2767
|
+
writeFile(targetDir, `${app.directory}/tailwind.config.ts`, createTailwindConfig());
|
|
2768
|
+
}
|
|
1958
2769
|
writeFile(targetDir, `${app.directory}/module-federation.config.ts`, 'shell' === app.kind ? createShellModuleFederationConfig() : createRemoteModuleFederationConfig(app));
|
|
1959
2770
|
writeFile(targetDir, `${app.directory}/src/routes/layout.tsx`, createLayout(app.id));
|
|
1960
2771
|
writeFile(targetDir, `${app.directory}/src/routes/[lang]/page.tsx`, 'shell' === app.kind ? createShellPage() : createRemotePage(app));
|
|
1961
|
-
if ('
|
|
2772
|
+
if ('shell' === app.kind) writeFile(targetDir, `${app.directory}/src/effect/recommendations-client.ts`, createShellEffectClient(scope));
|
|
2773
|
+
if (appHasEffectApi(app)) {
|
|
2774
|
+
writeFile(targetDir, `${app.directory}/shared/effect/api.ts`, createEffectSharedApi(app));
|
|
2775
|
+
writeFile(targetDir, `${app.directory}/api/effect/index.ts`, createEffectServiceEntry(scope, app, '../../shared/effect/api'));
|
|
2776
|
+
writeFile(targetDir, `${app.directory}/src/effect/${app.effectApi.stem}-client.ts`, createEffectClient(app, '../../shared/effect/api'));
|
|
2777
|
+
}
|
|
2778
|
+
if ('vertical' === app.kind || 'horizontal-remote' === app.kind) {
|
|
1962
2779
|
writeFile(targetDir, `${app.directory}/src/remote-entry.tsx`, createRemoteEntry(app));
|
|
1963
|
-
|
|
1964
|
-
writeFile(targetDir, `${app.directory}/src/components/${widgetFile}`, createRemoteWidget(app));
|
|
2780
|
+
writeFile(targetDir, `${app.directory}/src/components/${remoteWidgetFile(app)}.tsx`, createRemoteWidget(app));
|
|
1965
2781
|
}
|
|
1966
2782
|
if ('horizontal-design-system' === app.kind) {
|
|
1967
2783
|
writeFile(targetDir, `${app.directory}/src/components/button.tsx`, createDesignButton());
|
|
1968
2784
|
writeFile(targetDir, `${app.directory}/src/tokens.ts`, createDesignTokens());
|
|
1969
2785
|
}
|
|
1970
2786
|
}
|
|
1971
|
-
function writeEffectService(targetDir, scope, packageSource) {
|
|
1972
|
-
writeJson(targetDir, `${
|
|
1973
|
-
writeJson(targetDir, `${
|
|
1974
|
-
writeFile(targetDir, `${
|
|
1975
|
-
writeFile(targetDir, `${
|
|
1976
|
-
|
|
2787
|
+
function writeEffectService(targetDir, scope, packageSource, enableTailwind, service = effectService) {
|
|
2788
|
+
writeJson(targetDir, `${service.directory}/package.json`, createServicePackage(scope, packageSource, enableTailwind, service));
|
|
2789
|
+
writeJson(targetDir, `${service.directory}/tsconfig.json`, createPackageTsConfig(service.directory, true));
|
|
2790
|
+
writeFile(targetDir, `${service.directory}/src/modern-app-env.d.ts`, "/// <reference types='@modern-js/app-tools/types' />\n");
|
|
2791
|
+
writeFile(targetDir, `${service.directory}/src/ultramodern-build.ts`, createUltramodernBuildModule(scope, service));
|
|
2792
|
+
writeFile(targetDir, `${service.directory}/src/routes/layout.tsx`, createLayout(service.id));
|
|
2793
|
+
writeFile(targetDir, `${service.directory}/src/routes/page.tsx`, `import './index.css';
|
|
2794
|
+
|
|
2795
|
+
export default function ${toPascalCase(service.id)}Home() {
|
|
2796
|
+
return <main>${service.id} Effect service</main>;
|
|
1977
2797
|
}
|
|
1978
2798
|
`);
|
|
1979
|
-
writeFile(targetDir, `${
|
|
1980
|
-
|
|
1981
|
-
|
|
2799
|
+
writeFile(targetDir, `${service.directory}/src/routes/index.css`, createAppStyles(enableTailwind));
|
|
2800
|
+
if (enableTailwind) {
|
|
2801
|
+
writeFile(targetDir, `${service.directory}/postcss.config.mjs`, createPostcssConfig());
|
|
2802
|
+
writeFile(targetDir, `${service.directory}/tailwind.config.ts`, createTailwindConfig());
|
|
2803
|
+
}
|
|
2804
|
+
writeFile(targetDir, `${service.directory}/modern.config.ts`, createServiceModernConfigFor(service));
|
|
2805
|
+
writeFile(targetDir, `${service.directory}/api/effect/index.ts`, createEffectServiceEntry(scope, service));
|
|
2806
|
+
}
|
|
2807
|
+
function writeGenericSharedPackage(targetDir, scope, packageSource, sharedPackage) {
|
|
2808
|
+
writeJson(targetDir, `${sharedPackage.directory}/package.json`, createSharedPackage(scope, sharedPackage.id, sharedPackage.description, packageSource));
|
|
2809
|
+
writeJson(targetDir, `${sharedPackage.directory}/tsconfig.json`, {
|
|
2810
|
+
extends: `${relativeRootFor(sharedPackage.directory)}/tsconfig.base.json`,
|
|
2811
|
+
include: [
|
|
2812
|
+
'src'
|
|
2813
|
+
]
|
|
2814
|
+
});
|
|
2815
|
+
writeFile(targetDir, `${sharedPackage.directory}/src/index.ts`, `export const packageId = '${sharedPackage.id}';
|
|
2816
|
+
`);
|
|
1982
2817
|
}
|
|
1983
|
-
function writeSharedPackages(targetDir, scope) {
|
|
2818
|
+
function writeSharedPackages(targetDir, scope, packageSource) {
|
|
1984
2819
|
for (const sharedPackage of sharedPackages){
|
|
1985
|
-
writeJson(targetDir, `${sharedPackage.directory}/package.json`, createSharedPackage(scope, sharedPackage.id, sharedPackage.description));
|
|
2820
|
+
writeJson(targetDir, `${sharedPackage.directory}/package.json`, createSharedPackage(scope, sharedPackage.id, sharedPackage.description, packageSource));
|
|
1986
2821
|
writeJson(targetDir, `${sharedPackage.directory}/tsconfig.json`, {
|
|
1987
2822
|
extends: `${relativeRootFor(sharedPackage.directory)}/tsconfig.base.json`,
|
|
1988
2823
|
include: [
|
|
@@ -2004,26 +2839,286 @@ function writeSharedPackages(targetDir, scope) {
|
|
|
2004
2839
|
},
|
|
2005
2840
|
} as const;
|
|
2006
2841
|
`);
|
|
2007
|
-
writeFile(targetDir, 'packages/shared-effect-api/src/index.ts',
|
|
2008
|
-
id: string;
|
|
2009
|
-
title: string;
|
|
2842
|
+
writeFile(targetDir, 'packages/shared-effect-api/src/index.ts', createEffectSharedApi());
|
|
2010
2843
|
}
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
}
|
|
2016
|
-
|
|
2844
|
+
function readJsonFile(filePath) {
|
|
2845
|
+
return JSON.parse(node_fs.readFileSync(filePath, 'utf-8'));
|
|
2846
|
+
}
|
|
2847
|
+
function writeJsonFile(filePath, value) {
|
|
2848
|
+
node_fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
|
|
2849
|
+
}
|
|
2850
|
+
function appendEffectSharedApiContract(targetDir, service = effectService) {
|
|
2851
|
+
const relativePath = 'packages/shared-effect-api/src/index.ts';
|
|
2852
|
+
assertSafeRelativePath(relativePath);
|
|
2853
|
+
const filePath = node_path.join(targetDir, relativePath);
|
|
2854
|
+
ensureInsideRoot(targetDir, filePath);
|
|
2855
|
+
if (!node_fs.existsSync(filePath)) throw new Error(`Missing generated Effect API package: ${relativePath}`);
|
|
2856
|
+
const current = node_fs.readFileSync(filePath, 'utf-8');
|
|
2857
|
+
const apiExport = serviceEffectApiExport(service);
|
|
2858
|
+
if (current.includes(`export const ${apiExport} =`)) return;
|
|
2859
|
+
const contentWithImports = current.includes('@modern-js/plugin-bff/effect-client') ? current.trimEnd() : `${createEffectSharedApiImports()}\n${current.trimEnd()}`;
|
|
2860
|
+
node_fs.writeFileSync(filePath, `${contentWithImports}\n\n${createEffectSharedApiContract(service)}`, 'utf-8');
|
|
2861
|
+
}
|
|
2862
|
+
function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
|
|
2863
|
+
if (packageSource) return resolvePackageSource({
|
|
2864
|
+
targetDir: workspaceRoot,
|
|
2865
|
+
packageName: node_path.basename(workspaceRoot),
|
|
2866
|
+
modernVersion,
|
|
2867
|
+
packageSource
|
|
2868
|
+
});
|
|
2869
|
+
const metadataPath = node_path.join(workspaceRoot, '.modernjs/ultramodern-package-source.json');
|
|
2870
|
+
if (!node_fs.existsSync(metadataPath)) return resolvePackageSource({
|
|
2871
|
+
targetDir: workspaceRoot,
|
|
2872
|
+
packageName: node_path.basename(workspaceRoot),
|
|
2873
|
+
modernVersion
|
|
2874
|
+
});
|
|
2875
|
+
const metadata = readJsonFile(metadataPath);
|
|
2876
|
+
const aliases = metadata.modernPackages?.aliases ?? {};
|
|
2877
|
+
const firstAlias = Object.values(aliases).find((value)=>'string' == typeof value);
|
|
2878
|
+
const firstPackage = Object.keys(aliases)[0];
|
|
2879
|
+
const aliasScope = firstAlias?.match(/^@([^/]+)\//)?.[1];
|
|
2880
|
+
const unscopedName = firstPackage?.split('/').at(-1) ?? '';
|
|
2881
|
+
const aliasUnscopedName = firstAlias?.split('/').at(-1) ?? '';
|
|
2882
|
+
const aliasPackageNamePrefix = aliasUnscopedName && unscopedName && aliasUnscopedName.endsWith(unscopedName) ? aliasUnscopedName.slice(0, -unscopedName.length) : void 0;
|
|
2883
|
+
return {
|
|
2884
|
+
strategy: 'install' === metadata.strategy ? 'install' : 'workspace',
|
|
2885
|
+
modernPackageVersion: 'string' == typeof metadata.modernPackages?.specifier ? metadata.modernPackages.specifier : modernVersion,
|
|
2886
|
+
registry: metadata.modernPackages?.registry,
|
|
2887
|
+
aliasScope,
|
|
2888
|
+
aliasPackageNamePrefix
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2891
|
+
function assertValidMicroVerticalName(name) {
|
|
2892
|
+
const normalized = toKebabCase(name);
|
|
2893
|
+
if (!normalized || normalized !== name) throw new Error(`Invalid MicroVertical name "${name}". Use lowercase kebab-case.`);
|
|
2894
|
+
return normalized;
|
|
2895
|
+
}
|
|
2896
|
+
function nextAvailablePort(ports) {
|
|
2897
|
+
const numericPorts = Object.values(ports).filter((value)=>'number' == typeof value && Number.isFinite(value));
|
|
2898
|
+
return Math.max(3030, ...numericPorts) + 1;
|
|
2899
|
+
}
|
|
2900
|
+
function assertCanCreate(workspaceRoot, relativePath) {
|
|
2901
|
+
if (node_fs.existsSync(node_path.join(workspaceRoot, relativePath))) throw new Error(`Refusing to overwrite existing path: ${relativePath}`);
|
|
2902
|
+
}
|
|
2903
|
+
function addRootDevScript(workspaceRoot, scope, packageSuffix, scriptName) {
|
|
2904
|
+
const packagePath = node_path.join(workspaceRoot, 'package.json');
|
|
2905
|
+
const rootPackage = readJsonFile(packagePath);
|
|
2906
|
+
rootPackage.scripts ??= {};
|
|
2907
|
+
rootPackage.scripts[`dev:${scriptName}`] = `pnpm --filter ${ultramodern_workspace_packageName(scope, packageSuffix)} dev`;
|
|
2908
|
+
if ('string' == typeof rootPackage.scripts.dev && !rootPackage.scripts.dev.includes(ultramodern_workspace_packageName(scope, packageSuffix))) {
|
|
2909
|
+
const packageFilter = `--filter ${ultramodern_workspace_packageName(scope, packageSuffix)}`;
|
|
2910
|
+
rootPackage.scripts.dev = rootPackage.scripts.dev.endsWith(' dev') ? rootPackage.scripts.dev.replace(/ dev$/u, ` ${packageFilter} dev`) : `${rootPackage.scripts.dev} ${packageFilter}`;
|
|
2911
|
+
}
|
|
2912
|
+
writeJsonFile(packagePath, rootPackage);
|
|
2913
|
+
}
|
|
2914
|
+
function addShellZephyrDependency(workspaceRoot, scope, remote) {
|
|
2915
|
+
const packagePath = node_path.join(workspaceRoot, shellApp.directory, 'package.json');
|
|
2916
|
+
const shellPackage = readJsonFile(packagePath);
|
|
2917
|
+
shellPackage['zephyr:dependencies'] ??= {};
|
|
2918
|
+
shellPackage['zephyr:dependencies'][remoteDependencyAlias(remote)] = zephyrRemoteDependency(scope, remote);
|
|
2919
|
+
writeJsonFile(packagePath, shellPackage);
|
|
2920
|
+
}
|
|
2921
|
+
function addShellWorkspaceDependency(workspaceRoot, scope, remote) {
|
|
2922
|
+
if (!appHasEffectApi(remote)) return;
|
|
2923
|
+
const packagePath = node_path.join(workspaceRoot, shellApp.directory, 'package.json');
|
|
2924
|
+
const shellPackage = readJsonFile(packagePath);
|
|
2925
|
+
shellPackage.dependencies ??= {};
|
|
2926
|
+
shellPackage.dependencies[ultramodern_workspace_packageName(scope, remote.packageSuffix)] = WORKSPACE_PACKAGE_VERSION;
|
|
2927
|
+
writeJsonFile(packagePath, shellPackage);
|
|
2928
|
+
}
|
|
2929
|
+
function remoteTopologyEntry(scope, remote) {
|
|
2930
|
+
return {
|
|
2931
|
+
id: remote.id,
|
|
2932
|
+
kind: remote.kind,
|
|
2933
|
+
domain: remote.domain,
|
|
2934
|
+
package: ultramodern_workspace_packageName(scope, remote.packageSuffix),
|
|
2935
|
+
moduleFederation: {
|
|
2936
|
+
role: 'remote',
|
|
2937
|
+
name: remote.mfName,
|
|
2938
|
+
manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`,
|
|
2939
|
+
exposes: Object.keys(remote.exposes ?? {}),
|
|
2940
|
+
ssr: true,
|
|
2941
|
+
fallbackTelemetryEvent: 'modernjs:mv-runtime-parity',
|
|
2942
|
+
sharedContractVersion: 'mf-ssr-contract-v1'
|
|
2943
|
+
},
|
|
2944
|
+
...effectApiTopologyMetadata(remote) ? {
|
|
2945
|
+
api: effectApiTopologyMetadata(remote)
|
|
2946
|
+
} : {},
|
|
2947
|
+
ownership: remote.ownership
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
function ownershipEntry(scope, owner) {
|
|
2951
|
+
return {
|
|
2952
|
+
id: owner.id,
|
|
2953
|
+
package: ultramodern_workspace_packageName(scope, owner.packageSuffix),
|
|
2954
|
+
path: owner.directory,
|
|
2955
|
+
ownership: owner.ownership
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
function remotesFromTopology(topology, ports) {
|
|
2959
|
+
return (topology.remotes ?? []).map((remote)=>{
|
|
2960
|
+
const effectApi = remote.api?.effect ? {
|
|
2961
|
+
stem: 'string' == typeof remote.api.effect.basePath ? remote.api.effect.basePath.split('/').filter(Boolean).at(-1) ?? remote.domain ?? String(remote.id).replace(/^remote-/, '') : remote.domain ?? String(remote.id).replace(/^remote-/, ''),
|
|
2962
|
+
prefix: remote.api.effect.bff?.prefix ?? `/${remote.domain ?? String(remote.id).replace(/^remote-/, '')}-api`,
|
|
2963
|
+
consumedBy: Array.isArray(remote.api.effect.consumedBy) ? remote.api.effect.consumedBy : [
|
|
2964
|
+
shellApp.id,
|
|
2965
|
+
remote.id
|
|
2966
|
+
]
|
|
2967
|
+
} : void 0;
|
|
2968
|
+
return {
|
|
2969
|
+
id: remote.id,
|
|
2970
|
+
directory: '',
|
|
2971
|
+
packageSuffix: remote.package?.split('/').at(-1) ?? remote.id,
|
|
2972
|
+
displayName: remote.id,
|
|
2973
|
+
kind: remote.kind ?? 'vertical',
|
|
2974
|
+
domain: remote.domain ?? String(remote.id).replace(/^remote-/, ''),
|
|
2975
|
+
portEnv: '',
|
|
2976
|
+
port: 'number' == typeof ports[remote.id] ? ports[remote.id] : 0,
|
|
2977
|
+
mfName: remote.moduleFederation?.name ?? `remote${toPascalCase(remote.id)}`,
|
|
2978
|
+
...effectApi ? {
|
|
2979
|
+
effectApi
|
|
2980
|
+
} : {},
|
|
2981
|
+
ownership: remote.ownership ?? createNeutralOwnership(remote.id)
|
|
2982
|
+
};
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
function addUltramodernMicroVertical(options) {
|
|
2986
|
+
const name = assertValidMicroVerticalName(options.name);
|
|
2987
|
+
const rootPackage = readJsonFile(node_path.join(options.workspaceRoot, 'package.json'));
|
|
2988
|
+
const scope = toPackageScope(String(rootPackage.name ?? node_path.basename(options.workspaceRoot)));
|
|
2989
|
+
const topologyPath = node_path.join(options.workspaceRoot, 'topology/reference-topology.json');
|
|
2990
|
+
const ownershipPath = node_path.join(options.workspaceRoot, 'topology/ownership.json');
|
|
2991
|
+
const overlayPath = node_path.join(options.workspaceRoot, 'topology/local-overlays/development.json');
|
|
2992
|
+
for (const requiredPath of [
|
|
2993
|
+
topologyPath,
|
|
2994
|
+
ownershipPath,
|
|
2995
|
+
overlayPath
|
|
2996
|
+
])if (!node_fs.existsSync(requiredPath)) throw new Error(`Missing UltraModern workspace file: ${requiredPath}`);
|
|
2997
|
+
const topology = readJsonFile(topologyPath);
|
|
2998
|
+
const ownership = readJsonFile(ownershipPath);
|
|
2999
|
+
const overlay = readJsonFile(overlayPath);
|
|
3000
|
+
overlay.ports ??= {};
|
|
3001
|
+
const packageSource = existingPackageSource(options.workspaceRoot, options.modernVersion, options.packageSource);
|
|
3002
|
+
const enableTailwind = false !== options.enableTailwind;
|
|
3003
|
+
const port = nextAvailablePort(overlay.ports);
|
|
3004
|
+
if ('remote' === options.kind || 'horizontal-remote' === options.kind) {
|
|
3005
|
+
const remote = createRemoteDescriptor(name, options.kind, port);
|
|
3006
|
+
assertCanCreate(options.workspaceRoot, remote.directory);
|
|
3007
|
+
if ((topology.remotes ?? []).some((entry)=>entry.id === remote.id)) throw new Error(`Topology already contains ${remote.id}`);
|
|
3008
|
+
if (Object.values(overlay.ports).includes(remote.port)) throw new Error(`Development port ${remote.port} is already in use`);
|
|
3009
|
+
writeApp(options.workspaceRoot, scope, remote, packageSource, enableTailwind);
|
|
3010
|
+
topology.shell ??= {};
|
|
3011
|
+
topology.shell.remoteRefs ??= [];
|
|
3012
|
+
topology.shell.remoteRefs.push(remote.id);
|
|
3013
|
+
topology.shell.moduleFederation ??= {};
|
|
3014
|
+
topology.shell.moduleFederation.remotes ??= [];
|
|
3015
|
+
topology.shell.moduleFederation.remotes.push({
|
|
3016
|
+
id: remote.id,
|
|
3017
|
+
name: remote.mfName,
|
|
3018
|
+
manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`
|
|
3019
|
+
});
|
|
3020
|
+
topology.remotes ??= [];
|
|
3021
|
+
topology.remotes.push(remoteTopologyEntry(scope, remote));
|
|
3022
|
+
ownership.owners ??= [];
|
|
3023
|
+
ownership.owners.push(ownershipEntry(scope, remote));
|
|
3024
|
+
overlay.ports[remote.id] = remote.port;
|
|
3025
|
+
overlay.manifests ??= {};
|
|
3026
|
+
overlay.manifests[remote.id] = `http://localhost:${remote.port}/mf-manifest.json`;
|
|
3027
|
+
if (appHasEffectApi(remote)) {
|
|
3028
|
+
overlay.apis ??= {};
|
|
3029
|
+
overlay.apis[remote.id] = `http://localhost:${remote.port}${effectApiPrefix(remote)}`;
|
|
3030
|
+
}
|
|
3031
|
+
writeJsonFile(topologyPath, topology);
|
|
3032
|
+
writeJsonFile(ownershipPath, ownership);
|
|
3033
|
+
writeJsonFile(overlayPath, overlay);
|
|
3034
|
+
writeJsonFile(node_path.join(options.workspaceRoot, GENERATED_CONTRACT_PATH), createGeneratedContract(scope, [
|
|
3035
|
+
shellApp,
|
|
3036
|
+
...remotesFromTopology(topology, overlay.ports)
|
|
3037
|
+
], enableTailwind));
|
|
3038
|
+
const shellConfigPath = node_path.join(options.workspaceRoot, `${shellApp.directory}/module-federation.config.ts`);
|
|
3039
|
+
writeFileReplacing(options.workspaceRoot, `${shellApp.directory}/module-federation.config.ts`, createShellModuleFederationConfig(remotesFromTopology(topology, overlay.ports)));
|
|
3040
|
+
if (!node_fs.existsSync(shellConfigPath)) throw new Error('Shell Module Federation config was not regenerated');
|
|
3041
|
+
addShellZephyrDependency(options.workspaceRoot, scope, remote);
|
|
3042
|
+
addShellWorkspaceDependency(options.workspaceRoot, scope, remote);
|
|
3043
|
+
addRootDevScript(options.workspaceRoot, scope, remote.packageSuffix, name);
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3046
|
+
if ('service' === options.kind) {
|
|
3047
|
+
const service = createServiceDescriptor(name, port);
|
|
3048
|
+
assertCanCreate(options.workspaceRoot, service.directory);
|
|
3049
|
+
if ((topology.effectServices ?? []).some((entry)=>entry.id === service.id)) throw new Error(`Topology already contains ${service.id}`);
|
|
3050
|
+
writeEffectService(options.workspaceRoot, scope, packageSource, enableTailwind, service);
|
|
3051
|
+
appendEffectSharedApiContract(options.workspaceRoot, service);
|
|
3052
|
+
topology.effectServices ??= [];
|
|
3053
|
+
topology.effectServices.push({
|
|
3054
|
+
id: service.id,
|
|
3055
|
+
kind: 'effect-service',
|
|
3056
|
+
runtime: 'effect',
|
|
3057
|
+
package: ultramodern_workspace_packageName(scope, service.packageSuffix),
|
|
3058
|
+
consumedBy: [
|
|
3059
|
+
shellApp.id
|
|
3060
|
+
],
|
|
3061
|
+
bff: {
|
|
3062
|
+
prefix: serviceApiPrefix(service),
|
|
3063
|
+
openapi: '/openapi.json'
|
|
3064
|
+
},
|
|
3065
|
+
contract: {
|
|
3066
|
+
package: ultramodern_workspace_packageName(scope, 'shared-effect-api'),
|
|
3067
|
+
export: serviceEffectApiExport(service),
|
|
3068
|
+
path: 'packages/shared-effect-api/src/index.ts'
|
|
3069
|
+
},
|
|
3070
|
+
serverEntry: `${service.directory}/api/effect/index.ts`,
|
|
3071
|
+
basePath: `${serviceApiPrefix(service)}/effect/${effectApiStem(service)}`,
|
|
3072
|
+
...createEffectOperationContract(service),
|
|
3073
|
+
ownership: service.ownership
|
|
3074
|
+
});
|
|
3075
|
+
ownership.owners ??= [];
|
|
3076
|
+
ownership.owners.push(ownershipEntry(scope, service));
|
|
3077
|
+
overlay.ports[service.id] = service.port;
|
|
3078
|
+
overlay.services ??= {};
|
|
3079
|
+
overlay.services[service.id] = `http://localhost:${service.port}${serviceApiPrefix(service)}`;
|
|
3080
|
+
writeJsonFile(topologyPath, topology);
|
|
3081
|
+
writeJsonFile(ownershipPath, ownership);
|
|
3082
|
+
writeJsonFile(overlayPath, overlay);
|
|
3083
|
+
addRootDevScript(options.workspaceRoot, scope, service.packageSuffix, name);
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
3086
|
+
if ('shared' === options.kind) {
|
|
3087
|
+
const sharedPackage = createSharedPackageDescriptor(name);
|
|
3088
|
+
assertCanCreate(options.workspaceRoot, sharedPackage.directory);
|
|
3089
|
+
if ((topology.sharedPackages ?? []).some((entry)=>entry.id === sharedPackage.id)) throw new Error(`Topology already contains ${sharedPackage.id}`);
|
|
3090
|
+
writeGenericSharedPackage(options.workspaceRoot, scope, packageSource, sharedPackage);
|
|
3091
|
+
topology.sharedPackages ??= [];
|
|
3092
|
+
topology.sharedPackages.push({
|
|
3093
|
+
id: sharedPackage.id,
|
|
3094
|
+
package: ultramodern_workspace_packageName(scope, sharedPackage.id),
|
|
3095
|
+
path: sharedPackage.directory,
|
|
3096
|
+
description: sharedPackage.description
|
|
3097
|
+
});
|
|
3098
|
+
ownership.owners ??= [];
|
|
3099
|
+
ownership.owners.push(ownershipEntry(scope, {
|
|
3100
|
+
id: sharedPackage.id,
|
|
3101
|
+
packageSuffix: sharedPackage.id,
|
|
3102
|
+
directory: sharedPackage.directory,
|
|
3103
|
+
ownership: createNeutralOwnership(sharedPackage.id, 'tier-1-shared-contract')
|
|
3104
|
+
}));
|
|
3105
|
+
writeJsonFile(topologyPath, topology);
|
|
3106
|
+
writeJsonFile(ownershipPath, ownership);
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
throw new Error(`Unsupported MicroVertical kind: ${options.kind}`);
|
|
2017
3110
|
}
|
|
2018
3111
|
function generateUltramodernWorkspace(options) {
|
|
2019
3112
|
const scope = toPackageScope(options.packageName);
|
|
2020
3113
|
const packageSource = resolvePackageSource(options);
|
|
3114
|
+
const enableTailwind = false !== options.enableTailwind;
|
|
2021
3115
|
node_fs.mkdirSync(options.targetDir, {
|
|
2022
3116
|
recursive: true
|
|
2023
3117
|
});
|
|
2024
3118
|
copyRootTemplate(options.targetDir, {
|
|
2025
3119
|
packageName: options.packageName,
|
|
2026
|
-
packageScope: scope
|
|
3120
|
+
packageScope: scope,
|
|
3121
|
+
tailwindEnabled: String(enableTailwind)
|
|
2027
3122
|
});
|
|
2028
3123
|
writeJson(options.targetDir, 'package.json', createRootPackageJson(scope, packageSource));
|
|
2029
3124
|
writeJson(options.targetDir, 'tsconfig.base.json', createTsConfigBase());
|
|
@@ -2032,10 +3127,13 @@ function generateUltramodernWorkspace(options) {
|
|
|
2032
3127
|
writeJson(options.targetDir, 'topology/local-overlays/development.json', createDevelopmentOverlay());
|
|
2033
3128
|
writeJson(options.targetDir, '.modernjs/ultramodern-workspace-template-manifest.json', createTemplateManifest(options.modernVersion, packageSource));
|
|
2034
3129
|
writeJson(options.targetDir, '.modernjs/ultramodern-package-source.json', createPackageSourceMetadata(scope, packageSource));
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
3130
|
+
writeJson(options.targetDir, GENERATED_CONTRACT_PATH, createGeneratedContract(scope, [
|
|
3131
|
+
shellApp,
|
|
3132
|
+
...remoteApps
|
|
3133
|
+
], enableTailwind));
|
|
3134
|
+
writeApp(options.targetDir, scope, shellApp, packageSource, enableTailwind);
|
|
3135
|
+
for (const remote of remoteApps)writeApp(options.targetDir, scope, remote, packageSource, enableTailwind);
|
|
3136
|
+
writeSharedPackages(options.targetDir, scope, packageSource);
|
|
2039
3137
|
}
|
|
2040
3138
|
const src_dirname = node_path.dirname(fileURLToPath(import.meta.url));
|
|
2041
3139
|
const templateDir = node_path.resolve(src_dirname, '..', 'template');
|
|
@@ -2045,6 +3143,9 @@ const sha1Pattern = /^[0-9a-f]{40}$/;
|
|
|
2045
3143
|
const sha256Pattern = /^[0-9a-f]{64}$/;
|
|
2046
3144
|
const templateIdPattern = /^[a-z0-9][a-z0-9._-]*$/;
|
|
2047
3145
|
const packageNamePattern = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/;
|
|
3146
|
+
const src_TANSTACK_ROUTER_VERSION = '1.170.8';
|
|
3147
|
+
const src_TAILWIND_VERSION = '4.3.0';
|
|
3148
|
+
const src_TAILWIND_POSTCSS_VERSION = '4.3.0';
|
|
2048
3149
|
const requiredDeniedPaths = [
|
|
2049
3150
|
'.git/**',
|
|
2050
3151
|
'.npmrc',
|
|
@@ -2089,8 +3190,8 @@ function detectRouterFramework() {
|
|
|
2089
3190
|
'--router',
|
|
2090
3191
|
'-r'
|
|
2091
3192
|
]);
|
|
2092
|
-
if (!routerValue || '
|
|
2093
|
-
if ('
|
|
3193
|
+
if (!routerValue || 'tanstack' === routerValue) return 'tanstack';
|
|
3194
|
+
if ('react-router' === routerValue) return 'react-router';
|
|
2094
3195
|
console.error(i18n.t(localeKeys.error.invalidRouter, {
|
|
2095
3196
|
router: routerValue
|
|
2096
3197
|
}));
|
|
@@ -2231,6 +3332,7 @@ function createBuiltinTemplateManifest(version) {
|
|
|
2231
3332
|
'.browserslistrc',
|
|
2232
3333
|
'.github/**',
|
|
2233
3334
|
'.gitignore',
|
|
3335
|
+
'.mise.toml',
|
|
2234
3336
|
'.modernjs/**',
|
|
2235
3337
|
'.nvmrc',
|
|
2236
3338
|
'AGENTS.md',
|
|
@@ -2430,6 +3532,7 @@ function showHelp() {
|
|
|
2430
3532
|
if (localeKeys.help.optionUltramodernPackageSource) console.log(i18n.t(localeKeys.help.optionUltramodernPackageSource));
|
|
2431
3533
|
if (localeKeys.help.optionUltramodernPackageScope) console.log(i18n.t(localeKeys.help.optionUltramodernPackageScope));
|
|
2432
3534
|
if (localeKeys.help.optionUltramodernPackageNamePrefix) console.log(i18n.t(localeKeys.help.optionUltramodernPackageNamePrefix));
|
|
3535
|
+
if (localeKeys.help.optionMicroVertical) console.log(i18n.t(localeKeys.help.optionMicroVertical));
|
|
2433
3536
|
console.log(i18n.t(localeKeys.help.optionSub));
|
|
2434
3537
|
console.log('');
|
|
2435
3538
|
console.log(i18n.t(localeKeys.help.examples));
|
|
@@ -2443,6 +3546,7 @@ function showHelp() {
|
|
|
2443
3546
|
if (localeKeys.help.example8) console.log(i18n.t(localeKeys.help.example8));
|
|
2444
3547
|
if (localeKeys.help.example9) console.log(i18n.t(localeKeys.help.example9));
|
|
2445
3548
|
if (localeKeys.help.example10) console.log(i18n.t(localeKeys.help.example10));
|
|
3549
|
+
if (localeKeys.help.example11) console.log(i18n.t(localeKeys.help.example11));
|
|
2446
3550
|
console.log('');
|
|
2447
3551
|
console.log(i18n.t(localeKeys.help.moreInfo));
|
|
2448
3552
|
console.log('');
|
|
@@ -2468,12 +3572,21 @@ function detectSubprojectFlag() {
|
|
|
2468
3572
|
}
|
|
2469
3573
|
function detectTailwindFlag() {
|
|
2470
3574
|
const args = process.argv.slice(2);
|
|
2471
|
-
return args.includes('--tailwind');
|
|
3575
|
+
return !args.includes('--no-tailwind');
|
|
2472
3576
|
}
|
|
2473
3577
|
function detectWorkspaceProtocolFlag() {
|
|
2474
3578
|
const args = process.argv.slice(2);
|
|
2475
3579
|
return args.includes('--workspace');
|
|
2476
3580
|
}
|
|
3581
|
+
function detectMicroVerticalKind() {
|
|
3582
|
+
const kind = getOptionValue(process.argv.slice(2), [
|
|
3583
|
+
'--microvertical'
|
|
3584
|
+
]);
|
|
3585
|
+
if (!kind) return;
|
|
3586
|
+
if ('remote' === kind || 'horizontal-remote' === kind || 'service' === kind || 'shared' === kind) return kind;
|
|
3587
|
+
console.error('--microvertical must be one of: remote, horizontal-remote, service, shared');
|
|
3588
|
+
process.exit(1);
|
|
3589
|
+
}
|
|
2477
3590
|
function detectUltramodernWorkspaceFlag(createPackage) {
|
|
2478
3591
|
const args = process.argv.slice(2);
|
|
2479
3592
|
return args.includes(ULTRAMODERN_WORKSPACE_FLAG) || isBleedingDevCreatePackage(createPackage);
|
|
@@ -2574,7 +3687,8 @@ async function getProjectName() {
|
|
|
2574
3687
|
'--ultramodern-package-version',
|
|
2575
3688
|
'--ultramodern-package-registry',
|
|
2576
3689
|
'--ultramodern-package-scope',
|
|
2577
|
-
'--ultramodern-package-name-prefix'
|
|
3690
|
+
'--ultramodern-package-name-prefix',
|
|
3691
|
+
'--microvertical'
|
|
2578
3692
|
]);
|
|
2579
3693
|
const optionWithoutValue = new Set([
|
|
2580
3694
|
'--help',
|
|
@@ -2587,6 +3701,7 @@ async function getProjectName() {
|
|
|
2587
3701
|
'--tanstack',
|
|
2588
3702
|
'--bff',
|
|
2589
3703
|
'--tailwind',
|
|
3704
|
+
'--no-tailwind',
|
|
2590
3705
|
'--workspace',
|
|
2591
3706
|
ULTRAMODERN_WORKSPACE_FLAG
|
|
2592
3707
|
]);
|
|
@@ -2598,7 +3713,7 @@ async function getProjectName() {
|
|
|
2598
3713
|
i += 1;
|
|
2599
3714
|
continue;
|
|
2600
3715
|
}
|
|
2601
|
-
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);
|
|
3716
|
+
if (!(arg.startsWith('--lang=') || arg.startsWith('--router=') || arg.startsWith('--bff-runtime=') || arg.startsWith('--ultramodern-package-source=') || arg.startsWith('--ultramodern-package-version=') || arg.startsWith('--ultramodern-package-registry=') || arg.startsWith('--ultramodern-package-scope=') || arg.startsWith('--ultramodern-package-name-prefix=') || arg.startsWith('--microvertical='))) positionalArgs.push(arg);
|
|
2602
3717
|
}
|
|
2603
3718
|
}
|
|
2604
3719
|
const projectNameArg = positionalArgs[0];
|
|
@@ -2629,6 +3744,26 @@ async function main() {
|
|
|
2629
3744
|
const { name: projectName, useCurrentDir } = await getProjectName();
|
|
2630
3745
|
const targetDir = useCurrentDir ? process.cwd() : node_path.isAbsolute(projectName) ? projectName : node_path.resolve(process.cwd(), projectName);
|
|
2631
3746
|
const generatedPackageName = useCurrentDir || node_path.isAbsolute(projectName) ? node_path.basename(targetDir) : projectName;
|
|
3747
|
+
const createPackage = readCreatePackageJson();
|
|
3748
|
+
const version = createPackage.version || 'latest';
|
|
3749
|
+
const ultramodernPackageVersion = isBleedingDevCreatePackage(createPackage) ? getBleedingDevFrameworkVersion(createPackage, version) : version;
|
|
3750
|
+
const microVerticalKind = detectMicroVerticalKind();
|
|
3751
|
+
if (microVerticalKind) {
|
|
3752
|
+
const overridePackageSource = args.some((arg)=>arg.startsWith('--ultramodern-package-')) ? detectUltramodernPackageSource(args, ultramodernPackageVersion, createPackage) : void 0;
|
|
3753
|
+
addUltramodernMicroVertical({
|
|
3754
|
+
workspaceRoot: process.cwd(),
|
|
3755
|
+
name: generatedPackageName,
|
|
3756
|
+
kind: microVerticalKind,
|
|
3757
|
+
modernVersion: version,
|
|
3758
|
+
enableTailwind: detectTailwindFlag(),
|
|
3759
|
+
packageSource: overridePackageSource
|
|
3760
|
+
});
|
|
3761
|
+
const dim = '\x1b[2m\x1b[3m';
|
|
3762
|
+
const reset = '\x1b[0m';
|
|
3763
|
+
console.log(`${i18n.t(localeKeys.message.success)}\n`);
|
|
3764
|
+
console.log(`${dim} pnpm ultramodern:check${reset}\n`);
|
|
3765
|
+
return;
|
|
3766
|
+
}
|
|
2632
3767
|
if (node_fs.existsSync(targetDir)) {
|
|
2633
3768
|
const files = node_fs.readdirSync(targetDir);
|
|
2634
3769
|
if (files.length > 0) {
|
|
@@ -2638,15 +3773,13 @@ async function main() {
|
|
|
2638
3773
|
process.exit(1);
|
|
2639
3774
|
}
|
|
2640
3775
|
}
|
|
2641
|
-
const createPackage = readCreatePackageJson();
|
|
2642
|
-
const version = createPackage.version || 'latest';
|
|
2643
|
-
const ultramodernPackageVersion = isBleedingDevCreatePackage(createPackage) ? getBleedingDevFrameworkVersion(createPackage, version) : version;
|
|
2644
3776
|
const generateWorkspace = detectUltramodernWorkspaceFlag(createPackage);
|
|
2645
3777
|
if (generateWorkspace) {
|
|
2646
3778
|
generateUltramodernWorkspace({
|
|
2647
3779
|
targetDir,
|
|
2648
3780
|
packageName: generatedPackageName,
|
|
2649
3781
|
modernVersion: version,
|
|
3782
|
+
enableTailwind: detectTailwindFlag(),
|
|
2650
3783
|
packageSource: detectUltramodernPackageSource(args, ultramodernPackageVersion, createPackage)
|
|
2651
3784
|
});
|
|
2652
3785
|
const dim = '\x1b[2m\x1b[3m';
|
|
@@ -2680,6 +3813,9 @@ async function main() {
|
|
|
2680
3813
|
pluginTanstackVersion: singleAppModernPackageSpecifier('@modern-js/plugin-tanstack', packageSource, useWorkspaceProtocol),
|
|
2681
3814
|
pluginBffVersion: singleAppModernPackageSpecifier('@modern-js/plugin-bff', packageSource, useWorkspaceProtocol),
|
|
2682
3815
|
pluginI18nVersion: singleAppModernPackageSpecifier('@modern-js/plugin-i18n', packageSource, useWorkspaceProtocol),
|
|
3816
|
+
tanstackRouterVersion: src_TANSTACK_ROUTER_VERSION,
|
|
3817
|
+
tailwindVersion: src_TAILWIND_VERSION,
|
|
3818
|
+
tailwindPostcssVersion: src_TAILWIND_POSTCSS_VERSION,
|
|
2683
3819
|
isSubproject,
|
|
2684
3820
|
routerFramework,
|
|
2685
3821
|
bffRuntime,
|
|
@@ -2771,6 +3907,9 @@ function copyTemplate(src, dest, options) {
|
|
|
2771
3907
|
pluginTanstackVersion: options.pluginTanstackVersion,
|
|
2772
3908
|
pluginBffVersion: options.pluginBffVersion,
|
|
2773
3909
|
pluginI18nVersion: options.pluginI18nVersion,
|
|
3910
|
+
tanstackRouterVersion: options.tanstackRouterVersion,
|
|
3911
|
+
tailwindVersion: options.tailwindVersion,
|
|
3912
|
+
tailwindPostcssVersion: options.tailwindPostcssVersion,
|
|
2774
3913
|
isSubproject: options.isSubproject,
|
|
2775
3914
|
isTanstackRouter: 'tanstack' === options.routerFramework,
|
|
2776
3915
|
enableBff: 'none' !== options.bffRuntime,
|