@liujitcn/kratos-admin-cli 0.0.62 → 0.0.65

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/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { execFileSync } from "node:child_process";
3
3
  import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
4
4
  import { basename, dirname, join, resolve } from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import { cliMessage, resolveLocale } from "./messages.js";
6
7
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
7
8
  const templateRoot = resolve(packageRoot, "templates/business-workspace");
8
9
  const gitignoreTemplateName = "_gitignore";
@@ -16,11 +17,12 @@ export async function createBusinessWorkspace(options) {
16
17
  const target = resolve(cwd, options.projectName);
17
18
  const projectName = basename(target);
18
19
  const moduleNames = normalizeModuleNames(options.moduleNames);
20
+ const locale = resolveLocale();
19
21
  const primaryModuleName = moduleNames[0];
20
22
  const additionalModules = normalizeAdditionalModules(options.additionalModules ?? [], moduleNames);
21
- validateName(projectName, "项目名称");
23
+ validateName(projectName, "project_name_label");
22
24
  if (await pathExists(target))
23
- throw new Error(`目标目录已存在,拒绝覆盖: ${target}`);
25
+ throw new Error(cliMessage("target_exists", { target }));
24
26
  const packageVersion = await readCliPackageVersion();
25
27
  const primaryModuleTokens = createModuleTokens(primaryModuleName, packageVersion);
26
28
  const moduleManifestEntries = [
@@ -87,25 +89,25 @@ export async function createBusinessWorkspace(options) {
87
89
  __APP_DEPENDENCIES__: formatJsonValue(appDependencies, " "),
88
90
  __MODULE_FILTERS__: modulePackages.map(packageName => `--filter=${packageName}`).join(" "),
89
91
  __MODULE_MANIFEST__: moduleManifest,
90
- __MODULE_NAMES__: moduleNames.join("、"),
91
- __MODULE_PACKAGES__: modulePackages.map(packageName => `\`${packageName}\``).join("、"),
92
+ __MODULE_NAMES__: moduleNames.join(cliMessage("list_separator")),
93
+ __MODULE_PACKAGES__: modulePackages.map(packageName => `\`${packageName}\``).join(cliMessage("list_separator")),
92
94
  __MODULE_PATHS__: formatJsonValue(modulePaths, " "),
93
95
  __MODULE_TREE__: createModuleTree(moduleNames),
94
96
  __MODULE_TABLE_ROWS__: createModuleTableRows(moduleNames)
95
97
  };
96
98
  await mkdir(target, { recursive: false });
97
99
  try {
98
- await renderDirectory(templateRoot, target, tokens);
100
+ await renderDirectory(templateRoot, target, tokens, locale);
99
101
  const moduleTemplateRoot = resolve(templateRoot, "packages/modules/__MODULE_NAME__");
100
102
  for (const moduleName of moduleNames.slice(1)) {
101
103
  await renderDirectory(moduleTemplateRoot, resolve(target, "packages/modules", moduleName), {
102
104
  ...createModuleTokens(moduleName, packageVersion),
103
105
  __PROJECT_NAME__: projectName
104
- });
106
+ }, locale);
105
107
  }
106
108
  execFileSync(process.execPath, [resolve(target, "scripts/sync-locales.mjs"), "--write"], { stdio: "inherit" });
107
109
  if (options.kratosProject) {
108
- await renderDirectory(resolve(packageRoot, "templates/project-frontend"), dirname(target), tokens);
110
+ await renderDirectory(resolve(packageRoot, "templates/project-frontend"), dirname(target), tokens, locale);
109
111
  }
110
112
  }
