@liujitcn/kratos-admin-cli 0.0.36 → 0.0.38
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 +12 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +39 -10
- package/package.json +1 -1
- package/templates/business-workspace/.oxlintignore +14 -0
- package/templates/business-workspace/.prettierignore +9 -0
- package/templates/business-workspace/.prettierrc.cjs +41 -0
- package/templates/business-workspace/.stylelintignore +4 -0
- package/templates/business-workspace/.stylelintrc.cjs +39 -0
- package/templates/business-workspace/apps/admin/src/main.ts +11 -0
- package/templates/business-workspace/apps/admin/src/module-manifest.ts +8 -2
- package/templates/business-workspace/apps/admin/{vite.config.ts → vite.config.ts.tmpl} +1 -1
- package/templates/business-workspace/internal/lint-config/oxlint.json +38 -0
- package/templates/business-workspace/lint-staged.config.cjs +8 -0
- package/templates/business-workspace/package.json +62 -3
- package/templates/business-workspace/packages/modules/__MODULE_NAME__/package.json +1 -0
- package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/api/.gitkeep +0 -0
- package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/locales/en-US.json +1 -0
- package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/locales/ja-JP.json +1 -0
- package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/locales/zh-CN.json +1 -0
- package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/locales/zh-TW.json +1 -0
- package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/{module.ts → module.ts.tmpl} +4 -2
- package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/rpc/.gitkeep +0 -0
- package/templates/business-workspace/packages/modules/__MODULE_NAME__/test/.gitkeep +0 -0
- package/templates/business-workspace/scripts/generate-auto-import-types.mjs +40 -0
- package/templates/business-workspace/scripts/sync-locales.mjs +38 -0
- package/templates/business-workspace/turbo.json +5 -0
- package/templates/project-frontend/Makefile +369 -0
- package/templates/project-frontend/scripts/publish_packages.sh +56 -0
- package/templates/project-frontend/scripts/reinstall_dependencies.sh +37 -0
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ pnpm module:create ../shop-admin --module shop,order --with log
|
|
|
52
52
|
pnpm --filter @liujitcn/kratos-admin-cli test
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
CLI
|
|
55
|
+
CLI 默认引入内置 System;指定本地 `system` 时由本地模块继承并替代运行时注册,构建仍扫描内置包。`--module` 可重复使用,也接受逗号分隔名称,并为每个名称创建独立 module 包;`--with` 接收逗号分隔的额外已发布 module 名称,只装配依赖而不生成源码。CLI 拒绝覆盖已有目录;渲染失败时会清理本次创建的不完整目标目录。
|
|
56
56
|
|
|
57
57
|
生成结果遵守以下约束:
|
|
58
58
|
|
|
@@ -96,3 +96,14 @@ pnpm --filter @liujitcn/kratos-admin-cli test
|
|
|
96
96
|
发布包包含编译后的 `dist/index.js`、类型声明、workspace 模板和本 README。仓库级
|
|
97
97
|
`make -C frontend package-admin` 会先执行测试和构建,再生成
|
|
98
98
|
`@liujitcn/kratos-admin-cli` tarball。
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
### CLI 生成边界
|
|
102
|
+
|
|
103
|
+
CLI 直接生成完整宿主、本地业务模块、四种语言源文件与注册入口、类型检查、lint 和打包配置。
|
|
104
|
+
支持本地 `system` 与其他模块一起创建;管理端本地 System 继承内置能力,运行时只注册一次,
|
|
105
|
+
构建仍扫描内置源码。应用端本地模块使用独立导入别名,保留内置 System。
|
|
106
|
+
|
|
107
|
+
通过 `--kratos-project` 生成与 Go 后端配套的前端:H5 输出到 `backend/data/<terminal>`,
|
|
108
|
+
管理端 CLI 同时在 workspace 父目录创建共享 `Makefile` 与 `scripts`。Go 调用方仅传参执行 CLI。
|
|
109
|
+
新增语言或修改语言文件后运行 `pnpm i18n:sync`;`pnpm i18n:check` 校验注册文件是否同步。
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface CreateWorkspaceOptions {
|
|
|
9
9
|
additionalModules?: string[];
|
|
10
10
|
/** 生成命令的工作目录。 */
|
|
11
11
|
cwd?: string;
|
|
12
|
+
/** 生成与 Kratos 后端配套的公共前端工具和静态输出配置。 */
|
|
13
|
+
kratosProject?: boolean;
|
|
12
14
|
}
|
|
13
15
|
/** 创建包含宿主和业务模块包的 pnpm workspace。 */
|
|
14
16
|
export declare function createBusinessWorkspace(options: CreateWorkspaceOptions): Promise<string>;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
2
3
|
import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
3
4
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -23,11 +24,15 @@ export async function createBusinessWorkspace(options) {
|
|
|
23
24
|
const packageVersion = await readCliPackageVersion();
|
|
24
25
|
const primaryModuleTokens = createModuleTokens(primaryModuleName, packageVersion);
|
|
25
26
|
const moduleManifestEntries = [
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
...(!moduleNames.includes("system")
|
|
28
|
+
? [
|
|
29
|
+
{
|
|
30
|
+
packageName: "@liujitcn/kratos-admin-system",
|
|
31
|
+
moduleIdentifier: "systemAdminModule",
|
|
32
|
+
optimizeDependencies: officialModuleOptimizeDependencies.system
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
: []),
|
|
31
36
|
...moduleNames.map(name => ({
|
|
32
37
|
packageName: `@${name}/admin-module`,
|
|
33
38
|
moduleIdentifier: `${toCamelCase(name)}AdminModule`,
|
|
@@ -74,6 +79,10 @@ export async function createBusinessWorkspace(options) {
|
|
|
74
79
|
const tokens = {
|
|
75
80
|
...primaryModuleTokens,
|
|
76
81
|
__PROJECT_NAME__: projectName,
|
|
82
|
+
__FRONTEND_MODULE__: primaryModuleName,
|
|
83
|
+
__MODULES_SPACE__: moduleNames.join(" "),
|
|
84
|
+
__HOST_BUILD_CACHE__: String(!options.kratosProject),
|
|
85
|
+
__H5_OUTPUT__: options.kratosProject ? ',\n outputDirectory: "../../../../backend/data/admin"' : "",
|
|
77
86
|
__APP_PACKAGE__: `@${primaryModuleName}/admin-app`,
|
|
78
87
|
__APP_DEPENDENCIES__: formatJsonValue(appDependencies, " "),
|
|
79
88
|
__MODULE_FILTERS__: modulePackages.map(packageName => `--filter=${packageName}`).join(" "),
|
|
@@ -94,6 +103,10 @@ export async function createBusinessWorkspace(options) {
|
|
|
94
103
|
__PROJECT_NAME__: projectName
|
|
95
104
|
});
|
|
96
105
|
}
|
|
106
|
+
execFileSync(process.execPath, [resolve(target, "scripts/sync-locales.mjs"), "--write"], { stdio: "inherit" });
|
|
107
|
+
if (options.kratosProject) {
|
|
108
|
+
await renderDirectory(resolve(packageRoot, "templates/project-frontend"), dirname(target), tokens);
|
|
109
|
+
}
|
|
97
110
|
}
|
|
98
111
|
catch (error) {
|
|
99
112
|
await rm(target, { recursive: true, force: true });
|
|
@@ -115,7 +128,12 @@ export async function runCli(args = process.argv.slice(2)) {
|
|
|
115
128
|
if (!projectName || moduleNames.length === 0) {
|
|
116
129
|
throw new Error("用法: kratos-admin create <project> --module <module[,module...]>");
|
|
117
130
|
}
|
|
118
|
-
const target = await createBusinessWorkspace({
|
|
131
|
+
const target = await createBusinessWorkspace({
|
|
132
|
+
projectName,
|
|
133
|
+
moduleNames,
|
|
134
|
+
additionalModules: withModules,
|
|
135
|
+
kratosProject: args.includes("--kratos-project")
|
|
136
|
+
});
|
|
119
137
|
process.stdout.write(`已创建业务 workspace: ${target}\n`);
|
|
120
138
|
}
|
|
121
139
|
/** 渲染模板目录中的路径和文本占位符。 */
|
|
@@ -123,7 +141,7 @@ async function renderDirectory(source, target, tokens) {
|
|
|
123
141
|
await mkdir(target, { recursive: true });
|
|
124
142
|
const entries = await readdir(source, { withFileTypes: true });
|
|
125
143
|
for (const entry of entries) {
|
|
126
|
-
const renderedName = entry.name === gitignoreTemplateName ? ".gitignore" : replaceTokens(entry.name, tokens);
|
|
144
|
+
const renderedName = entry.name === gitignoreTemplateName ? ".gitignore" : replaceTokens(entry.name, tokens).replace(/\.tmpl$/, "");
|
|
127
145
|
const sourcePath = join(source, entry.name);
|
|
128
146
|
const targetPath = join(target, renderedName);
|
|
129
147
|
if (entry.isDirectory()) {
|
|
@@ -168,7 +186,7 @@ function normalizeModuleNames(moduleNames) {
|
|
|
168
186
|
if (!primaryModuleName)
|
|
169
187
|
throw new Error("至少需要一个业务模块名称");
|
|
170
188
|
normalized.forEach(name => validateName(name, "模块名称"));
|
|
171
|
-
const reservedName = normalized.find(name => name === "
|
|
189
|
+
const reservedName = normalized.find(name => name === "kratos-admin");
|
|
172
190
|
if (reservedName)
|
|
173
191
|
throw new Error(`自有模块名称不能使用保留名称: ${reservedName}`);
|
|
174
192
|
return [primaryModuleName, ...normalized.slice(1)];
|
|
@@ -186,7 +204,18 @@ function createModuleTokens(moduleName, packageVersion) {
|
|
|
186
204
|
__MODULE_PASCAL__: toPascalCase(moduleName),
|
|
187
205
|
__MODULE_PACKAGE__: `@${moduleName}/admin-module`,
|
|
188
206
|
__MODULE_IDENTIFIER__: `${toCamelCase(moduleName)}AdminModule`,
|
|
189
|
-
__CORE_VERSION__: `^${packageVersion}
|
|
207
|
+
__CORE_VERSION__: `^${packageVersion}`,
|
|
208
|
+
__SYSTEM_IMPORT__: moduleName === "system"
|
|
209
|
+
? 'import { systemAdminModule as baseSystemAdminModule } from "@liujitcn/kratos-admin-system";\n'
|
|
210
|
+
: "",
|
|
211
|
+
__MODULE_FIELDS__: moduleName === "system"
|
|
212
|
+
? `...baseSystemAdminModule,
|
|
213
|
+
views: { ...baseSystemAdminModule.views, ...viewModules },
|
|
214
|
+
messages: Object.fromEntries(Object.entries(LOCALE_MESSAGES).map(([locale, messages]) => [
|
|
215
|
+
locale, { ...baseSystemAdminModule.messages?.[locale], ...messages }
|
|
216
|
+
]))`
|
|
217
|
+
: `name: "${moduleName}",\n views: viewModules,\n messages: LOCALE_MESSAGES`,
|
|
218
|
+
__MODULE_DEPENDENCIES__: JSON.stringify(moduleName === "system" ? { "@liujitcn/kratos-admin-system": `^${packageVersion}` } : {})
|
|
190
219
|
};
|
|
191
220
|
}
|
|
192
221
|
/** 格式化嵌入模板的多行 JSON 值。 */
|
|
@@ -230,7 +259,7 @@ async function pathExists(path) {
|
|
|
230
259
|
/** 输出 CLI 使用说明。 */
|
|
231
260
|
function printHelp() {
|
|
232
261
|
process.stdout.write([
|
|
233
|
-
"kratos-admin create <project> --module <module[,module...]> [--module <module>] [--with other]",
|
|
262
|
+
"kratos-admin create <project> --module <module[,module...]> [--module <module>] [--with other] [--kratos-project]",
|
|
234
263
|
"",
|
|
235
264
|
"示例:",
|
|
236
265
|
" kratos-admin create shop-admin --module shop",
|
package/package.json
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// @see: https://www.prettier.cn
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
// 指定最大换行长度
|
|
5
|
+
printWidth: 130,
|
|
6
|
+
// 缩进制表符宽度 | 空格数
|
|
7
|
+
tabWidth: 2,
|
|
8
|
+
// 使用制表符而不是空格缩进行 (true:制表符,false:空格)
|
|
9
|
+
useTabs: false,
|
|
10
|
+
// 结尾不用分号 (true:有,false:没有)
|
|
11
|
+
semi: true,
|
|
12
|
+
// 使用单引号 (true:单引号,false:双引号)
|
|
13
|
+
singleQuote: false,
|
|
14
|
+
// 在对象字面量中决定是否将属性名用引号括起来 可选值 "<as-needed|consistent|preserve>"
|
|
15
|
+
quoteProps: "as-needed",
|
|
16
|
+
// 在JSX中使用单引号而不是双引号 (true:单引号,false:双引号)
|
|
17
|
+
jsxSingleQuote: false,
|
|
18
|
+
// 多行时尽可能打印尾随逗号 可选值"<none|es5|all>"
|
|
19
|
+
trailingComma: "none",
|
|
20
|
+
// 在对象,数组括号与文字之间加空格 "{ foo: bar }" (true:有,false:没有)
|
|
21
|
+
bracketSpacing: true,
|
|
22
|
+
// 将 > 多行元素放在最后一行的末尾,而不是单独放在下一行 (true:放末尾,false:单独一行)
|
|
23
|
+
bracketSameLine: false,
|
|
24
|
+
// (x) => {} 箭头函数参数只有一个时是否要有小括号 (avoid:省略括号,always:不省略括号)
|
|
25
|
+
arrowParens: "avoid",
|
|
26
|
+
// 指定要使用的解析器,不需要写文件开头的 @prettier
|
|
27
|
+
requirePragma: false,
|
|
28
|
+
// 可以在文件顶部插入一个特殊标记,指定该文件已使用 Prettier 格式化
|
|
29
|
+
insertPragma: false,
|
|
30
|
+
// 用于控制文本是否应该被换行以及如何进行换行
|
|
31
|
+
proseWrap: "preserve",
|
|
32
|
+
// 在html中空格是否是敏感的 "css" - 遵守 CSS 显示属性的默认值, "strict" - 空格被认为是敏感的 ,"ignore" - 空格被认为是不敏感的
|
|
33
|
+
htmlWhitespaceSensitivity: "css",
|
|
34
|
+
// 控制在 Vue 单文件组件中 <script> 和 <style> 标签内的代码缩进方式
|
|
35
|
+
vueIndentScriptAndStyle: false,
|
|
36
|
+
// 换行符使用 lf 结尾是 可选值 "<auto|lf|crlf|cr>"
|
|
37
|
+
endOfLine: "auto",
|
|
38
|
+
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码 (rangeStart:开始,rangeEnd:结束)
|
|
39
|
+
rangeStart: 0,
|
|
40
|
+
rangeEnd: Infinity
|
|
41
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @see: https://stylelint.io
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
root: true,
|
|
5
|
+
// 继承某些已有的规则
|
|
6
|
+
extends: [
|
|
7
|
+
"stylelint-config-standard", // 配置 stylelint 拓展插件
|
|
8
|
+
"stylelint-config-html/vue", // 配置 vue 中 template 样式格式化
|
|
9
|
+
"stylelint-config-standard-scss", // 配置 stylelint scss 插件
|
|
10
|
+
"stylelint-config-recommended-vue/scss", // 配置 vue 中 scss 样式格式化
|
|
11
|
+
"stylelint-config-recess-order" // 配置 stylelint css 属性书写顺序插件,
|
|
12
|
+
],
|
|
13
|
+
overrides: [
|
|
14
|
+
// 扫描 .vue/html 文件中的 <style> 标签内的样式
|
|
15
|
+
{
|
|
16
|
+
files: ["**/*.{vue,html}"],
|
|
17
|
+
customSyntax: "postcss-html"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
rules: {
|
|
21
|
+
"function-url-quotes": "always", // URL 的引号 "always(必须加上引号)"|"never(没有引号)"
|
|
22
|
+
"color-hex-length": "long", // 指定 16 进制颜色的简写或扩写 "short(16进制简写)"|"long(16进制扩写)"
|
|
23
|
+
"rule-empty-line-before": "never", // 要求或禁止在规则之前的空行 "always(规则之前必须始终有一个空行)"|"never(规则前绝不能有空行)"|"always-multi-line(多行规则之前必须始终有一个空行)"|"never-multi-line(多行规则之前绝不能有空行)"
|
|
24
|
+
"font-family-no-missing-generic-family-keyword": null, // 禁止在字体族名称列表中缺少通用字体族关键字
|
|
25
|
+
"property-no-unknown": null, // 禁止未知的属性
|
|
26
|
+
"no-empty-source": null, // 禁止空源码
|
|
27
|
+
"selector-class-pattern": null, // 强制选择器类名的格式
|
|
28
|
+
"value-no-vendor-prefix": null, // 关闭 vendor-prefix (为了解决多行省略 -webkit-box)
|
|
29
|
+
"no-descending-specificity": null, // 不允许较低特异性的选择器出现在覆盖较高特异性的选择器
|
|
30
|
+
"value-keyword-case": null, // 解决在 scss 中使用 v-bind 大写单词报错
|
|
31
|
+
"selector-pseudo-class-no-unknown": [
|
|
32
|
+
true,
|
|
33
|
+
{
|
|
34
|
+
ignorePseudoClasses: ["global", "v-deep", "deep"]
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
ignoreFiles: ["**/*.js", "**/*.jsx", "**/*.tsx", "**/*.ts"]
|
|
39
|
+
};
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { bootstrapAdminApp } from "@liujitcn/kratos-admin-core";
|
|
2
2
|
import adminModules from "./modules";
|
|
3
3
|
|
|
4
|
+
const globalState = JSON.parse(window.localStorage.getItem("admin-global") ?? "null") as {
|
|
5
|
+
primary?: string;
|
|
6
|
+
isDark?: boolean;
|
|
7
|
+
} | null;
|
|
8
|
+
if (globalState) {
|
|
9
|
+
const dot = document.querySelectorAll<HTMLElement>(".dot i");
|
|
10
|
+
const html = document.querySelector("html");
|
|
11
|
+
if (globalState.primary) dot.forEach(item => (item.style.background = globalState.primary ?? ""));
|
|
12
|
+
if (globalState.isDark && html) html.style.background = "#141414";
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
void bootstrapAdminApp({ modules: adminModules });
|
|
@@ -13,11 +13,17 @@ export interface AdminModuleManifestItem {
|
|
|
13
13
|
/** 当前宿主启用的管理端业务模块清单。 */
|
|
14
14
|
export const adminModuleManifest = [__MODULE_MANIFEST__] satisfies AdminModuleManifestItem[];
|
|
15
15
|
|
|
16
|
+
/** 编译清单保留内置 System 源码,供继承它的本地模块完成自动导入。 */
|
|
17
|
+
const adminBuildModules: Pick<AdminModuleManifestItem, "packageName" | "optimizeDependencies">[] = [
|
|
18
|
+
{ packageName: "@liujitcn/kratos-admin-system", optimizeDependencies: ["swagger-ui-dist/swagger-ui-bundle.js"] },
|
|
19
|
+
...adminModuleManifest.filter(item => item.packageName !== "@liujitcn/kratos-admin-system")
|
|
20
|
+
];
|
|
21
|
+
|
|
16
22
|
/** 当前宿主需要扫描的业务模块包。 */
|
|
17
|
-
export const adminModulePackages =
|
|
23
|
+
export const adminModulePackages = adminBuildModules.map(item => item.packageName);
|
|
18
24
|
|
|
19
25
|
/** 当前宿主需要预构建的业务模块依赖。 */
|
|
20
|
-
export const adminModuleOptimizeDependencies =
|
|
26
|
+
export const adminModuleOptimizeDependencies = adminBuildModules.flatMap(item => {
|
|
21
27
|
return item.optimizeDependencies?.map(dependency => `${item.packageName} > ${dependency}`) ?? [];
|
|
22
28
|
});
|
|
23
29
|
|
|
@@ -3,5 +3,5 @@ import { adminModuleOptimizeDependencies, adminModulePackages } from "./src/modu
|
|
|
3
3
|
|
|
4
4
|
export default defineAdminViteConfig({
|
|
5
5
|
modulePackages: adminModulePackages,
|
|
6
|
-
optimizeDependencies:
|
|
6
|
+
optimizeDependencies: adminModuleOptimizeDependencies__H5_OUTPUT__
|
|
7
7
|
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"env": {
|
|
4
|
+
"browser": true,
|
|
5
|
+
"node": true,
|
|
6
|
+
"es2020": true,
|
|
7
|
+
"vue": true
|
|
8
|
+
},
|
|
9
|
+
"plugins": ["vue", "typescript"],
|
|
10
|
+
"categories": {
|
|
11
|
+
"correctness": "warn",
|
|
12
|
+
"suspicious": "warn"
|
|
13
|
+
},
|
|
14
|
+
"rules": {
|
|
15
|
+
"no-var": "error",
|
|
16
|
+
"no-unused-vars": "error",
|
|
17
|
+
"no-empty-function": "error",
|
|
18
|
+
"no-unused-expressions": "off",
|
|
19
|
+
"no-extra-boolean-cast": "off",
|
|
20
|
+
"no-unsafe-finally": "off",
|
|
21
|
+
"no-shadow": "off",
|
|
22
|
+
"no-underscore-dangle": "off",
|
|
23
|
+
"prefer-const": "off",
|
|
24
|
+
"no-use-before-define": "off",
|
|
25
|
+
"typescript/prefer-ts-expect-error": "error",
|
|
26
|
+
"typescript/ban-ts-comment": "error",
|
|
27
|
+
"typescript/no-inferrable-types": "off",
|
|
28
|
+
"typescript/no-explicit-any": "off",
|
|
29
|
+
"typescript/ban-types": "off",
|
|
30
|
+
"typescript/no-namespace": "off",
|
|
31
|
+
"typescript/no-var-requires": "off",
|
|
32
|
+
"typescript/no-require-imports": "off",
|
|
33
|
+
"typescript/no-non-null-assertion": "off",
|
|
34
|
+
"vue/define-props-destructuring": "off",
|
|
35
|
+
"vue/no-deprecated-props-default-this": "off",
|
|
36
|
+
"vue/no-required-prop-with-default": "off"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
"*.{js,jsx,ts,tsx}": ["oxlint --fix --config ./internal/lint-config/oxlint.json", "prettier --write"],
|
|
3
|
+
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": ["prettier --write --parser json"],
|
|
4
|
+
"package.json": ["prettier --write"],
|
|
5
|
+
"*.vue": ["oxlint --fix --vue-plugin --config ./internal/lint-config/oxlint.json", "prettier --write", "stylelint --fix"],
|
|
6
|
+
"*.{scss,less,styl,html}": ["stylelint --fix", "prettier --write"],
|
|
7
|
+
"*.md": ["prettier --write"]
|
|
8
|
+
};
|
|
@@ -7,7 +7,15 @@
|
|
|
7
7
|
"build": "turbo run build --filter=__APP_PACKAGE__",
|
|
8
8
|
"build:package": "turbo run build:package __MODULE_FILTERS__",
|
|
9
9
|
"type:check": "turbo run type:check",
|
|
10
|
-
"package": "pnpm build:package && pnpm --recursive __MODULE_FILTERS__ pack"
|
|
10
|
+
"package": "pnpm build:package && pnpm --recursive __MODULE_FILTERS__ pack",
|
|
11
|
+
"test": "node --test",
|
|
12
|
+
"lint:oxlint": "oxlint --fix --vue-plugin --config ./internal/lint-config/oxlint.json --ignore-path .oxlintignore ./apps ./packages ./internal ./scripts",
|
|
13
|
+
"lint:lint-staged": "lint-staged",
|
|
14
|
+
"lint:prettier": "prettier --write \"{apps,packages,internal,scripts}/**/*.{js,mjs,ts,json,tsx,css,less,scss,vue,html,md,yaml}\"",
|
|
15
|
+
"lint:stylelint": "stylelint --cache --fix \"{apps,packages}/**/*.{vue,less,postcss,css,scss}\" --cache-location node_modules/.cache/stylelint/",
|
|
16
|
+
"generate:auto-import-types": "node scripts/generate-auto-import-types.mjs",
|
|
17
|
+
"i18n:sync": "node scripts/sync-locales.mjs --write",
|
|
18
|
+
"i18n:check": "node scripts/sync-locales.mjs"
|
|
11
19
|
},
|
|
12
20
|
"devDependencies": {
|
|
13
21
|
"@types/node": "^25.9.1",
|
|
@@ -15,10 +23,61 @@
|
|
|
15
23
|
"turbo": "^2.5.6",
|
|
16
24
|
"typescript": "6.0.3",
|
|
17
25
|
"vite": "^8.0.14",
|
|
18
|
-
"vue-tsc": "^3.3.2"
|
|
26
|
+
"vue-tsc": "^3.3.2",
|
|
27
|
+
"@commitlint/cli": "^18.4.3",
|
|
28
|
+
"@commitlint/config-conventional": "^18.4.3",
|
|
29
|
+
"@types/md5": "^2.3.5",
|
|
30
|
+
"@types/nprogress": "^0.2.3",
|
|
31
|
+
"@types/qs": "^6.9.15",
|
|
32
|
+
"@types/sortablejs": "^1.15.8",
|
|
33
|
+
"autoprefixer": "^10.4.19",
|
|
34
|
+
"cz-git": "1.13.1",
|
|
35
|
+
"czg": "^1.13.1",
|
|
36
|
+
"lint-staged": "^15.2.5",
|
|
37
|
+
"oxlint": "^1.67.0",
|
|
38
|
+
"postcss": "^8.4.38",
|
|
39
|
+
"postcss-html": "^1.8.1",
|
|
40
|
+
"prettier": "^3.3.2",
|
|
41
|
+
"standard-version": "^9.5.0",
|
|
42
|
+
"stylelint": "^17.12.0",
|
|
43
|
+
"stylelint-config-html": "^1.1.0",
|
|
44
|
+
"stylelint-config-recess-order": "^7.7.0",
|
|
45
|
+
"stylelint-config-recommended-scss": "^17.0.1",
|
|
46
|
+
"stylelint-config-recommended-vue": "^1.5.0",
|
|
47
|
+
"stylelint-config-standard": "^40.0.0",
|
|
48
|
+
"stylelint-config-standard-scss": "^17.0.0",
|
|
49
|
+
"stylelint-order": "8.1.1",
|
|
50
|
+
"ts-proto": "^2.11.8"
|
|
19
51
|
},
|
|
20
52
|
"engines": {
|
|
21
53
|
"node": "^20.19.0 || >=22.12.0"
|
|
22
54
|
},
|
|
23
|
-
"packageManager": "pnpm@10.33.4"
|
|
55
|
+
"packageManager": "pnpm@10.33.4",
|
|
56
|
+
"pnpm": {
|
|
57
|
+
"overrides": {
|
|
58
|
+
"@vueuse/core": "14.4.0",
|
|
59
|
+
"element-plus": "2.14.5",
|
|
60
|
+
"svg-baker>braces": "3.0.3",
|
|
61
|
+
"svg-baker>micromatch": "4.0.8",
|
|
62
|
+
"svg-baker>postcss": "8.5.26",
|
|
63
|
+
"vue": "3.5.42",
|
|
64
|
+
"vue-router": "5.3.1"
|
|
65
|
+
},
|
|
66
|
+
"onlyBuiltDependencies": [
|
|
67
|
+
"@parcel/watcher",
|
|
68
|
+
"es5-ext",
|
|
69
|
+
"esbuild",
|
|
70
|
+
"vue-demi",
|
|
71
|
+
"workbox-build"
|
|
72
|
+
],
|
|
73
|
+
"ignoredBuiltDependencies": [
|
|
74
|
+
"@scarf/scarf"
|
|
75
|
+
],
|
|
76
|
+
"auditConfig": {
|
|
77
|
+
"ignoreCves": [
|
|
78
|
+
"CVE-2025-71330",
|
|
79
|
+
"CVE-2025-71329"
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
24
83
|
}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { Component } from "vue";
|
|
2
2
|
import { defineAdminModule } from "@liujitcn/kratos-admin-core";
|
|
3
3
|
|
|
4
|
+
__SYSTEM_IMPORT__
|
|
5
|
+
import { LOCALE_MESSAGES } from "./locales/generated";
|
|
6
|
+
|
|
4
7
|
const viewModules = import.meta.glob<{ default: Component }>("./views/**/*.vue");
|
|
5
8
|
|
|
6
9
|
/** __MODULE_PASCAL__ 管理端业务模块。 */
|
|
7
10
|
export const __MODULE_IDENTIFIER__ = defineAdminModule({
|
|
8
|
-
|
|
9
|
-
views: viewModules
|
|
11
|
+
__MODULE_FIELDS__
|
|
10
12
|
});
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import { build } from "vite";
|
|
6
|
+
|
|
7
|
+
const adminRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const hostRequire = createRequire(resolve(adminRoot, "apps/admin/package.json"));
|
|
9
|
+
const coreRoot = dirname(hostRequire.resolve("@liujitcn/kratos-admin-core/package.json"));
|
|
10
|
+
const coreRequire = createRequire(resolve(coreRoot, "package.json"));
|
|
11
|
+
const { default: AutoImport } = await import(pathToFileURL(coreRequire.resolve("unplugin-auto-import/vite")).href);
|
|
12
|
+
const { ElementPlusResolver } = await import(pathToFileURL(coreRequire.resolve("unplugin-vue-components/resolvers")).href);
|
|
13
|
+
const imports = JSON.parse(readFileSync(resolve(coreRoot, "build/auto-imports.json"), "utf8"));
|
|
14
|
+
const virtualEntry = "virtual:kratos-auto-import-types";
|
|
15
|
+
|
|
16
|
+
await build({
|
|
17
|
+
configFile: false,
|
|
18
|
+
logLevel: "silent",
|
|
19
|
+
plugins: [
|
|
20
|
+
{
|
|
21
|
+
name: "kratos-auto-import-types-entry",
|
|
22
|
+
resolveId(id) {
|
|
23
|
+
return id === virtualEntry ? `\0${virtualEntry}` : undefined;
|
|
24
|
+
},
|
|
25
|
+
load(id) {
|
|
26
|
+
return id === `\0${virtualEntry}` ? "export {};" : undefined;
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
AutoImport({
|
|
30
|
+
dts: resolve(adminRoot, "apps/admin/src/auto-imports.d.ts"),
|
|
31
|
+
dtsMode: "overwrite",
|
|
32
|
+
imports: ["vue", "vue-router", imports],
|
|
33
|
+
resolvers: [ElementPlusResolver()]
|
|
34
|
+
})
|
|
35
|
+
],
|
|
36
|
+
build: {
|
|
37
|
+
write: false,
|
|
38
|
+
rollupOptions: { input: virtualEntry }
|
|
39
|
+
}
|
|
40
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, resolve } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
6
|
+
const modulesRoot = resolve(root, 'packages/modules')
|
|
7
|
+
const write = process.argv.includes('--write')
|
|
8
|
+
let expectedLocales
|
|
9
|
+
for (const entry of existsSync(modulesRoot) ? readdirSync(modulesRoot, { withFileTypes: true }) : []) {
|
|
10
|
+
if (!entry.isDirectory()) continue
|
|
11
|
+
const directory = resolve(modulesRoot, entry.name, 'src/locales')
|
|
12
|
+
const locales = readdirSync(directory).filter(file => file.endsWith('.json')).map(file => file.slice(0, -5))
|
|
13
|
+
.sort((a, b) => a === 'zh-CN' ? -1 : b === 'zh-CN' ? 1 : a.localeCompare(b))
|
|
14
|
+
if (!locales.includes('zh-CN')) throw new Error(`${entry.name} 缺少 zh-CN 语言包`)
|
|
15
|
+
if (expectedLocales && locales.join(',') !== expectedLocales) throw new Error(`${entry.name} 语言集合不一致`)
|
|
16
|
+
expectedLocales = locales.join(',')
|
|
17
|
+
const messages = Object.fromEntries(locales.map(locale => {
|
|
18
|
+
if (!/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(locale)) throw new Error(`语言代码无效:${locale}`)
|
|
19
|
+
return [locale, JSON.parse(readFileSync(resolve(directory, `${locale}.json`), 'utf8'))]
|
|
20
|
+
}))
|
|
21
|
+
const keys = Object.keys(messages['zh-CN']).sort().join('\0')
|
|
22
|
+
for (const locale of locales) {
|
|
23
|
+
if (Object.keys(messages[locale]).sort().join('\0') !== keys) throw new Error(`${entry.name}/${locale} 语言键集合不一致`)
|
|
24
|
+
}
|
|
25
|
+
const imports = locales.map((locale, index) => `import locale${index} from './${locale}.json'`)
|
|
26
|
+
const content = [
|
|
27
|
+
'/* 此文件由 scripts/sync-locales.mjs 生成,请勿手工修改。 */',
|
|
28
|
+
...imports, '', 'export const LOCALE_MESSAGES = {',
|
|
29
|
+
...locales.map((locale, index) => ` '${locale}': locale${index},`),
|
|
30
|
+
'} as const satisfies Record<string, Record<string, string>>', '',
|
|
31
|
+
'export type GeneratedLocale = keyof typeof LOCALE_MESSAGES',
|
|
32
|
+
"export const DEFAULT_LOCALE: GeneratedLocale = 'zh-CN'",
|
|
33
|
+
'export const SUPPORTED_LOCALES = Object.keys(LOCALE_MESSAGES) as GeneratedLocale[]', '',
|
|
34
|
+
].join('\n')
|
|
35
|
+
const file = resolve(directory, 'generated.ts')
|
|
36
|
+
if (write) writeFileSync(file, content)
|
|
37
|
+
else if (!existsSync(file) || readFileSync(file, 'utf8') !== content) throw new Error(`语言注册产物过期:${file},请执行 pnpm i18n:sync`)
|
|
38
|
+
}
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
"dependsOn": ["^build"],
|
|
6
6
|
"outputs": ["dist/**"]
|
|
7
7
|
},
|
|
8
|
+
"@__FRONTEND_MODULE__/admin-app#build": {
|
|
9
|
+
"dependsOn": ["^build"],
|
|
10
|
+
"cache": __HOST_BUILD_CACHE__,
|
|
11
|
+
"outputs": []
|
|
12
|
+
},
|
|
8
13
|
"build:package": {
|
|
9
14
|
"dependsOn": ["^build:package"],
|
|
10
15
|
"outputs": ["dist/package/**"]
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# Frontend Makefile
|
|
2
|
+
#
|
|
3
|
+
# 常用流程:
|
|
4
|
+
# 安装全部依赖:make init
|
|
5
|
+
# 启动全部前端:make run
|
|
6
|
+
# 检查全部前端:make check
|
|
7
|
+
# 生成全部前端 RPC:make ts
|
|
8
|
+
# 构建全部宿主:make build
|
|
9
|
+
# 生成全部 npm 包:make package
|
|
10
|
+
|
|
11
|
+
.PHONY: help init reinstall pnpm-admin pnpm-uni-app pnpm-taro-app \
|
|
12
|
+
ts types-admin ts-admin ts-uni-app ts-taro-app \
|
|
13
|
+
run run-all run-h5 run-mp-weixin run-admin run-uni-app run-uni-app-h5 run-uni-app-mp-weixin run-taro-app run-taro-app-h5 run-taro-app-mp-weixin \
|
|
14
|
+
check check-admin check-uni-app check-taro-app \
|
|
15
|
+
build build-all build-h5 build-mp-weixin build-admin build-uni-app build-uni-app-h5 build-uni-app-mp-weixin build-taro-app build-taro-app-h5 build-taro-app-mp-weixin build-package \
|
|
16
|
+
package package-admin package-uni-app package-taro-app \
|
|
17
|
+
publish publish-only publish-admin publish-uni-app publish-taro-app
|
|
18
|
+
|
|
19
|
+
# ===== 执行参数 =====
|
|
20
|
+
|
|
21
|
+
PNPM ?= pnpm
|
|
22
|
+
CI ?= 1
|
|
23
|
+
GO_BIN_DIR ?= $(or $(shell go env GOBIN),$(shell go env GOPATH)/bin)
|
|
24
|
+
API_DIR ?= ../backend/api
|
|
25
|
+
BUSINESS_MODULES := __MODULES_SPACE__
|
|
26
|
+
PROTO_FILES := $(shell find "$(API_DIR)/proto" -type f -name '*.proto' 2>/dev/null)
|
|
27
|
+
ADMIN_PROTO_FILES := $(filter-out $(foreach module,$(BUSINESS_MODULES),$(API_DIR)/proto/$(module)/app/%),$(PROTO_FILES))
|
|
28
|
+
APP_PROTO_FILES := $(filter-out $(foreach module,$(BUSINESS_MODULES),$(API_DIR)/proto/$(module)/admin/%),$(PROTO_FILES))
|
|
29
|
+
|
|
30
|
+
TS_PROTO_BIN ?= $(or \
|
|
31
|
+
$(shell command -v protoc-gen-ts_proto 2>/dev/null), \
|
|
32
|
+
$(wildcard $(CURDIR)/admin/node_modules/.bin/protoc-gen-ts_proto), \
|
|
33
|
+
$(wildcard $(CURDIR)/uni-app/node_modules/.bin/protoc-gen-ts_proto), \
|
|
34
|
+
$(wildcard $(CURDIR)/taro-app/node_modules/.bin/protoc-gen-ts_proto), \
|
|
35
|
+
$(lastword $(wildcard $(HOME)/.nvm/versions/node/*/bin/protoc-gen-ts_proto)) \
|
|
36
|
+
)
|
|
37
|
+
TS_PROTO_BIN_DIR := $(dir $(TS_PROTO_BIN))
|
|
38
|
+
|
|
39
|
+
# ===== npm 发布参数 =====
|
|
40
|
+
|
|
41
|
+
NPM_REGISTRY ?= https://registry.npmjs.org/
|
|
42
|
+
NPM_ACCESS ?= public
|
|
43
|
+
NPM_TAG ?= latest
|
|
44
|
+
NPM_PACK_DEST ?= dist/npm
|
|
45
|
+
NPM_SKIP_GIT_CHECKS ?= true
|
|
46
|
+
|
|
47
|
+
NPM_PUBLISH_ENV := NPM_REGISTRY="$(NPM_REGISTRY)" NPM_ACCESS="$(NPM_ACCESS)" NPM_TAG="$(NPM_TAG)" NPM_SKIP_GIT_CHECKS="$(NPM_SKIP_GIT_CHECKS)"
|
|
48
|
+
|
|
49
|
+
# ===== 依赖安装 =====
|
|
50
|
+
|
|
51
|
+
# 按管理后台、uni-app、Taro 的顺序安装全部前端依赖
|
|
52
|
+
init:
|
|
53
|
+
@$(MAKE) pnpm-admin
|
|
54
|
+
@$(MAKE) pnpm-uni-app
|
|
55
|
+
@$(MAKE) pnpm-taro-app
|
|
56
|
+
|
|
57
|
+
# 删除全部前端 node_modules 并重新安装依赖
|
|
58
|
+
reinstall:
|
|
59
|
+
@bash scripts/reinstall_dependencies.sh
|
|
60
|
+
|
|
61
|
+
# 安装管理后台依赖
|
|
62
|
+
pnpm-admin:
|
|
63
|
+
@cd admin && CI="$(CI)" $(PNPM) install
|
|
64
|
+
|
|
65
|
+
# 安装 uni-app 应用端依赖
|
|
66
|
+
pnpm-uni-app:
|
|
67
|
+
@cd uni-app && CI="$(CI)" $(PNPM) install
|
|
68
|
+
|
|
69
|
+
# 安装 Taro React 应用端依赖
|
|
70
|
+
pnpm-taro-app:
|
|
71
|
+
@cd taro-app && CI="$(CI)" $(PNPM) install
|
|
72
|
+
|
|
73
|
+
# ===== TypeScript RPC 生成 =====
|
|
74
|
+
|
|
75
|
+
# 生成管理端、uni-app 和 Taro 的全部 TypeScript RPC 代码。
|
|
76
|
+
ts: ts-admin ts-uni-app ts-taro-app
|
|
77
|
+
@echo "==> 全部 TypeScript RPC 代码生成完成"
|
|
78
|
+
|
|
79
|
+
# 生成管理端 TypeScript RPC 代码。
|
|
80
|
+
ts-admin: types-admin
|
|
81
|
+
ifneq ($(strip $(ADMIN_PROTO_FILES)),)
|
|
82
|
+
@echo "==> 生成管理端 TypeScript RPC 代码"
|
|
83
|
+
@test -n "$(TS_PROTO_BIN)" || (echo "未找到 protoc-gen-ts_proto,请先执行 make init,或全局安装 ts-proto" && exit 1)
|
|
84
|
+
@cd "$(API_DIR)" && \
|
|
85
|
+
set -e; for module in $(BUSINESS_MODULES); do \
|
|
86
|
+
if find "proto/$$module" ! -path "proto/$$module/app/*" -name '*.proto' -type f | grep -q .; then \
|
|
87
|
+
PATH="$(TS_PROTO_BIN_DIR):$(GO_BIN_DIR):$$PATH" buf generate --template "buf.admin.$$module.typescript.gen.yaml" --path "proto/$$module" --exclude-path "proto/$$module/app"; \
|
|
88
|
+
fi; done
|
|
89
|
+
@echo "==> 管理端 TypeScript RPC 代码生成完成"
|
|
90
|
+
else
|
|
91
|
+
@echo "未发现业务 Proto,跳过 admin RPC 生成"
|
|
92
|
+
endif
|
|
93
|
+
|
|
94
|
+
# 生成管理端自动导入声明。
|
|
95
|
+
types-admin:
|
|
96
|
+
@echo "==> 生成管理端自动导入声明"
|
|
97
|
+
@cd admin && CI="$(CI)" $(PNPM) run generate:auto-import-types
|
|
98
|
+
@echo "==> 管理端自动导入声明生成完成"
|
|
99
|
+
|
|
100
|
+
# 生成 uni-app 端 TypeScript RPC 代码。
|
|
101
|
+
ts-uni-app:
|
|
102
|
+
ifneq ($(strip $(APP_PROTO_FILES)),)
|
|
103
|
+
@echo "==> 生成 uni-app TypeScript RPC 代码"
|
|
104
|
+
@test -n "$(TS_PROTO_BIN)" || (echo "未找到 protoc-gen-ts_proto,请先执行 make init,或全局安装 ts-proto" && exit 1)
|
|
105
|
+
@cd "$(API_DIR)" && \
|
|
106
|
+
set -e; for module in $(BUSINESS_MODULES); do \
|
|
107
|
+
if find "proto/$$module" ! -path "proto/$$module/admin/*" -name '*.proto' -type f | grep -q .; then \
|
|
108
|
+
PATH="$(TS_PROTO_BIN_DIR):$(GO_BIN_DIR):$$PATH" buf generate --template "buf.uni-app.$$module.typescript.gen.yaml" --path "proto/$$module" --exclude-path "proto/$$module/admin"; \
|
|
109
|
+
fi; done
|
|
110
|
+
@echo "==> uni-app TypeScript RPC 代码生成完成"
|
|
111
|
+
else
|
|
112
|
+
@echo "未发现业务 Proto,跳过 uni-app RPC 生成"
|
|
113
|
+
endif
|
|
114
|
+
|
|
115
|
+
# 生成 Taro React 端 TypeScript RPC 代码。
|
|
116
|
+
ts-taro-app:
|
|
117
|
+
ifneq ($(strip $(APP_PROTO_FILES)),)
|
|
118
|
+
@echo "==> 生成 Taro React TypeScript RPC 代码"
|
|
119
|
+
@test -n "$(TS_PROTO_BIN)" || (echo "未找到 protoc-gen-ts_proto,请先执行 make init,或全局安装 ts-proto" && exit 1)
|
|
120
|
+
@cd "$(API_DIR)" && \
|
|
121
|
+
set -e; for module in $(BUSINESS_MODULES); do \
|
|
122
|
+
if find "proto/$$module" ! -path "proto/$$module/admin/*" -name '*.proto' -type f | grep -q .; then \
|
|
123
|
+
PATH="$(TS_PROTO_BIN_DIR):$(GO_BIN_DIR):$$PATH" buf generate --template "buf.taro-app.$$module.typescript.gen.yaml" --path "proto/$$module" --exclude-path "proto/$$module/admin"; \
|
|
124
|
+
fi; done
|
|
125
|
+
@echo "==> Taro React TypeScript RPC 代码生成完成"
|
|
126
|
+
else
|
|
127
|
+
@echo "未发现业务 Proto,跳过 taro-app RPC 生成"
|
|
128
|
+
endif
|
|
129
|
+
|
|
130
|
+
# ===== 本地运行 =====
|
|
131
|
+
|
|
132
|
+
# 并行启动管理后台、uni-app 和 Taro 的 H5/微信小程序开发环境
|
|
133
|
+
run:
|
|
134
|
+
@$(MAKE) run-all
|
|
135
|
+
|
|
136
|
+
# 并行启动全部前端开发环境;按 Ctrl-C 会同时停止所有子进程
|
|
137
|
+
run-all:
|
|
138
|
+
@set -e; \
|
|
139
|
+
pids=""; \
|
|
140
|
+
cleanup() { trap - INT TERM EXIT; test -z "$$pids" || kill $$pids 2>/dev/null || true; test -z "$$pids" || wait $$pids 2>/dev/null || true; }; \
|
|
141
|
+
trap cleanup INT TERM EXIT; \
|
|
142
|
+
$(MAKE) run-admin & pids="$$pids $$!"; \
|
|
143
|
+
$(MAKE) run-uni-app-h5 & pids="$$pids $$!"; \
|
|
144
|
+
$(MAKE) run-uni-app-mp-weixin & pids="$$pids $$!"; \
|
|
145
|
+
$(MAKE) run-taro-app-h5 & pids="$$pids $$!"; \
|
|
146
|
+
$(MAKE) run-taro-app-mp-weixin & pids="$$pids $$!"; \
|
|
147
|
+
wait
|
|
148
|
+
|
|
149
|
+
# 并行启动全部 H5 开发环境
|
|
150
|
+
run-h5:
|
|
151
|
+
@$(MAKE) --jobs=3 run-admin run-uni-app-h5 run-taro-app-h5
|
|
152
|
+
|
|
153
|
+
# 并行启动全部微信小程序开发环境
|
|
154
|
+
run-mp-weixin:
|
|
155
|
+
@$(MAKE) --jobs=2 run-uni-app-mp-weixin run-taro-app-mp-weixin
|
|
156
|
+
|
|
157
|
+
# 启动管理后台开发服务
|
|
158
|
+
run-admin:
|
|
159
|
+
@cd admin && CI="$(CI)" $(PNPM) run dev
|
|
160
|
+
|
|
161
|
+
# 启动 uni-app 微信小程序开发服务
|
|
162
|
+
run-uni-app:
|
|
163
|
+
@$(MAKE) run-uni-app-mp-weixin
|
|
164
|
+
|
|
165
|
+
# 启动 uni-app H5 开发服务
|
|
166
|
+
run-uni-app-h5:
|
|
167
|
+
@cd uni-app && CI="$(CI)" $(PNPM) run dev:h5
|
|
168
|
+
|
|
169
|
+
# 启动 uni-app 微信小程序开发服务
|
|
170
|
+
run-uni-app-mp-weixin:
|
|
171
|
+
@cd uni-app && CI="$(CI)" $(PNPM) run dev:mp-weixin
|
|
172
|
+
|
|
173
|
+
# 启动 Taro 微信小程序开发服务
|
|
174
|
+
run-taro-app:
|
|
175
|
+
@$(MAKE) run-taro-app-mp-weixin
|
|
176
|
+
|
|
177
|
+
# 启动 Taro H5 开发服务
|
|
178
|
+
run-taro-app-h5:
|
|
179
|
+
@cd taro-app && CI="$(CI)" $(PNPM) run dev:h5
|
|
180
|
+
|
|
181
|
+
# 启动 Taro 微信小程序开发服务
|
|
182
|
+
run-taro-app-mp-weixin:
|
|
183
|
+
@cd taro-app && CI="$(CI)" $(PNPM) run dev:mp-weixin
|
|
184
|
+
|
|
185
|
+
# ===== 代码检查 =====
|
|
186
|
+
|
|
187
|
+
# 检查管理后台的静态规则、类型和测试
|
|
188
|
+
check-admin:
|
|
189
|
+
@cd admin && \
|
|
190
|
+
CI="$(CI)" $(PNPM) exec oxlint --vue-plugin --config ./internal/lint-config/oxlint.json --ignore-path .oxlintignore ./apps ./packages ./internal ./scripts && \
|
|
191
|
+
CI="$(CI)" $(PNPM) run type:check && \
|
|
192
|
+
CI="$(CI)" $(PNPM) run test
|
|
193
|
+
|
|
194
|
+
# 检查 uni-app 的静态规则、类型、测试和包边界
|
|
195
|
+
check-uni-app:
|
|
196
|
+
@cd uni-app && \
|
|
197
|
+
CI="$(CI)" $(PNPM) exec eslint . && \
|
|
198
|
+
CI="$(CI)" $(PNPM) run tsc && \
|
|
199
|
+
CI="$(CI)" $(PNPM) run test && \
|
|
200
|
+
CI="$(CI)" $(PNPM) run check:exports
|
|
201
|
+
|
|
202
|
+
# 检查 Taro 的静态规则、类型、测试和包边界
|
|
203
|
+
check-taro-app:
|
|
204
|
+
@cd taro-app && \
|
|
205
|
+
CI="$(CI)" $(PNPM) exec eslint . --ext .js,.mjs,.ts,.tsx && \
|
|
206
|
+
CI="$(CI)" $(PNPM) run tsc && \
|
|
207
|
+
CI="$(CI)" $(PNPM) run test && \
|
|
208
|
+
CI="$(CI)" $(PNPM) run check:exports
|
|
209
|
+
|
|
210
|
+
# 按管理后台、uni-app、Taro 的顺序检查全部前端
|
|
211
|
+
check:
|
|
212
|
+
@$(MAKE) check-admin
|
|
213
|
+
@$(MAKE) check-uni-app
|
|
214
|
+
@$(MAKE) check-taro-app
|
|
215
|
+
@echo "==> 全部前端检查完成"
|
|
216
|
+
|
|
217
|
+
# ===== 应用与模块构建 =====
|
|
218
|
+
|
|
219
|
+
# 构建全部前端 H5 和微信小程序产物
|
|
220
|
+
build:
|
|
221
|
+
@$(MAKE) build-all
|
|
222
|
+
|
|
223
|
+
# 构建管理后台静态资源
|
|
224
|
+
build-admin:
|
|
225
|
+
@cd admin && CI="$(CI)" $(PNPM) run build
|
|
226
|
+
|
|
227
|
+
# 构建 uni-app H5 静态资源
|
|
228
|
+
build-uni-app:
|
|
229
|
+
@$(MAKE) build-uni-app-h5
|
|
230
|
+
|
|
231
|
+
# 构建 uni-app H5 静态资源
|
|
232
|
+
build-uni-app-h5:
|
|
233
|
+
@cd uni-app && CI="$(CI)" $(PNPM) run build:h5
|
|
234
|
+
|
|
235
|
+
# 构建 uni-app 微信小程序产物
|
|
236
|
+
build-uni-app-mp-weixin:
|
|
237
|
+
@cd uni-app && CI="$(CI)" $(PNPM) run build:mp-weixin
|
|
238
|
+
|
|
239
|
+
# 构建 Taro H5 静态资源
|
|
240
|
+
build-taro-app:
|
|
241
|
+
@$(MAKE) build-taro-app-h5
|
|
242
|
+
|
|
243
|
+
# 构建 Taro H5 静态资源
|
|
244
|
+
build-taro-app-h5:
|
|
245
|
+
@cd taro-app && CI="$(CI)" $(PNPM) run build:h5
|
|
246
|
+
|
|
247
|
+
# 构建 Taro 微信小程序产物
|
|
248
|
+
build-taro-app-mp-weixin:
|
|
249
|
+
@cd taro-app && CI="$(CI)" $(PNPM) run build:mp-weixin
|
|
250
|
+
|
|
251
|
+
# 构建管理后台、uni-app 和 Taro 的全部 H5 产物
|
|
252
|
+
build-h5:
|
|
253
|
+
@$(MAKE) build-admin
|
|
254
|
+
@$(MAKE) build-uni-app-h5
|
|
255
|
+
@$(MAKE) build-taro-app-h5
|
|
256
|
+
|
|
257
|
+
# 构建 uni-app 和 Taro 的全部微信小程序产物
|
|
258
|
+
build-mp-weixin:
|
|
259
|
+
@$(MAKE) build-uni-app-mp-weixin
|
|
260
|
+
@$(MAKE) build-taro-app-mp-weixin
|
|
261
|
+
|
|
262
|
+
# 构建管理后台、uni-app 和 Taro 的 H5/微信小程序全部宿主
|
|
263
|
+
build-all:
|
|
264
|
+
@$(MAKE) build-h5
|
|
265
|
+
@$(MAKE) build-mp-weixin
|
|
266
|
+
@echo "==> 全部前端宿主构建完成"
|
|
267
|
+
|
|
268
|
+
# 构建可供业务后台组合使用的管理端模块包
|
|
269
|
+
build-package:
|
|
270
|
+
@cd admin && CI="$(CI)" $(PNPM) run build:package
|
|
271
|
+
|
|
272
|
+
# ===== npm 打包 =====
|
|
273
|
+
|
|
274
|
+
# 检查并生成管理后台 npm 包
|
|
275
|
+
package-admin:
|
|
276
|
+
@$(MAKE) check-admin
|
|
277
|
+
@$(MAKE) build-package
|
|
278
|
+
@for package_dir in $(addprefix admin/packages/modules/,$(BUSINESS_MODULES)); do \
|
|
279
|
+
(cd "$$package_dir" && CI="$(CI)" $(PNPM) pack --pack-destination "$(NPM_PACK_DEST)") || exit 1; \
|
|
280
|
+
done
|
|
281
|
+
|
|
282
|
+
# 检查并生成 uni-app npm 包
|
|
283
|
+
package-uni-app:
|
|
284
|
+
@$(MAKE) check-uni-app
|
|
285
|
+
@cd uni-app && CI="$(CI)" $(PNPM) run build:packages
|
|
286
|
+
|
|
287
|
+
# 检查并生成 Taro npm 包
|
|
288
|
+
package-taro-app:
|
|
289
|
+
@$(MAKE) check-taro-app
|
|
290
|
+
@cd taro-app && CI="$(CI)" $(PNPM) run build:packages
|
|
291
|
+
|
|
292
|
+
# 按管理后台、uni-app、Taro 的顺序检查并生成全部 npm 包
|
|
293
|
+
package:
|
|
294
|
+
@$(MAKE) package-admin
|
|
295
|
+
@$(MAKE) package-uni-app
|
|
296
|
+
@$(MAKE) package-taro-app
|
|
297
|
+
@echo "==> 全部前端 npm 包生成完成"
|
|
298
|
+
|
|
299
|
+
# ===== npm 发布 =====
|
|
300
|
+
|
|
301
|
+
# 检查、打包并发布管理后台 npm 包
|
|
302
|
+
publish-admin:
|
|
303
|
+
@$(MAKE) package-admin
|
|
304
|
+
@$(NPM_PUBLISH_ENV) bash scripts/publish_packages.sh $(addprefix admin/packages/modules/,$(BUSINESS_MODULES))
|
|
305
|
+
|
|
306
|
+
# 检查、打包并发布 uni-app npm 包
|
|
307
|
+
publish-uni-app:
|
|
308
|
+
@$(MAKE) package-uni-app
|
|
309
|
+
@$(NPM_PUBLISH_ENV) bash scripts/publish_packages.sh $(addprefix uni-app/packages/modules/,$(BUSINESS_MODULES))
|
|
310
|
+
|
|
311
|
+
# 检查、打包并发布 Taro npm 包
|
|
312
|
+
publish-taro-app:
|
|
313
|
+
@$(MAKE) package-taro-app
|
|
314
|
+
@$(NPM_PUBLISH_ENV) bash scripts/publish_packages.sh $(addprefix taro-app/packages/modules/,$(BUSINESS_MODULES))
|
|
315
|
+
|
|
316
|
+
# 发布已经检查并构建的全部 npm 包
|
|
317
|
+
publish-only:
|
|
318
|
+
@$(NPM_PUBLISH_ENV) bash scripts/publish_packages.sh $(addprefix admin/packages/modules/,$(BUSINESS_MODULES)) $(addprefix uni-app/packages/modules/,$(BUSINESS_MODULES)) $(addprefix taro-app/packages/modules/,$(BUSINESS_MODULES))
|
|
319
|
+
|
|
320
|
+
# 按打包、发布的顺序发布全部 npm 包
|
|
321
|
+
publish:
|
|
322
|
+
@$(MAKE) package
|
|
323
|
+
@$(MAKE) publish-only
|
|
324
|
+
|
|
325
|
+
# ===== 帮助 =====
|
|
326
|
+
|
|
327
|
+
# 查看常用流程、全部目标及可覆盖参数
|
|
328
|
+
help:
|
|
329
|
+
@echo ""
|
|
330
|
+
@echo "常用流程:"
|
|
331
|
+
@echo " make init 安装三个 workspace 的依赖"
|
|
332
|
+
@echo " make run 启动全部前端开发环境(H5 + 微信小程序)"
|
|
333
|
+
@echo " make check 检查三个 workspace"
|
|
334
|
+
@echo " make ts 生成三个前端的 TypeScript RPC"
|
|
335
|
+
@echo " make build 构建管理端、uni-app、Taro 的 H5 + 微信小程序"
|
|
336
|
+
@echo " make package 检查并生成 业务 npm 包"
|
|
337
|
+
@echo " make publish 打包并发布 业务 npm 包"
|
|
338
|
+
@echo ""
|
|
339
|
+
@echo "可用目标:"
|
|
340
|
+
@awk '/^[a-zA-Z\-_0-9]+:/ { \
|
|
341
|
+
helpMessage = match(lastLine, /^# (.*)/); \
|
|
342
|
+
if (helpMessage) { \
|
|
343
|
+
helpCommand = substr($$1, 0, index($$1, ":")-1); \
|
|
344
|
+
helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \
|
|
345
|
+
printf "\033[36m %-20s\033[0m %s\n", helpCommand, helpMessage; \
|
|
346
|
+
} \
|
|
347
|
+
} \
|
|
348
|
+
{ lastLine = $$0 }' $(MAKEFILE_LIST)
|
|
349
|
+
@echo ""
|
|
350
|
+
@echo "常用参数(命令行使用 参数=值 覆盖):"
|
|
351
|
+
@printf " %-24s %s\n" "PNPM" "pnpm 命令,当前: $(PNPM)"
|
|
352
|
+
@printf " %-24s %s\n" "CI" "是否使用 CI 模式,当前: $(CI)"
|
|
353
|
+
@printf " %-24s %s\n" "API_DIR" "Backend Proto 目录,当前: $(API_DIR)"
|
|
354
|
+
@printf " %-24s %s\n" "NPM_REGISTRY" "npm registry,当前: $(NPM_REGISTRY)"
|
|
355
|
+
@printf " %-24s %s\n" "NPM_ACCESS" "npm 访问级别,当前: $(NPM_ACCESS)"
|
|
356
|
+
@printf " %-24s %s\n" "NPM_TAG" "npm dist-tag,当前: $(NPM_TAG)"
|
|
357
|
+
@printf " %-24s %s\n" "NPM_PACK_DEST" "pnpm pack 输出目录,当前: $(NPM_PACK_DEST)"
|
|
358
|
+
@printf " %-24s %s\n" "NPM_SKIP_GIT_CHECKS" "发布时跳过 pnpm Git 检查,当前: $(NPM_SKIP_GIT_CHECKS)"
|
|
359
|
+
@echo ""
|
|
360
|
+
@echo "示例:"
|
|
361
|
+
@echo " make run-admin"
|
|
362
|
+
@echo " make run-h5"
|
|
363
|
+
@echo " make run-mp-weixin"
|
|
364
|
+
@echo " make run-uni-app"
|
|
365
|
+
@echo " make run-uni-app-h5"
|
|
366
|
+
@echo " make check-taro-app"
|
|
367
|
+
@echo " make publish NPM_REGISTRY=https://registry.npmjs.org/ NPM_TAG=next"
|
|
368
|
+
|
|
369
|
+
.DEFAULT_GOAL := help
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
FRONTEND_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
7
|
+
|
|
8
|
+
registry="${NPM_REGISTRY:-https://registry.npmjs.org/}"
|
|
9
|
+
access="${NPM_ACCESS:-public}"
|
|
10
|
+
tag="${NPM_TAG:-latest}"
|
|
11
|
+
skip_git_checks="${NPM_SKIP_GIT_CHECKS:-true}"
|
|
12
|
+
expected_version="${NPM_EXPECTED_VERSION:-}"
|
|
13
|
+
|
|
14
|
+
publish_args=(--registry "${registry}" --access "${access}" --tag "${tag}")
|
|
15
|
+
if [[ "${skip_git_checks}" == "true" ]]; then
|
|
16
|
+
publish_args+=(--no-git-checks)
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
if [[ "$#" -eq 0 ]]; then
|
|
20
|
+
set -- [[[range .Modules]]]admin/packages/modules/[[[.]]] uni-app/packages/modules/[[[.]]] taro-app/packages/modules/[[[.]]] [[[end]]]
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
for package_dir in "$@"; do
|
|
24
|
+
package_path="${FRONTEND_DIR}/${package_dir}/package.json"
|
|
25
|
+
if [[ ! -f "${package_path}" ]]; then
|
|
26
|
+
echo "npm 包配置不存在: ${package_path}" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
metadata="$({ node -e '
|
|
31
|
+
const fs = require("fs")
|
|
32
|
+
const pkg = JSON.parse(fs.readFileSync(process.argv[1], "utf8"))
|
|
33
|
+
if (typeof pkg.name !== "string" || typeof pkg.version !== "string") {
|
|
34
|
+
throw new Error(`invalid package metadata: ${process.argv[1]}`)
|
|
35
|
+
}
|
|
36
|
+
process.stdout.write(`${pkg.name}\t${pkg.version}`)
|
|
37
|
+
' "${package_path}"; })"
|
|
38
|
+
IFS=$'\t' read -r package_name package_version <<<"${metadata}"
|
|
39
|
+
|
|
40
|
+
if [[ -n "${expected_version}" && "${package_version}" != "${expected_version}" ]]; then
|
|
41
|
+
echo "npm 包版本与发布 tag 不一致: ${package_name}@${package_version},期望 ${expected_version}" >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
if npm view "${package_name}@${package_version}" version --registry "${registry}" --json >/dev/null 2>&1; then
|
|
46
|
+
echo "==> 已发布,跳过 ${package_name}@${package_version}"
|
|
47
|
+
continue
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
echo "==> 正在发布 ${package_name}@${package_version}"
|
|
51
|
+
(
|
|
52
|
+
cd "${FRONTEND_DIR}/${package_dir}"
|
|
53
|
+
pnpm publish "${publish_args[@]}"
|
|
54
|
+
)
|
|
55
|
+
echo "==> 发布完成 ${package_name}@${package_version}"
|
|
56
|
+
done
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
FRONTEND_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
7
|
+
|
|
8
|
+
echo "==> 清理前端全部 node_modules"
|
|
9
|
+
find "${FRONTEND_DIR}" -type d -name node_modules -prune -print -exec rm -rf -- {} +
|
|
10
|
+
|
|
11
|
+
while IFS= read -r workspace_file; do
|
|
12
|
+
workspace_dir="${workspace_file%/pnpm-workspace.yaml}"
|
|
13
|
+
|
|
14
|
+
# 跳过 CLI 模板等嵌套 workspace,避免把占位项目当作当前项目安装。
|
|
15
|
+
nested_workspace=false
|
|
16
|
+
parent_dir="${workspace_dir}"
|
|
17
|
+
while [[ "${parent_dir}" != "${FRONTEND_DIR}" ]]; do
|
|
18
|
+
parent_dir="${parent_dir%/*}"
|
|
19
|
+
if [[ -f "${parent_dir}/pnpm-workspace.yaml" ]]; then
|
|
20
|
+
nested_workspace=true
|
|
21
|
+
break
|
|
22
|
+
fi
|
|
23
|
+
done
|
|
24
|
+
|
|
25
|
+
if [[ "${nested_workspace}" == true ]]; then
|
|
26
|
+
echo "==> 跳过嵌套 workspace:${workspace_dir#"${FRONTEND_DIR}/"}"
|
|
27
|
+
continue
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
echo "==> 安装 ${workspace_dir#"${FRONTEND_DIR}/"} 依赖"
|
|
31
|
+
(
|
|
32
|
+
cd "${workspace_dir}"
|
|
33
|
+
CI=1 pnpm install --frozen-lockfile
|
|
34
|
+
)
|
|
35
|
+
done < <(find "${FRONTEND_DIR}" -type f -name pnpm-workspace.yaml -print)
|
|
36
|
+
|
|
37
|
+
echo "==> 前端依赖重装完成"
|