Skip to content

Commit

Permalink
fix: 对等依赖
Browse files Browse the repository at this point in the history
  • Loading branch information
sj817 committed Aug 15, 2024
1 parent 2cc26ac commit ed26b69
Show file tree
Hide file tree
Showing 6 changed files with 337 additions and 328 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ jobs:
# 安装 pnpm
- run: npm install -g pnpm
if: ${{ steps.release.outputs.release_created }}
# 安装依赖
- run: pnpm install --ignore-scripts
# 安装依赖 不安装对等依赖
- run: pnpm install --ignore-scripts --no-peer-deps
env:
NODE_AUTH_TOKEN: ${{ secrets.RELEASE }}
if: ${{ steps.release.outputs.release_created }}
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,11 @@
"fix": "eslint lib/**/*.js --fix",
"fix:all": "eslint lib/**/*.js --fix && eslint lib/**/*.d.ts --fix",
"pub": "npm publish --access public",
"sort": "npx sort-package-json && sort-json tsconfig.json"
"sort": "npx sort-package-json && sort-json tsconfig.json",
"dev": "node lib/cli/dev.js"
},
"peerDependencies": {
"node-karin": "link:./lib"
},
"dependencies": {
"art-template": "4.13.2",
Expand Down
4 changes: 4 additions & 0 deletions src/cli/dev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { KarinCli, Mode, Runner, Lang } from './index'

const karin = new KarinCli()
karin.start(Mode.Dev, Lang.Js, Runner.Node)
324 changes: 324 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
import fs from 'fs'
import path from 'path'
import yaml from 'yaml'
import axios from 'axios'
import { fileURLToPath } from 'url'
import { exec as execCmd, spawn, ChildProcess } from 'child_process'
import { KarinCfgInit } from '../core/init/config'

export const enum Runner {
Node = 'node',
Tsx = 'tsx',
Pm2 = 'pm2'
}

export const enum Mode {
Dev = 'dev',
Prod = 'prod'
}

export const enum Lang {
Js = 'js',
Ts = 'ts'
}

export class KarinCli {
child: ChildProcess
filename: string
karinDir: string
file: string

constructor () {
process.env.karin_app_start_count = '0'
process.env.karin_app_watch = 'no'
/** 当前文件绝对路径 */
this.filename = fileURLToPath(import.meta.url)
/** karin目录 */
this.karinDir = path.join(path.dirname(this.filename), '../..')
/** 入口文件(注意后缀) */
this.file = path.join(path.dirname(this.filename), '../index.js')
this.child = null as unknown as ChildProcess
}

/**
* 获取pkg
* @param isNpm - 是否是npm包
*/
pkg (isNpm: boolean) {
const filePath = isNpm ? path.join(this.karinDir, 'package.json') : path.join(process.cwd(), 'package.json')
const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
return data
}

/**
* 获取配置文件路径
* @param name - 配置文件名
*/
getConfigPath (name: 'pm2' | 'server') {
const filePath = `./config/config/${name}.yaml`
if (!fs.existsSync(filePath)) return `${this.karinDir}/config/config/${name}.yaml`
return filePath
}

/**
* 获取pm2配置
* @param name - 配置文件名
*/
getConfigData (name: 'pm2' | 'server') {
const _path = this.getConfigPath(name)
const data = yaml.parse(fs.readFileSync(_path, 'utf-8'))
return data
}

/**
* 启动
* @param mode - 模式
* @param lang - 语言环境
* @param runner - 运行器
*/
start (mode: Mode, lang: Lang, runner: Runner) {
process.env.karin_app_mode = mode
process.env.karin_app_lang = lang
process.env.karin_app_runner = runner

let cmd: string[]

switch (runner) {
case Runner.Node:
cmd = [this.file]
break
case Runner.Tsx:
cmd = [this.file]
break
case Runner.Pm2: {
this.pm2()
return
}
}

/** 启动 */
this.child = spawn(runner, cmd, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], cwd: process.cwd(), env: process.env, shell: runner === Runner.Tsx })
/** 监听退出 */
this.child.once('exit', (code) => process.exit(code))
/** 监听子进程消息 */
this.child.on('message', (data: {
action: 'result'
env: NodeJS.ProcessEnv
}) => {
/** pm2重启 */
if (data.env.pm_id) return this.restart()

try {
/** 先结束进程 */
this.child.kill('SIGINT')
/** 停止监听 */
this.child.removeAllListeners()
/** 重启次数+1 */
const count = Number(process.env.karin_app_start_count) || 0
process.env.karin_app_start_count = String(count + 1)
} catch { }
return this.start(mode, lang, runner)
})
}

/**
* pm2重启
*/
async restart () {
const pm2Cfg = this.getConfigData('pm2')
const serverCfg = this.getConfigData('server')

/** 尝试获取pm2的进程id */
const port = serverCfg?.http?.port || 7000
const res = await this.Axios(`http://127.0.0.1:${port}/api/ping`, 1000)
if (res) {
await this.exec(`pm2 restart ${res.pm2_id}`)
} else {
await this.exec(`pm2 restart ${pm2Cfg?.apps[0]?.name || 'Karin'}`)
}

console.log('pm2服务已重启')
process.exit(0)
}