111
113
  catch (error) {
@@ -121,12 +123,12 @@ export async function runCli(args = process.argv.slice(2)) {
121
123
  return;
122
124
  }
123
125
  if (args[0] !== "create")
124
- throw new Error(`不支持的命令: ${args[0]}`);
126
+ throw new Error(cliMessage("unsupported_command", { command: args[0] }));
125
127
  const projectName = args[1];
126
128
  const moduleNames = [...readOptions(args, "--module"), ...readOptions(args, "--modules")];
127
129
  const withModules = readOptions(args, "--with");
128
130
  if (!projectName || moduleNames.length === 0) {
129
- throw new Error("用法: kratos-admin create <project> --module <module[,module...]>");
131
+ throw new Error(cliMessage("usage"));
130
132
  }
131
133
  const target = await createBusinessWorkspace({
132
134
  projectName,
@@ -134,19 +136,26 @@ export async function runCli(args = process.argv.slice(2)) {
134
136
  additionalModules: withModules,
135
137
  kratosProject: args.includes("--kratos-project")
136
138
  });
137
- process.stdout.write(`已创建业务 workspace: ${target}\n`);
139
+ process.stdout.write(`${cliMessage("workspace_created", { target })}\n`);
138
140
  }
139
141
  /** 渲染模板目录中的路径和文本占位符。 */
140
- async function renderDirectory(source, target, tokens) {
142
+ async function renderDirectory(source, target, tokens, locale) {
141
143
  await mkdir(target, { recursive: true });
142
144
  const entries = await readdir(source, { withFileTypes: true });
145
+ const localizedReadme = `README.${locale}.md`;
146
+ const hasLocalizedReadme = entries.some(entry => entry.name === localizedReadme);
143
147
  for (const entry of entries) {
144
- const renderedName = entry.name === gitignoreTemplateName ? ".gitignore" : replaceTokens(entry.name, tokens).replace(/\.tmpl$/, "");
148
+ if (entry.name === "README.md" && hasLocalizedReadme)
149
+ continue;
150
+ if (/^README\.(zh-CN|en-US)\.md$/.test(entry.name) && entry.name !== localizedReadme)
151
+ continue;
152
+ const sourceName = entry.name === localizedReadme ? "README.md" : entry.name;
153
+ const renderedName = entry.name === gitignoreTemplateName ? ".gitignore" : replaceTokens(sourceName, tokens).replace(/\.tmpl$/, "");
145
154
  const sourcePath = join(source, entry.name);
146
155
  const targetPath = join(target, renderedName);
147
156
  if (entry.isDirectory()) {
148
157
  await mkdir(targetPath, { recursive: true });
149
- await renderDirectory(sourcePath, targetPath, tokens);
158
+ await renderDirectory(sourcePath, targetPath, tokens, locale);
150
159
  continue;
151
160
  }
152
161
  const content = await readFile(sourcePath, "utf8");
@@ -157,7 +166,7 @@ async function renderDirectory(source, target, tokens) {
157
166
  async function readCliPackageVersion() {
158
167
  const packageJson = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
159
168
  if (typeof packageJson.version !== "string" || !packageJson.version)
160
- throw new Error("CLI package.json 缺少有效版本");
169
+ throw new Error(cliMessage("package_version_missing"));
161
170
  return packageJson.version;
162
171
  }
163
172
  /** 读取可重复且支持逗号分隔的命令行选项值。 */
@@ -167,7 +176,7 @@ function readOptions(args, option) {
167
176
  return [];
168
177
  const value = args[index + 1];
169
178
  if (!value || value.startsWith("--"))
170
- throw new Error(`选项 ${option} 缺少值`);
179
+ throw new Error(cliMessage("option_missing_value", { option }));
171
180
  return value
172
181
  .split(",")
173
182
  .map(item => item.trim())
@@ -175,26 +184,27 @@ function readOptions(args, option) {
175
184
  });
176
185
  }
177
186
  /** 校验项目与模块名称。 */
178
- function validateName(value, label) {
187
+ function validateName(value, labelKey) {
188
+ const label = cliMessage(labelKey);
179
189
  if (!kebabNamePattern.test(value))
180
- throw new Error(`${label}必须使用 kebab-case: ${value}`);
190
+ throw new Error(cliMessage("kebab_required", { label, value }));
181
191
  }
182
192
  /** 规范化自有模块列表并校验保留名称。 */
183
193
  function normalizeModuleNames(moduleNames) {
184
194
  const normalized = [...new Set(moduleNames.map(name => name.trim()).filter(Boolean))];
185
195
  const primaryModuleName = normalized[0];
186
196
  if (!primaryModuleName)
187
- throw new Error("至少需要一个业务模块名称");
188
- normalized.forEach(name => validateName(name, "模块名称"));
197
+ throw new Error(cliMessage("module_required"));
198
+ normalized.forEach(name => validateName(name, "module_name_label"));
189
199
  const reservedName = normalized.find(name => name === "kratos-admin");
190
200
  if (reservedName)
191
- throw new Error(`自有模块名称不能使用保留名称: ${reservedName}`);
201
+ throw new Error(cliMessage("reserved_module", { name: reservedName }));
192
202
  return [primaryModuleName, ...normalized.slice(1)];
193
203
  }
194
204
  /** 规范化额外模块列表并去重。 */
195
205
  function normalizeAdditionalModules(moduleNames, currentModules) {
196
206
  const normalized = [...new Set(moduleNames.map(name => name.trim()).filter(Boolean))];
197
- normalized.forEach(name => validateName(name, "额外模块名称"));
207
+ normalized.forEach(name => validateName(name, "additional_module_name_label"));
198
208
  return normalized.filter(name => name !== "system" && !currentModules.includes(name));
199
209
  }
200
210
  /** 创建单个自有模块模板使用的占位符。 */
@@ -229,9 +239,7 @@ function createModuleTree(moduleNames) {
229
239
  }
230
240
  /** 创建 README 中的业务模块目录说明。 */
231
241
  function createModuleTableRows(moduleNames) {
232
- return moduleNames
233
- .map(name => `| \`packages/modules/${name}/\` | 可独立发布的 \`@${name}/admin-module\` 业务 module。 |`)
234
- .join("\n");
242
+ return moduleNames.map(name => cliMessage("module_table_row", { name })).join("\n");
235
243
  }
236
244
  /** 将 kebab-case 转换为 camelCase。 */
237
245
  function toCamelCase(value) {
@@ -261,7 +269,7 @@ function printHelp() {
261
269
  process.stdout.write([
262
270
  "kratos-admin create <project> --module <module[,module...]> [--module <module>] [--with other] [--kratos-project]",
263
271
  "",
264
- "示例:",
272
+ cliMessage("examples_heading"),
265
273
  " kratos-admin create business-admin --module business",
266
274
  " kratos-admin create business-admin --module business,report",
267
275
  " kratos-admin create business-admin --module business --module report",
@@ -0,0 +1,46 @@
1
+ const messages = {
2
+ "zh-CN": {
3
+ project_name_label: "项目名称",
4
+ module_name_label: "模块名称",
5
+ additional_module_name_label: "额外模块名称",
6
+ examples_heading: "示例:",
7
+ target_exists: "目标目录已存在,拒绝覆盖: {target}",
8
+ unsupported_command: "不支持的命令: {command}",
9
+ usage: "用法: kratos-admin create <project> --module <module[,module...]>",
10
+ package_version_missing: "CLI package.json 缺少有效版本",
11
+ option_missing_value: "选项 {option} 缺少值",
12
+ kebab_required: "{label}必须使用 kebab-case: {value}",
13
+ module_required: "至少需要一个业务模块名称",
14
+ reserved_module: "自有模块名称不能使用保留名称: {name}",
15
+ workspace_created: "已创建业务 workspace: {target}",
16
+ list_separator: "、",
17
+ module_table_row: "| `packages/modules/{name}/` | 可独立发布为 `@{name}/admin-module` 的业务模块。 |"
18
+ },
19
+ "en-US": {
20
+ project_name_label: "Project name",
21
+ module_name_label: "Module name",
22
+ additional_module_name_label: "Additional module name",
23
+ examples_heading: "Examples:",
24
+ target_exists: "The target directory already exists; refusing to overwrite: {target}",
25
+ unsupported_command: "Unsupported command: {command}",
26
+ usage: "Usage: kratos-admin create <project> --module <module[,module...]>",
27
+ package_version_missing: "CLI package.json is missing a valid version",
28
+ option_missing_value: "Option {option} is missing a value",
29
+ kebab_required: "{label} must use kebab-case: {value}",
30
+ module_required: "At least one business module is required",
31
+ reserved_module: "A custom module cannot use the reserved name: {name}",
32
+ workspace_created: "Business workspace created: {target}",
33
+ list_separator: ", ",
34
+ module_table_row: "| `packages/modules/{name}/` | An independently publishable `@{name}/admin-module` business module. |"
35
+ }
36
+ };
37
+ /** 根据环境变量选择 CLI 文案语言。 */
38
+ export function resolveLocale() {
39
+ const value = process.env.KRATOS_ADMIN_LOCALE ?? process.env.LC_ALL ?? process.env.LANG ?? "";
40
+ return value.toLowerCase().startsWith("zh") ? "zh-CN" : "en-US";
41
+ }
42
+ /** 渲染 CLI 稳定消息并替换命名参数。 */
43
+ export function cliMessage(key, params = {}) {
44
+ const template = messages[resolveLocale()][key] ?? messages["en-US"][key] ?? key;
45
+ return template.replace(/\{([A-Za-z0-9_]+)\}/g, (_, name) => params[name] ?? `{${name}}`);
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liujitcn/kratos-admin-cli",
3
- "version": "0.0.62",
3
+ "version": "0.0.65",
4
4
  "type": "module",
5
5
  "description": "Create pnpm workspace projects for kratos-admin business modules",
6
6
  "license": "MIT",
@@ -18,6 +18,7 @@
18
18
  "files": [
19
19
  "dist/index.d.ts",
20
20
  "dist/index.js",
21
+ "dist/messages.js",
21
22
  "templates",
22
23
  "README.md"
23
24
  ],
@@ -0,0 +1,64 @@
1
+ <!-- prettier-ignore -->
2
+ # __PROJECT_NAME__
3
+
4
+ An independent business admin workspace built on kratos-admin. It includes a thin host, the System module, and custom modules (__MODULE_NAMES__). Dependencies flow from `apps/admin` to business modules and then to `@liujitcn/kratos-admin-core`.
5
+
6
+ ## Files and directories
7
+
8
+ ```text
9
+ __PROJECT_NAME__
10
+ ├── apps/admin
11
+ │ ├── src
12
+ │ ├── package.json
13
+ │ └── README.md
14
+ __MODULE_TREE__
15
+ ├── scripts
16
+ │ └── build-package.mjs
17
+ ├── .gitignore
18
+ ├── package.json
19
+ ├── pnpm-workspace.yaml
20
+ ├── README.md
21
+ ├── tsconfig.json
22
+ └── turbo.json
23
+ ```
24
+
25
+ | Path | Purpose |
26
+ | --- | --- |
27
+ | `apps/admin/` | Runnable thin host that starts the app and selects enabled business modules. |
28
+ __MODULE_TABLE_ROWS__
29
+ | `scripts/build-package.mjs` | Produces publishable module source and TypeScript declarations. |
30
+ | `.gitignore` | Excludes dependencies, caches, and build artifacts. |
31
+ | `package.json` | Defines workspace commands, tooling, and runtime versions. |
32
+ | `pnpm-workspace.yaml` | Defines the host and business module workspace packages. |
33
+ | `README.md` | Documents this workspace's files and development workflow. |
34
+ | `tsconfig.json` | TypeScript configuration and source mappings for custom modules. |
35
+ | `turbo.json` | Defines development, build, package-build, and type-check tasks. |
36
+
37
+ Each directory containing a `package.json` has its own README with details about that package.
38
+
39
+ ## Development
40
+
41
+ ```bash
42
+ pnpm install
43
+ pnpm dev
44
+ pnpm type:check
45
+ pnpm build
46
+ pnpm build:package
47
+ pnpm package
48
+ ```
49
+
50
+ | Command | Purpose |
51
+ | --- | --- |
52
+ | `pnpm dev` | Starts the `apps/admin` development server. |
53
+ | `pnpm type:check` | Checks types in the host and all business modules. |
54
+ | `pnpm build` | Builds the host application. |
55
+ | `pnpm build:package` | Creates npm publish directories for custom modules. |
56
+ | `pnpm package` | Builds custom modules and creates npm packages. |
57
+
58
+ Keep business APIs, RPCs, pages, and components in `packages/modules/<module>/src`. Backend menu component paths for business pages must use the module prefix, such as `shop/index/index`.
59
+
60
+ The host module manifest is `apps/admin/src/module-manifest.ts`. It loads `@liujitcn/kratos-admin-system` first, followed by custom modules in the order given at creation. `apps/admin/src/modules.ts` loads and exports the enabled modules. When adding another module, update the host dependencies and manifest.
61
+
62
+ Module registration order only controls explicit `staticViews` replacements: a later module replaces the same static view key. Ordinary business pages are isolated by `<module>/<view>` and cannot replace another module's page by reusing its name.
63
+
64
+ Use Vue Router for navigation between modules. Cross-module code may only use public npm subpaths; the host must not import module `src` directories.
@@ -1,7 +1,7 @@
1
1
  <!-- prettier-ignore -->
2
2
  # __PROJECT_NAME__
3
3
 
4
- 基于 kratos-admin 的独立业务管理端 workspace。默认包含一个薄宿主、System module 和自有 module(**MODULE_NAMES**);依赖方向为 `apps/admin -> business module -> @liujitcn/kratos-admin-core`。
4
+ 基于 kratos-admin 的独立业务管理端 workspace。默认包含一个薄宿主、System module 和自有 module(__MODULE_NAMES__);依赖方向为 `apps/admin -> business module -> @liujitcn/kratos-admin-core`。
5
5
 
6
6
  ## 目录与文件
7
7
 
@@ -26,7 +26,7 @@ __MODULE_TREE__
26
26
  | ------------- | --------------------------------------------------- |
27
27
  | `apps/admin/` | 可运行的薄宿主,只负责启动和选择启用的业务 module。 |
28
28
 
29
- **MODULE_TABLE_ROWS**
29
+ __MODULE_TABLE_ROWS__
30
30
  | `scripts/build-package.mjs` | 生成自有 module 的发布源码和 TypeScript 声明。 |
31
31
  | `.gitignore` | 忽略依赖、缓存和构建产物。 |
32
32
  | `package.json` | 声明 workspace 公共命令、工具依赖和运行时版本。 |
@@ -0,0 +1,58 @@
1
+ <!-- prettier-ignore -->
2
+ # __APP_PACKAGE__
3
+
4
+ The admin host for `__PROJECT_NAME__`. This private package contains no business logic. It combines `@liujitcn/kratos-admin-system`, custom modules (__MODULE_PACKAGES__), and any selected additional modules.
5
+
6
+ ## Files and directories
7
+
8
+ ```text
9
+ apps/admin
10
+ ├── src
11
+ │ ├── main.ts
12
+ │ ├── module-manifest.ts
13
+ │ ├── modules.ts
14
+ │ └── vite-env.d.ts
15
+ ├── .env
16
+ ├── .env.development
17
+ ├── .env.production
18
+ ├── favicon.svg
19
+ ├── index.html
20
+ ├── package.json
21
+ ├── README.md
22
+ ├── tsconfig.json
23
+ └── vite.config.ts
24
+ ```
25
+
26
+ | Path | Purpose |
27
+ | --- | --- |
28
+ | `src/main.ts` | Passes all default-exported modules to core to start the Vue app. |
29
+ | `src/module-manifest.ts` | Declares module loaders, package names, and optional pre-bundled dependencies. |
30
+ | `src/modules.ts` | Loads and default-exports the modules enabled for this host. |
31
+ | `src/vite-env.d.ts` | Includes Vite client and core global types. |
32
+ | `.env` | Shared app title, port, and other environment variables. |
33
+ | `.env.development` | Development API URL and proxy configuration. |
34
+ | `.env.production` | Production API URL and build configuration. |
35
+ | `favicon.svg` | Browser tab icon for the admin app. |
36
+ | `index.html` | Vite HTML entry point and app mount node. |
37
+ | `package.json` | Declares host commands and core, current module, and extra module dependencies. |
38
+ | `README.md` | Documents this host's files and development workflow. |
39
+ | `tsconfig.json` | TypeScript configuration for the host. |
40
+ | `vite.config.ts` | Combines core configuration with build settings derived from the module manifest. |
41
+
42
+ ## Module composition
43
+
44
+ `src/module-manifest.ts` is the only source of host module configuration. Each manifest entry declares its npm package, runtime loader, and optional pre-bundled dependencies. `src/modules.ts` loads and default-exports all modules, while `vite.config.ts` derives module scanning settings from the manifest. To add or remove a module, update the host dependency and this manifest.
45
+
46
+ Registration order only affects explicit `staticViews` replacements: a later module replaces an earlier module's matching static view key. Ordinary business pages are isolated by `<module>/<view>` and cannot replace pages in other modules by sharing a name.
47
+
48
+ Use Vue Router for navigation between modules. Cross-module code may only import public npm subpaths; the host must not import module `src` directories.
49
+
50
+ ## Commands
51
+
52
+ ```bash
53
+ pnpm --filter __APP_PACKAGE__ dev
54
+ pnpm --filter __APP_PACKAGE__ type:check
55
+ pnpm --filter __APP_PACKAGE__ build
56
+ ```
57
+
58
+ For LAN access, put a shared certificate in the workspace root `certs` directory and set `VITE_HTTPS=true` in `.env.development.local`. You can also specify certificate paths with `VITE_HTTPS_KEY` and `VITE_HTTPS_CERT`.
@@ -1,7 +1,7 @@
1
1
  <!-- prettier-ignore -->
2
2
  # __APP_PACKAGE__
3
3
 
4
- `__PROJECT_NAME__` 的管理端宿主。该包私有且不实现业务,默认先组合 `@liujitcn/kratos-admin-system`,再组合自有 module **MODULE_PACKAGES** 和所选的其他业务 module。
4
+ `__PROJECT_NAME__` 的管理端宿主。该包私有且不实现业务,默认先组合 `@liujitcn/kratos-admin-system`,再组合自有 module __MODULE_PACKAGES__ 和所选的其他业务 module。
5
5
 
6
6
  ## 目录与文件
7
7
 
@@ -0,0 +1,66 @@
1
+ <!-- prettier-ignore -->
2
+ # __MODULE_PACKAGE__
3
+
4
+ The __MODULE_PASCAL__ business module for the admin app in `__PROJECT_NAME__`. APIs, RPCs, pages, and business components are maintained together in this package, which can be published to npm and composed into different hosts.
5
+
6
+ ## Files and directories
7
+
8
+ ```text
9
+ packages/modules/__MODULE_NAME__
10
+ ├── src
11
+ │ ├── api # Request files follow the complete Proto path and service file name
12
+ │ ├── rpc
13
+ │ │ └── README.md # Proto-generated directory notes
14
+ │ ├── components # Optional components shared inside this module
15
+ │ ├── views
16
+ │ │ └── index
17
+ │ │ └── index.vue
18
+ │ ├── index.ts
19
+ │ └── module.ts
20
+ ├── package.json
21
+ ├── README.md
22
+ ├── tsconfig.json
23
+ └── tsconfig.package.json
24
+ ```
25
+
26
+ | Path | Purpose |
27
+ | --- | --- |
28
+ | `src/index.ts` | npm entry point exporting `__MODULE_IDENTIFIER__`. |
29
+ | `src/module.ts` | Collects `src/views/**/*.vue` and declares the `__MODULE_NAME__` module. |
30
+ | `src/api/<proto-path>/*.ts` | Request wrappers matching the full Proto path and service filename. |
31
+ | `src/rpc/README.md` | Describes this module's RPC generation directory and public package subpath. |
32
+ | `src/views/index/index.vue` | Starter page included in the template. |
33
+ | `package.json` | Declares dependencies, module entry, API/RPC subpaths, and npm publish settings. |
34
+ | `README.md` | Documents this business module's files and integration workflow. |
35
+ | `tsconfig.json` | Development-time type-check configuration. |
36
+ | `tsconfig.package.json` | Generates declaration files for npm publishing. |
37
+
38
+ ## Development guidelines
39
+
40
+ - Place APIs under `src/api/<proto-path>` to match the complete Proto path, for example `src/api/base/v1` and `src/api/system/admin/v1`. Keep filenames aligned with service filenames. Preserve full RPC paths under `src/rpc/<proto-domain>/<version>`; do not flatten them.
41
+ - Put pages under `src/views`. Keep page-specific components next to the page; create `src/components` only when multiple pages in this module share a component.
42
+ - Import core capabilities through public subpaths of `@liujitcn/kratos-admin-core`; do not depend on core source directories.
43
+ - Use Vue Router for navigation between modules. Cross-module code reuse requires an explicit public npm export from the other module.
44
+ - Prefix dynamic menu component paths with `__MODULE_NAME__/`. The starter page path is `__MODULE_NAME__/index/index`; `index/index` is not supported.
45
+ - Replace core static pages only by explicitly mapping them in `staticViews` using the `ADMIN_STATIC_VIEWS` export from core.
46
+
47
+ The module runtime interface is `__MODULE_IDENTIFIER__`, exported from `src/index.ts`. APIs and RPCs are public through `package.json#exports`; pages are registered only through `AdminModule.views` and are not exported as npm subpaths. Add explicit component exports only for genuine cross-module reuse. Files under `src` do not become public automatically.
48
+
49
+ Host integration:
50
+
51
+ ```ts
52
+ export const adminModuleManifest = [
53
+ {
54
+ packageName: "__MODULE_PACKAGE__",
55
+ load: async () => (await import("__MODULE_PACKAGE__")).__MODULE_IDENTIFIER__
56
+ }
57
+ ];
58
+ ```
59
+
60
+ ## Commands
61
+
62
+ ```bash
63
+ pnpm --filter __MODULE_PACKAGE__ type:check
64
+ pnpm --filter __MODULE_PACKAGE__ build:package
65
+ pnpm --filter __MODULE_PACKAGE__ pack
66
+ ```
@@ -0,0 +1,14 @@
1
+ # RPC
2
+
3
+ This directory contains TypeScript generated from the current module's Proto dependencies. Preserve the full Proto path, for example:
4
+
5
+ ```text
6
+ src/rpc
7
+ ├── base/v1
8
+ ├── common/v1
9
+ └── system/admin/v1
10
+ ```
11
+
12
+ Generate only the services and transitive types used by this module's APIs and pages. Generated files belong to this module and are exposed through the package's `./rpc/*` subpath. Do not generate business RPCs into core or duplicate their models by hand.
13
+
14
+ Update RPC files only with the generation command configured by the consuming project. Keep relative imports exactly as emitted; do not flatten the directory structure for appearance.
@@ -1,6 +1,7 @@
1
1
  import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import { execFileSync } from "node:child_process";
3
3
  import { resolve } from "node:path";
4
+ import { workspaceMessage } from "./locale-messages.mjs";
4
5
 
5
6
  const packageRoot = resolve(process.cwd(), process.argv[2] ?? ".");
6
7
  const declarationRoot = resolve(packageRoot, "dist/declarations");
@@ -18,4 +19,4 @@ await cp(resolve(packageRoot, "src"), resolve(outputRoot, "src"), { recursive: t
18
19
  await cp(declarationRoot, resolve(outputRoot, "declarations"), { recursive: true });
19
20
  await writeFile(resolve(outputRoot, "declarations/index.d.ts"), 'export * from "./src/index";\n');
20
21
  await rm(declarationRoot, { recursive: true, force: true });
21
- console.log(`已生成 ${packageJson.name} 发布文件`);
22
+ console.log(workspaceMessage("package_built", { name: packageJson.name }));
@@ -0,0 +1,29 @@
1
+ const messages = {
2
+ "zh-CN": {
3
+ package_built: "已生成 {name} 发布文件",
4
+ locale_missing_default: "{module} 缺少 zh-CN 语言包",
5
+ locale_set_mismatch: "{module} 语言集合不一致",
6
+ locale_code_invalid: "语言代码无效:{locale}",
7
+ locale_keys_mismatch: "{module}/{locale} 语言键集合不一致",
8
+ locale_bundle_outdated: "语言注册产物过期:{file},请执行 pnpm i18n:sync"
9
+ },
10
+ "en-US": {
11
+ package_built: "Built package artifacts for {name}",
12
+ locale_missing_default: "{module} is missing the zh-CN locale bundle",
13
+ locale_set_mismatch: "{module} has an inconsistent locale set",
14
+ locale_code_invalid: "Invalid locale code: {locale}",
15
+ locale_keys_mismatch: "Locale message keys differ for {module}/{locale}",
16
+ locale_bundle_outdated: "Locale bundle is out of date: {file}. Run pnpm i18n:sync."
17
+ }
18
+ };
19
+
20
+ /** 根据生成工作区的语言偏好渲染脚手架消息。 */
21
+ export function workspaceMessage(key, params = {}) {
22
+ const locale = (process.env.KRATOS_ADMIN_LOCALE ?? process.env.LC_ALL ?? process.env.LANG ?? "")
23
+ .toLowerCase()
24
+ .startsWith("zh")
25
+ ? "zh-CN"
26
+ : "en-US";
27
+ const template = messages[locale][key] ?? messages["en-US"][key] ?? key;
28
+ return template.replace(/\{([A-Za-z0-9_]+)\}/g, (_, name) => params[name] ?? `{${name}}`);
29
+ }
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
2
2
  import { dirname, resolve } from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
+ import { workspaceMessage } from './locale-messages.mjs'
4
5
 
5
6
  const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
6
7
  const modulesRoot = resolve(root, 'packages/modules')
@@ -11,16 +12,16 @@ for (const entry of existsSync(modulesRoot) ? readdirSync(modulesRoot, { withFil
11
12
  const directory = resolve(modulesRoot, entry.name, 'src/locales')
12
13
  const locales = readdirSync(directory).filter(file => file.endsWith('.json')).map(file => file.slice(0, -5))
13
14
  .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} 语言集合不一致`)
15
+ if (!locales.includes('zh-CN')) throw new Error(workspaceMessage('locale_missing_default', { module: entry.name }))
16
+ if (expectedLocales && locales.join(',') !== expectedLocales) throw new Error(workspaceMessage('locale_set_mismatch', { module: entry.name }))
16
17
  expectedLocales = locales.join(',')
17
18
  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
+ if (!/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(locale)) throw new Error(workspaceMessage('locale_code_invalid', { locale }))
19
20
  return [locale, JSON.parse(readFileSync(resolve(directory, `${locale}.json`), 'utf8'))]
20
21
  }))
21
22
  const keys = Object.keys(messages['zh-CN']).sort().join('\0')
22
23
  for (const locale of locales) {
23
- if (Object.keys(messages[locale]).sort().join('\0') !== keys) throw new Error(`${entry.name}/${locale} 语言键集合不一致`)
24
+ if (Object.keys(messages[locale]).sort().join('\0') !== keys) throw new Error(workspaceMessage('locale_keys_mismatch', { module: entry.name, locale }))
24
25
  }
25
26
  const imports = locales.map((locale, index) => `import locale${index} from './${locale}.json'`)
26
27
  const content = [
@@ -34,5 +35,5 @@ for (const entry of existsSync(modulesRoot) ? readdirSync(modulesRoot, { withFil
34
35
  ].join('\n')
35
36
  const file = resolve(directory, 'generated.ts')
36
37
  if (write) writeFileSync(file, content)
37
- else if (!existsSync(file) || readFileSync(file, 'utf8') !== content) throw new Error(`语言注册产物过期:${file},请执行 pnpm i18n:sync`)
38
+ else if (!existsSync(file) || readFileSync(file, 'utf8') !== content) throw new Error(workspaceMessage('locale_bundle_outdated', { file }))
38
39
  }