@liujitcn/kratos-taro-app-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/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "type": "git",
8
8
  "url": "git@github.com:liujitcn/kratos-admin.git"
9
9
  },
10
- "version": "0.0.62",
10
+ "version": "0.0.65",
11
11
  "type": "module",
12
12
  "bin": {
13
13
  "kratos-taro-app": "./bin/kratos-taro-app.mjs"
package/src/index.mjs CHANGED
@@ -1,11 +1,12 @@
1
1
  import { execFileSync } from 'node:child_process'
2
2
  import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
3
3
  import { basename, resolve } from 'node:path'
4
+ import { cliMessage, resolveCliLocale } from './messages.mjs'
4
5
 
5
6
  const cliPackage = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
6
7
  const publicPackageVersion = cliPackage.version
7
8
  if (typeof publicPackageVersion !== 'string' || !publicPackageVersion) {
8
- throw new Error('CLI package.json 缺少有效版本')
9
+ throw new Error(cliMessage('package_version_missing'))
9
10
  }
10
11
 
11
12
  const taroVersion = '4.2.1'
@@ -16,7 +17,7 @@ const h5RootFontSource =
16
17
  /** 创建独立 Kratos Taro React workspace。 */
17
18
  export function scaffoldKratosTaroApp(targetPath, options = {}) {
18
19
  const target = resolve(targetPath)
19
- if (existsSync(target)) throw new Error(`目标目录已存在:${target}`)
20
+ if (existsSync(target)) throw new Error(cliMessage('target_exists', { target }))
20
21
  const projectName = basename(target)
21
22
  validateProjectName(projectName)
22
23
  const modules = [...new Set(options.modules ?? [])]
@@ -74,58 +75,19 @@ export async function run(args = process.argv.slice(2)) {
74
75
  printHelp()
75
76
  return
76
77
  }
77
- if (args[0] !== 'create') throw new Error(`不支持的命令:${args[0]}`)
78
+ if (args[0] !== 'create') throw new Error(cliMessage('unsupported_command', { command: args[0] }))
78
79
  const targetPath = args[1]
79
80
  if (!targetPath || targetPath.startsWith('--')) {
80
- throw new Error('用法: kratos-taro-app create <目录> [--module <名称>] [--with <包名>]')
81
+ throw new Error(cliMessage('usage'))
81
82
  }
82
83
  const modules = readOptions(args.slice(2), '--module')
83
84
  const packages = readOptions(args.slice(2), '--with')
84
85
  const target = scaffoldKratosTaroApp(targetPath, { modules, packages, kratosProject: args.includes('--kratos-project') })
85
- process.stdout.write(`已创建 Taro workspace:${target}\n`)
86
+ process.stdout.write(`${cliMessage('workspace_created', { target })}\n`)
86
87
  }
87
88
 
88
89
  function writeWorkspaceReadme(target, projectName) {
89
- write(
90
- target,
91
- 'README.md',
92
- `# ${projectName}
93
-
94
- \`${projectName}\` 是由 \`@liujitcn/kratos-taro-app-cli\` 创建的独立 pnpm workspace,使用 Taro、React 和 TypeScript,支持 H5 与微信小程序。
95
-
96
- ## 目录结构
97
-
98
- \`\`\`text
99
- ${projectName}
100
- ├── apps/taro-app # 私有 Taro 宿主
101
- ├── packages/modules # workspace 内本地业务模块
102
- ├── package.json # 公共命令和开发依赖
103
- ├── pnpm-workspace.yaml # workspace 包范围
104
- └── tsconfig.json # 共享 TypeScript 配置
105
- \`\`\`
106
-
107
- 宿主只负责入口、模块清单和平台构建配置。运行时底座、UI 主题和默认业务页分别由 \`@liujitcn/kratos-taro-app-core\`、\`@liujitcn/kratos-taro-app-ui\` 与 \`@liujitcn/kratos-taro-app-system\` 提供。
108
-
109
- ## 开发
110
-
111
- \`\`\`bash
112
- pnpm install
113
- pnpm dev:h5
114
- pnpm dev:mp-weixin
115
- pnpm build:h5
116
- pnpm build:mp-weixin
117
- pnpm tsc
118
- \`\`\`
119
-
120
- 开发产物位于 apps/taro-app/dist/dev/<平台>,生产产物位于 apps/taro-app/dist/build/<平台>(平台为 h5 或 mp-weixin);Kratos 配套项目的 H5 生产产物输出到 backend/web/taro-app。微信开发者工具默认使用 dist/dev/mp-weixin,发布时导入 dist/build/mp-weixin。
121
-
122
- 模块装配入口是 \`apps/taro-app/src/module-manifest.ts\`。模块顺序决定静态视图覆盖优先级;新增页面时同步维护模块自己的 \`src/pages.ts\` 和视图映射。
123
-
124
- 环境文件位于 workspace 根目录,变量名与 uni-app 保持一致:\`VITE_APP_PORT\`、\`VITE_APP_BASE_PATH\`、\`VITE_APP_BASE_API\`、\`VITE_APP_API_URL\`、\`VITE_APP_STATIC_API\`、\`VITE_APP_STATIC_URL\`。H5 会在基础模式文件上叠加对应的 \`*-h5\` 文件。
125
-
126
- H5 通过局域网 IP 访问时,先在仓库根目录运行 \`bash scripts/generate-dev-cert.sh 192.168.1.100\` 生成证书,再在 \`.env.development-h5.local\` 中设置 \`VITE_APP_HTTPS=true\`;后端使用 HTTPS 时同时设置 \`VITE_APP_API_URL=https://localhost:7001\`。
127
- `,
128
- )
90
+ write(target, 'README.md', renderLocalizedReadme('workspace.md', { __PROJECT_NAME__: projectName }))
129
91
  }
130
92
 
131
93
  function writeEnvironmentFiles(target) {
@@ -137,7 +99,7 @@ function writeEnvironmentFiles(target) {
137
99
  write(
138
100
  target,
139
101
  '.env.development-h5',
140
- 'VITE_APP_PORT=5002\nVITE_APP_HTTPS=false\n# VITE_APP_HTTPS_KEY=../../certs/dev-key.pem\n# VITE_APP_HTTPS_CERT=../../certs/dev-cert.pem\n# 后端使用 HTTPS 时,在 .env.development-h5.local 覆盖 VITE_APP_API_URL=https://localhost:7001\nVITE_APP_BASE_PATH=/\nVITE_APP_BASE_API=/api\nVITE_APP_API_URL=http://localhost:7001\nVITE_APP_STATIC_API=\nVITE_APP_STATIC_URL=http://localhost:7001\n',
102
+ `VITE_APP_PORT=5002\nVITE_APP_HTTPS=false\n# VITE_APP_HTTPS_KEY=../../certs/dev-key.pem\n# VITE_APP_HTTPS_CERT=../../certs/dev-cert.pem\n# ${cliMessage('https_environment_hint')}\nVITE_APP_BASE_PATH=/\nVITE_APP_BASE_API=/api\nVITE_APP_API_URL=http://localhost:7001\nVITE_APP_STATIC_API=\nVITE_APP_STATIC_URL=http://localhost:7001\n`,
141
103
  )
142
104
  write(
143
105
  target,
@@ -207,16 +169,7 @@ function writeHost(target, projectName, modules, packages, options) {
207
169
  write(
208
170
  target,
209
171
  'apps/taro-app/README.md',
210
- `# @local/kratos-taro-app
211
-
212
- \`apps/taro-app\` 是 \`${projectName}\` 的私有 Taro React 宿主,负责装配模块并提供 H5、微信小程序构建入口,不承载可复用业务实现。
213
-
214
- 开发产物位于 apps/taro-app/dist/dev/<平台>,生产产物位于 apps/taro-app/dist/build/<平台>(平台为 h5 或 mp-weixin);Kratos 配套项目的 H5 生产产物输出到 backend/web/taro-app。微信开发者工具默认使用 dist/dev/mp-weixin,发布时导入 dist/build/mp-weixin。
215
-
216
- 固定启动页位于 \`src/pages/bootstrap\`。其他模块页面由 core runner 在构建期间临时生成包装器、页面配置与静态资源,构建结束后会自动恢复宿主目录。
217
-
218
- 通过局域网 IP 访问 H5 时,可复用仓库根 certs 下的共享证书,并在 .env.development-h5.local 中设置 VITE_APP_HTTPS=true;后端使用 HTTPS 时同步设置 VITE_APP_API_URL=https://localhost:7001。
219
- `,
172
+ renderLocalizedReadme('host.md', { __PROJECT_NAME__: projectName }),
220
173
  )
221
174
  write(
222
175
  target,
@@ -377,15 +330,10 @@ function writeLocalModule(target, projectName, name) {
377
330
  write(
378
331
  target,
379
332
  `packages/modules/${name}/README.md`,
380
- `# @local/${name}
381
-
382
- \`@local/${name}\` 是 \`${projectName}\` workspace 内的本地 Taro 业务模块,通过 core 的公开接口接入宿主。
383
-
384
- - 页面放在 \`src/views\`,页面配置登记在 \`src/pages.ts\`。
385
- - 稳定视图键登记在 \`src/index.ts\`,后注册模块具有更高覆盖优先级。
386
- - 请求、认证、导航和状态能力只能通过公开包入口使用。
387
- - \`src/build.ts\` 是 runner 使用的构建期描述,不承载运行时逻辑。
388
- `,
333
+ renderLocalizedReadme('module.md', {
334
+ __PROJECT_NAME__: projectName,
335
+ __MODULE_NAME__: name,
336
+ }),
389
337
  )
390
338
  write(
391
339
  target,
@@ -457,6 +405,7 @@ import { dirname, resolve } from 'node:path'
457
405
  import { defineConfig, type UserConfigExport } from '@tarojs/cli'
458
406
  import { dotenvParse } from '@tarojs/helper'
459
407
  import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'
408
+ import { runnerMessage } from '@liujitcn/kratos-taro-app-core/build'
460
409
  import devConfig from './dev'
461
410
  import prodConfig from './prod'
462
411
 
@@ -469,7 +418,7 @@ function resolveHttpsOptions(env: Record<string, string>, root: string) {
469
418
  const keyPath = resolve(root, env.VITE_APP_HTTPS_KEY || '../../certs/dev-key.pem')
470
419
  const certPath = resolve(root, env.VITE_APP_HTTPS_CERT || '../../certs/dev-cert.pem')
471
420
  if (!existsSync(keyPath) || !existsSync(certPath)) {
472
- throw new Error('VITE_APP_HTTPS 已开启,但未找到证书文件,请先在仓库根目录运行 scripts/generate-dev-cert.sh;期望路径:' + keyPath + ' 和 ' + certPath)
421
+ throw new Error(runnerMessage('https_certificate_missing', { keyPath, certPath }))
473
422
  }
474
423
  return { key: readFileSync(keyPath), cert: readFileSync(certPath) }
475
424
  }
@@ -619,10 +568,10 @@ function readOptions(args, option) {
619
568
  const argument = args[index]
620
569
  if (argument === '--kratos-project') continue
621
570
  if (argument !== '--module' && argument !== '--with') {
622
- throw new Error(`未知参数:${argument}`)
571
+ throw new Error(cliMessage('unknown_argument', { argument }))
623
572
  }
624
573
  const value = args[++index]
625
- if (!value || value.startsWith('--')) throw new Error(`选项 ${argument} 缺少值`)
574
+ if (!value || value.startsWith('--')) throw new Error(cliMessage('option_missing_value', { option: argument }))
626
575
  if (argument === option) {
627
576
  values.push(
628
577
  ...value
@@ -636,16 +585,16 @@ function readOptions(args, option) {
636
585
  }
637
586
 
638
587
  function validateProjectName(name) {
639
- if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new Error(`项目名必须使用 kebab-case:${name}`)
588
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new Error(cliMessage('project_name_invalid', { name }))
640
589
  }
641
590
 
642
591
  /** 校验本地业务模块名称。 */
643
592
  function validateModuleName(name) {
644
- if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new Error(`模块名无效:${name}`)
593
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new Error(cliMessage('module_name_invalid', { name }))
645
594
  }
646
595
 
647
596
  function validatePackageName(name) {
648
- if (!/^(?:@[a-z0-9-]+\/)?[a-z0-9-]+$/.test(name)) throw new Error(`包名无效:${name}`)
597
+ if (!/^(?:@[a-z0-9-]+\/)?[a-z0-9-]+$/.test(name)) throw new Error(cliMessage('package_name_invalid', { name }))
649
598
  }
650
599
 
651
600
  function toCamelCase(value) {
@@ -665,12 +614,12 @@ function json(value) {
665
614
  function printHelp() {
666
615
  process.stdout.write(
667
616
  [
668
- 'kratos-taro-app create <目录> [--module <名称[,名称...]>] [--with <包名>]',
617
+ cliMessage('usage'),
669
618
  '',
670
- '示例:',
671
- ' kratos-taro-app create customer-app',
672
- ' kratos-taro-app create business-app --module business,report',
673
- ' kratos-taro-app create customer-app --with @acme/customer-module',
619
+ cliMessage('examples_heading'),
620
+ cliMessage('example_create'),
621
+ cliMessage('example_modules'),
622
+ cliMessage('example_package'),
674
623
  '',
675
624
  ].join('\n'),
676
625
  )
@@ -691,6 +640,18 @@ function renderWorkspaceTemplates(source, target, tokens) {
691
640
  }
692
641
  }
693
642
 
643
+ /** 按 CLI 语言加载并替换 workspace README 模板。 */
644
+ function renderLocalizedReadme(name, tokens) {
645
+ const template = readFileSync(
646
+ new URL(`../templates/readmes/${resolveCliLocale()}/${name}`, import.meta.url),
647
+ 'utf8',
648
+ )
649
+ return Object.entries(tokens).reduce(
650
+ (content, [key, value]) => content.replaceAll(key, () => value),
651
+ template,
652
+ )
653
+ }
654
+
694
655
  /** 创建业务模块语言源文件及 API、RPC、测试目录,注册产物由同步命令生成。 */
695
656
  function writeModuleLocales(target, name) {
696
657
  for (const directory of ['src/api', 'src/rpc', 'test']) {
@@ -0,0 +1,52 @@
1
+ const messages = {
2
+ 'zh-CN': {
3
+ package_version_missing: 'CLI package.json 缺少有效版本',
4
+ target_exists: '目标目录已存在:{target}',
5
+ unsupported_command: '不支持的命令:{command}',
6
+ usage: '用法: kratos-taro-app create <目录> [--module <名称>] [--with <包名>]',
7
+ workspace_created: '已创建 Taro workspace:{target}',
8
+ unknown_argument: '未知参数:{argument}',
9
+ option_missing_value: '选项 {option} 缺少值',
10
+ project_name_invalid: '项目名必须使用 kebab-case:{name}',
11
+ module_name_invalid: '模块名无效:{name}',
12
+ package_name_invalid: '包名无效:{name}',
13
+ examples_heading: '示例:',
14
+ example_create: ' kratos-taro-app create customer-app',
15
+ example_modules: ' kratos-taro-app create business-app --module business,report',
16
+ example_package: ' kratos-taro-app create customer-app --with @acme/customer-module',
17
+ https_environment_hint: '后端使用 HTTPS 时,在 .env.development-h5.local 中设置 VITE_APP_API_URL=https://localhost:7001。',
18
+ },
19
+ 'en-US': {
20
+ package_version_missing: 'CLI package.json is missing a valid version',
21
+ target_exists: 'The target directory already exists: {target}',
22
+ unsupported_command: 'Unsupported command: {command}',
23
+ usage: 'Usage: kratos-taro-app create <directory> [--module <name>] [--with <package>]',
24
+ workspace_created: 'Taro workspace created: {target}',
25
+ unknown_argument: 'Unknown argument: {argument}',
26
+ option_missing_value: 'Option {option} is missing a value',
27
+ project_name_invalid: 'Project name must use kebab-case: {name}',
28
+ module_name_invalid: 'Invalid module name: {name}',
29
+ package_name_invalid: 'Invalid package name: {name}',
30
+ examples_heading: 'Examples:',
31
+ example_create: ' kratos-taro-app create customer-app',
32
+ example_modules: ' kratos-taro-app create business-app --module business,report',
33
+ example_package: ' kratos-taro-app create customer-app --with @acme/customer-module',
34
+ https_environment_hint: 'When the backend uses HTTPS, set VITE_APP_API_URL=https://localhost:7001 in .env.development-h5.local.',
35
+ },
36
+ }
37
+
38
+ /** 解析脚手架文案所用的语言区域。 */
39
+ export function resolveCliLocale() {
40
+ return (process.env.KRATOS_ADMIN_LOCALE ?? process.env.LC_ALL ?? process.env.LANG ?? '')
41
+ .toLowerCase()
42
+ .startsWith('zh')
43
+ ? 'zh-CN'
44
+ : 'en-US'
45
+ }
46
+
47
+ /** 根据环境变量选择 Taro CLI 文案语言。 */
48
+ export function cliMessage(key, params = {}) {
49
+ const locale = resolveCliLocale()
50
+ const template = messages[locale][key] ?? messages['en-US'][key] ?? key
51
+ return template.replace(/\{([A-Za-z0-9_]+)\}/g, (_, name) => params[name] ?? `{${name}}`)
52
+ }
@@ -0,0 +1,9 @@
1
+ # @local/kratos-taro-app
2
+
3
+ `apps/taro-app` is the private Taro React host for `__PROJECT_NAME__`. It assembles modules and provides H5 and WeChat Mini Program build entry points; reusable business implementations belong in modules.
4
+
5
+ Development output is under `apps/taro-app/dist/dev/<platform>` and production output under `apps/taro-app/dist/build/<platform>` (`h5` or `mp-weixin`). For a Kratos-integrated project, the H5 production build is written to `backend/web/taro-app`. WeChat Developer Tools should use `dist/dev/mp-weixin`; import `dist/build/mp-weixin` for release.
6
+
7
+ The fixed startup page is in `src/pages/bootstrap`. The core runner temporarily generates wrappers, page configuration, and static assets for other module pages during builds, then restores the host directory.
8
+
9
+ To access H5 over a LAN IP, use the shared certificate under the repository's `certs` directory and set `VITE_APP_HTTPS=true` in `.env.development-h5.local`. If the backend uses HTTPS, also set `VITE_APP_API_URL=https://localhost:7001`.
@@ -0,0 +1,8 @@
1
+ # @local/__MODULE_NAME__
2
+
3
+ `@local/__MODULE_NAME__` is a local Taro business module in the `__PROJECT_NAME__` workspace. It integrates with the host through the public core APIs.
4
+
5
+ - Put pages in `src/views` and register page configuration in `src/pages.ts`.
6
+ - Register stable view keys in `src/index.ts`; modules declared later take precedence when overriding static views.
7
+ - Use only public package entry points for requests, authentication, navigation, and state.
8
+ - `src/build.ts` describes the module to the build-time runner and must not contain runtime logic.
@@ -0,0 +1,35 @@
1
+ # __PROJECT_NAME__
2
+
3
+ `__PROJECT_NAME__` is an independent pnpm workspace created by `@liujitcn/kratos-taro-app-cli`. It uses Taro, React, and TypeScript, and supports H5 and WeChat Mini Programs.
4
+
5
+ ## Directory Structure
6
+
7
+ ```text
8
+ __PROJECT_NAME__
9
+ ├── apps/taro-app # Private Taro host
10
+ ├── packages/modules # Local workspace business modules
11
+ ├── package.json # Shared commands and development dependencies
12
+ ├── pnpm-workspace.yaml # Workspace package patterns
13
+ └── tsconfig.json # Shared TypeScript configuration
14
+ ```
15
+
16
+ The host contains only the entry point, module manifest, and platform build configuration. Runtime foundations, UI theme, and default business pages are provided by `@liujitcn/kratos-taro-app-core`, `@liujitcn/kratos-taro-app-ui`, and `@liujitcn/kratos-taro-app-system`.
17
+
18
+ ## Development
19
+
20
+ ```bash
21
+ pnpm install
22
+ pnpm dev:h5
23
+ pnpm dev:mp-weixin
24
+ pnpm build:h5
25
+ pnpm build:mp-weixin
26
+ pnpm tsc
27
+ ```
28
+
29
+ Development output is under `apps/taro-app/dist/dev/<platform>` and production output under `apps/taro-app/dist/build/<platform>` (`h5` or `mp-weixin`). For a Kratos-integrated project, the H5 production build is written to `backend/web/taro-app`. WeChat Developer Tools should use `dist/dev/mp-weixin`; import `dist/build/mp-weixin` for release.
30
+
31
+ The module assembly entry is `apps/taro-app/src/module-manifest.ts`. Declaration order determines static view override priority; update each module's `src/pages.ts` and view mapping when adding pages.
32
+
33
+ Environment files are at the workspace root and use the same names as uni-app: `VITE_APP_PORT`, `VITE_APP_BASE_PATH`, `VITE_APP_BASE_API`, `VITE_APP_API_URL`, `VITE_APP_STATIC_API`, and `VITE_APP_STATIC_URL`. H5 overlays the matching `*-h5` file on the base mode file.
34
+
35
+ To access H5 over a LAN IP, run `bash scripts/generate-dev-cert.sh 192.168.1.100` from the repository root, then set `VITE_APP_HTTPS=true` in `.env.development-h5.local`. If the backend uses HTTPS, also set `VITE_APP_API_URL=https://localhost:7001`.
@@ -0,0 +1,9 @@
1
+ # @local/kratos-taro-app
2
+
3
+ `apps/taro-app` 是 `__PROJECT_NAME__` 的私有 Taro React 宿主,负责装配模块并提供 H5、微信小程序构建入口,不承载可复用业务实现。
4
+
5
+ 开发产物位于 apps/taro-app/dist/dev/<平台>,生产产物位于 apps/taro-app/dist/build/<平台>(平台为 h5 或 mp-weixin);Kratos 配套项目的 H5 生产产物输出到 backend/web/taro-app。微信开发者工具默认使用 dist/dev/mp-weixin,发布时导入 dist/build/mp-weixin。
6
+
7
+ 固定启动页位于 `src/pages/bootstrap`。其他模块页面由 core runner 在构建期间临时生成包装器、页面配置与静态资源,构建结束后会自动恢复宿主目录。
8
+
9
+ 通过局域网 IP 访问 H5 时,可复用仓库根 certs 下的共享证书,并在 .env.development-h5.local 中设置 VITE_APP_HTTPS=true;后端使用 HTTPS 时同步设置 VITE_APP_API_URL=https://localhost:7001。
@@ -0,0 +1,8 @@
1
+ # @local/__MODULE_NAME__
2
+
3
+ `@local/__MODULE_NAME__` 是 `__PROJECT_NAME__` workspace 内的本地 Taro 业务模块,通过 core 的公开接口接入宿主。
4
+
5
+ - 页面放在 `src/views`,页面配置登记在 `src/pages.ts`。
6
+ - 稳定视图键登记在 `src/index.ts`,后注册模块具有更高覆盖优先级。
7
+ - 请求、认证、导航和状态能力只能通过公开包入口使用。
8
+ - `src/build.ts` 是 runner 使用的构建期描述,不承载运行时逻辑。
@@ -0,0 +1,35 @@
1
+ # __PROJECT_NAME__
2
+
3
+ `__PROJECT_NAME__` 是由 `@liujitcn/kratos-taro-app-cli` 创建的独立 pnpm workspace,使用 Taro、React 和 TypeScript,支持 H5 与微信小程序。
4
+
5
+ ## 目录结构
6
+
7
+ ```text
8
+ __PROJECT_NAME__
9
+ ├── apps/taro-app # 私有 Taro 宿主
10
+ ├── packages/modules # workspace 内本地业务模块
11
+ ├── package.json # 公共命令和开发依赖
12
+ ├── pnpm-workspace.yaml # workspace 包范围
13
+ └── tsconfig.json # 共享 TypeScript 配置
14
+ ```
15
+
16
+ 宿主只负责入口、模块清单和平台构建配置。运行时底座、UI 主题和默认业务页分别由 `@liujitcn/kratos-taro-app-core`、`@liujitcn/kratos-taro-app-ui` 与 `@liujitcn/kratos-taro-app-system` 提供。
17
+
18
+ ## 开发
19
+
20
+ ```bash
21
+ pnpm install
22
+ pnpm dev:h5
23
+ pnpm dev:mp-weixin
24
+ pnpm build:h5
25
+ pnpm build:mp-weixin
26
+ pnpm tsc
27
+ ```
28
+
29
+ 开发产物位于 apps/taro-app/dist/dev/<平台>,生产产物位于 apps/taro-app/dist/build/<平台>(平台为 h5 或 mp-weixin);Kratos 配套项目的 H5 生产产物输出到 backend/web/taro-app。微信开发者工具默认使用 dist/dev/mp-weixin,发布时导入 dist/build/mp-weixin。
30
+
31
+ 模块装配入口是 `apps/taro-app/src/module-manifest.ts`。模块顺序决定静态视图覆盖优先级;新增页面时同步维护模块自己的 `src/pages.ts` 和视图映射。
32
+
33
+ 环境文件位于 workspace 根目录,变量名与 uni-app 保持一致:`VITE_APP_PORT`、`VITE_APP_BASE_PATH`、`VITE_APP_BASE_API`、`VITE_APP_API_URL`、`VITE_APP_STATIC_API`、`VITE_APP_STATIC_URL`。H5 会在基础模式文件上叠加对应的 `*-h5` 文件。
34
+
35
+ H5 通过局域网 IP 访问时,先在仓库根目录运行 `bash scripts/generate-dev-cert.sh 192.168.1.100` 生成证书,再在 `.env.development-h5.local` 中设置 `VITE_APP_HTTPS=true`;后端使用 HTTPS 时同时设置 `VITE_APP_API_URL=https://localhost:7001`。
@@ -2,6 +2,7 @@ import { readdir, readFile, stat } from 'node:fs/promises'
2
2
  import { dirname, extname, relative, resolve, sep } from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
4
  import ts from 'typescript'
5
+ import { workspaceMessage } from './locale-messages.mjs'
5
6
 
6
7
  const workspaceRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
7
8
  const coreRoot = resolve(workspaceRoot, 'packages/core')
@@ -30,7 +31,7 @@ for (const packageFile of packageFiles) {
30
31
  const prefix = target.includes('*') ? target.slice(0, target.indexOf('*')) : target
31
32
  const checkPath = resolve(packageRoot, prefix)
32
33
  if (!(await pathExists(checkPath))) {
33
- throw new Error(`${metadata.name} 导出目标不存在:${target}`)
34
+ throw new Error(workspaceMessage('export_target_missing', { name: metadata.name, target }))
34
35
  }
35
36
  }
36
37
  }
@@ -40,8 +41,8 @@ const distinctPublicVersions = new Set(publicPackageVersions.values())
40
41
  if (distinctPublicVersions.size > 1 || [...distinctPublicVersions].some((version) => !version)) {
41
42
  const versions = [...publicPackageVersions]
42
43
  .map(([name, version]) => `${name}@${version}`)
43
- .join('、')
44
- throw new Error(`Taro 公开包版本必须保持一致:${versions}`)
44
+ .join(workspaceMessage('list_separator'))
45
+ throw new Error(workspaceMessage('public_version_mismatch', { versions }))
45
46
  }
46
47
 
47
48
  for (const sourceFile of allFiles.filter((file) => sourceExtensions.has(extname(file)))) {
@@ -51,22 +52,18 @@ for (const sourceFile of allFiles.filter((file) => sourceExtensions.has(extname(
51
52
  const specifier = imported.fileName
52
53
  const line = source.slice(0, imported.pos).split('\n').length
53
54
  if ((specifier === '@' || specifier.startsWith('@/')) && !isWithin(sourceFile, coreRoot)) {
54
- violations.push(
55
- `${format(sourceFile, line)}: 非 core 代码不得使用 core 私有别名 ${specifier}`,
56
- )
55
+ violations.push(workspaceMessage('core_alias_forbidden', { location: format(sourceFile, line), specifier }))
57
56
  continue
58
57
  }
59
58
  if (specifier.startsWith('@system/') && !isWithin(sourceFile, systemRoot)) {
60
- violations.push(
61
- `${format(sourceFile, line)}: 非 system 代码不得使用 system 私有别名 ${specifier}`,
62
- )
59
+ violations.push(workspaceMessage('system_alias_forbidden', { location: format(sourceFile, line), specifier }))
63
60
  continue
64
61
  }
65
62
  if (specifier.startsWith('.')) {
66
63
  const sourcePackage = findPackageByPath(sourceFile)
67
64
  const targetPackage = findPackageByPath(resolve(dirname(sourceFile), specifier))
68
65
  if (sourcePackage && targetPackage && sourcePackage !== targetPackage) {
69
- violations.push(`${format(sourceFile, line)}: 禁止通过相对路径跨包引用 ${specifier}`)
66
+ violations.push(workspaceMessage('cross_package_relative_forbidden', { location: format(sourceFile, line), specifier }))
70
67
  }
71
68
  continue
72
69
  }
@@ -79,32 +76,28 @@ for (const sourceFile of allFiles.filter((file) => sourceExtensions.has(extname(
79
76
  .get(packageName)
80
77
  .exportEntries.some(([pattern]) => matchExportPattern(pattern, subpath))
81
78
  if (!exported) {
82
- violations.push(
83
- `${format(sourceFile, line)}: ${specifier} 未在 ${packageName} exports 中公开`,
84
- )
79
+ violations.push(workspaceMessage('export_not_public', { location: format(sourceFile, line), specifier, packageName }))
85
80
  }
86
81
  }
87
82
  }
88
83
 
89
84
  for (const [packageName, { packageRoot }] of packages) {
90
85
  if (packageName === '@liujitcn/kratos-taro-app-core' && packageRoot !== coreRoot) {
91
- violations.push(`${packageName}: core 包目录异常`)
86
+ violations.push(workspaceMessage('core_dir_unexpected', { packageName }))
92
87
  }
93
88
  if (packageName === '@liujitcn/kratos-taro-app-ui' && packageRoot !== uiRoot) {
94
- violations.push(`${packageName}: UI 包目录异常`)
89
+ violations.push(workspaceMessage('ui_dir_unexpected', { packageName }))
95
90
  }
96
91
  if (packageName === '@liujitcn/kratos-taro-app-system' && packageRoot !== systemRoot) {
97
- violations.push(`${packageName}: system 包目录异常`)
92
+ violations.push(workspaceMessage('system_dir_unexpected', { packageName }))
98
93
  }
99
94
  }
100
95
 
101
96
  if (violations.length) {
102
- console.error(
103
- `Taro package exports 检查失败:\n${violations.map((item) => ` ${item}`).join('\n')}`,
104
- )
97
+ console.error(workspaceMessage('exports_check_failed', { violations: violations.map((item) => ` ${item}`).join('\n') }))
105
98
  process.exit(1)
106
99
  }
107
- console.log(`Taro package exports 检查通过(${[...packages.keys()].join('、')})`)
100
+ console.log(workspaceMessage('exports_check_passed', { packages: [...packages.keys()].join(workspaceMessage('list_separator')) }))
108
101
 
109
102
  async function collectFiles(directory) {
110
103
  const files = []
@@ -0,0 +1,53 @@
1
+ const messages = {
2
+ 'zh-CN': {
3
+ missing_default_locale: '{module} 缺少 zh-CN 语言包',
4
+ locale_set_mismatch: '{module} 语言集合不一致',
5
+ locale_code_invalid: '语言代码无效:{locale}',
6
+ key_set_mismatch: '{module}/{locale} 语言键集合不一致',
7
+ bundle_outdated: '语言注册产物过期:{file},请执行 pnpm i18n:sync',
8
+ export_target_missing: '{name} 导出目标不存在:{target}',
9
+ public_version_mismatch: 'Taro 公开包版本必须保持一致:{versions}',
10
+ core_alias_forbidden: '{location}: 非 core 代码不得使用 core 私有别名 {specifier}',
11
+ ui_alias_forbidden: '{location}: 非 UI 代码不得使用 UI 私有别名 {specifier}',
12
+ system_alias_forbidden: '{location}: 非 system 代码不得使用 system 私有别名 {specifier}',
13
+ cross_package_relative_forbidden: '{location}: 禁止通过相对路径跨包引用 {specifier}',
14
+ export_not_public: '{location}: {specifier} 未在 {packageName} exports 中公开',
15
+ core_dir_unexpected: '{packageName}: core 包目录异常',
16
+ ui_dir_unexpected: '{packageName}: UI 包目录异常',
17
+ system_dir_unexpected: '{packageName}: system 包目录异常',
18
+ exports_check_failed: 'Taro package exports 检查失败:\n{violations}',
19
+ exports_check_passed: 'Taro package exports 检查通过({packages})',
20
+ list_separator: '、',
21
+ },
22
+ 'en-US': {
23
+ missing_default_locale: '{module} is missing the zh-CN locale bundle',
24
+ locale_set_mismatch: '{module} has a different locale set',
25
+ locale_code_invalid: 'Invalid locale code: {locale}',
26
+ key_set_mismatch: 'Locale message keys differ for {module}/{locale}',
27
+ bundle_outdated: 'Locale bundle is out of date: {file}. Run pnpm i18n:sync.',
28
+ export_target_missing: '{name} export target does not exist: {target}',
29
+ public_version_mismatch: 'Taro public package versions must match: {versions}',
30
+ core_alias_forbidden: '{location}: Core private aliases cannot be used outside core: {specifier}',
31
+ ui_alias_forbidden: '{location}: UI private aliases cannot be used outside UI: {specifier}',
32
+ system_alias_forbidden: '{location}: System private aliases cannot be used outside system: {specifier}',
33
+ cross_package_relative_forbidden: '{location}: Cross-package relative import is forbidden: {specifier}',
34
+ export_not_public: '{location}: {specifier} is not exposed by {packageName} exports',
35
+ core_dir_unexpected: '{packageName}: unexpected core package directory',
36
+ ui_dir_unexpected: '{packageName}: unexpected UI package directory',
37
+ system_dir_unexpected: '{packageName}: unexpected system package directory',
38
+ exports_check_failed: 'Taro package exports check failed:\n{violations}',
39
+ exports_check_passed: 'Taro package exports check passed ({packages})',
40
+ list_separator: ', ',
41
+ },
42
+ }
43
+
44
+ /** 根据工作区语言偏好渲染脚手架诊断文案。 */
45
+ export function workspaceMessage(key, params = {}) {
46
+ const locale = (process.env.KRATOS_ADMIN_LOCALE ?? process.env.LC_ALL ?? process.env.LANG ?? '')
47
+ .toLowerCase()
48
+ .startsWith('zh')
49
+ ? 'zh-CN'
50
+ : 'en-US'
51
+ const template = messages[locale][key] ?? messages['en-US'][key] ?? key
52
+ return template.replace(/\{([A-Za-z0-9_]+)\}/g, (_, name) => params[name] ?? `{${name}}`)
53
+ }
@@ -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('missing_default_locale', { 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('key_set_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('bundle_outdated', { file }))
38
39
  }