@liujitcn/kratos-taro-app-cli 0.0.1

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 ADDED
@@ -0,0 +1,18 @@
1
+ # @liujitcn/kratos-taro-app-cli
2
+
3
+ `@liujitcn/kratos-taro-app-cli` 用于创建模块化的 Taro React workspace。生成项目默认装配 Kratos core、UI 和 system 包,支持 H5 与微信小程序。
4
+
5
+ ## 使用
6
+
7
+ ```bash
8
+ pnpm dlx @liujitcn/kratos-taro-app-cli create customer-app
9
+ pnpm dlx @liujitcn/kratos-taro-app-cli create shop-app --module shop,order
10
+ pnpm dlx @liujitcn/kratos-taro-app-cli create customer-app --with @acme/customer-module
11
+ ```
12
+
13
+ - `--module`:创建 workspace 内的本地业务模块,可重复使用或用逗号分隔。
14
+ - `--with`:装配一个已发布的 Taro 业务模块,可重复使用。
15
+
16
+ 本地模块包含运行时入口、页面清单和构建期入口。模块页面由 core runner 在构建期间装配到私有宿主,不需要提交生成的页面包装器。
17
+
18
+ JavaScript 调用方也可以直接使用 `scaffoldKratosTaroApp(target, options)`。
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from '../src/index.mjs'
4
+
5
+ await run(process.argv.slice(2)).catch((error) => {
6
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
7
+ process.exitCode = 1
8
+ })
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@liujitcn/kratos-taro-app-cli",
3
+ "description": "CLI for creating modular Kratos Taro React workspaces.",
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "bin": {
7
+ "kratos-taro-app": "./bin/kratos-taro-app.mjs"
8
+ },
9
+ "exports": {
10
+ ".": "./src/index.mjs",
11
+ "./package.json": "./package.json"
12
+ },
13
+ "files": [
14
+ "bin",
15
+ "src",
16
+ "README.md"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "scripts": {
22
+ "build": "pnpm pack --pack-destination ../../dist/npm",
23
+ "build:entries": "node -e \"process.exit(0)\"",
24
+ "tsc": "node -e \"process.exit(0)\""
25
+ }
26
+ }
package/src/index.mjs ADDED
@@ -0,0 +1,627 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
2
+ import { basename, resolve } from 'node:path'
3
+
4
+ const cliPackage = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
5
+ const publicPackageVersion = cliPackage.version
6
+ if (typeof publicPackageVersion !== 'string' || !publicPackageVersion) {
7
+ throw new Error('CLI package.json 缺少有效版本')
8
+ }
9
+
10
+ const taroVersion = '4.2.1'
11
+ const reactVersion = '18.3.1'
12
+
13
+ /** 创建独立 Kratos Taro React workspace。 */
14
+ export function scaffoldKratosTaroApp(targetPath, options = {}) {
15
+ const target = resolve(targetPath)
16
+ if (existsSync(target)) throw new Error(`目标目录已存在:${target}`)
17
+ const projectName = basename(target)
18
+ validateProjectName(projectName)
19
+ const modules = [...new Set(options.modules ?? [])]
20
+ const packages = [...new Set(options.packages ?? [])]
21
+ modules.forEach(validateModuleName)
22
+ packages.forEach(validatePackageName)
23
+ mkdirSync(resolve(target, 'apps/taro-app/src/pages/bootstrap'), { recursive: true })
24
+ modules.forEach((name) => {
25
+ mkdirSync(resolve(target, `packages/modules/${name}/src/views`), { recursive: true })
26
+ })
27
+
28
+ write(
29
+ target,
30
+ '.gitignore',
31
+ 'node_modules\ndist\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',
32
+ )
33
+ write(
34
+ target,
35
+ 'pnpm-workspace.yaml',
36
+ 'packages:\n - apps/*\n - packages/modules/*\n',
37
+ )
38
+ write(
39
+ target,
40
+ 'package.json',
41
+ json({
42
+ name: projectName,
43
+ description: `Independent pnpm workspace for ${projectName}.`,
44
+ private: true,
45
+ packageManager: 'pnpm@10.13.1',
46
+ scripts: {
47
+ 'prepare:modules':
48
+ "pnpm --recursive --filter './packages/modules/**' --if-present run build:entries",
49
+ 'dev:h5': 'pnpm prepare:modules && pnpm --filter @local/kratos-taro-app dev:h5',
50
+ 'dev:mp-weixin':
51
+ 'pnpm prepare:modules && pnpm --filter @local/kratos-taro-app dev:mp-weixin',
52
+ 'build:h5': 'pnpm prepare:modules && pnpm --filter @local/kratos-taro-app build:h5',
53
+ 'build:mp-weixin':
54
+ 'pnpm prepare:modules && pnpm --filter @local/kratos-taro-app build:mp-weixin',
55
+ tsc: 'pnpm --recursive --if-present run tsc',
56
+ },
57
+ devDependencies: {
58
+ '@babel/core': '7.28.4',
59
+ '@babel/preset-react': '7.28.5',
60
+ '@types/node': '20.19.9',
61
+ '@types/react': '18.3.23',
62
+ '@types/react-dom': '18.3.7',
63
+ 'babel-preset-taro': taroVersion,
64
+ 'cross-env': '7.0.3',
65
+ esbuild: '0.25.8',
66
+ sass: '1.89.2',
67
+ 'tsconfig-paths-webpack-plugin': '4.2.0',
68
+ typescript: '5.8.3',
69
+ webpack: '5.91.0',
70
+ },
71
+ pnpm: {
72
+ onlyBuiltDependencies: ['@swc/core', '@tarojs/binding', 'esbuild'],
73
+ },
74
+ }),
75
+ )
76
+ write(
77
+ target,
78
+ 'tsconfig.json',
79
+ json({
80
+ compilerOptions: {
81
+ target: 'ES2020',
82
+ module: 'ESNext',
83
+ moduleResolution: 'Bundler',
84
+ allowSyntheticDefaultImports: true,
85
+ esModuleInterop: true,
86
+ forceConsistentCasingInFileNames: true,
87
+ resolveJsonModule: true,
88
+ skipLibCheck: true,
89
+ strict: true,
90
+ jsx: 'react-jsx',
91
+ types: ['node'],
92
+ },
93
+ }),
94
+ )
95
+ writeWorkspaceReadme(target, projectName)
96
+ writeHost(target, projectName, modules, packages)
97
+ modules.forEach((name) => writeLocalModule(target, projectName, name))
98
+ return target
99
+ }
100
+
101
+ /** 解析命令行并运行脚手架。 */
102
+ export async function run(args = process.argv.slice(2)) {
103
+ if (!args.length || args.includes('--help') || args.includes('-h')) {
104
+ printHelp()
105
+ return
106
+ }
107
+ if (args[0] !== 'create') throw new Error(`不支持的命令:${args[0]}`)
108
+ const targetPath = args[1]
109
+ if (!targetPath || targetPath.startsWith('--')) {
110
+ throw new Error('用法: kratos-taro-app create <目录> [--module <名称>] [--with <包名>]')
111
+ }
112
+ const modules = readOptions(args.slice(2), '--module')
113
+ const packages = readOptions(args.slice(2), '--with')
114
+ const target = scaffoldKratosTaroApp(targetPath, { modules, packages })
115
+ process.stdout.write(`已创建 Taro workspace:${target}\n`)
116
+ }
117
+
118
+ function writeWorkspaceReadme(target, projectName) {
119
+ write(
120
+ target,
121
+ 'README.md',
122
+ `# ${projectName}
123
+
124
+ \`${projectName}\` 是由 \`@liujitcn/kratos-taro-app-cli\` 创建的独立 pnpm workspace,使用 Taro、React 和 TypeScript,支持 H5 与微信小程序。
125
+
126
+ ## 目录结构
127
+
128
+ \`\`\`text
129
+ ${projectName}
130
+ ├── apps/taro-app # 私有 Taro 宿主
131
+ ├── packages/modules # workspace 内本地业务模块
132
+ ├── package.json # 公共命令和开发依赖
133
+ ├── pnpm-workspace.yaml # workspace 包范围
134
+ └── tsconfig.json # 共享 TypeScript 配置
135
+ \`\`\`
136
+
137
+ 宿主只负责入口、模块清单和平台构建配置。运行时底座、UI 主题和默认业务页分别由 \`@liujitcn/kratos-taro-app-core\`、\`@liujitcn/kratos-taro-app-ui\` 与 \`@liujitcn/kratos-taro-app-system\` 提供。
138
+
139
+ ## 开发
140
+
141
+ \`\`\`bash
142
+ pnpm install
143
+ pnpm dev:h5
144
+ pnpm dev:mp-weixin
145
+ pnpm build:h5
146
+ pnpm build:mp-weixin
147
+ pnpm tsc
148
+ \`\`\`
149
+
150
+ 模块装配入口是 \`apps/taro-app/src/module-manifest.ts\`。模块顺序决定静态视图覆盖优先级;新增页面时同步维护模块自己的 \`src/pages.ts\` 和视图映射。
151
+ `,
152
+ )
153
+ }
154
+
155
+ function writeHost(target, projectName, modules, packages) {
156
+ const localDependencies = Object.fromEntries(modules.map((name) => [`@local/${name}`, 'workspace:*']))
157
+ const publishedDependencies = Object.fromEntries(packages.map((name) => [name, 'latest']))
158
+ write(
159
+ target,
160
+ 'apps/taro-app/package.json',
161
+ json({
162
+ name: '@local/kratos-taro-app',
163
+ description: `Private Taro React host for ${projectName}.`,
164
+ version: '0.0.1',
165
+ private: true,
166
+ scripts: {
167
+ 'dev:h5':
168
+ 'cross-env NODE_ENV=development node scripts/run-taro.mjs --type h5 --watch --mode development',
169
+ 'dev:mp-weixin':
170
+ 'cross-env NODE_ENV=development node scripts/run-taro.mjs --type weapp --watch --mode development',
171
+ 'build:h5':
172
+ 'cross-env KRATOS_TARO_OUTPUT_ROOT=dist/build/h5 KRATOS_TARO_PUBLIC_PATH=/ node scripts/run-taro.mjs --type h5 --mode production',
173
+ 'build:mp-weixin':
174
+ 'cross-env KRATOS_TARO_OUTPUT_ROOT=dist/build/mp-weixin node scripts/run-taro.mjs --type weapp --mode production',
175
+ tsc: 'tsc --noEmit -p tsconfig.json',
176
+ },
177
+ dependencies: {
178
+ '@babel/runtime': '7.28.4',
179
+ '@liujitcn/kratos-taro-app-core': `^${publicPackageVersion}`,
180
+ '@liujitcn/kratos-taro-app-system': `^${publicPackageVersion}`,
181
+ '@liujitcn/kratos-taro-app-ui': `^${publicPackageVersion}`,
182
+ '@tarojs/components': taroVersion,
183
+ '@tarojs/helper': taroVersion,
184
+ '@tarojs/plugin-framework-react': taroVersion,
185
+ '@tarojs/plugin-platform-h5': taroVersion,
186
+ '@tarojs/plugin-platform-weapp': taroVersion,
187
+ '@tarojs/react': taroVersion,
188
+ '@tarojs/runtime': taroVersion,
189
+ '@tarojs/shared': taroVersion,
190
+ '@tarojs/taro': taroVersion,
191
+ react: reactVersion,
192
+ 'react-dom': reactVersion,
193
+ ...localDependencies,
194
+ ...publishedDependencies,
195
+ },
196
+ devDependencies: {
197
+ '@pmmmwh/react-refresh-webpack-plugin': '0.5.17',
198
+ '@tarojs/cli': taroVersion,
199
+ '@tarojs/taro-loader': taroVersion,
200
+ '@tarojs/webpack5-runner': taroVersion,
201
+ 'react-refresh': '0.14.2',
202
+ },
203
+ }),
204
+ )
205
+ write(
206
+ target,
207
+ 'apps/taro-app/README.md',
208
+ `# @local/kratos-taro-app
209
+
210
+ \`apps/taro-app\` 是 \`${projectName}\` 的私有 Taro React 宿主,负责装配模块并提供 H5、微信小程序构建入口,不承载可复用业务实现。
211
+
212
+ 固定启动页位于 \`src/pages/bootstrap\`。其他模块页面由 core runner 在构建期间临时生成包装器、页面配置与静态资源,构建结束后会自动恢复宿主目录。
213
+ `,
214
+ )
215
+ write(target, 'apps/taro-app/scripts/run-taro.mjs', "import '@liujitcn/kratos-taro-app-core/runner'\n")
216
+ write(
217
+ target,
218
+ 'apps/taro-app/babel.config.cjs',
219
+ `module.exports = {
220
+ presets: [['taro', { framework: 'react', ts: true, compiler: 'webpack5' }]],
221
+ }
222
+ `,
223
+ )
224
+ write(
225
+ target,
226
+ 'apps/taro-app/tsconfig.json',
227
+ json({
228
+ extends: '../../tsconfig.json',
229
+ compilerOptions: {
230
+ baseUrl: '.',
231
+ paths: { '@/*': ['src/*'] },
232
+ types: ['node', '@tarojs/taro', '@tarojs/components'],
233
+ },
234
+ include: ['src', 'config', 'types'],
235
+ }),
236
+ )
237
+ write(target, 'apps/taro-app/config/dev.ts', platformConfig(true))
238
+ write(target, 'apps/taro-app/config/prod.ts', platformConfig(false))
239
+ const modulePackages = [
240
+ '@liujitcn/kratos-taro-app-core',
241
+ '@liujitcn/kratos-taro-app-ui',
242
+ '@liujitcn/kratos-taro-app-system',
243
+ ...modules.map((name) => `@local/${name}`),
244
+ ...packages,
245
+ ]
246
+ write(target, 'apps/taro-app/config/index.ts', hostConfig(projectName, modulePackages))
247
+ write(
248
+ target,
249
+ 'apps/taro-app/project.config.json',
250
+ json({
251
+ miniprogramRoot: './dist/build/mp-weixin',
252
+ projectname: projectName,
253
+ description: `${projectName} Taro React application`,
254
+ appid: '',
255
+ setting: {
256
+ urlCheck: false,
257
+ es6: false,
258
+ enhance: false,
259
+ compileHotReLoad: false,
260
+ postcss: false,
261
+ minified: true,
262
+ },
263
+ compileType: 'miniprogram',
264
+ }),
265
+ )
266
+ write(
267
+ target,
268
+ 'apps/taro-app/src/index.html',
269
+ `<!doctype html>
270
+ <html lang="zh-CN">
271
+ <head>
272
+ <meta charset="utf-8" />
273
+ <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover" />
274
+ <meta name="format-detection" content="telephone=no,address=no" />
275
+ <title>${projectName}</title>
276
+ <script><%= htmlWebpackPlugin.options.script %></script>
277
+ </head>
278
+ <body><div id="app"></div></body>
279
+ </html>
280
+ `,
281
+ )
282
+ write(
283
+ target,
284
+ 'apps/taro-app/src/app.config.base.json',
285
+ json({
286
+ pages: ['pages/bootstrap/index'],
287
+ window: {
288
+ backgroundTextStyle: 'light',
289
+ navigationBarBackgroundColor: '#f8f8f8',
290
+ navigationBarTitleText: '',
291
+ navigationBarTextStyle: 'black',
292
+ backgroundColor: '#f8f8f8',
293
+ },
294
+ }),
295
+ )
296
+ write(
297
+ target,
298
+ 'apps/taro-app/src/app.config.ts',
299
+ "import config from './app.config.base.json'\n\nexport default defineAppConfig(config)\n",
300
+ )
301
+ write(
302
+ target,
303
+ 'apps/taro-app/src/app.scss',
304
+ "@use '@liujitcn/kratos-taro-app-ui/styles/theme.scss';\n@use '@liujitcn/kratos-taro-app-core/styles/base.scss';\n",
305
+ )
306
+ write(
307
+ target,
308
+ 'apps/taro-app/src/app.tsx',
309
+ `import type { PropsWithChildren } from 'react'
310
+ import { useLaunch } from '@tarojs/taro'
311
+ import { bootstrapKratosTaroApp } from '@liujitcn/kratos-taro-app-core'
312
+ import { moduleManifest } from './module-manifest'
313
+ import './app.scss'
314
+
315
+ /** Taro 宿主根组件。 */
316
+ export default function App({ children }: PropsWithChildren) {
317
+ useLaunch(() => bootstrapKratosTaroApp({ modules: moduleManifest }))
318
+ return children
319
+ }
320
+ `,
321
+ )
322
+ write(target, 'apps/taro-app/src/module-manifest.ts', moduleManifest(modules, packages))
323
+ write(
324
+ target,
325
+ 'apps/taro-app/src/pages/bootstrap/index.tsx',
326
+ `import { View } from '@tarojs/components'
327
+ import Taro, { useLoad } from '@tarojs/taro'
328
+ import { resolveStaticView } from '@liujitcn/kratos-taro-app-core'
329
+
330
+ /** 固定启动页,模块注册完成后进入启动状态页。 */
331
+ export default function BootstrapPage() {
332
+ useLoad((options) => {
333
+ const route = resolveStaticView('BOOTSTRAP_LOADING') ?? 'pages/status/index'
334
+ const target = options?.route ? decodeURIComponent(options.route) : 'app/home'
335
+ void Taro.reLaunch({
336
+ url: \`/\${route}?state=BOOTSTRAP_LOADING&bootstrap=1&route=\${encodeURIComponent(target)}\`,
337
+ })
338
+ })
339
+ return <View />
340
+ }
341
+ `,
342
+ )
343
+ write(
344
+ target,
345
+ 'apps/taro-app/src/pages/bootstrap/index.config.ts',
346
+ "export default definePageConfig({ navigationStyle: 'custom', navigationBarTitleText: '' })\n",
347
+ )
348
+ }
349
+
350
+ function writeLocalModule(target, projectName, name) {
351
+ const identifier = `${toCamelCase(name)}Module`
352
+ write(
353
+ target,
354
+ `packages/modules/${name}/package.json`,
355
+ json({
356
+ name: `@local/${name}`,
357
+ description: `Local Kratos Taro module for ${name}.`,
358
+ version: '0.0.1',
359
+ type: 'module',
360
+ files: ['dist', 'src', 'README.md'],
361
+ exports: {
362
+ '.': './src/index.ts',
363
+ './build': {
364
+ types: './src/build.ts',
365
+ import: './dist/build.mjs',
366
+ default: './dist/build.mjs',
367
+ },
368
+ './views/*': './src/views/*',
369
+ './package.json': './package.json',
370
+ },
371
+ scripts: {
372
+ 'build:entries':
373
+ 'esbuild src/build.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/build.mjs',
374
+ tsc: 'tsc --noEmit -p tsconfig.json',
375
+ },
376
+ dependencies: {
377
+ '@liujitcn/kratos-taro-app-core': `^${publicPackageVersion}`,
378
+ '@tarojs/components': taroVersion,
379
+ '@tarojs/taro': taroVersion,
380
+ react: reactVersion,
381
+ },
382
+ }),
383
+ )
384
+ write(
385
+ target,
386
+ `packages/modules/${name}/tsconfig.json`,
387
+ json({
388
+ extends: '../../../tsconfig.json',
389
+ compilerOptions: { types: ['node', '@tarojs/taro', '@tarojs/components'] },
390
+ include: ['src'],
391
+ }),
392
+ )
393
+ write(
394
+ target,
395
+ `packages/modules/${name}/README.md`,
396
+ `# @local/${name}
397
+
398
+ \`@local/${name}\` 是 \`${projectName}\` workspace 内的本地 Taro 业务模块,通过 core 的公开接口接入宿主。
399
+
400
+ - 页面放在 \`src/views\`,页面配置登记在 \`src/pages.ts\`。
401
+ - 稳定视图键登记在 \`src/index.ts\`,后注册模块具有更高覆盖优先级。
402
+ - 请求、认证、导航和状态能力只能通过公开包入口使用。
403
+ - \`src/build.ts\` 是 runner 使用的构建期描述,不承载运行时逻辑。
404
+ `,
405
+ )
406
+ write(
407
+ target,
408
+ `packages/modules/${name}/src/pages.ts`,
409
+ `import type { KratosTaroPageConfig } from '@liujitcn/kratos-taro-app-core'
410
+
411
+ /** ${name} 页面编译配置。 */
412
+ export const ${toCamelCase(name)}Pages: Record<string, KratosTaroPageConfig> = {}
413
+ `,
414
+ )
415
+ write(
416
+ target,
417
+ `packages/modules/${name}/src/index.ts`,
418
+ `import { defineKratosTaroModule } from '@liujitcn/kratos-taro-app-core'
419
+ import { ${toCamelCase(name)}Pages } from './pages'
420
+
421
+ /** ${name} 业务模块。 */
422
+ export const ${identifier} = defineKratosTaroModule({
423
+ name: '@local/${name}',
424
+ pages: ${toCamelCase(name)}Pages,
425
+ views: {},
426
+ })
427
+ `,
428
+ )
429
+ write(
430
+ target,
431
+ `packages/modules/${name}/src/build.ts`,
432
+ `import { dirname, resolve } from 'node:path'
433
+ import { fileURLToPath } from 'node:url'
434
+ import { defineKratosTaroBuildModule } from '@liujitcn/kratos-taro-app-core/build'
435
+ import { ${toCamelCase(name)}Pages } from './pages'
436
+
437
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
438
+
439
+ /** ${name} 构建期模块描述。 */
440
+ export const buildModule = defineKratosTaroBuildModule({
441
+ name: '@local/${name}',
442
+ root: packageRoot,
443
+ pages: ${toCamelCase(name)}Pages,
444
+ })
445
+ `,
446
+ )
447
+ }
448
+
449
+ function moduleManifest(modules, packages) {
450
+ const imports = [
451
+ "import { coreModule } from '@liujitcn/kratos-taro-app-core'",
452
+ "import { systemModule } from '@liujitcn/kratos-taro-app-system'",
453
+ ...modules.map(
454
+ (name) => `import { ${toCamelCase(name)}Module } from '@local/${name}'`,
455
+ ),
456
+ ...packages.map((name, index) => `import packageModule${index} from '${name}'`),
457
+ ]
458
+ const members = [
459
+ 'coreModule',
460
+ 'systemModule',
461
+ ...modules.map((name) => `${toCamelCase(name)}Module`),
462
+ ...packages.map((_, index) => `packageModule${index}`),
463
+ ]
464
+ return `${imports.join('\n')}\n\n/** 宿主唯一模块清单,顺序决定静态视图覆盖优先级。 */\nexport const moduleManifest = [${members.join(', ')}]\n`
465
+ }
466
+
467
+ function hostConfig(projectName, packageNames) {
468
+ return `import { createRequire } from 'node:module'
469
+ import { dirname, resolve } from 'node:path'
470
+ import { defineConfig, type UserConfigExport } from '@tarojs/cli'
471
+ import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'
472
+ import devConfig from './dev'
473
+ import prodConfig from './prod'
474
+
475
+ const packageNames = ${JSON.stringify(packageNames, null, 2)}
476
+ const hostRequire = createRequire(resolve(__dirname, '../package.json'))
477
+
478
+ export default defineConfig<'webpack5'>(async (merge) => {
479
+ const outputRoot = process.env.KRATOS_TARO_OUTPUT_ROOT || 'dist'
480
+ const publicPath = process.env.KRATOS_TARO_PUBLIC_PATH || '/'
481
+ const apiTargetUrl = process.env.KRATOS_TARO_API_URL || 'http://localhost:7001'
482
+ const packageRoots = Object.fromEntries(
483
+ packageNames.map((name) => [name, dirname(hostRequire.resolve(\`\${name}/package.json\`))]),
484
+ )
485
+ const sourceRoots = Object.values(packageRoots).map((root) => resolve(root, 'src'))
486
+ const aliases = Object.fromEntries([
487
+ ...packageNames.map((name) => [\`\${name}/static\`, resolve(__dirname, '../src/static')]),
488
+ ...Object.entries(packageRoots).map(([name, root]) => [name, resolve(root, 'src')]),
489
+ ])
490
+ const configureWebpack = (chain: any) => {
491
+ chain.resolve.plugin('tsconfig-paths').use(TsconfigPathsPlugin)
492
+ chain.merge({ resolve: { fallback: { crypto: false } } })
493
+ sourceRoots.forEach((root) => chain.module.rule('script').include.add(root))
494
+ }
495
+ const baseConfig: UserConfigExport<'webpack5'> = {
496
+ projectName: ${JSON.stringify(projectName)},
497
+ date: ${JSON.stringify(new Date().toISOString().slice(0, 10))},
498
+ designWidth: 750,
499
+ deviceRatio: { 375: 2, 640: 2.34 / 2, 750: 1, 828: 1.81 / 2 },
500
+ sourceRoot: 'src',
501
+ outputRoot,
502
+ framework: 'react',
503
+ compiler: {
504
+ type: 'webpack5',
505
+ prebundle: { enable: false },
506
+ },
507
+ compile: { include: sourceRoots },
508
+ cache: { enable: true },
509
+ copy: {
510
+ patterns: [
511
+ {
512
+ from: resolve(__dirname, '../src/static'),
513
+ to: resolve(__dirname, '..', outputRoot, 'static'),
514
+ },
515
+ ],
516
+ options: {},
517
+ },
518
+ defineConstants: {
519
+ 'process.env.KRATOS_TARO_API_BASE': JSON.stringify(process.env.KRATOS_TARO_API_BASE || '/api'),
520
+ 'process.env.KRATOS_TARO_API_URL': JSON.stringify(apiTargetUrl),
521
+ 'process.env.KRATOS_TARO_PUBLIC_PATH': JSON.stringify(publicPath),
522
+ 'process.env.KRATOS_TARO_STATIC_URL': JSON.stringify(process.env.KRATOS_TARO_STATIC_URL || apiTargetUrl),
523
+ },
524
+ alias: aliases,
525
+ mini: {
526
+ postcss: { pxtransform: { enable: true, config: {} }, cssModules: { enable: false } },
527
+ webpackChain: configureWebpack,
528
+ },
529
+ h5: {
530
+ publicPath,
531
+ staticDirectory: 'static',
532
+ router: { mode: 'hash' },
533
+ devServer: {
534
+ port: 5002,
535
+ host: '0.0.0.0',
536
+ proxy: {
537
+ '/api': { target: apiTargetUrl, changeOrigin: true },
538
+ '/events': { target: apiTargetUrl, changeOrigin: true },
539
+ },
540
+ },
541
+ output: {
542
+ filename: 'assets/[name].[contenthash:8].js',
543
+ chunkFilename: 'assets/[name].[contenthash:8].js',
544
+ },
545
+ miniCssExtractPluginOption: {
546
+ ignoreOrder: true,
547
+ filename: 'assets/[name].[contenthash:8].css',
548
+ chunkFilename: 'assets/[name].[contenthash:8].css',
549
+ },
550
+ postcss: { autoprefixer: { enable: true, config: {} }, cssModules: { enable: false } },
551
+ webpackChain: configureWebpack,
552
+ },
553
+ }
554
+ return process.env.NODE_ENV === 'development'
555
+ ? merge({}, baseConfig, devConfig)
556
+ : merge({}, baseConfig, prodConfig)
557
+ })
558
+ `
559
+ }
560
+
561
+ function platformConfig(development) {
562
+ return `import type { UserConfigExport } from '@tarojs/cli'
563
+
564
+ export default ${JSON.stringify(
565
+ development ? { logger: { quiet: false, stats: true }, mini: {}, h5: {} } : { mini: {}, h5: {} },
566
+ null,
567
+ 2,
568
+ )} satisfies UserConfigExport<'webpack5'>
569
+ `
570
+ }
571
+
572
+ function readOptions(args, option) {
573
+ const values = []
574
+ for (let index = 0; index < args.length; index += 1) {
575
+ const argument = args[index]
576
+ if (argument !== '--module' && argument !== '--with') {
577
+ throw new Error(`未知参数:${argument}`)
578
+ }
579
+ const value = args[++index]
580
+ if (!value || value.startsWith('--')) throw new Error(`选项 ${argument} 缺少值`)
581
+ if (argument === option) {
582
+ values.push(...value.split(',').map((item) => item.trim()).filter(Boolean))
583
+ }
584
+ }
585
+ return [...new Set(values)]
586
+ }
587
+
588
+ function validateProjectName(name) {
589
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new Error(`项目名必须使用 kebab-case:${name}`)
590
+ }
591
+
592
+ function validateModuleName(name) {
593
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new Error(`模块名无效:${name}`)
594
+ if (name === 'system') throw new Error('模块名不能使用保留名称:system')
595
+ }
596
+
597
+ function validatePackageName(name) {
598
+ if (!/^(?:@[a-z0-9-]+\/)?[a-z0-9-]+$/.test(name)) throw new Error(`包名无效:${name}`)
599
+ }
600
+
601
+ function toCamelCase(value) {
602
+ return value.replace(/-([a-z0-9])/g, (_, character) => character.toUpperCase())
603
+ }
604
+
605
+ function write(root, file, content) {
606
+ const target = resolve(root, file)
607
+ mkdirSync(resolve(target, '..'), { recursive: true })
608
+ writeFileSync(target, content)
609
+ }
610
+
611
+ function json(value) {
612
+ return `${JSON.stringify(value, null, 2)}\n`
613
+ }
614
+
615
+ function printHelp() {
616
+ process.stdout.write(
617
+ [
618
+ 'kratos-taro-app create <目录> [--module <名称[,名称...]>] [--with <包名>]',
619
+ '',
620
+ '示例:',
621
+ ' kratos-taro-app create customer-app',
622
+ ' kratos-taro-app create shop-app --module shop,order',
623
+ ' kratos-taro-app create customer-app --with @acme/customer-module',
624
+ '',
625
+ ].join('\n'),
626
+ )
627
+ }