@bleedingdev/modern-js-create 3.2.0-ultramodern.99 → 3.4.0-ultramodern.0
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 +281 -106
- package/bin/run.js +1 -0
- package/dist/cjs/create-package-root.cjs +63 -0
- package/dist/cjs/index.cjs +658 -0
- package/dist/cjs/locale/en.cjs +101 -0
- package/dist/cjs/locale/index.cjs +50 -0
- package/dist/cjs/locale/zh.cjs +101 -0
- package/dist/cjs/ultramodern-package-source.cjs +135 -0
- package/dist/cjs/ultramodern-workspace/add-vertical.cjs +603 -0
- package/dist/cjs/ultramodern-workspace/app-files.cjs +223 -0
- package/dist/cjs/ultramodern-workspace/codesmith.cjs +134 -0
- package/dist/cjs/ultramodern-workspace/contracts.cjs +837 -0
- package/dist/cjs/ultramodern-workspace/demo-components.cjs +422 -0
- package/dist/cjs/ultramodern-workspace/descriptors.cjs +222 -0
- package/dist/cjs/ultramodern-workspace/effect-api.cjs +952 -0
- package/dist/cjs/ultramodern-workspace/fs-io.cjs +191 -0
- package/dist/cjs/ultramodern-workspace/generation-result.cjs +158 -0
- package/dist/cjs/ultramodern-workspace/index.cjs +51 -0
- package/dist/cjs/ultramodern-workspace/locales.cjs +173 -0
- package/dist/cjs/ultramodern-workspace/module-federation.cjs +487 -0
- package/dist/cjs/ultramodern-workspace/naming.cjs +161 -0
- package/dist/cjs/ultramodern-workspace/overlays.cjs +120 -0
- package/dist/cjs/ultramodern-workspace/package-json.cjs +406 -0
- package/dist/cjs/ultramodern-workspace/package-source.cjs +59 -0
- package/dist/cjs/ultramodern-workspace/policy.cjs +248 -0
- package/dist/cjs/ultramodern-workspace/public-api.cjs +47 -0
- package/dist/cjs/ultramodern-workspace/public-surface.cjs +268 -0
- package/dist/cjs/ultramodern-workspace/routes.cjs +375 -0
- package/dist/cjs/ultramodern-workspace/types.cjs +61 -0
- package/dist/cjs/ultramodern-workspace/versions.cjs +161 -0
- package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +154 -0
- package/dist/cjs/ultramodern-workspace/write-workspace.cjs +197 -0
- package/dist/esm/create-package-root.js +14 -0
- package/dist/esm/index.js +621 -0
- package/dist/esm/locale/en.js +63 -0
- package/dist/esm/locale/index.js +9 -0
- package/dist/esm/locale/zh.js +63 -0
- package/dist/esm/ultramodern-package-source.js +63 -0
- package/dist/esm/ultramodern-workspace/add-vertical.js +511 -0
- package/dist/esm/ultramodern-workspace/app-files.js +149 -0
- package/dist/esm/ultramodern-workspace/codesmith.js +86 -0
- package/dist/esm/ultramodern-workspace/contracts.js +742 -0
- package/dist/esm/ultramodern-workspace/demo-components.js +363 -0
- package/dist/esm/ultramodern-workspace/descriptors.js +133 -0
- package/dist/esm/ultramodern-workspace/effect-api.js +854 -0
- package/dist/esm/ultramodern-workspace/fs-io.js +90 -0
- package/dist/esm/ultramodern-workspace/generation-result.js +102 -0
- package/dist/esm/ultramodern-workspace/index.js +3 -0
- package/dist/esm/ultramodern-workspace/locales.js +122 -0
- package/dist/esm/ultramodern-workspace/module-federation.js +415 -0
- package/dist/esm/ultramodern-workspace/naming.js +71 -0
- package/dist/esm/ultramodern-workspace/overlays.js +79 -0
- package/dist/esm/ultramodern-workspace/package-json.js +338 -0
- package/dist/esm/ultramodern-workspace/package-source.js +21 -0
- package/dist/esm/ultramodern-workspace/policy.js +183 -0
- package/dist/esm/ultramodern-workspace/public-api.js +2 -0
- package/dist/esm/ultramodern-workspace/public-surface.js +183 -0
- package/dist/esm/ultramodern-workspace/routes.js +280 -0
- package/dist/esm/ultramodern-workspace/types.js +16 -0
- package/dist/esm/ultramodern-workspace/versions.js +36 -0
- package/dist/esm/ultramodern-workspace/workspace-scripts.js +92 -0
- package/dist/esm/ultramodern-workspace/write-workspace.js +143 -0
- package/dist/esm-node/create-package-root.js +15 -0
- package/dist/esm-node/index.js +622 -0
- package/dist/esm-node/locale/en.js +64 -0
- package/dist/esm-node/locale/index.js +10 -0
- package/dist/esm-node/locale/zh.js +64 -0
- package/dist/esm-node/ultramodern-package-source.js +64 -0
- package/dist/esm-node/ultramodern-workspace/add-vertical.js +512 -0
- package/dist/esm-node/ultramodern-workspace/app-files.js +150 -0
- package/dist/esm-node/ultramodern-workspace/codesmith.js +87 -0
- package/dist/esm-node/ultramodern-workspace/contracts.js +743 -0
- package/dist/esm-node/ultramodern-workspace/demo-components.js +364 -0
- package/dist/esm-node/ultramodern-workspace/descriptors.js +134 -0
- package/dist/esm-node/ultramodern-workspace/effect-api.js +855 -0
- package/dist/esm-node/ultramodern-workspace/fs-io.js +91 -0
- package/dist/esm-node/ultramodern-workspace/generation-result.js +103 -0
- package/dist/esm-node/ultramodern-workspace/index.js +4 -0
- package/dist/esm-node/ultramodern-workspace/locales.js +123 -0
- package/dist/esm-node/ultramodern-workspace/module-federation.js +416 -0
- package/dist/esm-node/ultramodern-workspace/naming.js +72 -0
- package/dist/esm-node/ultramodern-workspace/overlays.js +80 -0
- package/dist/esm-node/ultramodern-workspace/package-json.js +339 -0
- package/dist/esm-node/ultramodern-workspace/package-source.js +22 -0
- package/dist/esm-node/ultramodern-workspace/policy.js +184 -0
- package/dist/esm-node/ultramodern-workspace/public-api.js +3 -0
- package/dist/esm-node/ultramodern-workspace/public-surface.js +184 -0
- package/dist/esm-node/ultramodern-workspace/routes.js +281 -0
- package/dist/esm-node/ultramodern-workspace/types.js +17 -0
- package/dist/esm-node/ultramodern-workspace/versions.js +37 -0
- package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +93 -0
- package/dist/esm-node/ultramodern-workspace/write-workspace.js +144 -0
- package/dist/types/create-package-root.d.ts +1 -0
- package/dist/types/locale/en.d.ts +12 -8
- package/dist/types/locale/index.d.ts +125 -2
- package/dist/types/locale/zh.d.ts +12 -8
- package/dist/types/ultramodern-package-source.d.ts +28 -0
- package/dist/types/ultramodern-workspace/add-vertical.d.ts +36 -0
- package/dist/types/ultramodern-workspace/app-files.d.ts +14 -0
- package/dist/types/ultramodern-workspace/codesmith.d.ts +53 -0
- package/dist/types/ultramodern-workspace/contracts.d.ts +21 -0
- package/dist/types/ultramodern-workspace/demo-components.d.ts +9 -0
- package/dist/types/ultramodern-workspace/descriptors.d.ts +39 -0
- package/dist/types/ultramodern-workspace/effect-api.d.ts +73 -0
- package/dist/types/ultramodern-workspace/fs-io.d.ts +18 -0
- package/dist/types/ultramodern-workspace/generation-result.d.ts +14 -0
- package/dist/types/ultramodern-workspace/index.d.ts +4 -0
- package/dist/types/ultramodern-workspace/locales.d.ts +183 -0
- package/dist/types/ultramodern-workspace/module-federation.d.ts +16 -0
- package/dist/types/ultramodern-workspace/naming.d.ts +16 -0
- package/dist/types/ultramodern-workspace/overlays.d.ts +6 -0
- package/dist/types/ultramodern-workspace/package-json.d.ts +12 -0
- package/dist/types/ultramodern-workspace/package-source.d.ts +2 -0
- package/dist/types/ultramodern-workspace/policy.d.ts +60 -0
- package/dist/types/ultramodern-workspace/public-api.d.ts +3 -0
- package/dist/types/ultramodern-workspace/public-surface.d.ts +37 -0
- package/dist/types/ultramodern-workspace/routes.d.ts +25 -0
- package/dist/types/ultramodern-workspace/types.d.ts +197 -0
- package/dist/types/ultramodern-workspace/versions.d.ts +40 -0
- package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +10 -0
- package/dist/types/ultramodern-workspace/write-workspace.d.ts +4 -0
- package/package.json +58 -15
- package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +23 -10
- package/template-workspace/.mise.toml.handlebars +1 -0
- package/template-workspace/{AGENTS.md → AGENTS.md.handlebars} +20 -9
- package/template-workspace/README.md.handlebars +85 -14
- package/template-workspace/lefthook.yml +18 -4
- package/template-workspace/pnpm-workspace.yaml.handlebars +45 -0
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +69 -75
- package/template-workspace/scripts/setup-agent-reference-repos.mjs +8 -6
- package/templates/app/shell-frame.tsx +49 -0
- package/templates/app/ultramodern-route-head.tsx.handlebars +142 -0
- package/templates/packages/shared-contracts-index.ts +466 -0
- package/templates/workspace-scripts/assert-mf-types.mjs.handlebars +69 -0
- package/templates/workspace-scripts/check-ultramodern-i18n-boundaries.mjs +9 -0
- package/templates/workspace-scripts/generate-public-surface-assets.mjs +529 -0
- package/templates/workspace-scripts/proof-cloudflare-version.mjs +125 -0
- package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +851 -0
- package/templates/workspace-scripts/ultramodern-performance-readiness.config.mjs +7 -0
- package/templates/workspace-scripts/ultramodern-performance-readiness.mjs +223 -0
- package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +986 -0
- package/dist/index.js +0 -6625
- package/dist/types/ultramodern-workspace.d.ts +0 -31
- package/template/.agents/skills-lock.json +0 -34
- package/template/.browserslistrc +0 -4
- package/template/.codex/hooks.json +0 -16
- package/template/.github/renovate.json +0 -53
- package/template/.github/workflows/ultramodern-gates.yml.handlebars +0 -54
- package/template/.gitignore.handlebars +0 -30
- package/template/.mise.toml.handlebars +0 -2
- package/template/.nvmrc +0 -2
- package/template/AGENTS.md +0 -23
- package/template/README.md +0 -105
- package/template/api/effect/index.ts.handlebars +0 -34
- package/template/api/lambda/hello.ts.handlebars +0 -6
- package/template/config/public/locales/cs/translation.json +0 -39
- package/template/config/public/locales/en/translation.json +0 -39
- package/template/lefthook.yml +0 -10
- package/template/modern.config.ts.handlebars +0 -71
- package/template/oxfmt.config.ts +0 -15
- package/template/oxlint.config.ts +0 -19
- package/template/package.json.handlebars +0 -66
- package/template/pnpm-workspace.yaml +0 -29
- package/template/postcss.config.mjs.handlebars +0 -6
- package/template/rstest.config.mts +0 -5
- package/template/scripts/bootstrap-agent-skills.mjs +0 -228
- package/template/scripts/check-i18n-strings.mjs +0 -94
- package/template/scripts/validate-ultramodern.mjs.handlebars +0 -538
- package/template/shared/effect/api.ts.handlebars +0 -17
- package/template/src/modern-app-env.d.ts +0 -3
- package/template/src/modern.runtime.ts.handlebars +0 -23
- package/template/src/routes/[lang]/page.tsx.handlebars +0 -211
- package/template/src/routes/index.css.handlebars +0 -129
- package/template/src/routes/layout.tsx.handlebars +0 -10
- package/template/tailwind.config.ts.handlebars +0 -10
- package/template/tests/tsconfig.json +0 -7
- package/template/tests/ultramodern.contract.test.ts.handlebars +0 -78
- package/template/tsconfig.json +0 -120
- package/template-workspace/pnpm-workspace.yaml +0 -35
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const ZH_LOCALE = {
|
|
2
|
+
prompt: {
|
|
3
|
+
projectName: '请输入项目名称: ',
|
|
4
|
+
legacyModernJsConfirmation: '输入 "{confirmation}" 以继续使用原始 Modern.js 初始化: '
|
|
5
|
+
},
|
|
6
|
+
error: {
|
|
7
|
+
projectNameEmpty: '错误: 项目名称不能为空',
|
|
8
|
+
directoryExists: '错误: 目录 "{projectName}" 已存在且不为空',
|
|
9
|
+
verticalNameMissing: '错误: 缺少 MicroVertical 名称。请使用 <名称> --vertical、--vertical=<名称> 或 --vertical-name <名称>。',
|
|
10
|
+
verticalNameAmbiguous: '错误: MicroVertical 名称不明确:{firstSource} 的 "{firstName}" 与 {secondSource} 的 "{secondName}" 不一致。',
|
|
11
|
+
legacyModernJsNotConfirmed: '已中止。UltraModern.js 仍是默认的免交互初始化方案。',
|
|
12
|
+
createFailed: '创建项目时出错:'
|
|
13
|
+
},
|
|
14
|
+
message: {
|
|
15
|
+
welcome: '🚀 欢迎使用 UltraModern.js',
|
|
16
|
+
success: '✨ 创建成功!',
|
|
17
|
+
nextSteps: '📋 下一步:',
|
|
18
|
+
step1: 'cd {projectName}',
|
|
19
|
+
step2: 'pnpm install',
|
|
20
|
+
step3: 'pnpm dev',
|
|
21
|
+
legacyModernJsWarning: "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n严重警告:你正在退出 ULTRAMODERN.JS 默认配置。\n免交互默认值是最佳 UltraModern.js 配置:\n结构化 SuperApp 工作区、presetUltramodern、TanStack Router、\nEffect BFF、Tailwind CSS v4,以及 BleedingDev 包版本队列。\n原始 Modern.js 初始化是危险的显式选择路径。\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
|
22
|
+
},
|
|
23
|
+
help: {
|
|
24
|
+
title: '🚀 UltraModern.js 项目创建工具',
|
|
25
|
+
description: '默认创建带完整质量基线的 UltraModern.js SuperApp 工作区',
|
|
26
|
+
usage: '📖 用法:',
|
|
27
|
+
usageExample: ' pnpm dlx @bleedingdev/modern-js-create [项目名称] [选项]',
|
|
28
|
+
options: '⚙️ 选项:',
|
|
29
|
+
optionHelp: ' -h, --help 显示帮助信息',
|
|
30
|
+
optionVersion: ' -v, --version 显示版本信息',
|
|
31
|
+
optionLang: ' -l, --lang 设置语言 (默认 en;zh 需显式选择)',
|
|
32
|
+
optionTailwind: ' --no-tailwind 禁用默认 Tailwind CSS v4 工作区样式',
|
|
33
|
+
optionBff: ' --bff 保留默认的 Effect BFF 脚手架(每个 MicroVertical 自带 Effect BFF)',
|
|
34
|
+
optionBffRuntime: ' --bff-runtime 选择 MicroVertical 脚手架的 BFF 运行时(支持: effect;默认: effect)',
|
|
35
|
+
optionWorkspace: ' --workspace 对 @modern-js 依赖使用 workspace 协议(用于本地 monorepo 联调)',
|
|
36
|
+
optionUltramodernPackageSource: ' --ultramodern-package-source 选择 UltraModern 依赖来源(workspace 或 install;BleedingDev 默认使用 install alias)',
|
|
37
|
+
optionUltramodernPackageVersion: ' --ultramodern-package-version 为 install 依赖来源固定精确的 BleedingDev 框架版本',
|
|
38
|
+
optionUltramodernPackageRegistry: ' --ultramodern-package-registry install 依赖来源使用的 npm registry 地址',
|
|
39
|
+
optionUltramodernPackageScope: ' --ultramodern-package-scope npm alias 安装使用的发布 scope(例如 bleedingdev)',
|
|
40
|
+
optionUltramodernPackageNamePrefix: ' --ultramodern-package-name-prefix npm alias 包名前缀(默认:modern-js-)',
|
|
41
|
+
optionVertical: ' --vertical[=<名称>] 修改当前已有的 UltraModern 工作区,并接入 MicroVertical',
|
|
42
|
+
optionVerticalName: ' --vertical-name <名称> 为自动化工作流显式指定 MicroVertical 名称',
|
|
43
|
+
optionDryRun: ' --dry-run 预览 MicroVertical 修改计划但不写入文件(与 --vertical 一起使用)',
|
|
44
|
+
optionCodeSmithOverlay: ' --codesmith-overlay <包或路径> 生成完成后运行显式配置的 CodeSmith overlay',
|
|
45
|
+
optionLegacyModernJs: ' --legacy-modern-js 在大型警告和输入确认后,选择原始 Modern.js 初始化',
|
|
46
|
+
examples: '💡 示例:',
|
|
47
|
+
example1: ' pnpm dlx @bleedingdev/modern-js-create my-workspace',
|
|
48
|
+
example2: ' pnpm dlx @bleedingdev/modern-js-create my-workspace --lang zh',
|
|
49
|
+
example3: ' pnpm dlx @bleedingdev/modern-js-create my-workspace --no-tailwind',
|
|
50
|
+
example4: ' pnpm dlx @bleedingdev/modern-js-create --help',
|
|
51
|
+
example5: ' pnpm dlx @bleedingdev/modern-js-create .',
|
|
52
|
+
example6: ' pnpm dlx @bleedingdev/modern-js-create my-workspace --workspace',
|
|
53
|
+
example7: ' pnpm dlx @bleedingdev/modern-js-create catalog --vertical',
|
|
54
|
+
example8: ' pnpm dlx @bleedingdev/modern-js-create --vertical=catalog',
|
|
55
|
+
example9: ' pnpm dlx @bleedingdev/modern-js-create catalog --vertical --dry-run',
|
|
56
|
+
example10: ' pnpm dlx @bleedingdev/modern-js-create catalog --vertical --codesmith-overlay ./overlay-generator',
|
|
57
|
+
moreInfo: '📚 更多信息: https://modernjs.dev'
|
|
58
|
+
},
|
|
59
|
+
version: {
|
|
60
|
+
message: '{name} 版本: {version}'
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
export { ZH_LOCALE };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const WORKSPACE_PACKAGE_VERSION = 'workspace:*';
|
|
2
|
+
const BLEEDINGDEV_CREATE_PACKAGE = '@bleedingdev/modern-js-create';
|
|
3
|
+
const BLEEDINGDEV_PACKAGE_SCOPE = 'bleedingdev';
|
|
4
|
+
const BLEEDINGDEV_PACKAGE_NAME_PREFIX = 'modern-js-';
|
|
5
|
+
const BLEEDINGDEV_FRAMEWORK_VERSION_ENV = 'MODERN_CREATE_ULTRAMODERN_FRAMEWORK_VERSION';
|
|
6
|
+
const ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES = [
|
|
7
|
+
'@modern-js/create',
|
|
8
|
+
'@modern-js/code-tools',
|
|
9
|
+
'@modern-js/runtime',
|
|
10
|
+
'@modern-js/app-tools',
|
|
11
|
+
'@modern-js/tsconfig',
|
|
12
|
+
'@modern-js/plugin-i18n',
|
|
13
|
+
'@modern-js/plugin-tanstack',
|
|
14
|
+
'@modern-js/plugin-bff',
|
|
15
|
+
'@modern-js/adapter-rstest'
|
|
16
|
+
];
|
|
17
|
+
const ULTRAMODERN_WORKSPACE_MODERN_PACKAGES = [
|
|
18
|
+
'@modern-js/create',
|
|
19
|
+
'@modern-js/code-tools',
|
|
20
|
+
'@modern-js/app-tools',
|
|
21
|
+
'@modern-js/plugin-bff',
|
|
22
|
+
'@modern-js/plugin-i18n',
|
|
23
|
+
'@modern-js/plugin-tanstack',
|
|
24
|
+
'@modern-js/runtime'
|
|
25
|
+
];
|
|
26
|
+
function modernPackageVersion(packageSource) {
|
|
27
|
+
return 'install' === packageSource.strategy ? packageSource.modernPackageVersion : WORKSPACE_PACKAGE_VERSION;
|
|
28
|
+
}
|
|
29
|
+
function modernAliasPackageName(packageName, packageSource) {
|
|
30
|
+
if (!packageSource.aliasScope) return packageName;
|
|
31
|
+
const scope = packageSource.aliasScope.replace(/^@/, '');
|
|
32
|
+
const unscopedName = packageName.split('/').at(-1);
|
|
33
|
+
return `@${scope}/${packageSource.aliasPackageNamePrefix ?? ''}${unscopedName}`;
|
|
34
|
+
}
|
|
35
|
+
function modernPackageSpecifier(packageName, packageSource) {
|
|
36
|
+
if ('install' !== packageSource.strategy) return WORKSPACE_PACKAGE_VERSION;
|
|
37
|
+
if (!packageSource.aliasScope) return packageSource.modernPackageVersion;
|
|
38
|
+
return `npm:${modernAliasPackageName(packageName, packageSource)}@${packageSource.modernPackageVersion}`;
|
|
39
|
+
}
|
|
40
|
+
function modernPackageAliases(packageNames, packageSource) {
|
|
41
|
+
if (!packageSource.aliasScope) return;
|
|
42
|
+
return Object.fromEntries(packageNames.map((packageName)=>[
|
|
43
|
+
packageName,
|
|
44
|
+
modernAliasPackageName(packageName, packageSource)
|
|
45
|
+
]));
|
|
46
|
+
}
|
|
47
|
+
function createModernPackagesMetadata(packageNames, packageSource, options = {}) {
|
|
48
|
+
const includeAliases = options.includeAliases ?? Boolean(packageSource.aliasScope);
|
|
49
|
+
const aliases = includeAliases ? modernPackageAliases(packageNames, packageSource) : void 0;
|
|
50
|
+
return {
|
|
51
|
+
packages: [
|
|
52
|
+
...packageNames
|
|
53
|
+
],
|
|
54
|
+
specifier: modernPackageVersion(packageSource),
|
|
55
|
+
...packageSource.registry ? {
|
|
56
|
+
registry: packageSource.registry
|
|
57
|
+
} : {},
|
|
58
|
+
...aliases ? {
|
|
59
|
+
aliases
|
|
60
|
+
} : {}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export { BLEEDINGDEV_CREATE_PACKAGE, BLEEDINGDEV_FRAMEWORK_VERSION_ENV, BLEEDINGDEV_PACKAGE_NAME_PREFIX, BLEEDINGDEV_PACKAGE_SCOPE, ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES, ULTRAMODERN_WORKSPACE_MODERN_PACKAGES, WORKSPACE_PACKAGE_VERSION, createModernPackagesMetadata, modernAliasPackageName, modernPackageAliases, modernPackageSpecifier, modernPackageVersion };
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import node_fs from "node:fs";
|
|
2
|
+
import node_os from "node:os";
|
|
3
|
+
import node_path from "node:path";
|
|
4
|
+
import { WORKSPACE_PACKAGE_VERSION } from "../ultramodern-package-source.js";
|
|
5
|
+
import { createAppEnvDts, createAppRuntimeConfig, createShellFrameComponent } from "./app-files.js";
|
|
6
|
+
import { createGeneratedContract } from "./contracts.js";
|
|
7
|
+
import { createShellPage, createShellRemoteComponents } from "./demo-components.js";
|
|
8
|
+
import { GENERATED_CONTRACT_PATH, appHasEffectApi, appI18nNamespace, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, effectApiPrefix, remoteDependencyAlias, shellApp, zephyrRemoteDependency } from "./descriptors.js";
|
|
9
|
+
import { createShellEffectClient, effectApiTopologyMetadata } from "./effect-api.js";
|
|
10
|
+
import { readJsonFile, writeFileReplacing, writeJsonFile } from "./fs-io.js";
|
|
11
|
+
import { createFileSnapshot, createGenerationResult, diffFileSnapshots } from "./generation-result.js";
|
|
12
|
+
import { createAppPublicLocaleMessages } from "./locales.js";
|
|
13
|
+
import { createShellModuleFederationConfig } from "./module-federation.js";
|
|
14
|
+
import { assertUniqueTailwindPrefixes, normalizePath, packageName, toEnvSegment, toKebabCase, toPackageScope, toPascalCase } from "./naming.js";
|
|
15
|
+
import { runCodeSmithOverlays } from "./overlays.js";
|
|
16
|
+
import { createAppPackage, createRootPackageJson } from "./package-json.js";
|
|
17
|
+
import { resolvePackageSource } from "./package-source.js";
|
|
18
|
+
import { createCloudflareDeployContract } from "./policy.js";
|
|
19
|
+
import { createPublicWebAppArtifacts, rewriteWorkspaceAssetsForApp } from "./public-surface.js";
|
|
20
|
+
import { isRecord } from "./types.js";
|
|
21
|
+
import { writeGeneratedWorkspaceScripts } from "./workspace-scripts.js";
|
|
22
|
+
import { writeApp } from "./write-workspace.js";
|
|
23
|
+
const FIRST_VERTICAL_PORT = 4101;
|
|
24
|
+
const TOPOLOGY_PATH = 'topology/reference-topology.json';
|
|
25
|
+
const OWNERSHIP_PATH = 'topology/ownership.json';
|
|
26
|
+
const DEVELOPMENT_OVERLAY_PATH = 'topology/local-overlays/development.json';
|
|
27
|
+
const PACKAGE_SOURCE_METADATA_PATH = '.modernjs/ultramodern-package-source.json';
|
|
28
|
+
function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
|
|
29
|
+
if (packageSource) return resolvePackageSource({
|
|
30
|
+
targetDir: workspaceRoot,
|
|
31
|
+
packageName: node_path.basename(workspaceRoot),
|
|
32
|
+
modernVersion,
|
|
33
|
+
packageSource
|
|
34
|
+
});
|
|
35
|
+
const metadataPath = node_path.join(workspaceRoot, '.modernjs/ultramodern-package-source.json');
|
|
36
|
+
if (!node_fs.existsSync(metadataPath)) return resolvePackageSource({
|
|
37
|
+
targetDir: workspaceRoot,
|
|
38
|
+
packageName: node_path.basename(workspaceRoot),
|
|
39
|
+
modernVersion
|
|
40
|
+
});
|
|
41
|
+
const metadata = readJsonFile(metadataPath);
|
|
42
|
+
const aliases = metadata.modernPackages?.aliases ?? {};
|
|
43
|
+
const firstAlias = Object.values(aliases).find((value)=>'string' == typeof value);
|
|
44
|
+
const firstPackage = Object.keys(aliases)[0];
|
|
45
|
+
const aliasScope = firstAlias?.match(/^@([^/]+)\//)?.[1];
|
|
46
|
+
const unscopedName = firstPackage?.split('/').at(-1) ?? '';
|
|
47
|
+
const aliasUnscopedName = firstAlias?.split('/').at(-1) ?? '';
|
|
48
|
+
const aliasPackageNamePrefix = aliasUnscopedName && unscopedName && aliasUnscopedName.endsWith(unscopedName) ? aliasUnscopedName.slice(0, -unscopedName.length) : void 0;
|
|
49
|
+
return {
|
|
50
|
+
strategy: 'install' === metadata.strategy ? 'install' : 'workspace',
|
|
51
|
+
modernPackageVersion: 'string' == typeof metadata.modernPackages?.specifier ? metadata.modernPackages.specifier : modernVersion,
|
|
52
|
+
registry: metadata.modernPackages?.registry,
|
|
53
|
+
aliasScope,
|
|
54
|
+
aliasPackageNamePrefix
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function existingTailwindEnabled(workspaceRoot) {
|
|
58
|
+
const contractPath = node_path.join(workspaceRoot, GENERATED_CONTRACT_PATH);
|
|
59
|
+
if (!node_fs.existsSync(contractPath)) return true;
|
|
60
|
+
const contract = readJsonFile(contractPath);
|
|
61
|
+
const apps = isRecord(contract) && Array.isArray(contract.apps) ? contract.apps : [];
|
|
62
|
+
const shell = apps.find((app)=>isRecord(app) && app.id === shellApp.id);
|
|
63
|
+
return shell?.styling && isRecord(shell.styling) ? false !== shell.styling.tailwind : true;
|
|
64
|
+
}
|
|
65
|
+
function assertValidVerticalName(name) {
|
|
66
|
+
const normalized = toKebabCase(name);
|
|
67
|
+
if (!normalized || normalized !== name) throw new Error(`Invalid Vertical name "${name}". Use lowercase kebab-case.`);
|
|
68
|
+
return normalized;
|
|
69
|
+
}
|
|
70
|
+
function nextAvailablePort(ports) {
|
|
71
|
+
const numericPorts = Object.values(ports).filter((value)=>'number' == typeof value && Number.isFinite(value));
|
|
72
|
+
return Math.max(FIRST_VERTICAL_PORT - 1, ...numericPorts) + 1;
|
|
73
|
+
}
|
|
74
|
+
function assertCanCreate(workspaceRoot, relativePath) {
|
|
75
|
+
if (node_fs.existsSync(node_path.join(workspaceRoot, relativePath))) throw new Error(`Refusing to overwrite existing path: ${relativePath}`);
|
|
76
|
+
}
|
|
77
|
+
function updateRootWorkspaceScripts(workspaceRoot, scope, packageSource, remotes) {
|
|
78
|
+
const packagePath = node_path.join(workspaceRoot, 'package.json');
|
|
79
|
+
const rootPackage = readJsonFile(packagePath);
|
|
80
|
+
const generatedRootPackage = createRootPackageJson(scope, packageSource, remotes);
|
|
81
|
+
rootPackage.scripts = generatedRootPackage.scripts;
|
|
82
|
+
writeJsonFile(packagePath, rootPackage);
|
|
83
|
+
}
|
|
84
|
+
function rewriteShellAppFiles(workspaceRoot, scope, packageSource, enableTailwind, remotes) {
|
|
85
|
+
const shellHost = createShellHost(remotes);
|
|
86
|
+
const publicWeb = createPublicWebAppArtifacts(shellHost);
|
|
87
|
+
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/package.json`), createAppPackage(scope, shellHost, packageSource, enableTailwind, remotes));
|
|
88
|
+
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/modern-app-env.d.ts`, createAppEnvDts(shellHost, remotes));
|
|
89
|
+
writeFileReplacing(workspaceRoot, publicWeb.jsonLdHelperFile.path, publicWeb.jsonLdHelperFile.content);
|
|
90
|
+
writeFileReplacing(workspaceRoot, publicWeb.routeMetadataFile.path, publicWeb.routeMetadataFile.content);
|
|
91
|
+
writeFileReplacing(workspaceRoot, publicWeb.routeHeadFile.path, publicWeb.routeHeadFile.content);
|
|
92
|
+
for (const generatedFile of publicWeb.routeMetaFiles)writeFileReplacing(workspaceRoot, generatedFile.path, generatedFile.content);
|
|
93
|
+
rewriteWorkspaceAssetsForApp(workspaceRoot, shellHost);
|
|
94
|
+
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/modern.runtime.ts`, createAppRuntimeConfig(shellHost, scope, remotes));
|
|
95
|
+
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/en/translation.json`), createAppPublicLocaleMessages(shellHost, 'en', remotes));
|
|
96
|
+
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/en/${appI18nNamespace(shellHost)}.json`), createAppPublicLocaleMessages(shellHost, 'en', remotes));
|
|
97
|
+
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/cs/translation.json`), createAppPublicLocaleMessages(shellHost, 'cs', remotes));
|
|
98
|
+
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/cs/${appI18nNamespace(shellHost)}.json`), createAppPublicLocaleMessages(shellHost, 'cs', remotes));
|
|
99
|
+
writeFileReplacing(workspaceRoot, `${shellApp.directory}/module-federation.config.ts`, createShellModuleFederationConfig(scope, remotes));
|
|
100
|
+
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/[lang]/page.tsx`, createShellPage(remotes));
|
|
101
|
+
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/vertical-components.tsx`, createShellRemoteComponents(scope, remotes));
|
|
102
|
+
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/shell-frame.tsx`, createShellFrameComponent());
|
|
103
|
+
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/effect/vertical-clients.ts`, createShellEffectClient(scope, remotes));
|
|
104
|
+
}
|
|
105
|
+
function addShellZephyrDependency(workspaceRoot, scope, remote) {
|
|
106
|
+
const packagePath = node_path.join(workspaceRoot, shellApp.directory, 'package.json');
|
|
107
|
+
const shellPackage = readJsonFile(packagePath);
|
|
108
|
+
shellPackage['zephyr:dependencies'] ??= {};
|
|
109
|
+
shellPackage['zephyr:dependencies'][remoteDependencyAlias(remote)] = zephyrRemoteDependency(scope, remote);
|
|
110
|
+
writeJsonFile(packagePath, shellPackage);
|
|
111
|
+
}
|
|
112
|
+
function addShellWorkspaceDependency(workspaceRoot, scope, remote) {
|
|
113
|
+
if (!appHasEffectApi(remote)) return;
|
|
114
|
+
const packagePath = node_path.join(workspaceRoot, shellApp.directory, 'package.json');
|
|
115
|
+
const shellPackage = readJsonFile(packagePath);
|
|
116
|
+
shellPackage.dependencies ??= {};
|
|
117
|
+
shellPackage.dependencies[packageName(scope, remote.packageSuffix)] = WORKSPACE_PACKAGE_VERSION;
|
|
118
|
+
writeJsonFile(packagePath, shellPackage);
|
|
119
|
+
}
|
|
120
|
+
function verticalTopologyEntry(scope, vertical, remotes = []) {
|
|
121
|
+
return {
|
|
122
|
+
id: vertical.id,
|
|
123
|
+
kind: vertical.kind,
|
|
124
|
+
domain: vertical.domain,
|
|
125
|
+
package: packageName(scope, vertical.packageSuffix),
|
|
126
|
+
path: vertical.directory,
|
|
127
|
+
moduleFederation: {
|
|
128
|
+
role: 'remote',
|
|
129
|
+
name: vertical.mfName,
|
|
130
|
+
manifestUrl: `http://localhost:${vertical.port}/mf-manifest.json`,
|
|
131
|
+
exposes: Object.keys(vertical.exposes ?? {}),
|
|
132
|
+
...vertical.verticalRefs?.length ? {
|
|
133
|
+
verticalRefs: vertical.verticalRefs,
|
|
134
|
+
remotes: createModuleFederationRemoteContracts(vertical, remotes)
|
|
135
|
+
} : {},
|
|
136
|
+
ssr: true,
|
|
137
|
+
fallbackTelemetryEvent: 'modernjs:mv-runtime-parity',
|
|
138
|
+
sharedContractVersion: 'mf-ssr-contract-v1'
|
|
139
|
+
},
|
|
140
|
+
...effectApiTopologyMetadata(vertical) ? {
|
|
141
|
+
api: effectApiTopologyMetadata(vertical)
|
|
142
|
+
} : {},
|
|
143
|
+
cloudflare: createCloudflareDeployContract(scope, vertical),
|
|
144
|
+
ownership: vertical.ownership
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function ownershipEntry(scope, owner) {
|
|
148
|
+
return {
|
|
149
|
+
id: owner.id,
|
|
150
|
+
package: packageName(scope, owner.packageSuffix),
|
|
151
|
+
path: owner.directory,
|
|
152
|
+
ownership: owner.ownership
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function verticalsFromTopology(topology, ports) {
|
|
156
|
+
return (topology.verticals ?? []).map((vertical)=>{
|
|
157
|
+
const domain = vertical.domain ?? String(vertical.id);
|
|
158
|
+
const packageSuffix = vertical.package?.split('/').at(-1) ?? domain;
|
|
159
|
+
const effectApi = vertical.api?.effect ? {
|
|
160
|
+
stem: 'string' == typeof vertical.api.effect.basePath ? vertical.api.effect.basePath.split('/').filter(Boolean).at(-1) ?? domain : domain,
|
|
161
|
+
prefix: vertical.api.effect.bff?.prefix ?? `/${domain}-api`,
|
|
162
|
+
consumedBy: Array.isArray(vertical.api.effect.consumedBy) ? vertical.api.effect.consumedBy : [
|
|
163
|
+
shellApp.id,
|
|
164
|
+
vertical.id
|
|
165
|
+
]
|
|
166
|
+
} : void 0;
|
|
167
|
+
return {
|
|
168
|
+
id: vertical.id,
|
|
169
|
+
directory: 'string' == typeof vertical.path ? vertical.path : `verticals/${domain}`,
|
|
170
|
+
packageSuffix,
|
|
171
|
+
displayName: vertical.displayName ?? `${toPascalCase(domain)} Vertical`,
|
|
172
|
+
kind: 'vertical',
|
|
173
|
+
domain,
|
|
174
|
+
portEnv: `VERTICAL_${toEnvSegment(domain)}_PORT`,
|
|
175
|
+
port: 'number' == typeof ports[vertical.id] ? ports[vertical.id] : 0,
|
|
176
|
+
mfName: vertical.moduleFederation?.name ?? `vertical${toPascalCase(domain)}`,
|
|
177
|
+
...Array.isArray(vertical.moduleFederation?.exposes) ? {
|
|
178
|
+
exposes: Object.fromEntries(vertical.moduleFederation.exposes.map((expose)=>[
|
|
179
|
+
expose,
|
|
180
|
+
'./Route' === expose ? './src/federation-entry.tsx' : './Widget' === expose ? `./src/components/${domain}-widget.tsx` : ''
|
|
181
|
+
]))
|
|
182
|
+
} : {},
|
|
183
|
+
...Array.isArray(vertical.moduleFederation?.verticalRefs) ? {
|
|
184
|
+
verticalRefs: vertical.moduleFederation.verticalRefs
|
|
185
|
+
} : Array.isArray(vertical.moduleFederation?.remotes) ? {
|
|
186
|
+
verticalRefs: vertical.moduleFederation.remotes.map((entry)=>entry.id).filter((id)=>'string' == typeof id)
|
|
187
|
+
} : {},
|
|
188
|
+
...effectApi ? {
|
|
189
|
+
effectApi
|
|
190
|
+
} : {},
|
|
191
|
+
ownership: vertical.ownership ?? createNeutralOwnership(vertical.id)
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function prepareAddUltramodernVertical(options) {
|
|
196
|
+
const name = assertValidVerticalName(options.name);
|
|
197
|
+
const topologyPath = node_path.join(options.workspaceRoot, TOPOLOGY_PATH);
|
|
198
|
+
const ownershipPath = node_path.join(options.workspaceRoot, OWNERSHIP_PATH);
|
|
199
|
+
const overlayPath = node_path.join(options.workspaceRoot, DEVELOPMENT_OVERLAY_PATH);
|
|
200
|
+
const rootPackage = readRequiredJsonObject(node_path.join(options.workspaceRoot, 'package.json'));
|
|
201
|
+
const topology = readRequiredJsonObject(topologyPath);
|
|
202
|
+
const ownership = readRequiredJsonObject(ownershipPath);
|
|
203
|
+
const overlay = readRequiredJsonObject(overlayPath);
|
|
204
|
+
readRequiredJsonObject(node_path.join(options.workspaceRoot, GENERATED_CONTRACT_PATH));
|
|
205
|
+
readRequiredJsonObject(node_path.join(options.workspaceRoot, PACKAGE_SOURCE_METADATA_PATH));
|
|
206
|
+
assertOptionalJsonObject(topology.shell, 'topology.shell', topologyPath);
|
|
207
|
+
assertOptionalJsonArray(topology.verticals, 'topology.verticals', topologyPath);
|
|
208
|
+
assertOptionalJsonArray(ownership.owners, 'ownership.owners', ownershipPath);
|
|
209
|
+
assertOptionalJsonObject(overlay.ports, 'overlay.ports', overlayPath);
|
|
210
|
+
assertOptionalJsonObject(overlay.manifests, 'overlay.manifests', overlayPath);
|
|
211
|
+
assertOptionalJsonObject(overlay.apis, 'overlay.apis', overlayPath);
|
|
212
|
+
overlay.ports ??= {};
|
|
213
|
+
const scope = toPackageScope(String(rootPackage.name ?? node_path.basename(options.workspaceRoot)));
|
|
214
|
+
const packageSource = existingPackageSource(options.workspaceRoot, options.modernVersion, options.packageSource);
|
|
215
|
+
const enableTailwind = options.enableTailwind ?? existingTailwindEnabled(options.workspaceRoot);
|
|
216
|
+
const existingVerticals = verticalsFromTopology(topology, overlay.ports);
|
|
217
|
+
const port = nextAvailablePort(overlay.ports);
|
|
218
|
+
const vertical = createVerticalDescriptor(name, port);
|
|
219
|
+
const updatedVerticals = [
|
|
220
|
+
...existingVerticals,
|
|
221
|
+
vertical
|
|
222
|
+
];
|
|
223
|
+
const allApps = [
|
|
224
|
+
shellApp,
|
|
225
|
+
...updatedVerticals
|
|
226
|
+
];
|
|
227
|
+
assertCanCreate(options.workspaceRoot, vertical.directory);
|
|
228
|
+
validateWorkspaceAppDescriptors(allApps);
|
|
229
|
+
validateUniqueWorkspaceAppDescriptors(allApps);
|
|
230
|
+
assertUniqueTailwindPrefixes(allApps);
|
|
231
|
+
return {
|
|
232
|
+
name,
|
|
233
|
+
scope,
|
|
234
|
+
topologyPath,
|
|
235
|
+
ownershipPath,
|
|
236
|
+
overlayPath,
|
|
237
|
+
rootPackage,
|
|
238
|
+
topology,
|
|
239
|
+
ownership,
|
|
240
|
+
overlay,
|
|
241
|
+
packageSource,
|
|
242
|
+
enableTailwind,
|
|
243
|
+
vertical,
|
|
244
|
+
updatedVerticals
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function readRequiredJsonObject(filePath) {
|
|
248
|
+
if (!node_fs.existsSync(filePath)) throw new Error(`Missing UltraModern workspace file: ${filePath}`);
|
|
249
|
+
const value = readJsonFile(filePath);
|
|
250
|
+
if (!isRecord(value)) throw new Error(`UltraModern workspace file must contain a JSON object: ${filePath}`);
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
function assertOptionalJsonObject(value, label, filePath) {
|
|
254
|
+
if (void 0 !== value && !isRecord(value)) throw new Error(`${label} in ${filePath} must be a JSON object`);
|
|
255
|
+
}
|
|
256
|
+
function assertOptionalJsonArray(value, label, filePath) {
|
|
257
|
+
if (void 0 !== value && !Array.isArray(value)) throw new Error(`${label} in ${filePath} must be a JSON array`);
|
|
258
|
+
}
|
|
259
|
+
function validateWorkspaceAppDescriptors(apps) {
|
|
260
|
+
for (const app of apps){
|
|
261
|
+
const appLabel = 'string' == typeof app.id && app.id ? app.id : '<unknown>';
|
|
262
|
+
assertNonEmptyString(app.id, `app id for ${appLabel}`);
|
|
263
|
+
assertNonEmptyString(app.directory, `directory for ${appLabel}`);
|
|
264
|
+
assertSafeOutputPath(app.directory, appLabel);
|
|
265
|
+
assertNonEmptyString(app.packageSuffix, `package suffix for ${appLabel}`);
|
|
266
|
+
assertNonEmptyString(app.displayName, `display name for ${appLabel}`);
|
|
267
|
+
if ('shell' !== app.kind && 'vertical' !== app.kind) throw new Error(`Invalid app kind for ${appLabel}: ${String(app.kind)}`);
|
|
268
|
+
assertNonEmptyString(app.portEnv, `port env for ${appLabel}`);
|
|
269
|
+
if ('number' != typeof app.port || !Number.isFinite(app.port) || app.port <= 0) throw new Error(`Invalid development port for ${appLabel}`);
|
|
270
|
+
assertNonEmptyString(app.mfName, `Module Federation name for ${appLabel}`);
|
|
271
|
+
if (app.effectApi) {
|
|
272
|
+
assertNonEmptyString(app.effectApi.prefix, `Effect API prefix for ${appLabel}`);
|
|
273
|
+
if (!app.effectApi.prefix.startsWith('/')) throw new Error(`Effect API prefix for ${appLabel} must start with "/"`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function validateUniqueWorkspaceAppDescriptors(apps) {
|
|
278
|
+
assertUniqueAppField(apps, 'app id', (app)=>app.id);
|
|
279
|
+
assertUniqueAppField(apps, 'package suffix', (app)=>app.packageSuffix);
|
|
280
|
+
assertUniqueAppField(apps, 'output path', (app)=>normalizePath(app.directory));
|
|
281
|
+
assertUniqueAppField(apps, 'Module Federation name', (app)=>app.mfName);
|
|
282
|
+
assertUniqueAppField(apps, 'development port', (app)=>String(app.port));
|
|
283
|
+
assertUniqueAppField(apps, 'Effect API prefix', (app)=>app.effectApi?.prefix);
|
|
284
|
+
assertUniqueAppField(apps, 'manifest environment name', (app)=>'vertical' === app.kind ? createRemoteManifestEnv(app) : void 0);
|
|
285
|
+
}
|
|
286
|
+
function assertUniqueAppField(apps, label, readValue) {
|
|
287
|
+
const seen = new Map();
|
|
288
|
+
for (const app of apps){
|
|
289
|
+
const value = readValue(app);
|
|
290
|
+
if (!value) continue;
|
|
291
|
+
const previousId = seen.get(value);
|
|
292
|
+
if (previousId) throw new Error(`Duplicate ${label} "${value}" for ${previousId} and ${app.id}`);
|
|
293
|
+
seen.set(value, app.id);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function assertNonEmptyString(value, label) {
|
|
297
|
+
if ('string' != typeof value || 0 === value.length) throw new Error(`Invalid ${label}`);
|
|
298
|
+
}
|
|
299
|
+
function assertSafeOutputPath(relativePath, appId) {
|
|
300
|
+
if (node_path.isAbsolute(relativePath) || relativePath.split(/[\\/]+/u).includes('..')) throw new Error(`Unsafe output path for ${appId}: ${relativePath}`);
|
|
301
|
+
}
|
|
302
|
+
function planUltramodernVertical(options) {
|
|
303
|
+
const preflight = prepareAddUltramodernVertical(options);
|
|
304
|
+
const tempRoot = node_fs.mkdtempSync(node_path.join(node_os.tmpdir(), 'um-vertical-plan-'));
|
|
305
|
+
const copiedWorkspaceRoot = node_path.join(tempRoot, 'workspace');
|
|
306
|
+
try {
|
|
307
|
+
copyWorkspaceForPlan(options.workspaceRoot, copiedWorkspaceRoot);
|
|
308
|
+
const plannedResult = addUltramodernVertical({
|
|
309
|
+
...options,
|
|
310
|
+
workspaceRoot: copiedWorkspaceRoot,
|
|
311
|
+
overlays: void 0
|
|
312
|
+
});
|
|
313
|
+
return createVerticalPlan(preflight, {
|
|
314
|
+
...plannedResult,
|
|
315
|
+
workspaceRoot: options.workspaceRoot
|
|
316
|
+
});
|
|
317
|
+
} finally{
|
|
318
|
+
node_fs.rmSync(tempRoot, {
|
|
319
|
+
recursive: true,
|
|
320
|
+
force: true
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function copyWorkspaceForPlan(workspaceRoot, copiedWorkspaceRoot) {
|
|
325
|
+
const skippedDirectories = new Set([
|
|
326
|
+
'.git',
|
|
327
|
+
'.nx',
|
|
328
|
+
'.output',
|
|
329
|
+
'coverage',
|
|
330
|
+
'dist',
|
|
331
|
+
'node_modules'
|
|
332
|
+
]);
|
|
333
|
+
node_fs.cpSync(workspaceRoot, copiedWorkspaceRoot, {
|
|
334
|
+
recursive: true,
|
|
335
|
+
filter: (sourcePath)=>!skippedDirectories.has(node_path.basename(sourcePath))
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
function createVerticalPlan(preflight, result) {
|
|
339
|
+
const { scope, vertical, updatedVerticals } = preflight;
|
|
340
|
+
const manifestUrl = `http://localhost:${vertical.port}/mf-manifest.json`;
|
|
341
|
+
return {
|
|
342
|
+
...result,
|
|
343
|
+
dryRun: true,
|
|
344
|
+
selectedPort: vertical.port,
|
|
345
|
+
moduleFederationRemote: {
|
|
346
|
+
id: vertical.id,
|
|
347
|
+
name: vertical.mfName,
|
|
348
|
+
manifestUrl
|
|
349
|
+
},
|
|
350
|
+
...vertical.effectApi ? {
|
|
351
|
+
effectApiPrefix: effectApiPrefix(vertical)
|
|
352
|
+
} : {},
|
|
353
|
+
jsonMutations: createDryRunJsonMutations(preflight, manifestUrl),
|
|
354
|
+
shellDependencyChanges: createShellDependencyChanges(scope, vertical),
|
|
355
|
+
generatedContractChanges: [
|
|
356
|
+
{
|
|
357
|
+
path: GENERATED_CONTRACT_PATH,
|
|
358
|
+
addedAppIds: [
|
|
359
|
+
vertical.id
|
|
360
|
+
],
|
|
361
|
+
shellVerticalRefs: updatedVerticals.map((vertical)=>vertical.id)
|
|
362
|
+
}
|
|
363
|
+
]
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function createDryRunJsonMutations(preflight, manifestUrl) {
|
|
367
|
+
const { scope, vertical } = preflight;
|
|
368
|
+
const effectApiMutation = appHasEffectApi(vertical) ? [
|
|
369
|
+
{
|
|
370
|
+
path: DEVELOPMENT_OVERLAY_PATH,
|
|
371
|
+
pointer: `/apis/${vertical.id}`,
|
|
372
|
+
description: `Add local Effect API URL for ${vertical.id}`,
|
|
373
|
+
value: `http://localhost:${vertical.port}${effectApiPrefix(vertical)}`
|
|
374
|
+
}
|
|
375
|
+
] : [];
|
|
376
|
+
return [
|
|
377
|
+
{
|
|
378
|
+
path: TOPOLOGY_PATH,
|
|
379
|
+
pointer: '/shell/verticalRefs/-',
|
|
380
|
+
description: `Add ${vertical.id} to the shell vertical references`,
|
|
381
|
+
value: vertical.id
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
path: TOPOLOGY_PATH,
|
|
385
|
+
pointer: '/shell/moduleFederation/remotes/-',
|
|
386
|
+
description: `Register ${vertical.id} as a Module Federation remote`,
|
|
387
|
+
value: {
|
|
388
|
+
id: vertical.id,
|
|
389
|
+
name: vertical.mfName,
|
|
390
|
+
manifestUrl
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
path: TOPOLOGY_PATH,
|
|
395
|
+
pointer: '/verticals/-',
|
|
396
|
+
description: `Add topology entry for ${vertical.id}`,
|
|
397
|
+
value: verticalTopologyEntry(scope, vertical)
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
path: OWNERSHIP_PATH,
|
|
401
|
+
pointer: '/owners/-',
|
|
402
|
+
description: `Add ownership entry for ${vertical.id}`,
|
|
403
|
+
value: ownershipEntry(scope, vertical)
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
path: DEVELOPMENT_OVERLAY_PATH,
|
|
407
|
+
pointer: `/ports/${vertical.id}`,
|
|
408
|
+
description: `Reserve development port ${vertical.port}`,
|
|
409
|
+
value: vertical.port
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
path: DEVELOPMENT_OVERLAY_PATH,
|
|
413
|
+
pointer: `/manifests/${vertical.id}`,
|
|
414
|
+
description: `Add local Module Federation manifest URL for ${vertical.id}`,
|
|
415
|
+
value: manifestUrl
|
|
416
|
+
},
|
|
417
|
+
...effectApiMutation,
|
|
418
|
+
{
|
|
419
|
+
path: 'package.json',
|
|
420
|
+
pointer: "/scripts",
|
|
421
|
+
description: "Regenerate workspace scripts for the new vertical set"
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
path: `${shellApp.directory}/package.json`,
|
|
425
|
+
pointer: '/dependencies',
|
|
426
|
+
description: `Wire shell dependencies for ${vertical.id}`
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
path: GENERATED_CONTRACT_PATH,
|
|
430
|
+
pointer: '/apps',
|
|
431
|
+
description: `Regenerate contract with ${vertical.id}`
|
|
432
|
+
}
|
|
433
|
+
];
|
|
434
|
+
}
|
|
435
|
+
function createShellDependencyChanges(scope, vertical) {
|
|
436
|
+
return [
|
|
437
|
+
{
|
|
438
|
+
path: `${shellApp.directory}/package.json`,
|
|
439
|
+
section: 'zephyr:dependencies',
|
|
440
|
+
packageName: remoteDependencyAlias(vertical),
|
|
441
|
+
version: zephyrRemoteDependency(scope, vertical)
|
|
442
|
+
},
|
|
443
|
+
...appHasEffectApi(vertical) ? [
|
|
444
|
+
{
|
|
445
|
+
path: `${shellApp.directory}/package.json`,
|
|
446
|
+
section: 'dependencies',
|
|
447
|
+
packageName: packageName(scope, vertical.packageSuffix),
|
|
448
|
+
version: WORKSPACE_PACKAGE_VERSION
|
|
449
|
+
}
|
|
450
|
+
] : []
|
|
451
|
+
];
|
|
452
|
+
}
|
|
453
|
+
function addUltramodernVertical(options) {
|
|
454
|
+
const beforeFiles = createFileSnapshot(options.workspaceRoot);
|
|
455
|
+
const { scope, topologyPath, ownershipPath, overlayPath, topology, ownership, overlay, packageSource, enableTailwind, vertical, updatedVerticals } = prepareAddUltramodernVertical(options);
|
|
456
|
+
writeApp(options.workspaceRoot, scope, vertical, packageSource, enableTailwind);
|
|
457
|
+
topology.shell ??= {};
|
|
458
|
+
topology.shell.verticalRefs ??= [];
|
|
459
|
+
topology.shell.verticalRefs.push(vertical.id);
|
|
460
|
+
topology.shell.moduleFederation ??= {};
|
|
461
|
+
topology.shell.moduleFederation.remotes ??= [];
|
|
462
|
+
topology.shell.moduleFederation.remotes.push({
|
|
463
|
+
id: vertical.id,
|
|
464
|
+
name: vertical.mfName,
|
|
465
|
+
manifestUrl: `http://localhost:${vertical.port}/mf-manifest.json`
|
|
466
|
+
});
|
|
467
|
+
topology.verticals ??= [];
|
|
468
|
+
topology.verticals.push(verticalTopologyEntry(scope, vertical));
|
|
469
|
+
ownership.owners ??= [];
|
|
470
|
+
ownership.owners.push(ownershipEntry(scope, vertical));
|
|
471
|
+
overlay.ports[vertical.id] = vertical.port;
|
|
472
|
+
overlay.manifests ??= {};
|
|
473
|
+
overlay.manifests[vertical.id] = `http://localhost:${vertical.port}/mf-manifest.json`;
|
|
474
|
+
overlay.apis ??= {};
|
|
475
|
+
overlay.apis[vertical.id] = `http://localhost:${vertical.port}${effectApiPrefix(vertical)}`;
|
|
476
|
+
writeJsonFile(topologyPath, topology);
|
|
477
|
+
writeJsonFile(ownershipPath, ownership);
|
|
478
|
+
writeJsonFile(overlayPath, overlay);
|
|
479
|
+
writeJsonFile(node_path.join(options.workspaceRoot, GENERATED_CONTRACT_PATH), createGeneratedContract(scope, [
|
|
480
|
+
{
|
|
481
|
+
...shellApp,
|
|
482
|
+
verticalRefs: updatedVerticals.map((vertical)=>vertical.id)
|
|
483
|
+
},
|
|
484
|
+
...updatedVerticals
|
|
485
|
+
], enableTailwind));
|
|
486
|
+
rewriteShellAppFiles(options.workspaceRoot, scope, packageSource, enableTailwind, updatedVerticals);
|
|
487
|
+
writeGeneratedWorkspaceScripts(options.workspaceRoot, scope, enableTailwind, updatedVerticals);
|
|
488
|
+
addShellZephyrDependency(options.workspaceRoot, scope, vertical);
|
|
489
|
+
addShellWorkspaceDependency(options.workspaceRoot, scope, vertical);
|
|
490
|
+
updateRootWorkspaceScripts(options.workspaceRoot, scope, packageSource, updatedVerticals);
|
|
491
|
+
const afterFiles = createFileSnapshot(options.workspaceRoot);
|
|
492
|
+
const { createdPaths, rewrittenPaths } = diffFileSnapshots(beforeFiles, afterFiles);
|
|
493
|
+
const result = createGenerationResult({
|
|
494
|
+
operation: 'vertical',
|
|
495
|
+
workspaceRoot: options.workspaceRoot,
|
|
496
|
+
packageScope: scope,
|
|
497
|
+
packageSource,
|
|
498
|
+
createdApps: [
|
|
499
|
+
vertical
|
|
500
|
+
],
|
|
501
|
+
createdPaths,
|
|
502
|
+
rewrittenPaths
|
|
503
|
+
});
|
|
504
|
+
runCodeSmithOverlays({
|
|
505
|
+
workspaceRoot: options.workspaceRoot,
|
|
506
|
+
overlays: options.overlays,
|
|
507
|
+
result
|
|
508
|
+
});
|
|
509
|
+
return result;
|
|
510
|
+
}
|
|
511
|
+
export { addShellWorkspaceDependency, addShellZephyrDependency, addUltramodernVertical, assertCanCreate, assertValidVerticalName, existingPackageSource, existingTailwindEnabled, nextAvailablePort, ownershipEntry, planUltramodernVertical, prepareAddUltramodernVertical, rewriteShellAppFiles, updateRootWorkspaceScripts, verticalTopologyEntry, verticalsFromTopology };
|