/**
* pm2启动
*/
async pm2 () {
console.log('pm2启动中...')
const filePath = this.getConfigPath('pm2')
const data = this.getConfigData('pm2')

/** 修正入口文件路径 兼容0.6.28以前的版本 */
if (!fs.existsSync('./src') && filePath === './config/config/pm2.yaml') {
const script = './node_modules/node-karin/lib/index.js'
if (data.apps[0].script !== script) {
data.apps[0].script = script
fs.writeFileSync(filePath, yaml.stringify(data))
}
}

const cmd = `pm2 start ${filePath} --env ${JSON.stringify(process.env)}`
await this.exec(cmd)

console.log('pm2服务已启动 可执行 【npx karin log】 查看日志')
process.exit(0)
}

/**
* pm2结束进程
*/
async stop () {
console.log('pm2服务停止中...')
const pm2Cfg = this.getConfigData('pm2')
const serverCfg = this.getConfigData('server')
const port = serverCfg?.http?.port || 7000
const res = await this.Axios(`http://127.0.0.1:${port}/api/ping`, 1000)
if (res) {
await this.exec(`pm2 delete ${res.pm2_id}`)
} else {
await this.exec(`pm2 delete ${pm2Cfg?.apps[0]?.name || 'Karin'}`)
}

console.log('pm2服务已停止')
process.exit(0)
}

/**
* pm2查看日志
*/
async log () {
const pm2Cfg = this.getConfigData('pm2')
const serverCfg = this.getConfigData('server')
const port = serverCfg?.http?.port || 7000
const res = await this.Axios(`http://127.0.0.1:${port}/api/ping`, 1000)
const lines = pm2Cfg?.lines || 1000
const cmd = process.platform === 'win32' ? 'pm2.cmd' : 'pm2'

const type = res ? res.pm_id : pm2Cfg?.apps[0]?.name || 'Karin'

spawn(cmd, ['logs', type, '--lines', lines], { stdio: 'inherit', shell: true, cwd: process.cwd() })
}

/**
* 更新依赖
*/
async update () {
/** 屏蔽的依赖包列表 */
const pkgdependencies = [
'art-template',
'axios',
'chalk',
'chokidar',
'commander',
'express',
'level',
'lodash',
'log4js',
'moment',
'node-schedule',
'redis',
'ws',
'yaml',
]

const list = Object.keys(this.pkg(false).dependencies).filter(key => !pkgdependencies.includes(key))

/** 获取包管理器 */
const pkg = new KarinCfgInit().getRegistry()
const cmd = pkg === 'yarn' ? 'yarn upgrade' : `${pkg} update`

/** 异步并发更新依赖 */
await Promise.all(list.map(async item => {
try {
/** 检查是否已经是最新版本 */
const local = await this.getLocalVersion(item, pkg)
const remote = await this.getRemoteVersion(item, pkg)
if (local === remote) {
console.log(`[依赖更新] ${item} 已经是最新~`)
return
}

console.log(`[依赖更新] ${item} 当前版本: ${local} 最新版本: ${remote}`)

await this.exec(`${cmd} ${item}@latest`)
console.log(`[依赖更新] ${item} 更新完成~`)
} catch (error: any) {
console.error(`[依赖更新] ${item} 更新失败:`)
console.error(`error.stack: ${error.stack}`)
console.error(`error.message: ${error.message}`)
}
}))

console.log('所有依赖已更新完成~')
}

/**
* 获取指定包的本地版本
* @param name - 包名
* @param pkg - 包管理器
* @returns - 版本号
*/
async getLocalVersion (name: string, pkg: 'pnpm' | 'cnpm' | 'yarn' | 'npm') {
const cmd = pkg === 'yarn' ? `yarn list --pattern ${name}` : `${pkg} list ${name} --depth=0`
const text = await this.exec(cmd)

/** pnpm特殊处理 */
if (pkg === 'pnpm') {
const reg = new RegExp(`${name}\\s+([\\d.]+)`, 'gm')
const res = reg.exec(text)
return res?.[1] || '0.0.0'
}

const reg = new RegExp(`${name}@(\\d+\\.\\d+\\.\\d+)`, 'gm')
const res = reg.exec(text)
return res?.[1] || '0.0.0'
}

/**
* 获取指定包的最新版本
* @param name - 包名
* @param pkg - 包管理器
*/
async getRemoteVersion (name: string, pkg: 'pnpm' | 'cnpm' | 'yarn' | 'npm') {
const cmd = `${pkg} info ${name} version`
const text = await this.exec(cmd)
/** yarn特殊处理 */
if (pkg === 'yarn') {
const lines = text.split('\n').map(line => line.trim())
const ver = lines.find(line => /^\d+\.\d+\.\d+$/.test(line))
return ver || ''
}

return text.trim()
}

/**
* 封装exec
* @param cmd - 命令
*/
exec (cmd: string): Promise<string> {
return new Promise((resolve, reject) => {
execCmd(cmd, (error, stdout, stderr) => {
if (stdout) return resolve(stdout.trim())
if (error) return reject(error)
return reject(stderr)
})
})
}

/**
* 封装axios 超时返回false
* @param url - 请求地址
* @param timeout - 超时时间
* @returns - 请求结果
*/
async Axios (url: string, timeout: number) {
try {
const res = await axios.get(url, { timeout })
return res.data
} catch {
return false
}
}
}
Loading

0 comments on commit ed26b69

Please sign in to comment.