@liujitcn/kratos-uni-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 +11 -0
- package/bin/kratos-uni-app.mjs +3 -1
- package/package.json +3 -2
- package/src/index.mjs +51 -74
- package/templates/workspace/apps/uni-app/src/App.vue +15 -0
- package/templates/workspace/apps/uni-app/src/main.ts +28 -0
- package/templates/workspace/apps/uni-app/src/vite-env.d.ts +9 -0
- package/templates/workspace/apps/uni-app/tsconfig.json +4 -0
- package/templates/workspace/eslint.config.mjs +79 -0
- package/templates/workspace/package.json +53 -0
- package/templates/workspace/scripts/check-package-exports.mjs +148 -0
- package/templates/workspace/scripts/sync-locales.mjs +46 -0
package/README.md
CHANGED
|
@@ -48,3 +48,14 @@ pnpm dlx @liujitcn/kratos-uni-app-cli create my-app --with @acme/pay
|
|
|
48
48
|
`pnpm` 与 `tar`,不安装生成项目的依赖,并在结束后清理临时目录。
|
|
49
49
|
|
|
50
50
|
包构建和脚手架验证命令见 [workspace 文档](../../README.md)。
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### CLI 生成边界
|
|
54
|
+
|
|
55
|
+
CLI 直接生成完整宿主、本地业务模块、四种语言源文件与注册入口、类型检查、lint 和打包配置。
|
|
56
|
+
支持本地 `system` 与其他模块一起创建;管理端本地 System 继承内置能力,运行时只注册一次,
|
|
57
|
+
构建仍扫描内置源码。应用端本地模块使用独立导入别名,保留内置 System。
|
|
58
|
+
|
|
59
|
+
通过 `--kratos-project` 生成与 Go 后端配套的前端:H5 输出到 `backend/data/<terminal>`,
|
|
60
|
+
管理端 CLI 同时在 workspace 父目录创建共享 `Makefile` 与 `scripts`。Go 调用方仅传参执行 CLI。
|
|
61
|
+
新增语言或修改语言文件后运行 `pnpm i18n:sync`;`pnpm i18n:check` 校验注册文件是否同步。
|
package/bin/kratos-uni-app.mjs
CHANGED
|
@@ -8,10 +8,12 @@ if (args[0] !== 'create' || !args[1]) {
|
|
|
8
8
|
} else {
|
|
9
9
|
const modules = []
|
|
10
10
|
const packages = []
|
|
11
|
+
let kratosProject = false
|
|
11
12
|
for (let index = 2; index < args.length; index += 1) {
|
|
12
13
|
if (args[index] === '--module' && args[index + 1]) modules.push(args[++index])
|
|
13
14
|
else if (args[index] === '--with' && args[index + 1]) packages.push(args[++index])
|
|
15
|
+
else if (args[index] === '--kratos-project') kratosProject = true
|
|
14
16
|
else throw new Error(`未知参数:${args[index]}`)
|
|
15
17
|
}
|
|
16
|
-
scaffoldKratosApp(args[1], { modules, packages })
|
|
18
|
+
scaffoldKratosApp(args[1], { modules, packages, kratosProject })
|
|
17
19
|
}
|
package/package.json
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
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
|
"files": [
|
|
13
13
|
"assets",
|
|
14
14
|
"bin",
|
|
15
15
|
"src",
|
|
16
|
-
"README.md"
|
|
16
|
+
"README.md",
|
|
17
|
+
"templates"
|
|
17
18
|
],
|
|
18
19
|
"bin": {
|
|
19
20
|
"kratos-uni-app": "./bin/kratos-uni-app.mjs"
|
package/src/index.mjs
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import {
|
|
3
|
+
copyFileSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from 'node:fs'
|
|
2
10
|
import { basename, resolve } from 'node:path'
|
|
3
11
|
|
|
4
12
|
const cliPackage = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
|
|
@@ -22,43 +30,9 @@ export function scaffoldKratosApp(targetPath, options = {}) {
|
|
|
22
30
|
)
|
|
23
31
|
|
|
24
32
|
write(target, 'pnpm-workspace.yaml', 'packages:\n - apps/*\n - packages/modules/*\n')
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
json({
|
|
29
|
-
name: projectName,
|
|
30
|
-
description: `Independent pnpm workspace for ${projectName}.`,
|
|
31
|
-
private: true,
|
|
32
|
-
packageManager: 'pnpm@10.13.1',
|
|
33
|
-
scripts: {
|
|
34
|
-
prepare: 'pnpm run prepare:modules',
|
|
35
|
-
'prepare:modules': 'node -e "process.exit(0)"',
|
|
36
|
-
'dev:h5': 'pnpm --filter @liujitcn/kratos-uni-app dev:h5',
|
|
37
|
-
'dev:mp-weixin': 'pnpm --filter @liujitcn/kratos-uni-app dev:mp-weixin',
|
|
38
|
-
'build:h5': 'pnpm --filter @liujitcn/kratos-uni-app build:h5',
|
|
39
|
-
'build:mp-weixin': 'pnpm --filter @liujitcn/kratos-uni-app build:mp-weixin',
|
|
40
|
-
tsc: 'pnpm --recursive --if-present run tsc',
|
|
41
|
-
},
|
|
42
|
-
devDependencies: {
|
|
43
|
-
'@dcloudio/types': '^3.4.8',
|
|
44
|
-
'@rushstack/eslint-patch': '^1.1.4',
|
|
45
|
-
'@uni-helper/uni-app-types': '1.0.0-alpha.6',
|
|
46
|
-
'@uni-helper/uni-ui-types': '1.0.0-alpha.6',
|
|
47
|
-
'@vue/compiler-sfc': '3.4.21',
|
|
48
|
-
'@vue/eslint-config-prettier': '10.2.0',
|
|
49
|
-
'@vue/eslint-config-typescript': '14.7.0',
|
|
50
|
-
'@vue/tsconfig': '^0.7.0',
|
|
51
|
-
eslint: '9.39.1',
|
|
52
|
-
'miniprogram-api-typings': '^4.0.5',
|
|
53
|
-
prettier: '3.6.2',
|
|
54
|
-
typescript: '5.4.5',
|
|
55
|
-
'vue-tsc': '^1.8.8',
|
|
56
|
-
},
|
|
57
|
-
pnpm: {
|
|
58
|
-
onlyBuiltDependencies: ['esbuild', 'core-js', 'core-js-pure', 'vue-demi'],
|
|
59
|
-
},
|
|
60
|
-
}),
|
|
61
|
-
)
|
|
33
|
+
renderWorkspaceTemplates(new URL('../templates/workspace/', import.meta.url), target, {
|
|
34
|
+
__PROJECT_NAME__: projectName,
|
|
35
|
+
})
|
|
62
36
|
writeEnvironmentFiles(target)
|
|
63
37
|
copyFileSync(
|
|
64
38
|
new URL('../assets/favicon.ico', import.meta.url),
|
|
@@ -135,8 +109,7 @@ H5 通过局域网 IP 访问时,先在仓库根目录运行 bash scripts/gener
|
|
|
135
109
|
'node -e "import(\'@liujitcn/kratos-uni-app-core/vite\').then(({ recoverStalePageTransaction }) => recoverStalePageTransaction())"',
|
|
136
110
|
'dev:h5': 'pnpm run recover:pages && uni --mode development-h5',
|
|
137
111
|
'dev:mp-weixin': 'pnpm run recover:pages && uni -p mp-weixin --mode development',
|
|
138
|
-
'build:h5':
|
|
139
|
-
'pnpm run recover:pages && UNI_OUTPUT_DIR=../../../../backend/data/uni-app uni build --mode production-h5',
|
|
112
|
+
'build:h5': `pnpm run recover:pages && UNI_OUTPUT_DIR=${options.kratosProject ? '../../../../backend/data/uni-app' : 'dist/build/h5'} uni build --mode production-h5`,
|
|
140
113
|
'build:mp-weixin': 'pnpm run recover:pages && uni build -p mp-weixin --mode production',
|
|
141
114
|
tsc: 'vue-tsc --noEmit -p tsconfig.json',
|
|
142
115
|
},
|
|
@@ -224,39 +197,6 @@ onLoad((options) => {
|
|
|
224
197
|
})
|
|
225
198
|
</script>
|
|
226
199
|
<template><view /></template>
|
|
227
|
-
`,
|
|
228
|
-
)
|
|
229
|
-
write(
|
|
230
|
-
target,
|
|
231
|
-
'apps/uni-app/src/main.ts',
|
|
232
|
-
`import {
|
|
233
|
-
bootstrapKratosApp,
|
|
234
|
-
initializeAppNavigation,
|
|
235
|
-
pinia,
|
|
236
|
-
registerKratosAppModules,
|
|
237
|
-
registerUserStoreExtension,
|
|
238
|
-
} from '@liujitcn/kratos-uni-app-core'
|
|
239
|
-
import { createSSRApp } from 'vue'
|
|
240
|
-
import App from './App.vue'
|
|
241
|
-
import { moduleManifest } from './module-manifest'
|
|
242
|
-
|
|
243
|
-
registerKratosAppModules(moduleManifest)
|
|
244
|
-
registerUserStoreExtension({
|
|
245
|
-
onLogin: initializeAppNavigation,
|
|
246
|
-
onLogout: initializeAppNavigation,
|
|
247
|
-
onSilentLogout: initializeAppNavigation,
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
export function createApp() {
|
|
251
|
-
return bootstrapKratosApp({ app: App, createSSRApp, pinia, modules: moduleManifest })
|
|
252
|
-
}
|
|
253
|
-
`,
|
|
254
|
-
)
|
|
255
|
-
write(
|
|
256
|
-
target,
|
|
257
|
-
'apps/uni-app/src/App.vue',
|
|
258
|
-
`<script setup lang="ts"></script>
|
|
259
|
-
<style lang="scss">@use '@liujitcn/kratos-uni-app-core/styles/base.scss';</style>
|
|
260
200
|
`,
|
|
261
201
|
)
|
|
262
202
|
write(target, 'apps/uni-app/src/uni.scss', "@forward '@liujitcn/kratos-uni-app-core/uni.scss';\n")
|
|
@@ -393,6 +333,7 @@ export default defineConfig(({ mode }) => {
|
|
|
393
333
|
'./views/*': './src/views/*',
|
|
394
334
|
'./package.json': './package.json',
|
|
395
335
|
},
|
|
336
|
+
scripts: { build: 'pnpm pack --pack-destination ../../../dist/npm' },
|
|
396
337
|
dependencies: { '@liujitcn/kratos-uni-app-core': `^${publicPackageVersion}` },
|
|
397
338
|
}),
|
|
398
339
|
)
|
|
@@ -427,7 +368,10 @@ packages/modules/${name}
|
|
|
427
368
|
target,
|
|
428
369
|
`packages/modules/${name}/src/index.mjs`,
|
|
429
370
|
`import { defineKratosAppModule } from '@liujitcn/kratos-uni-app-core/module'
|
|
430
|
-
|
|
371
|
+
import { LOCALE_MESSAGES } from './locales/generated.mjs'
|
|
372
|
+
|
|
373
|
+
/** ${name} 业务模块,注册本地页面与语言资源。 */
|
|
374
|
+
export default defineKratosAppModule({ name: '@local/${name}', pages: {}, views: {}, messages: LOCALE_MESSAGES })
|
|
431
375
|
`,
|
|
432
376
|
)
|
|
433
377
|
write(
|
|
@@ -438,6 +382,10 @@ export default module
|
|
|
438
382
|
`,
|
|
439
383
|
)
|
|
440
384
|
})
|
|
385
|
+
modules.forEach((name) => writeModuleLocales(target, name))
|
|
386
|
+
execFileSync(process.execPath, [resolve(target, 'scripts/sync-locales.mjs'), '--write'], {
|
|
387
|
+
stdio: 'inherit',
|
|
388
|
+
})
|
|
441
389
|
return target
|
|
442
390
|
}
|
|
443
391
|
|
|
@@ -464,6 +412,7 @@ function writeEnvironmentFiles(target) {
|
|
|
464
412
|
)
|
|
465
413
|
}
|
|
466
414
|
|
|
415
|
+
/** 校验本地业务模块名称。 */
|
|
467
416
|
function validateModuleName(name) {
|
|
468
417
|
if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new Error(`模块名无效:${name}`)
|
|
469
418
|
}
|
|
@@ -481,3 +430,31 @@ function write(root, file, content) {
|
|
|
481
430
|
function json(value) {
|
|
482
431
|
return `${JSON.stringify(value, null, 2)}\n`
|
|
483
432
|
}
|
|
433
|
+
|
|
434
|
+
/** 渲染随 CLI 发布的宿主与工具链模板。 */
|
|
435
|
+
function renderWorkspaceTemplates(source, target, tokens) {
|
|
436
|
+
for (const entry of readdirSync(source, { withFileTypes: true })) {
|
|
437
|
+
const input = new URL(entry.name + (entry.isDirectory() ? '/' : ''), source)
|
|
438
|
+
const output = resolve(target, entry.name)
|
|
439
|
+
if (entry.isDirectory()) {
|
|
440
|
+
mkdirSync(output, { recursive: true })
|
|
441
|
+
renderWorkspaceTemplates(input, output, tokens)
|
|
442
|
+
} else {
|
|
443
|
+
const content = Object.entries(tokens).reduce(
|
|
444
|
+
(value, [key, replacement]) => value.replaceAll(key, replacement),
|
|
445
|
+
readFileSync(input, 'utf8'),
|
|
446
|
+
)
|
|
447
|
+
writeFileSync(output, content)
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** 创建业务模块语言源文件及 API、RPC、测试目录,注册产物由同步命令生成。 */
|
|
453
|
+
function writeModuleLocales(target, name) {
|
|
454
|
+
for (const directory of ['src/api', 'src/rpc', 'test']) {
|
|
455
|
+
write(target, `packages/modules/${name}/${directory}/.gitkeep`, '')
|
|
456
|
+
}
|
|
457
|
+
for (const locale of ['zh-CN', 'en-US', 'zh-TW', 'ja-JP']) {
|
|
458
|
+
write(target, `packages/modules/${name}/src/locales/${locale}.json`, '{}\n')
|
|
459
|
+
}
|
|
460
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onHide, onLaunch, onShow } from '@dcloudio/uni-app'
|
|
3
|
+
import {
|
|
4
|
+
pauseNotificationPolling,
|
|
5
|
+
resumeNotificationPolling,
|
|
6
|
+
} from '@liujitcn/kratos-uni-app-system'
|
|
7
|
+
|
|
8
|
+
onLaunch(() => console.log('App Launch'))
|
|
9
|
+
onShow(() => resumeNotificationPolling())
|
|
10
|
+
onHide(() => pauseNotificationPolling())
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<style lang="scss">
|
|
14
|
+
@use '@liujitcn/kratos-uni-app-core/styles/base.scss';
|
|
15
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bootstrapKratosApp,
|
|
3
|
+
initializeAppNavigation,
|
|
4
|
+
pinia,
|
|
5
|
+
registerKratosAppModules,
|
|
6
|
+
registerUserStoreExtension,
|
|
7
|
+
} from '@liujitcn/kratos-uni-app-core'
|
|
8
|
+
import { createSSRApp } from 'vue'
|
|
9
|
+
import App from './App.vue'
|
|
10
|
+
import { moduleManifest } from './module-manifest'
|
|
11
|
+
import '@liujitcn/kratos-uni-app-system'
|
|
12
|
+
|
|
13
|
+
registerKratosAppModules(moduleManifest)
|
|
14
|
+
registerUserStoreExtension({
|
|
15
|
+
onLogin: initializeAppNavigation,
|
|
16
|
+
onLogout: initializeAppNavigation,
|
|
17
|
+
onSilentLogout: initializeAppNavigation,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
/** 创建 uni-app 宿主实例。 */
|
|
21
|
+
export function createApp() {
|
|
22
|
+
return bootstrapKratosApp({
|
|
23
|
+
app: App,
|
|
24
|
+
createSSRApp,
|
|
25
|
+
pinia,
|
|
26
|
+
modules: moduleManifest,
|
|
27
|
+
})
|
|
28
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import pluginVue from 'eslint-plugin-vue'
|
|
2
|
+
import prettier from '@vue/eslint-config-prettier'
|
|
3
|
+
import {
|
|
4
|
+
configureVueProject,
|
|
5
|
+
defineConfigWithVueTs,
|
|
6
|
+
vueTsConfigs,
|
|
7
|
+
} from '@vue/eslint-config-typescript'
|
|
8
|
+
|
|
9
|
+
const projectFiles = ['**/*.{vue,js,jsx,cjs,mjs,ts,tsx,cts,mts}']
|
|
10
|
+
|
|
11
|
+
configureVueProject({
|
|
12
|
+
scriptLangs: ['ts', 'js'],
|
|
13
|
+
rootDir: import.meta.dirname,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export default defineConfigWithVueTs(
|
|
17
|
+
{
|
|
18
|
+
ignores: [
|
|
19
|
+
'logs',
|
|
20
|
+
'*.log',
|
|
21
|
+
'npm-debug.log*',
|
|
22
|
+
'yarn-debug.log*',
|
|
23
|
+
'yarn-error.log*',
|
|
24
|
+
'pnpm-debug.log*',
|
|
25
|
+
'lerna-debug.log*',
|
|
26
|
+
'node_modules',
|
|
27
|
+
'**/dist/**',
|
|
28
|
+
'.DS_Store',
|
|
29
|
+
'*.local',
|
|
30
|
+
'.eslintrc.cjs',
|
|
31
|
+
'**/src/rpc/**',
|
|
32
|
+
// 语言注册文件由同步命令维护,避免格式化后被判为过期。
|
|
33
|
+
'**/src/locales/generated.mjs',
|
|
34
|
+
'apps/uni-app/src/pages/**',
|
|
35
|
+
'!apps/uni-app/src/pages/bootstrap/**',
|
|
36
|
+
'apps/uni-app/src/pagesMember/**',
|
|
37
|
+
'packages/core/src/module.mjs',
|
|
38
|
+
'packages/core/src/vite.mjs',
|
|
39
|
+
'packages/modules/system/src/index.mjs',
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
pluginVue.configs['flat/essential'],
|
|
43
|
+
vueTsConfigs.recommended,
|
|
44
|
+
prettier,
|
|
45
|
+
{
|
|
46
|
+
files: projectFiles,
|
|
47
|
+
languageOptions: {
|
|
48
|
+
ecmaVersion: 'latest',
|
|
49
|
+
globals: {
|
|
50
|
+
uni: 'readonly',
|
|
51
|
+
wx: 'readonly',
|
|
52
|
+
WechatMiniprogram: 'readonly',
|
|
53
|
+
getCurrentPages: 'readonly',
|
|
54
|
+
UniApp: 'readonly',
|
|
55
|
+
UniHelper: 'readonly',
|
|
56
|
+
Page: 'readonly',
|
|
57
|
+
AnyObject: 'readonly',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
rules: {
|
|
61
|
+
'prettier/prettier': [
|
|
62
|
+
'warn',
|
|
63
|
+
{
|
|
64
|
+
singleQuote: true,
|
|
65
|
+
semi: false,
|
|
66
|
+
printWidth: 100,
|
|
67
|
+
trailingComma: 'all',
|
|
68
|
+
endOfLine: 'auto',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
'vue/multi-word-component-names': 'off',
|
|
72
|
+
'vue/no-setup-props-reactivity-loss': 'off',
|
|
73
|
+
'vue/no-deprecated-html-element-is': 'off',
|
|
74
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
75
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
76
|
+
'@typescript-eslint/no-empty-object-type': 'off',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
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": "pnpm run prepare:modules",
|
|
8
|
+
"prepare:modules": "pnpm --recursive --filter './packages/modules/**' --if-present run build:entries",
|
|
9
|
+
"dev:h5": "pnpm --filter @liujitcn/kratos-uni-app dev:h5",
|
|
10
|
+
"dev:mp-weixin": "pnpm --filter @liujitcn/kratos-uni-app dev:mp-weixin",
|
|
11
|
+
"build:h5": "pnpm --filter @liujitcn/kratos-uni-app build:h5",
|
|
12
|
+
"build:mp-weixin": "pnpm --filter @liujitcn/kratos-uni-app build:mp-weixin",
|
|
13
|
+
"tsc": "pnpm --recursive --if-present run tsc",
|
|
14
|
+
"test": "node --test",
|
|
15
|
+
"build:packages": "pnpm --recursive --filter './packages/modules/**' run build",
|
|
16
|
+
"check:exports": "pnpm run prepare:modules && node scripts/check-package-exports.mjs",
|
|
17
|
+
"lint": "eslint . --fix",
|
|
18
|
+
"i18n:sync": "node scripts/sync-locales.mjs --write",
|
|
19
|
+
"i18n:check": "node scripts/sync-locales.mjs"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@dcloudio/types": "^3.4.8",
|
|
23
|
+
"@rushstack/eslint-patch": "^1.1.4",
|
|
24
|
+
"@uni-helper/uni-app-types": "1.0.0-alpha.6",
|
|
25
|
+
"@uni-helper/uni-ui-types": "1.0.0-alpha.6",
|
|
26
|
+
"@vue/compiler-sfc": "3.4.21",
|
|
27
|
+
"@vue/eslint-config-prettier": "10.2.0",
|
|
28
|
+
"@vue/eslint-config-typescript": "14.7.0",
|
|
29
|
+
"@vue/tsconfig": "^0.7.0",
|
|
30
|
+
"eslint": "9.39.1",
|
|
31
|
+
"miniprogram-api-typings": "^4.0.5",
|
|
32
|
+
"prettier": "3.6.2",
|
|
33
|
+
"typescript": "5.4.5",
|
|
34
|
+
"vue-tsc": "^1.8.8",
|
|
35
|
+
"eslint-plugin-vue": "10.8.0",
|
|
36
|
+
"ts-proto": "^2.7.7",
|
|
37
|
+
"turbo": "^2.10.7"
|
|
38
|
+
},
|
|
39
|
+
"pnpm": {
|
|
40
|
+
"onlyBuiltDependencies": [
|
|
41
|
+
"esbuild",
|
|
42
|
+
"@parcel/watcher",
|
|
43
|
+
"core-js",
|
|
44
|
+
"core-js-pure",
|
|
45
|
+
"vue-demi"
|
|
46
|
+
],
|
|
47
|
+
"auditConfig": {
|
|
48
|
+
"ignoreCves": [
|
|
49
|
+
"CVE-2024-6783"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
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 appRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
7
|
+
const coreRoot = resolve(appRoot, 'packages/core')
|
|
8
|
+
const systemRoot = resolve(appRoot, 'packages/modules/system')
|
|
9
|
+
const ignoredDirectories = new Set(['dist', 'node_modules'])
|
|
10
|
+
const sourceExtensions = new Set(['.js', '.mjs', '.ts', '.vue'])
|
|
11
|
+
const allFiles = await collectFiles(appRoot)
|
|
12
|
+
const packageFiles = allFiles.filter((file) => file.endsWith(`${sep}package.json`))
|
|
13
|
+
const packages = new Map()
|
|
14
|
+
const publicPackageVersions = new Map()
|
|
15
|
+
const violations = []
|
|
16
|
+
|
|
17
|
+
for (const packageFile of packageFiles) {
|
|
18
|
+
const metadata = JSON.parse(await readFile(packageFile, 'utf8'))
|
|
19
|
+
if (!metadata.name || !metadata.exports) continue
|
|
20
|
+
if (!metadata.private) {
|
|
21
|
+
publicPackageVersions.set(metadata.name, metadata.version)
|
|
22
|
+
}
|
|
23
|
+
const packageRoot = dirname(packageFile)
|
|
24
|
+
const exportEntries = Object.entries(metadata.exports)
|
|
25
|
+
packages.set(metadata.name, { packageRoot, exportEntries })
|
|
26
|
+
for (const [, value] of exportEntries) {
|
|
27
|
+
for (const target of collectTargets(value)) {
|
|
28
|
+
const prefix = target.includes('*') ? target.slice(0, target.indexOf('*')) : target
|
|
29
|
+
const checkPath = target.includes('*')
|
|
30
|
+
? resolve(packageRoot, prefix)
|
|
31
|
+
: resolve(packageRoot, target)
|
|
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(`uni-app 公开包版本必须保持一致:${versions}`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const sourceFile of allFiles.filter((file) => sourceExtensions.has(extname(file)))) {
|
|
48
|
+
if (sourceFile.includes(`${sep}src${sep}rpc${sep}`) || isGeneratedEntry(sourceFile)) 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
|
+
if (violations.length) {
|
|
90
|
+
console.error(
|
|
91
|
+
`uni-app package exports 检查失败:\n${violations.map((item) => ` ${item}`).join('\n')}`,
|
|
92
|
+
)
|
|
93
|
+
process.exit(1)
|
|
94
|
+
}
|
|
95
|
+
console.log(`uni-app package exports 检查通过(${[...packages.keys()].join('、')})`)
|
|
96
|
+
|
|
97
|
+
async function collectFiles(directory) {
|
|
98
|
+
const files = []
|
|
99
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
100
|
+
if (entry.isDirectory() && ignoredDirectories.has(entry.name)) continue
|
|
101
|
+
const path = resolve(directory, entry.name)
|
|
102
|
+
if (entry.isDirectory()) files.push(...(await collectFiles(path)))
|
|
103
|
+
else files.push(path)
|
|
104
|
+
}
|
|
105
|
+
return files
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function collectTargets(value) {
|
|
109
|
+
if (typeof value === 'string') return [value]
|
|
110
|
+
if (!value || typeof value !== 'object') return []
|
|
111
|
+
return Object.values(value).flatMap(collectTargets)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function pathExists(path) {
|
|
115
|
+
try {
|
|
116
|
+
await stat(path)
|
|
117
|
+
return true
|
|
118
|
+
} catch {
|
|
119
|
+
return false
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function matchExportPattern(pattern, subpath) {
|
|
124
|
+
if (!pattern.includes('*')) return pattern === subpath
|
|
125
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace('\\*', '.+')
|
|
126
|
+
return new RegExp(`^${escaped}$`).test(subpath)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function findPackageByPath(file) {
|
|
130
|
+
return [...packages.values()].find(({ packageRoot }) => isWithin(file, packageRoot))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isWithin(file, directory) {
|
|
134
|
+
const path = relative(directory, file)
|
|
135
|
+
return path === '' || (!path.startsWith('..') && !path.startsWith(sep))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isGeneratedEntry(file) {
|
|
139
|
+
return (
|
|
140
|
+
file === resolve(coreRoot, 'src/module.mjs') ||
|
|
141
|
+
file === resolve(coreRoot, 'src/vite.mjs') ||
|
|
142
|
+
file === resolve(systemRoot, 'src/index.mjs')
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function format(file, line) {
|
|
147
|
+
return `${relative(appRoot, file)}:${line}`
|
|
148
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
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)
|
|
10
|
+
? readdirSync(modulesRoot, { withFileTypes: true })
|
|
11
|
+
: []) {
|
|
12
|
+
if (!entry.isDirectory()) continue
|
|
13
|
+
const directory = resolve(modulesRoot, entry.name, 'src/locales')
|
|
14
|
+
const locales = readdirSync(directory)
|
|
15
|
+
.filter((file) => file.endsWith('.json'))
|
|
16
|
+
.map((file) => file.slice(0, -5))
|
|
17
|
+
.sort((a, b) => (a === 'zh-CN' ? -1 : b === 'zh-CN' ? 1 : a.localeCompare(b)))
|
|
18
|
+
if (!locales.includes('zh-CN')) throw new Error(`${entry.name} 缺少 zh-CN 语言包`)
|
|
19
|
+
if (expectedLocales && locales.join(',') !== expectedLocales)
|
|
20
|
+
throw new Error(`${entry.name} 语言集合不一致`)
|
|
21
|
+
expectedLocales = locales.join(',')
|
|
22
|
+
const messages = Object.fromEntries(
|
|
23
|
+
locales.map((locale) => {
|
|
24
|
+
if (!/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(locale))
|
|
25
|
+
throw new Error(`语言代码无效:${locale}`)
|
|
26
|
+
return [locale, JSON.parse(readFileSync(resolve(directory, `${locale}.json`), 'utf8'))]
|
|
27
|
+
}),
|
|
28
|
+
)
|
|
29
|
+
const keys = Object.keys(messages['zh-CN']).sort().join('\0')
|
|
30
|
+
for (const locale of locales) {
|
|
31
|
+
if (Object.keys(messages[locale]).sort().join('\0') !== keys)
|
|
32
|
+
throw new Error(`${entry.name}/${locale} 语言键集合不一致`)
|
|
33
|
+
}
|
|
34
|
+
// 模块在 Vite 配置阶段由 Node 直接加载,使用无需 TS/JSON loader 的原生 ESM。
|
|
35
|
+
const content = [
|
|
36
|
+
'/* 此文件由 scripts/sync-locales.mjs 生成,请勿手工修改。 */',
|
|
37
|
+
`export const LOCALE_MESSAGES = ${JSON.stringify(messages, null, 2)}`,
|
|
38
|
+
"export const DEFAULT_LOCALE = 'zh-CN'",
|
|
39
|
+
'export const SUPPORTED_LOCALES = Object.keys(LOCALE_MESSAGES)',
|
|
40
|
+
'',
|
|
41
|
+
].join('\n')
|
|
42
|
+
const file = resolve(directory, 'generated.mjs')
|
|
43
|
+
if (write) writeFileSync(file, content)
|
|
44
|
+
else if (!existsSync(file) || readFileSync(file, 'utf8') !== content)
|
|
45
|
+
throw new Error(`语言注册产物过期:${file},请执行 pnpm i18n:sync`)
|
|
46
|
+
}
|