@liujitcn/kratos-taro-app-cli 0.0.37 → 0.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -1
- package/package.json +3 -2
- package/src/index.mjs +49 -76
- package/templates/workspace/.eslintrc.cjs +19 -0
- package/templates/workspace/apps/taro-app/src/app.tsx +19 -0
- package/templates/workspace/apps/taro-app/tsconfig.json +11 -0
- package/templates/workspace/apps/taro-app/types/assets.d.ts +5 -0
- package/templates/workspace/package.json +75 -0
- package/templates/workspace/scripts/check-package-exports.mjs +152 -0
- package/templates/workspace/scripts/sync-locales.mjs +38 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
pnpm dlx @liujitcn/kratos-taro-app-cli create customer-app
|
|
9
|
-
pnpm dlx @liujitcn/kratos-taro-app-cli create
|
|
9
|
+
pnpm dlx @liujitcn/kratos-taro-app-cli create business-app --module business,report
|
|
10
10
|
pnpm dlx @liujitcn/kratos-taro-app-cli create customer-app --with @acme/customer-module
|
|
11
11
|
```
|
|
12
12
|
|
|
@@ -25,3 +25,16 @@ JavaScript 调用方也可以直接使用 `scaffoldKratosTaroApp(target, options
|
|
|
25
25
|
在 Taro workspace 根目录执行 `pnpm test` 会实际打包、解压并运行包内 CLI,核对生成的
|
|
26
26
|
图标内容和本地业务模块,避免只测试源码而遗漏 npm 发布资源。打包产物和生成项目均位于
|
|
27
27
|
临时目录,测试结束后自动清理。
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### CLI 生成边界
|
|
31
|
+
|
|
32
|
+
CLI 直接生成完整宿主、本地业务模块、四种语言源文件与注册入口、类型检查、lint 和打包配置。
|
|
33
|
+
支持本地 `system` 与其他模块一起创建;管理端本地 System 继承内置能力,运行时只注册一次,
|
|
34
|
+
构建仍扫描内置源码。应用端本地模块使用独立导入别名,保留内置 System。
|
|
35
|
+
|
|
36
|
+
通过 `--kratos-project` 生成与 Go 后端配套的前端:H5 输出到 `backend/data/<terminal>`,
|
|
37
|
+
管理端 CLI 同时在 workspace 父目录创建共享 `Makefile` 与 `scripts`。Go 调用方仅传参执行 CLI。
|
|
38
|
+
新增语言或修改语言文件后运行 `pnpm i18n:sync`;`pnpm i18n:check` 校验注册文件是否同步。
|
|
39
|
+
|
|
40
|
+
H5 宿主通过 `esnextModules` 将已装配 npm 源码包加入 Taro 样式处理,确保 px 按 750 设计稿转换为 rem;仅设置脚本的 `compile.include` 无法覆盖样式转换。第三方组件仍遵循 Taro 默认处理规则。
|
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.
|
|
10
|
+
"version": "0.0.39",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"bin": {
|
|
13
13
|
"kratos-taro-app": "./bin/kratos-taro-app.mjs"
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"assets",
|
|
21
21
|
"bin",
|
|
22
22
|
"src",
|
|
23
|
-
"README.md"
|
|
23
|
+
"README.md",
|
|
24
|
+
"templates"
|
|
24
25
|
],
|
|
25
26
|
"publishConfig": {
|
|
26
27
|
"access": "public"
|
package/src/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
3
|
import { basename, resolve } from 'node:path'
|
|
3
4
|
|
|
4
5
|
const cliPackage = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
|
|
@@ -33,44 +34,6 @@ export function scaffoldKratosTaroApp(targetPath, options = {}) {
|
|
|
33
34
|
'node_modules\ndist\n.kratos-taro-app-pages-state.json\n.kratos-taro-app-pages-state.json.*\napps/taro-app/src/pages/*\n!apps/taro-app/src/pages/bootstrap/\n!apps/taro-app/src/pages/bootstrap/**\napps/taro-app/src/pages?*/\n',
|
|
34
35
|
)
|
|
35
36
|
write(target, 'pnpm-workspace.yaml', 'packages:\n - apps/*\n - packages/modules/*\n')
|
|
36
|
-
write(
|
|
37
|
-
target,
|
|
38
|
-
'package.json',
|
|
39
|
-
json({
|
|
40
|
-
name: projectName,
|
|
41
|
-
description: `Independent pnpm workspace for ${projectName}.`,
|
|
42
|
-
private: true,
|
|
43
|
-
packageManager: 'pnpm@10.13.1',
|
|
44
|
-
scripts: {
|
|
45
|
-
'prepare:modules':
|
|
46
|
-
"pnpm --recursive --filter './packages/modules/**' --if-present run build:entries",
|
|
47
|
-
'dev:h5': 'pnpm prepare:modules && pnpm --filter @local/kratos-taro-app dev:h5',
|
|
48
|
-
'dev:mp-weixin':
|
|
49
|
-
'pnpm prepare:modules && pnpm --filter @local/kratos-taro-app dev:mp-weixin',
|
|
50
|
-
'build:h5': 'pnpm prepare:modules && pnpm --filter @local/kratos-taro-app build:h5',
|
|
51
|
-
'build:mp-weixin':
|
|
52
|
-
'pnpm prepare:modules && pnpm --filter @local/kratos-taro-app build:mp-weixin',
|
|
53
|
-
tsc: 'pnpm --recursive --if-present run tsc',
|
|
54
|
-
},
|
|
55
|
-
devDependencies: {
|
|
56
|
-
'@babel/core': '7.28.4',
|
|
57
|
-
'@babel/preset-react': '7.28.5',
|
|
58
|
-
'@types/node': '20.19.9',
|
|
59
|
-
'@types/react': '18.3.23',
|
|
60
|
-
'@types/react-dom': '18.3.7',
|
|
61
|
-
'babel-preset-taro': taroVersion,
|
|
62
|
-
'cross-env': '7.0.3',
|
|
63
|
-
esbuild: '0.25.8',
|
|
64
|
-
sass: '1.89.2',
|
|
65
|
-
'tsconfig-paths-webpack-plugin': '4.2.0',
|
|
66
|
-
typescript: '5.8.3',
|
|
67
|
-
webpack: '5.91.0',
|
|
68
|
-
},
|
|
69
|
-
pnpm: {
|
|
70
|
-
onlyBuiltDependencies: ['@swc/core', '@tarojs/binding', 'esbuild'],
|
|
71
|
-
},
|
|
72
|
-
}),
|
|
73
|
-
)
|
|
74
37
|
write(
|
|
75
38
|
target,
|
|
76
39
|
'tsconfig.json',
|
|
@@ -91,14 +54,17 @@ export function scaffoldKratosTaroApp(targetPath, options = {}) {
|
|
|
91
54
|
}),
|
|
92
55
|
)
|
|
93
56
|
writeWorkspaceReadme(target, projectName)
|
|
57
|
+
renderWorkspaceTemplates(new URL('../templates/workspace/', import.meta.url), target, { __PROJECT_NAME__: projectName })
|
|
94
58
|
writeEnvironmentFiles(target)
|
|
95
|
-
writeHost(target, projectName, modules, packages)
|
|
59
|
+
writeHost(target, projectName, modules, packages, options)
|
|
96
60
|
write(target, 'apps/taro-app/src/static/h5-root-font.js', `${h5RootFontSource}\n`)
|
|
97
61
|
copyFileSync(
|
|
98
62
|
new URL('../assets/favicon.ico', import.meta.url),
|
|
99
63
|
resolve(target, 'apps/taro-app/src/static/favicon.ico'),
|
|
100
64
|
)
|
|
101
65
|
modules.forEach((name) => writeLocalModule(target, projectName, name))
|
|
66
|
+
modules.forEach((name) => writeModuleLocales(target, name))
|
|
67
|
+
execFileSync(process.execPath, [resolve(target, 'scripts/sync-locales.mjs'), '--write'], { stdio: 'inherit' })
|
|
102
68
|
return target
|
|
103
69
|
}
|
|
104
70
|
|
|
@@ -115,7 +81,7 @@ export async function run(args = process.argv.slice(2)) {
|
|
|
115
81
|
}
|
|
116
82
|
const modules = readOptions(args.slice(2), '--module')
|
|
117
83
|
const packages = readOptions(args.slice(2), '--with')
|
|
118
|
-
const target = scaffoldKratosTaroApp(targetPath, { modules, packages })
|
|
84
|
+
const target = scaffoldKratosTaroApp(targetPath, { modules, packages, kratosProject: args.includes('--kratos-project') })
|
|
119
85
|
process.stdout.write(`已创建 Taro workspace:${target}\n`)
|
|
120
86
|
}
|
|
121
87
|
|
|
@@ -183,7 +149,8 @@ function writeEnvironmentFiles(target) {
|
|
|
183
149
|
)
|
|
184
150
|
}
|
|
185
151
|
|
|
186
|
-
|
|
152
|
+
/** 生成 Taro 宿主与平台构建配置。 */
|
|
153
|
+
function writeHost(target, projectName, modules, packages, options) {
|
|
187
154
|
const localDependencies = Object.fromEntries(
|
|
188
155
|
modules.map((name) => [`@local/${name}`, 'workspace:*']),
|
|
189
156
|
)
|
|
@@ -202,7 +169,7 @@ function writeHost(target, projectName, modules, packages) {
|
|
|
202
169
|
'dev:mp-weixin':
|
|
203
170
|
'cross-env NODE_ENV=development node scripts/run-taro.mjs --type weapp --watch --mode development',
|
|
204
171
|
'build:h5':
|
|
205
|
-
|
|
172
|
+
`cross-env KRATOS_TARO_OUTPUT_ROOT=${options.kratosProject ? '../../../../backend/data/taro-app' : 'dist/build/h5'} node scripts/run-taro.mjs --type h5 --mode production`,
|
|
206
173
|
'build:mp-weixin':
|
|
207
174
|
'cross-env KRATOS_TARO_OUTPUT_ROOT=dist/build/mp-weixin node scripts/run-taro.mjs --type weapp --mode production',
|
|
208
175
|
tsc: 'tsc --noEmit -p tsconfig.json',
|
|
@@ -260,19 +227,6 @@ function writeHost(target, projectName, modules, packages) {
|
|
|
260
227
|
}
|
|
261
228
|
`,
|
|
262
229
|
)
|
|
263
|
-
write(
|
|
264
|
-
target,
|
|
265
|
-
'apps/taro-app/tsconfig.json',
|
|
266
|
-
json({
|
|
267
|
-
extends: '../../tsconfig.json',
|
|
268
|
-
compilerOptions: {
|
|
269
|
-
baseUrl: '.',
|
|
270
|
-
paths: { '@/*': ['src/*'] },
|
|
271
|
-
types: ['node', '@tarojs/taro', '@tarojs/components'],
|
|
272
|
-
},
|
|
273
|
-
include: ['src', 'config', 'types'],
|
|
274
|
-
}),
|
|
275
|
-
)
|
|
276
230
|
write(target, 'apps/taro-app/config/dev.ts', platformConfig(true))
|
|
277
231
|
write(target, 'apps/taro-app/config/prod.ts', platformConfig(false))
|
|
278
232
|
const modulePackages = [
|
|
@@ -343,22 +297,6 @@ function writeHost(target, projectName, modules, packages) {
|
|
|
343
297
|
'apps/taro-app/src/app.scss',
|
|
344
298
|
"@use '@liujitcn/kratos-taro-app-ui/styles/theme.scss';\n@use '@liujitcn/kratos-taro-app-core/styles/base.scss';\n",
|
|
345
299
|
)
|
|
346
|
-
write(
|
|
347
|
-
target,
|
|
348
|
-
'apps/taro-app/src/app.tsx',
|
|
349
|
-
`import type { PropsWithChildren } from 'react'
|
|
350
|
-
import { useLaunch } from '@tarojs/taro'
|
|
351
|
-
import { bootstrapKratosTaroApp } from '@liujitcn/kratos-taro-app-core'
|
|
352
|
-
import { moduleManifest } from './module-manifest'
|
|
353
|
-
import './app.scss'
|
|
354
|
-
|
|
355
|
-
/** Taro 宿主根组件。 */
|
|
356
|
-
export default function App({ children }: PropsWithChildren) {
|
|
357
|
-
useLaunch(() => bootstrapKratosTaroApp({ modules: moduleManifest }))
|
|
358
|
-
return children
|
|
359
|
-
}
|
|
360
|
-
`,
|
|
361
|
-
)
|
|
362
300
|
write(target, 'apps/taro-app/src/module-manifest.ts', moduleManifest(modules, packages))
|
|
363
301
|
write(
|
|
364
302
|
target,
|
|
@@ -387,6 +325,7 @@ export default function BootstrapPage() {
|
|
|
387
325
|
)
|
|
388
326
|
}
|
|
389
327
|
|
|
328
|
+
/** 生成本地 Taro 模块与独立构建入口。 */
|
|
390
329
|
function writeLocalModule(target, projectName, name) {
|
|
391
330
|
const identifier = `${toCamelCase(name)}Module`
|
|
392
331
|
write(
|
|
@@ -409,6 +348,7 @@ function writeLocalModule(target, projectName, name) {
|
|
|
409
348
|
'./package.json': './package.json',
|
|
410
349
|
},
|
|
411
350
|
scripts: {
|
|
351
|
+
build: 'pnpm build:entries && pnpm pack --pack-destination ../../../dist/npm',
|
|
412
352
|
'build:entries':
|
|
413
353
|
'esbuild src/build.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/build.mjs',
|
|
414
354
|
tsc: 'tsc --noEmit -p tsconfig.json',
|
|
@@ -457,12 +397,14 @@ export const ${toCamelCase(name)}Pages: Record<string, KratosTaroPageConfig> = {
|
|
|
457
397
|
`packages/modules/${name}/src/index.ts`,
|
|
458
398
|
`import { defineKratosTaroModule } from '@liujitcn/kratos-taro-app-core'
|
|
459
399
|
import { ${toCamelCase(name)}Pages } from './pages'
|
|
400
|
+
import { LOCALE_MESSAGES } from './locales/generated'
|
|
460
401
|
|
|
461
402
|
/** ${name} 业务模块。 */
|
|
462
403
|
export const ${identifier} = defineKratosTaroModule({
|
|
463
404
|
name: '@local/${name}',
|
|
464
405
|
pages: ${toCamelCase(name)}Pages,
|
|
465
406
|
views: {},
|
|
407
|
+
messages: LOCALE_MESSAGES,
|
|
466
408
|
})
|
|
467
409
|
`,
|
|
468
410
|
)
|
|
@@ -486,22 +428,24 @@ export const buildModule = defineKratosTaroBuildModule({
|
|
|
486
428
|
)
|
|
487
429
|
}
|
|
488
430
|
|
|
431
|
+
/** 生成模块清单,导入别名避免本地 system 与内置模块重名。 */
|
|
489
432
|
function moduleManifest(modules, packages) {
|
|
490
433
|
const imports = [
|
|
491
434
|
"import { coreModule } from '@liujitcn/kratos-taro-app-core'",
|
|
492
435
|
"import { systemModule } from '@liujitcn/kratos-taro-app-system'",
|
|
493
|
-
...modules.map((name) => `import { ${toCamelCase(name)}Module } from '@local/${name}'`),
|
|
436
|
+
...modules.map((name, index) => `import { ${toCamelCase(name)}Module as localModule${index} } from '@local/${name}'`),
|
|
494
437
|
...packages.map((name, index) => `import packageModule${index} from '${name}'`),
|
|
495
438
|
]
|
|
496
439
|
const members = [
|
|
497
440
|
'coreModule',
|
|
498
441
|
'systemModule',
|
|
499
|
-
...modules.map((
|
|
442
|
+
...modules.map((_, index) => `localModule${index}`),
|
|
500
443
|
...packages.map((_, index) => `packageModule${index}`),
|
|
501
444
|
]
|
|
502
445
|
return `${imports.join('\n')}\n\n/** 宿主唯一模块清单,顺序决定静态视图覆盖优先级。 */\nexport const moduleManifest = [${members.join(', ')}]\n`
|
|
503
446
|
}
|
|
504
447
|
|
|
448
|
+
/** 生成宿主构建配置,让已装配 npm 源码包参与脚本和样式编译。 */
|
|
505
449
|
function hostConfig(projectName, packageNames) {
|
|
506
450
|
return `import { createRequire } from 'node:module'
|
|
507
451
|
import { existsSync, readFileSync } from 'node:fs'
|
|
@@ -604,6 +548,8 @@ export default defineConfig<'webpack5'>(async (merge) => {
|
|
|
604
548
|
webpackChain: configureWebpack,
|
|
605
549
|
},
|
|
606
550
|
h5: {
|
|
551
|
+
// Taro 默认跳过 node_modules 样式,已装配源码包需要进行 px 到 rem 转换。
|
|
552
|
+
esnextModules: sourceRoots,
|
|
607
553
|
publicPath,
|
|
608
554
|
staticDirectory: 'static',
|
|
609
555
|
router: { mode: 'hash' },
|
|
@@ -649,10 +595,12 @@ export default ${JSON.stringify(
|
|
|
649
595
|
`
|
|
650
596
|
}
|
|
651
597
|
|
|
598
|
+
/** 读取重复模块参数并识别项目集成开关。 */
|
|
652
599
|
function readOptions(args, option) {
|
|
653
600
|
const values = []
|
|
654
601
|
for (let index = 0; index < args.length; index += 1) {
|
|
655
602
|
const argument = args[index]
|
|
603
|
+
if (argument === '--kratos-project') continue
|
|
656
604
|
if (argument !== '--module' && argument !== '--with') {
|
|
657
605
|
throw new Error(`未知参数:${argument}`)
|
|
658
606
|
}
|
|
@@ -674,9 +622,9 @@ function validateProjectName(name) {
|
|
|
674
622
|
if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new Error(`项目名必须使用 kebab-case:${name}`)
|
|
675
623
|
}
|
|
676
624
|
|
|
625
|
+
/** 校验本地业务模块名称。 */
|
|
677
626
|
function validateModuleName(name) {
|
|
678
627
|
if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new Error(`模块名无效:${name}`)
|
|
679
|
-
if (name === 'system') throw new Error('模块名不能使用保留名称:system')
|
|
680
628
|
}
|
|
681
629
|
|
|
682
630
|
function validatePackageName(name) {
|
|
@@ -704,9 +652,34 @@ function printHelp() {
|
|
|
704
652
|
'',
|
|
705
653
|
'示例:',
|
|
706
654
|
' kratos-taro-app create customer-app',
|
|
707
|
-
' kratos-taro-app create
|
|
655
|
+
' kratos-taro-app create business-app --module business,report',
|
|
708
656
|
' kratos-taro-app create customer-app --with @acme/customer-module',
|
|
709
657
|
'',
|
|
710
658
|
].join('\n'),
|
|
711
659
|
)
|
|
712
660
|
}
|
|
661
|
+
|
|
662
|
+
/** 渲染随 CLI 发布的宿主与工具链模板。 */
|
|
663
|
+
function renderWorkspaceTemplates(source, target, tokens) {
|
|
664
|
+
for (const entry of readdirSync(source, { withFileTypes: true })) {
|
|
665
|
+
const input = new URL(entry.name + (entry.isDirectory() ? '/' : ''), source)
|
|
666
|
+
const output = resolve(target, entry.name)
|
|
667
|
+
if (entry.isDirectory()) {
|
|
668
|
+
mkdirSync(output, { recursive: true })
|
|
669
|
+
renderWorkspaceTemplates(input, output, tokens)
|
|
670
|
+
} else {
|
|
671
|
+
const content = Object.entries(tokens).reduce((value, [key, replacement]) => value.replaceAll(key, replacement), readFileSync(input, 'utf8'))
|
|
672
|
+
writeFileSync(output, content)
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/** 创建业务模块语言源文件及 API、RPC、测试目录,注册产物由同步命令生成。 */
|
|
678
|
+
function writeModuleLocales(target, name) {
|
|
679
|
+
for (const directory of ['src/api', 'src/rpc', 'test']) {
|
|
680
|
+
write(target, `packages/modules/${name}/${directory}/.gitkeep`, '')
|
|
681
|
+
}
|
|
682
|
+
for (const locale of ['zh-CN', 'en-US', 'zh-TW', 'ja-JP']) {
|
|
683
|
+
write(target, `packages/modules/${name}/src/locales/${locale}.json`, '{}\n')
|
|
684
|
+
}
|
|
685
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
root: true,
|
|
3
|
+
extends: ['taro/react'],
|
|
4
|
+
parserOptions: {
|
|
5
|
+
project: false,
|
|
6
|
+
requireConfigFile: false,
|
|
7
|
+
},
|
|
8
|
+
rules: {
|
|
9
|
+
'react/react-in-jsx-scope': 'off',
|
|
10
|
+
},
|
|
11
|
+
ignorePatterns: [
|
|
12
|
+
'**/dist/**',
|
|
13
|
+
'**/src/rpc/**',
|
|
14
|
+
'node_modules/**',
|
|
15
|
+
'apps/taro-app/src/pages/**',
|
|
16
|
+
'!apps/taro-app/src/pages/bootstrap/**',
|
|
17
|
+
'apps/taro-app/src/pagesMember/**',
|
|
18
|
+
],
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PropsWithChildren } from 'react'
|
|
2
|
+
import { useDidHide, useDidShow, useLaunch } from '@tarojs/taro'
|
|
3
|
+
import { bootstrapKratosTaroApp } from '@liujitcn/kratos-taro-app-core'
|
|
4
|
+
import {
|
|
5
|
+
pauseNotificationPolling,
|
|
6
|
+
resumeNotificationPolling,
|
|
7
|
+
} from '@liujitcn/kratos-taro-app-system'
|
|
8
|
+
import { moduleManifest } from './module-manifest'
|
|
9
|
+
import './app.scss'
|
|
10
|
+
|
|
11
|
+
/** Taro 宿主根组件。 */
|
|
12
|
+
export default function App({ children }: PropsWithChildren) {
|
|
13
|
+
useLaunch(() => {
|
|
14
|
+
bootstrapKratosTaroApp({ modules: moduleManifest })
|
|
15
|
+
})
|
|
16
|
+
useDidShow(() => resumeNotificationPolling())
|
|
17
|
+
useDidHide(() => pauseNotificationPolling())
|
|
18
|
+
return children
|
|
19
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"baseUrl": ".",
|
|
5
|
+
"paths": { "@/*": ["src/*"] },
|
|
6
|
+
"types": ["node", "@tarojs/taro", "@tarojs/components"],
|
|
7
|
+
"allowImportingTsExtensions": true,
|
|
8
|
+
"noEmit": true
|
|
9
|
+
},
|
|
10
|
+
"include": ["src", "config", "types"]
|
|
11
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"description": "Independent pnpm workspace for __PROJECT_NAME__.",
|
|
4
|
+
"private": true,
|
|
5
|
+
"packageManager": "pnpm@10.13.1",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"prepare:modules": "pnpm --recursive --filter './packages/modules/**' --if-present run build:entries",
|
|
8
|
+
"dev:h5": "pnpm prepare:modules && pnpm --filter @local/kratos-taro-app dev:h5",
|
|
9
|
+
"dev:mp-weixin": "pnpm prepare:modules && pnpm --filter @local/kratos-taro-app dev:mp-weixin",
|
|
10
|
+
"build:h5": "pnpm prepare:modules && pnpm --filter @local/kratos-taro-app build:h5",
|
|
11
|
+
"build:mp-weixin": "pnpm prepare:modules && pnpm --filter @local/kratos-taro-app build:mp-weixin",
|
|
12
|
+
"tsc": "pnpm --recursive --if-present run tsc",
|
|
13
|
+
"test": "node --test",
|
|
14
|
+
"build:packages": "pnpm --recursive --filter './packages/modules/**' run build",
|
|
15
|
+
"check:exports": "pnpm run prepare:modules && node scripts/check-package-exports.mjs",
|
|
16
|
+
"lint": "eslint . --fix --ext .js,.mjs,.ts,.tsx",
|
|
17
|
+
"i18n:sync": "node scripts/sync-locales.mjs --write",
|
|
18
|
+
"i18n:check": "node scripts/sync-locales.mjs"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@babel/core": "7.28.4",
|
|
22
|
+
"@babel/preset-react": "7.28.5",
|
|
23
|
+
"@types/node": "20.19.9",
|
|
24
|
+
"@types/react": "18.3.23",
|
|
25
|
+
"@types/react-dom": "18.3.7",
|
|
26
|
+
"babel-preset-taro": "4.2.1",
|
|
27
|
+
"cross-env": "7.0.3",
|
|
28
|
+
"esbuild": "0.25.8",
|
|
29
|
+
"sass": "1.89.2",
|
|
30
|
+
"tsconfig-paths-webpack-plugin": "4.2.0",
|
|
31
|
+
"typescript": "5.8.3",
|
|
32
|
+
"webpack": "5.91.0",
|
|
33
|
+
"@tarojs/plugin-platform-h5": "4.2.1",
|
|
34
|
+
"@tarojs/plugin-platform-weapp": "4.2.1",
|
|
35
|
+
"eslint": "8.57.1",
|
|
36
|
+
"eslint-config-taro": "4.2.1",
|
|
37
|
+
"eslint-plugin-react": "7.37.5",
|
|
38
|
+
"eslint-plugin-react-hooks": "4.6.2",
|
|
39
|
+
"less": "4.8.1",
|
|
40
|
+
"prettier": "3.6.2",
|
|
41
|
+
"ts-proto": "2.7.7",
|
|
42
|
+
"turbo": "2.5.5"
|
|
43
|
+
},
|
|
44
|
+
"pnpm": {
|
|
45
|
+
"overrides": {
|
|
46
|
+
"swiper": "12.1.2",
|
|
47
|
+
"tmp": "0.2.6"
|
|
48
|
+
},
|
|
49
|
+
"onlyBuiltDependencies": [
|
|
50
|
+
"@swc/core",
|
|
51
|
+
"@tarojs/binding",
|
|
52
|
+
"@tarojs/cli",
|
|
53
|
+
"esbuild"
|
|
54
|
+
],
|
|
55
|
+
"ignoredBuiltDependencies": [
|
|
56
|
+
"@nutui/nutui-react-taro",
|
|
57
|
+
"@parcel/watcher",
|
|
58
|
+
"core-js",
|
|
59
|
+
"core-js-pure"
|
|
60
|
+
],
|
|
61
|
+
"auditConfig": {
|
|
62
|
+
"ignoreCves": [
|
|
63
|
+
"CVE-2022-25900",
|
|
64
|
+
"CVE-2024-29415",
|
|
65
|
+
"CVE-2025-25975",
|
|
66
|
+
"CVE-2022-37620",
|
|
67
|
+
"CVE-2026-53486",
|
|
68
|
+
"CVE-2026-10732",
|
|
69
|
+
"CVE-2025-71330",
|
|
70
|
+
"CVE-2025-71329",
|
|
71
|
+
"CVE-2026-39243"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from 'node:fs/promises'
|
|
2
|
+
import { dirname, extname, relative, resolve, sep } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import ts from 'typescript'
|
|
5
|
+
|
|
6
|
+
const workspaceRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
7
|
+
const coreRoot = resolve(workspaceRoot, 'packages/core')
|
|
8
|
+
const uiRoot = resolve(workspaceRoot, 'packages/ui')
|
|
9
|
+
const systemRoot = resolve(workspaceRoot, 'packages/modules/system')
|
|
10
|
+
const ignoredDirectories = new Set(['.git', '.turbo', 'dist', 'node_modules'])
|
|
11
|
+
const sourceExtensions = new Set(['.js', '.mjs', '.ts', '.tsx'])
|
|
12
|
+
const allFiles = await collectFiles(workspaceRoot)
|
|
13
|
+
const packageFiles = allFiles.filter((file) => file.endsWith(`${sep}package.json`))
|
|
14
|
+
const packages = new Map()
|
|
15
|
+
const publicPackageVersions = new Map()
|
|
16
|
+
const violations = []
|
|
17
|
+
|
|
18
|
+
for (const packageFile of packageFiles) {
|
|
19
|
+
const metadata = JSON.parse(await readFile(packageFile, 'utf8'))
|
|
20
|
+
if (!metadata.name || !metadata.exports) continue
|
|
21
|
+
if (!metadata.private) {
|
|
22
|
+
publicPackageVersions.set(metadata.name, metadata.version)
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
const packageRoot = dirname(packageFile)
|
|
26
|
+
const exportEntries = Object.entries(metadata.exports)
|
|
27
|
+
packages.set(metadata.name, { packageRoot, exportEntries })
|
|
28
|
+
for (const [, value] of exportEntries) {
|
|
29
|
+
for (const target of collectTargets(value)) {
|
|
30
|
+
const prefix = target.includes('*') ? target.slice(0, target.indexOf('*')) : target
|
|
31
|
+
const checkPath = resolve(packageRoot, prefix)
|
|
32
|
+
if (!(await pathExists(checkPath))) {
|
|
33
|
+
throw new Error(`${metadata.name} 导出目标不存在:${target}`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const distinctPublicVersions = new Set(publicPackageVersions.values())
|
|
40
|
+
if (distinctPublicVersions.size > 1 || [...distinctPublicVersions].some((version) => !version)) {
|
|
41
|
+
const versions = [...publicPackageVersions]
|
|
42
|
+
.map(([name, version]) => `${name}@${version}`)
|
|
43
|
+
.join('、')
|
|
44
|
+
throw new Error(`Taro 公开包版本必须保持一致:${versions}`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const sourceFile of allFiles.filter((file) => sourceExtensions.has(extname(file)))) {
|
|
48
|
+
if (sourceFile.includes(`${sep}src${sep}rpc${sep}`)) continue
|
|
49
|
+
const source = await readFile(sourceFile, 'utf8')
|
|
50
|
+
for (const imported of ts.preProcessFile(source, true, true).importedFiles) {
|
|
51
|
+
const specifier = imported.fileName
|
|
52
|
+
const line = source.slice(0, imported.pos).split('\n').length
|
|
53
|
+
if ((specifier === '@' || specifier.startsWith('@/')) && !isWithin(sourceFile, coreRoot)) {
|
|
54
|
+
violations.push(
|
|
55
|
+
`${format(sourceFile, line)}: 非 core 代码不得使用 core 私有别名 ${specifier}`,
|
|
56
|
+
)
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
if (specifier.startsWith('@system/') && !isWithin(sourceFile, systemRoot)) {
|
|
60
|
+
violations.push(
|
|
61
|
+
`${format(sourceFile, line)}: 非 system 代码不得使用 system 私有别名 ${specifier}`,
|
|
62
|
+
)
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
if (specifier.startsWith('.')) {
|
|
66
|
+
const sourcePackage = findPackageByPath(sourceFile)
|
|
67
|
+
const targetPackage = findPackageByPath(resolve(dirname(sourceFile), specifier))
|
|
68
|
+
if (sourcePackage && targetPackage && sourcePackage !== targetPackage) {
|
|
69
|
+
violations.push(`${format(sourceFile, line)}: 禁止通过相对路径跨包引用 ${specifier}`)
|
|
70
|
+
}
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
const packageName = [...packages.keys()].find(
|
|
74
|
+
(name) => specifier === name || specifier.startsWith(`${name}/`),
|
|
75
|
+
)
|
|
76
|
+
if (!packageName) continue
|
|
77
|
+
const subpath = specifier === packageName ? '.' : `./${specifier.slice(packageName.length + 1)}`
|
|
78
|
+
const exported = packages
|
|
79
|
+
.get(packageName)
|
|
80
|
+
.exportEntries.some(([pattern]) => matchExportPattern(pattern, subpath))
|
|
81
|
+
if (!exported) {
|
|
82
|
+
violations.push(
|
|
83
|
+
`${format(sourceFile, line)}: ${specifier} 未在 ${packageName} exports 中公开`,
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const [packageName, { packageRoot }] of packages) {
|
|
90
|
+
if (packageName === '@liujitcn/kratos-taro-app-core' && packageRoot !== coreRoot) {
|
|
91
|
+
violations.push(`${packageName}: core 包目录异常`)
|
|
92
|
+
}
|
|
93
|
+
if (packageName === '@liujitcn/kratos-taro-app-ui' && packageRoot !== uiRoot) {
|
|
94
|
+
violations.push(`${packageName}: UI 包目录异常`)
|
|
95
|
+
}
|
|
96
|
+
if (packageName === '@liujitcn/kratos-taro-app-system' && packageRoot !== systemRoot) {
|
|
97
|
+
violations.push(`${packageName}: system 包目录异常`)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (violations.length) {
|
|
102
|
+
console.error(
|
|
103
|
+
`Taro package exports 检查失败:\n${violations.map((item) => ` ${item}`).join('\n')}`,
|
|
104
|
+
)
|
|
105
|
+
process.exit(1)
|
|
106
|
+
}
|
|
107
|
+
console.log(`Taro package exports 检查通过(${[...packages.keys()].join('、')})`)
|
|
108
|
+
|
|
109
|
+
async function collectFiles(directory) {
|
|
110
|
+
const files = []
|
|
111
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
112
|
+
if (entry.isDirectory() && ignoredDirectories.has(entry.name)) continue
|
|
113
|
+
const path = resolve(directory, entry.name)
|
|
114
|
+
if (entry.isDirectory()) files.push(...(await collectFiles(path)))
|
|
115
|
+
else files.push(path)
|
|
116
|
+
}
|
|
117
|
+
return files
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function collectTargets(value) {
|
|
121
|
+
if (typeof value === 'string') return [value]
|
|
122
|
+
if (!value || typeof value !== 'object') return []
|
|
123
|
+
return Object.values(value).flatMap(collectTargets)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function pathExists(path) {
|
|
127
|
+
try {
|
|
128
|
+
await stat(path)
|
|
129
|
+
return true
|
|
130
|
+
} catch {
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function matchExportPattern(pattern, subpath) {
|
|
136
|
+
if (!pattern.includes('*')) return pattern === subpath
|
|
137
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace('\\*', '.+')
|
|
138
|
+
return new RegExp(`^${escaped}$`).test(subpath)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function findPackageByPath(file) {
|
|
142
|
+
return [...packages.values()].find(({ packageRoot }) => isWithin(file, packageRoot))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isWithin(file, directory) {
|
|
146
|
+
const path = relative(directory, file)
|
|
147
|
+
return path === '' || (!path.startsWith('..') && !path.startsWith(sep))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function format(file, line) {
|
|
151
|
+
return `${relative(workspaceRoot, file)}:${line}`
|
|
152
|
+
}
|
|
@@ -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
|
+
}
|