Commit 3a798993 by chunhong.mu

feat(root): enhance dev/build scripts with interactive mode and pnpm config

- Add .npmrc with isolated node-linker for UniApp compatibility
- Enhance dev-project.ts and build-project.ts with interactive selection
- Support --project, --env, --platform parameters
- Auto-detect UniApp vs Web projects for platform selection
- Update ARCHITECTURE.md with new script usage
- Add pnpm overrides for axios version lock
- Update commitlint config scope rules
parent c2388472
# pnpm monorepo 配置
strict-peer-dependencies=false
auto-install-peers=true
shamefully-hoist=true
node-linker=isolated
...@@ -45,11 +45,23 @@ common-web/ ...@@ -45,11 +45,23 @@ common-web/
**使用方式** **使用方式**
```bash ```bash
# 启动项目开发服务器 # 交互式启动(推荐)
pnpm dev:project --project=official-site-web pnpm dev:project
# 会引导选择项目、环境、平台
# 构建指定项目 # 交互式构建(推荐)
pnpm build:project --project=official-site-web pnpm build:project
# 会引导选择项目、环境、平台
# 参数化调用
pnpm dev:project --project=official-site-web --env=test
pnpm build:project --project=partner-mp --env=production --platform=mp-weixin
# 参数说明
# --project=<name> 项目名称
# --env=<env> 环境:test/staging/production
# --platform=<plat> 平台(仅小程序需要):mp-weixin/h5/mp-alipay 等
# -p <plat> 平台简写参数
# 创建新项目 # 创建新项目
pnpm new:project admin-web vue pnpm new:project admin-web vue
...@@ -192,7 +204,70 @@ packages/admin-web/ ...@@ -192,7 +204,70 @@ packages/admin-web/
└── tsconfig.json └── tsconfig.json
``` ```
### 4.3 项目内各层职责 ### 4.3 UniApp 小程序项目目录结构(如 partner-mp)
```
packages/partner-mp/
├── src/
│ ├── pages/ # 页面(路由入口)
│ ├── components/ # 组件
│ │ ├── business/ # 业务组件
│ │ └── common/ # 通用组件
│ ├── composables/ # 组合式函数
│ ├── api/ # API 层(HTTP 调用)
│ ├── services/ # 服务层(业务逻辑编排)
│ ├── store/ # 状态管理(Pinia)
│ ├── utils/ # 工具函数
│ ├── config/ # 项目配置
│ ├── constants/ # 常量定义
│ ├── modules/ # 业务模块
│ ├── controllers/ # 控制器
│ ├── model/ # 数据模型
│ ├── App.vue
│ ├── main.ts
│ ├── manifest.json # UniApp 应用配置
│ └── pages.json # 页面路由配置
├── public/ # 静态资源
├── .env.development # 开发环境
├── .env.test # 测试环境
├── .env.staging # 预发环境
├── .env.production # 生产环境
├── vite.config.ts
├── package.json
└── tsconfig.json
```
**开发脚本**
```bash
# 开发(默认微信小程序)
pnpm dev:project --project=partner-mp
# 构建(默认微信小程序)
pnpm build:project --project=partner-mp
```
**平台支持**
| 脚本 | 说明 |
|------|------|
| `pnpm dev` | 开发微信小程序(默认) |
| `pnpm dev:mp-alipay` | 开发支付宝小程序 |
| `pnpm dev:h5` | 开发 H5 |
| `pnpm build` | 构建微信小程序(默认) |
| `pnpm build:mp-alipay` | 构建支付宝小程序 |
| `pnpm build:h5` | 构建 H5 |
**环境模式**
| 模式 | 对应文件 | 说明 |
|------|---------|------|
| 默认 | `.env.development` | 开发环境 |
| `--mode test` | `.env.test` | 测试环境 |
| `--mode staging` | `.env.staging` | 预发环境 |
| `--mode production` | `.env.production` | 生产环境 |
### 4.4 项目内各层职责
| 目录 | 职责 | 示例 | | 目录 | 职责 | 示例 |
| -------------- | ------------------------ | --------------------------------- | | -------------- | ------------------------ | --------------------------------- |
......
export default { export default {
extends: ['@commitlint/config-conventional'], extends: ['@commitlint/config-conventional'],
rules: { rules: {
'scope-enum': [2, 'always', ["root","*","@common/utils","@common/vue-kit","official-site-web","test-demo-web"]], 'scope-enum': [2, 'always', ["root", "*", "@common/utils", "@common/vue-kit", "official-site-web", "partner-mp", "test-demo-web"]],
'scope-empty': [2, 'never'] 'scope-empty': [2, 'never'],
} },
} }
export { useWindowWidth } from './use-window-width'
...@@ -36,5 +36,15 @@ ...@@ -36,5 +36,15 @@
"lint-staged": "^15.2.10", "lint-staged": "^15.2.10",
"tsx": "^4.19.2", "tsx": "^4.19.2",
"typescript": "^5.6.3" "typescript": "^5.6.3"
},
"pnpm": {
"overrides": {
"axios": "^0.27.2"
},
"peerDependencyRules": {
"ignoreMissing": [
"*"
]
}
} }
} }
This diff could not be displayed because it is too large.
import { execSync } from 'node:child_process' import { execSync } from "node:child_process"
import { resolve } from 'node:path' import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"
import { join, resolve } from "node:path"
import { createInterface } from "node:readline"
// 解析命令行参数
const args = process.argv.slice(2) const args = process.argv.slice(2)
const project = args.find(a => a.startsWith('--project'))?.split('=')[1] let projectName: string | null = null
let envName: string | null = null
let platform: string | null = null
if (!project) { for (let i = 0; i < args.length; i++) {
console.error('Usage: pnpm build:project --project=<name>') const arg = args[i]
if (arg === "--project" && args[i + 1]) {
projectName = args[++i]
}
else if (arg === "--env" && args[i + 1]) {
envName = args[++i]
}
else if (arg === "--platform" && args[i + 1]) {
platform = args[++i]
}
else if (arg.startsWith("--project=")) {
projectName = arg.split("=")[1]
}
else if (arg.startsWith("--env=")) {
envName = arg.split("=")[1]
}
else if (arg.startsWith("--platform=")) {
platform = arg.split("=")[1]
}
else if (arg === "-p" && args[i + 1]) {
platform = args[++i]
}
// 用户便利:weixin-mp → mp-weixin
if (platform === "weixin-mp") {
platform = "mp-weixin"
}
}
const packagesDir = resolve("packages")
if (!existsSync(packagesDir)) {
console.error("Packages directory not found:", packagesDir)
process.exit(1) process.exit(1)
} }
const projectDir = resolve('packages', project) const projects = readdirSync(packagesDir).filter((item) => {
console.log(`Building ${project}...`) const itemPath = join(packagesDir, item)
return statSync(itemPath).isDirectory()
})
if (projects.length === 0) {
console.error("No projects found in packages directory")
process.exit(1)
}
const envs = [
{ label: "test (测试环境)", value: "test" },
{ label: "staging (预发环境)", value: "staging" },
{ label: "production (生产环境)", value: "production" },
]
const commonPlatforms = ["mp-weixin", "h5", "mp-alipay", "mp-baidu", "mp-toutiao"]
// 判断是否是 UniApp 小程序项目
function isMiniProgramProject(projectPath: string): boolean {
const packageJsonPath = join(projectPath, "package.json")
if (!existsSync(packageJsonPath)) return false
try {
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"))
// 检查是否有 uni 相关依赖或脚本
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
if (deps["@dcloudio/uni-app"] || deps["@dcloudio/vite-plugin-uni"]) {
return true
}
// 检查是否有 mp-weixin 相关脚本
if (pkg.scripts) {
const scripts = Object.keys(pkg.scripts)
if (scripts.some(s => s.includes("mp-weixin") || s.includes("mp-alipay"))) {
return true
}
}
}
catch {
return false
}
return false
}
// 执行命令
function executeCommand(projectName: string, envName: string, platform: string) {
const projectPath = join(packagesDir, projectName)
const packageJsonPath = join(projectPath, "package.json")
if (!existsSync(packageJsonPath)) {
console.error(`package.json not found in project ${projectName}`)
process.exit(1)
}
console.log(`\nBuilding project ${projectName} in ${envName} environment on ${platform}...`)
const isMiniProgram = isMiniProgramProject(projectPath)
const useScript = platform.includes(":") || platform === "custom"
// 切换到项目目录
process.chdir(projectPath)
try {
if (useScript) {
// 使用 pnpm run script
let scriptName: string
if (platform === "h5") {
scriptName = `build:h5`
}
else {
scriptName = `build:${platform}`
}
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"))
if (!pkg.scripts || !pkg.scripts[scriptName]) {
console.error(`Script ${scriptName} not found in project ${projectName}`)
process.exit(1)
}
execSync(`cd ${projectDir} && pnpm build`, { stdio: 'inherit' }) console.log(`Executing: pnpm run ${scriptName}`)
execSync(`pnpm run ${scriptName}`, { stdio: "inherit" })
}
else if (isMiniProgram) {
// UniApp 项目使用 npx uni build
let cmd = "npx uni build"
if (platform !== "h5") {
cmd += ` -p ${platform}`
}
cmd += ` --mode ${envName}`
console.log(`Executing: ${cmd}`)
execSync(cmd, { stdio: "inherit" })
}
else {
// Vue/Nuxt 项目直接使用 pnpm build
console.log(`Executing: pnpm build`)
execSync("pnpm build", { stdio: "inherit" })
}
}
catch (error: any) {
console.error(`Error running command: ${error.message}`)
process.exit(1)
}
}
// 交互式选择
function askQuestion(query: string): Promise<string> {
const rl = createInterface({
input: process.stdin,
output: process.stdout,
})
return new Promise((resolve) => {
rl.question(query, (answer) => {
rl.close()
resolve(answer.trim())
})
})
}
async function runInteractive() {
// 1. 选择项目
console.log("\n=== 选择项目 ===")
projects.forEach((p, i) => console.log(`${i + 1}. ${p}`))
let selectedProject: string
if (projectName && projects.includes(projectName)) {
selectedProject = projectName
console.log(`已选择项目: ${selectedProject}`)
}
else {
const answer = await askQuestion("\n请输入项目序号或名称: ")
const index = Number.parseInt(answer) - 1
if (!Number.isNaN(index) && index >= 0 && index < projects.length) {
selectedProject = projects[index]
}
else if (projects.includes(answer)) {
selectedProject = answer
}
else {
console.error("无效的选择")
process.exit(1)
}
}
// 2. 选择环境
console.log("\n=== 选择环境 ===")
envs.forEach((e, i) => console.log(`${i + 1}. ${e.label}`))
let selectedEnv: string
if (envName && envs.some(e => e.value === envName)) {
selectedEnv = envName
console.log(`已选择环境: ${selectedEnv}`)
}
else {
const answer = await askQuestion("\n请输入环境序号: ")
const index = Number.parseInt(answer) - 1
if (!Number.isNaN(index) && index >= 0 && index < envs.length) {
selectedEnv = envs[index].value
}
else {
console.error("无效的选择")
process.exit(1)
}
}
// 3. 判断是否需要选择平台
const projectPath = join(packagesDir, selectedProject)
const isMiniProgram = isMiniProgramProject(projectPath)
let selectedPlatform: string
if (isMiniProgram) {
console.log("\n=== 选择构建平台 ===")
commonPlatforms.forEach((p, i) => console.log(`${i + 1}. ${p}`))
if (platform && commonPlatforms.includes(platform)) {
selectedPlatform = platform
console.log(`已选择平台: ${selectedPlatform}`)
}
else {
const answer = await askQuestion("\n请输入平台序号 (默认 1): ")
const index = Number.parseInt(answer || "1") - 1
if (!Number.isNaN(index) && index >= 0 && index < commonPlatforms.length) {
selectedPlatform = commonPlatforms[index]
}
else {
selectedPlatform = commonPlatforms[0] // 默认 mp-weixin
}
}
}
else {
selectedPlatform = "default"
console.log("\n该项目为 Web 项目,无需选择平台")
}
console.log(`\n开始构建: ${selectedProject} | ${selectedEnv} | ${selectedPlatform}`)
executeCommand(selectedProject, selectedEnv, selectedPlatform)
}
// 主逻辑
if (projectName && envName) {
// 参数完整,直接执行
const finalPlatform = platform || "mp-weixin"
executeCommand(projectName, envName, finalPlatform)
}
else {
// 进入交互模式
runInteractive()
}
import { execSync } from 'node:child_process' import { execSync } from "node:child_process"
import { resolve } from 'node:path' import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"
import { join, resolve } from "node:path"
import { createInterface } from "node:readline"
// 解析命令行参数
const args = process.argv.slice(2) const args = process.argv.slice(2)
const project = args.find(a => a.startsWith('--project'))?.split('=')[1] let projectName: string | null = null
let envName: string | null = null
let platform: string | null = null
if (!project) { for (let i = 0; i < args.length; i++) {
console.error('Usage: pnpm dev:project --project=<name>') const arg = args[i]
if (arg === "--project" && args[i + 1]) {
projectName = args[++i]
}
else if (arg === "--env" && args[i + 1]) {
envName = args[++i]
}
else if (arg === "--platform" && args[i + 1]) {
platform = args[++i]
}
else if (arg.startsWith("--project=")) {
projectName = arg.split("=")[1]
}
else if (arg.startsWith("--env=")) {
envName = arg.split("=")[1]
}
else if (arg.startsWith("--platform=")) {
platform = arg.split("=")[1]
}
else if (arg === "-p" && args[i + 1]) {
platform = args[++i]
}
// 用户便利:weixin-mp → mp-weixin
if (platform === "weixin-mp") {
platform = "mp-weixin"
}
}
const packagesDir = resolve("packages")
if (!existsSync(packagesDir)) {
console.error("Packages directory not found:", packagesDir)
process.exit(1) process.exit(1)
} }
const projectDir = resolve('packages', project) const projects = readdirSync(packagesDir).filter((item) => {
console.log(`Starting dev server for ${project}...`) const itemPath = join(packagesDir, item)
return statSync(itemPath).isDirectory()
})
if (projects.length === 0) {
console.error("No projects found in packages directory")
process.exit(1)
}
const envs = [
{ label: "test (测试环境)", value: "test" },
{ label: "staging (预发环境)", value: "staging" },
{ label: "production (生产环境)", value: "production" },
]
const commonPlatforms = ["mp-weixin", "h5", "mp-alipay", "mp-baidu", "mp-toutiao"]
// 判断是否是 UniApp 小程序项目
function isMiniProgramProject(projectPath: string): boolean {
const packageJsonPath = join(projectPath, "package.json")
if (!existsSync(packageJsonPath)) return false
try {
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"))
// 检查是否有 uni 相关依赖或脚本
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
if (deps["@dcloudio/uni-app"] || deps["@dcloudio/vite-plugin-uni"]) {
return true
}
// 检查是否有 mp-weixin 相关脚本
if (pkg.scripts) {
const scripts = Object.keys(pkg.scripts)
if (scripts.some(s => s.includes("mp-weixin") || s.includes("mp-alipay"))) {
return true
}
}
}
catch {
return false
}
return false
}
// 执行命令
function executeCommand(projectName: string, envName: string, platform: string) {
const projectPath = join(packagesDir, projectName)
const packageJsonPath = join(projectPath, "package.json")
if (!existsSync(packageJsonPath)) {
console.error(`package.json not found in project ${projectName}`)
process.exit(1)
}
console.log(`\nStarting dev for project ${projectName} in ${envName} environment on ${platform}...`)
const isMiniProgram = isMiniProgramProject(projectPath)
const useScript = platform.includes(":") || platform === "custom"
// 切换到项目目录
process.chdir(projectPath)
try {
if (useScript) {
// 使用 pnpm run script
let scriptName: string
if (platform === "h5") {
scriptName = `dev:h5`
}
else {
scriptName = `dev:${platform}`
}
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"))
if (!pkg.scripts || !pkg.scripts[scriptName]) {
console.error(`Script ${scriptName} not found in project ${projectName}`)
process.exit(1)
}
execSync(`cd ${projectDir} && pnpm dev`, { stdio: 'inherit' }) console.log(`Executing: pnpm run ${scriptName}`)
execSync(`pnpm run ${scriptName}`, { stdio: "inherit" })
}
else if (isMiniProgram) {
// UniApp 项目使用 npx uni
let cmd = "npx uni"
if (platform !== "h5") {
cmd += ` -p ${platform}`
}
cmd += ` --mode ${envName}`
console.log(`Executing: ${cmd}`)
execSync(cmd, { stdio: "inherit" })
}
else {
// Vue/Nuxt 项目直接使用 pnpm dev
console.log(`Executing: pnpm dev`)
execSync("pnpm dev", { stdio: "inherit" })
}
}
catch (error: any) {
console.error(`Error running command: ${error.message}`)
process.exit(1)
}
}
// 交互式选择
function askQuestion(query: string): Promise<string> {
const rl = createInterface({
input: process.stdin,
output: process.stdout,
})
return new Promise((resolve) => {
rl.question(query, (answer) => {
rl.close()
resolve(answer.trim())
})
})
}
async function runInteractive() {
// 1. 选择项目
console.log("\n=== 选择项目 ===")
projects.forEach((p, i) => console.log(`${i + 1}. ${p}`))
let selectedProject: string
if (projectName && projects.includes(projectName)) {
selectedProject = projectName
console.log(`已选择项目: ${selectedProject}`)
}
else {
const answer = await askQuestion("\n请输入项目序号或名称: ")
const index = Number.parseInt(answer) - 1
if (!Number.isNaN(index) && index >= 0 && index < projects.length) {
selectedProject = projects[index]
}
else if (projects.includes(answer)) {
selectedProject = answer
}
else {
console.error("无效的选择")
process.exit(1)
}
}
// 2. 选择环境
console.log("\n=== 选择环境 ===")
envs.forEach((e, i) => console.log(`${i + 1}. ${e.label}`))
let selectedEnv: string
if (envName && envs.some(e => e.value === envName)) {
selectedEnv = envName
console.log(`已选择环境: ${selectedEnv}`)
}
else {
const answer = await askQuestion("\n请输入环境序号: ")
const index = Number.parseInt(answer) - 1
if (!Number.isNaN(index) && index >= 0 && index < envs.length) {
selectedEnv = envs[index].value
}
else {
console.error("无效的选择")
process.exit(1)
}
}
// 3. 判断是否需要选择平台
const projectPath = join(packagesDir, selectedProject)
const isMiniProgram = isMiniProgramProject(projectPath)
let selectedPlatform: string
if (isMiniProgram) {
console.log("\n=== 选择构建平台 ===")
commonPlatforms.forEach((p, i) => console.log(`${i + 1}. ${p}`))
if (platform && commonPlatforms.includes(platform)) {
selectedPlatform = platform
console.log(`已选择平台: ${selectedPlatform}`)
}
else {
const answer = await askQuestion("\n请输入平台序号 (默认 1): ")
const index = Number.parseInt(answer || "1") - 1
if (!Number.isNaN(index) && index >= 0 && index < commonPlatforms.length) {
selectedPlatform = commonPlatforms[index]
}
else {
selectedPlatform = commonPlatforms[0] // 默认 mp-weixin
}
}
}
else {
selectedPlatform = "default"
console.log("\n该项目为 Web 项目,无需选择平台")
}
console.log(`\n开始开发: ${selectedProject} | ${selectedEnv} | ${selectedPlatform}`)
executeCommand(selectedProject, selectedEnv, selectedPlatform)
}
// 主逻辑
if (projectName && envName) {
// 参数完整,直接执行
const finalPlatform = platform || "mp-weixin"
executeCommand(projectName, envName, finalPlatform)
}
else {
// 进入交互模式
runInteractive()
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment