@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.
Files changed (30) hide show
  1. package/README.md +12 -1
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +39 -10
  4. package/package.json +1 -1
  5. package/templates/business-workspace/.oxlintignore +14 -0
  6. package/templates/business-workspace/.prettierignore +9 -0
  7. package/templates/business-workspace/.prettierrc.cjs +41 -0
  8. package/templates/business-workspace/.stylelintignore +4 -0
  9. package/templates/business-workspace/.stylelintrc.cjs +39 -0
  10. package/templates/business-workspace/apps/admin/src/main.ts +11 -0
  11. package/templates/business-workspace/apps/admin/src/module-manifest.ts +8 -2
  12. package/templates/business-workspace/apps/admin/{vite.config.ts → vite.config.ts.tmpl} +1 -1
  13. package/templates/business-workspace/internal/lint-config/oxlint.json +38 -0
  14. package/templates/business-workspace/lint-staged.config.cjs +8 -0
  15. package/templates/business-workspace/package.json +62 -3
  16. package/templates/business-workspace/packages/modules/__MODULE_NAME__/package.json +1 -0
  17. package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/api/.gitkeep +0 -0
  18. package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/locales/en-US.json +1 -0
  19. package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/locales/ja-JP.json +1 -0
  20. package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/locales/zh-CN.json +1 -0
  21. package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/locales/zh-TW.json +1 -0
  22. package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/{module.ts → module.ts.tmpl} +4 -2
  23. package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/rpc/.gitkeep +0 -0
  24. package/templates/business-workspace/packages/modules/__MODULE_NAME__/test/.gitkeep +0 -0
  25. package/templates/business-workspace/scripts/generate-auto-import-types.mjs +40 -0
  26. package/templates/business-workspace/scripts/sync-locales.mjs +38 -0
  27. package/templates/business-workspace/turbo.json +5 -0
  28. package/templates/project-frontend/Makefile +369 -0
  29. package/templates/project-frontend/scripts/publish_packages.sh +56 -0
  30. 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 始终先引入 `@liujitcn/kratos-admin-system`。`--module` 可重复使用,也接受逗号分隔名称,并为每个名称创建独立 module 包;`--with` 接收逗号分隔的额外已发布 module 名称,只装配依赖而不生成源码。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
- packageName: "@liujitcn/kratos-admin-system",
28
- moduleIdentifier: "systemAdminModule",
29
- optimizeDependencies: officialModuleOptimizeDependencies.system
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({ projectName, moduleNames, additionalModules: withModules });
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 === "system" || name === "kratos-admin");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liujitcn/kratos-admin-cli",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "type": "module",
5
5
  "description": "Create pnpm workspace projects for kratos-admin business modules",
6
6
  "license": "MIT",
@@ -0,0 +1,14 @@
1
+ *.sh
2
+ node_modules
3
+ *.md
4
+ *.woff
5
+ *.ttf
6
+ .vscode
7
+ .idea
8
+ dist
9
+ /public
10
+ /docs
11
+ .local
12
+ /bin
13
+ /src/mock/*
14
+ stats.html
@@ -0,0 +1,9 @@
1
+ /dist/*
2
+ .local
3
+ /node_modules/**
4
+
5
+ **/*.svg
6
+ **/*.sh
7
+
8
+ /public/*
9
+ stats.html
@@ -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,4 @@
1
+ /dist/*
2
+ /public/*
3
+ public/*
4
+ stats.html
@@ -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 = adminModuleManifest.map(item => item.packageName);
23
+ export const adminModulePackages = adminBuildModules.map(item => item.packageName);
18
24
 
19
25
  /** 当前宿主需要预构建的业务模块依赖。 */
20
- export const adminModuleOptimizeDependencies = adminModuleManifest.flatMap(item => {
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: adminModuleOptimizeDependencies
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
  }
@@ -10,6 +10,7 @@
10
10
  "type:check": "vue-tsc --noEmit --skipLibCheck",
11
11
  "prepack": "pnpm build:package"
12
12
  },
13
+ "dependencies": __MODULE_DEPENDENCIES__,
13
14
  "devDependencies": {
14
15
  "@liujitcn/kratos-admin-core": "__CORE_VERSION__"
15
16
  },
@@ -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
- name: "__MODULE_NAME__",
9
- views: viewModules
11
+ __MODULE_FIELDS__
10
12
  });
@@ -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 "==> 前端依赖重装完成"