@liujitcn/kratos-admin-cli 0.0.62 → 0.0.64
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 +33 -25
- package/dist/messages.js +46 -0
- package/package.json +2 -1
- package/templates/business-workspace/README.en-US.md +64 -0
- package/templates/business-workspace/README.md +2 -2
- package/templates/business-workspace/apps/admin/README.en-US.md +58 -0
- package/templates/business-workspace/apps/admin/README.md +1 -1
- package/templates/business-workspace/packages/modules/__MODULE_NAME__/README.en-US.md +66 -0
- package/templates/business-workspace/packages/modules/__MODULE_NAME__/src/rpc/README.en-US.md +14 -0
- package/templates/business-workspace/scripts/build-package.mjs +2 -1
- package/templates/business-workspace/scripts/locale-messages.mjs +29 -0
- package/templates/business-workspace/scripts/sync-locales.mjs +6 -5
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(
|
|
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(
|
|
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("
|
|
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(
|
|
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
|
-
|
|
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("
|
|
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(
|
|
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,
|
|
187
|
+
function validateName(value, labelKey) {
|
|
188
|
+
const label = cliMessage(labelKey);
|
|
179
189
|
if (!kebabNamePattern.test(value))
|
|
180
|
-
throw new Error(
|
|
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(
|
|
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",
|
package/dist/messages.js
ADDED
|
@@ -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.
|
|
3
|
+
"version": "0.0.64",
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
15
|
-
if (expectedLocales && locales.join(',') !== expectedLocales) throw new Error(
|
|
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(
|
|
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(
|
|
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(
|
|
38
|
+
else if (!existsSync(file) || readFileSync(file, 'utf8') !== content) throw new Error(workspaceMessage('locale_bundle_outdated', { file }))
|
|
38
39
|
}
|