project-runner 0.1.0 → 0.1.2

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.
@@ -1,118 +0,0 @@
1
- import { stat } from 'fs/promises'
2
- import { join } from 'path'
3
-
4
- export interface DependencyStatus {
5
- hasNodeModules: boolean
6
- needsInstall: boolean
7
- reason?: string
8
- }
9
-
10
- // Lockfile 列表
11
- const LOCKFILES = [
12
- 'bun.lockb',
13
- 'bun.lock',
14
- 'pnpm-lock.yaml',
15
- 'yarn.lock',
16
- 'package-lock.json',
17
- ]
18
-
19
- /**
20
- * 检测项目的依赖状态
21
- * 判断是否需要执行 install
22
- */
23
- export async function checkDependencyStatus(projectDir: string): Promise<DependencyStatus> {
24
- const nodeModulesPath = join(projectDir, 'node_modules')
25
-
26
- // 1. 检查 node_modules 是否存在
27
- const nodeModulesExists = await directoryExists(nodeModulesPath)
28
- if (!nodeModulesExists) {
29
- return {
30
- hasNodeModules: false,
31
- needsInstall: true,
32
- reason: 'node_modules 不存在',
33
- }
34
- }
35
-
36
- // 2. 检查 lockfile 是否比 node_modules 更新
37
- const lockfilePath = await findLockfile(projectDir)
38
- if (lockfilePath) {
39
- const lockfileMtime = await getModifiedTime(lockfilePath)
40
- const nodeModulesMtime = await getModifiedTime(nodeModulesPath)
41
-
42
- if (lockfileMtime && nodeModulesMtime && lockfileMtime > nodeModulesMtime) {
43
- return {
44
- hasNodeModules: true,
45
- needsInstall: true,
46
- reason: 'lockfile 已更新',
47
- }
48
- }
49
- }
50
-
51
- // 3. 检查 package.json 是否比 node_modules 更新
52
- const packageJsonPath = join(projectDir, 'package.json')
53
- const packageJsonMtime = await getModifiedTime(packageJsonPath)
54
- const nodeModulesMtime = await getModifiedTime(nodeModulesPath)
55
-
56
- if (packageJsonMtime && nodeModulesMtime && packageJsonMtime > nodeModulesMtime) {
57
- return {
58
- hasNodeModules: true,
59
- needsInstall: true,
60
- reason: 'package.json 已更新',
61
- }
62
- }
63
-
64
- // 依赖状态正常
65
- return {
66
- hasNodeModules: true,
67
- needsInstall: false,
68
- }
69
- }
70
-
71
- /**
72
- * 查找项目中的 lockfile
73
- */
74
- async function findLockfile(projectDir: string): Promise<string | null> {
75
- for (const lockfile of LOCKFILES) {
76
- const lockfilePath = join(projectDir, lockfile)
77
- if (await fileExists(lockfilePath)) {
78
- return lockfilePath
79
- }
80
- }
81
- return null
82
- }
83
-
84
- /**
85
- * 获取文件/目录的修改时间
86
- */
87
- async function getModifiedTime(path: string): Promise<number | null> {
88
- try {
89
- const stats = await stat(path)
90
- return stats.mtimeMs
91
- } catch {
92
- return null
93
- }
94
- }
95
-
96
- /**
97
- * 检查目录是否存在
98
- */
99
- async function directoryExists(path: string): Promise<boolean> {
100
- try {
101
- const stats = await stat(path)
102
- return stats.isDirectory()
103
- } catch {
104
- return false
105
- }
106
- }
107
-
108
- /**
109
- * 检查文件是否存在
110
- */
111
- async function fileExists(path: string): Promise<boolean> {
112
- try {
113
- const stats = await stat(path)
114
- return stats.isFile()
115
- } catch {
116
- return false
117
- }
118
- }
@@ -1,56 +0,0 @@
1
- import { detectPackageManager, type PackageManagerInfo } from './package-manager'
2
- import { analyzeScripts, type ScriptsInfo } from './scripts'
3
- import { checkDependencyStatus, type DependencyStatus } from './dependencies'
4
-
5
- export interface ProjectInfo {
6
- type: 'nodejs' | 'python' | 'unknown'
7
- packageManager: PackageManagerInfo
8
- scripts: ScriptsInfo | null
9
- dependencies: DependencyStatus
10
- name?: string
11
- version?: string
12
- description?: string
13
- }
14
-
15
- /**
16
- * 分析项目,获取完整的项目信息
17
- */
18
- export async function analyzeProject(projectDir: string): Promise<ProjectInfo> {
19
- // 检测项目类型(目前仅支持 Node.js)
20
- const packageJsonPath = `${projectDir}/package.json`
21
- const hasPackageJson = await Bun.file(packageJsonPath).exists()
22
-
23
- if (!hasPackageJson) {
24
- return {
25
- type: 'unknown',
26
- packageManager: { name: 'npm', source: 'default' },
27
- scripts: null,
28
- dependencies: { hasNodeModules: false, needsInstall: false },
29
- }
30
- }
31
-
32
- // 读取 package.json 基本信息
33
- const packageJson = await Bun.file(packageJsonPath).json().catch(() => ({}))
34
-
35
- // 并行执行检测
36
- const [packageManager, scripts, dependencies] = await Promise.all([
37
- detectPackageManager(projectDir),
38
- analyzeScripts(projectDir),
39
- checkDependencyStatus(projectDir),
40
- ])
41
-
42
- return {
43
- type: 'nodejs',
44
- packageManager,
45
- scripts,
46
- dependencies,
47
- name: packageJson.name,
48
- version: packageJson.version,
49
- description: packageJson.description,
50
- }
51
- }
52
-
53
- // 导出子模块
54
- export { detectPackageManager, type PackageManagerInfo } from './package-manager'
55
- export { analyzeScripts, type ScriptsInfo, getAvailableScripts, hasScript } from './scripts'
56
- export { checkDependencyStatus, type DependencyStatus } from './dependencies'
@@ -1,119 +0,0 @@
1
- // 包管理器类型
2
- export type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun'
3
-
4
- // 包管理器检测来源
5
- export type DetectionSource = 'packageManager' | 'volta' | 'lockfile' | 'default'
6
-
7
- export interface PackageManagerInfo {
8
- name: PackageManager
9
- version?: string
10
- source: DetectionSource
11
- }
12
-
13
- // Lockfile 检测映射
14
- const LOCKFILE_MAP: Record<string, PackageManager> = {
15
- 'bun.lockb': 'bun',
16
- 'bun.lock': 'bun',
17
- 'pnpm-lock.yaml': 'pnpm',
18
- 'yarn.lock': 'yarn',
19
- 'package-lock.json': 'npm',
20
- }
21
-
22
- /**
23
- * 检测项目使用的包管理器
24
- * 优先级: packageManager 字段 > volta 字段 > lockfile
25
- */
26
- export async function detectPackageManager(projectDir: string): Promise<PackageManagerInfo> {
27
- const packageJsonPath = `${projectDir}/package.json`
28
-
29
- // 读取 package.json
30
- const packageJson = await readPackageJson(packageJsonPath)
31
- if (!packageJson) {
32
- return { name: 'npm', source: 'default' }
33
- }
34
-
35
- // 1. 检查 packageManager 字段 (corepack)
36
- if (packageJson.packageManager) {
37
- const match = packageJson.packageManager.match(/^(npm|yarn|pnpm|bun)@(.+)$/)
38
- if (match) {
39
- return {
40
- name: match[1] as PackageManager,
41
- version: match[2],
42
- source: 'packageManager',
43
- }
44
- }
45
- }
46
-
47
- // 2. 检查 volta 字段
48
- if (packageJson.volta) {
49
- for (const pm of ['pnpm', 'yarn', 'npm'] as PackageManager[]) {
50
- if (packageJson.volta[pm]) {
51
- return {
52
- name: pm,
53
- version: packageJson.volta[pm],
54
- source: 'volta',
55
- }
56
- }
57
- }
58
- }
59
-
60
- // 3. 检测 lockfile
61
- for (const [lockfile, pm] of Object.entries(LOCKFILE_MAP)) {
62
- const exists = await Bun.file(`${projectDir}/${lockfile}`).exists()
63
- if (exists) {
64
- return { name: pm, source: 'lockfile' }
65
- }
66
- }
67
-
68
- // 默认使用 npm
69
- return { name: 'npm', source: 'default' }
70
- }
71
-
72
- /**
73
- * 获取包管理器的运行命令
74
- */
75
- export function getRunCommand(pm: PackageManager, script: string): string[] {
76
- switch (pm) {
77
- case 'bun':
78
- return ['bun', 'run', script]
79
- case 'pnpm':
80
- return ['pnpm', script]
81
- case 'yarn':
82
- return ['yarn', script]
83
- case 'npm':
84
- default:
85
- return ['npm', 'run', script]
86
- }
87
- }
88
-
89
- /**
90
- * 获取包管理器的安装命令
91
- */
92
- export function getInstallCommand(pm: PackageManager): string[] {
93
- switch (pm) {
94
- case 'bun':
95
- return ['bun', 'install']
96
- case 'pnpm':
97
- return ['pnpm', 'install']
98
- case 'yarn':
99
- return ['yarn', 'install']
100
- case 'npm':
101
- default:
102
- return ['npm', 'install']
103
- }
104
- }
105
-
106
- /**
107
- * 读取 package.json
108
- */
109
- async function readPackageJson(path: string): Promise<any | null> {
110
- try {
111
- const file = Bun.file(path)
112
- if (await file.exists()) {
113
- return await file.json()
114
- }
115
- } catch {
116
- // 忽略错误
117
- }
118
- return null
119
- }
@@ -1,98 +0,0 @@
1
- export interface ScriptsInfo {
2
- scripts: Record<string, string>
3
- // 识别出的主要命令(dev, test, build, start)
4
- detected: {
5
- dev?: string // 开发启动脚本名
6
- test?: string // 测试脚本名
7
- build?: string // 构建脚本名
8
- start?: string // 生产启动脚本名
9
- }
10
- }
11
-
12
- // 开发命令的常见名称(按优先级排序)
13
- const DEV_PATTERNS = ['dev', 'serve', 'start:dev', 'develop', 'watch']
14
-
15
- // 测试命令的常见名称
16
- const TEST_PATTERNS = ['test', 'test:unit', 'test:all', 'spec']
17
-
18
- // 构建命令的常见名称
19
- const BUILD_PATTERNS = ['build', 'compile', 'bundle', 'dist']
20
-
21
- // 生产启动命令的常见名称
22
- const START_PATTERNS = ['start', 'serve', 'preview', 'production']
23
-
24
- /**
25
- * 读取并分析 package.json 的 scripts 字段
26
- */
27
- export async function analyzeScripts(projectDir: string): Promise<ScriptsInfo | null> {
28
- const packageJsonPath = `${projectDir}/package.json`
29
-
30
- try {
31
- const file = Bun.file(packageJsonPath)
32
- if (!await file.exists()) {
33
- return null
34
- }
35
-
36
- const packageJson = await file.json()
37
- const scripts = packageJson.scripts || {}
38
-
39
- // 智能识别主要命令
40
- const detected = {
41
- dev: findMatchingScript(scripts, DEV_PATTERNS),
42
- test: findMatchingScript(scripts, TEST_PATTERNS),
43
- build: findMatchingScript(scripts, BUILD_PATTERNS),
44
- start: findMatchingScript(scripts, START_PATTERNS),
45
- }
46
-
47
- return { scripts, detected }
48
- } catch {
49
- return null
50
- }
51
- }
52
-
53
- /**
54
- * 从 scripts 中找到匹配的脚本名
55
- * @param scripts package.json 的 scripts 对象
56
- * @param patterns 要匹配的模式列表(按优先级排序)
57
- */
58
- function findMatchingScript(scripts: Record<string, string>, patterns: string[]): string | undefined {
59
- const scriptNames = Object.keys(scripts)
60
-
61
- // 按优先级检查每个模式
62
- for (const pattern of patterns) {
63
- // 精确匹配
64
- if (scripts[pattern]) {
65
- return pattern
66
- }
67
- // 模糊匹配(包含模式的脚本名)
68
- const fuzzyMatch = scriptNames.find(name =>
69
- name.toLowerCase().includes(pattern.toLowerCase())
70
- )
71
- if (fuzzyMatch) {
72
- return fuzzyMatch
73
- }
74
- }
75
-
76
- return undefined
77
- }
78
-
79
- /**
80
- * 获取所有可用的脚本列表
81
- */
82
- export function getAvailableScripts(scriptsInfo: ScriptsInfo): string[] {
83
- return Object.keys(scriptsInfo.scripts)
84
- }
85
-
86
- /**
87
- * 检查脚本是否存在
88
- */
89
- export function hasScript(scriptsInfo: ScriptsInfo, scriptName: string): boolean {
90
- return scriptName in scriptsInfo.scripts
91
- }
92
-
93
- /**
94
- * 获取脚本的实际命令内容
95
- */
96
- export function getScriptCommand(scriptsInfo: ScriptsInfo, scriptName: string): string | undefined {
97
- return scriptsInfo.scripts[scriptName]
98
- }
package/src/cli/info.ts DELETED
@@ -1,87 +0,0 @@
1
- import { analyzeProject } from '../analyzer'
2
- import { colors } from '../utils/log'
3
-
4
- /**
5
- * pr info 命令
6
- * 显示项目分析结果
7
- */
8
- export async function infoCommand(projectDir: string) {
9
- const project = await analyzeProject(projectDir)
10
-
11
- if (project.type === 'unknown') {
12
- console.log(`${colors.red}✗${colors.reset} 未检测到项目类型`)
13
- console.log(' 请确保当前目录包含 package.json 或其他项目配置文件')
14
- return
15
- }
16
-
17
- // 项目基本信息
18
- console.log()
19
- console.log(`${colors.cyan}${colors.bold}pr - 项目分析结果${colors.reset}`)
20
- console.log('─'.repeat(40))
21
-
22
- if (project.name) {
23
- console.log(`${colors.bold}项目名称:${colors.reset} ${project.name}`)
24
- }
25
- if (project.version) {
26
- console.log(`${colors.bold}版本:${colors.reset} ${project.version}`)
27
- }
28
- if (project.description) {
29
- console.log(`${colors.bold}描述:${colors.reset} ${project.description}`)
30
- }
31
-
32
- console.log(`${colors.bold}项目类型:${colors.reset} ${project.type}`)
33
-
34
- // 包管理器信息
35
- const pm = project.packageManager
36
- let pmInfo = pm.name
37
- if (pm.version) {
38
- pmInfo += `@${pm.version}`
39
- }
40
- pmInfo += ` ${colors.dim}(${pm.source})${colors.reset}`
41
- console.log(`${colors.bold}包管理器:${colors.reset} ${pmInfo}`)
42
-
43
- // 依赖状态
44
- const deps = project.dependencies
45
- const depsStatus = deps.needsInstall
46
- ? `${colors.yellow}需要安装${colors.reset} (${deps.reason})`
47
- : `${colors.green}已就绪${colors.reset}`
48
- console.log(`${colors.bold}依赖状态:${colors.reset} ${depsStatus}`)
49
-
50
- console.log()
51
-
52
- // Scripts 信息
53
- if (project.scripts) {
54
- const { scripts, detected } = project.scripts
55
-
56
- // 显示识别的主要命令
57
- console.log(`${colors.bold}识别的命令:${colors.reset}`)
58
- if (detected.dev) {
59
- console.log(` ${colors.green}pr run${colors.reset} → ${pm.name} ${detected.dev}`)
60
- }
61
- if (detected.test) {
62
- console.log(` ${colors.green}pr test${colors.reset} → ${pm.name} ${detected.test}`)
63
- }
64
- if (detected.build) {
65
- console.log(` ${colors.green}pr build${colors.reset} → ${pm.name} ${detected.build}`)
66
- }
67
- if (detected.start) {
68
- console.log(` ${colors.green}pr start${colors.reset} → ${pm.name} ${detected.start}`)
69
- }
70
-
71
- console.log()
72
-
73
- // 显示所有可用脚本
74
- const allScripts = Object.keys(scripts)
75
- if (allScripts.length > 0) {
76
- console.log(`${colors.bold}所有脚本:${colors.reset}`)
77
- for (const name of allScripts) {
78
- const cmd = scripts[name]
79
- // 截断过长的命令
80
- const displayCmd = cmd.length > 40 ? cmd.slice(0, 40) + '...' : cmd
81
- console.log(` ${colors.cyan}${name}${colors.reset} ${colors.dim}→ ${displayCmd}${colors.reset}`)
82
- }
83
- }
84
- }
85
-
86
- console.log()
87
- }
package/src/cli/run.ts DELETED
@@ -1,117 +0,0 @@
1
- import { analyzeProject, type ProjectInfo } from '../analyzer'
2
- import { getRunCommand, getInstallCommand } from '../analyzer/package-manager'
3
- import { execute } from '../runner/executor'
4
- import { log, error, warn, success, info, newline } from '../utils/log'
5
-
6
- type ScriptType = 'dev' | 'test' | 'build' | 'start'
7
-
8
- interface RunOptions {
9
- noInstall?: boolean
10
- scriptType?: ScriptType
11
- }
12
-
13
- /**
14
- * qy run 命令
15
- * 完整流程:检测 → install → 启动
16
- */
17
- export async function runCommand(projectDir: string, options: RunOptions = {}) {
18
- const { noInstall = false, scriptType = 'dev' } = options
19
-
20
- // 1. 分析项目
21
- log('正在分析项目...')
22
- const project = await analyzeProject(projectDir)
23
-
24
- if (project.type === 'unknown') {
25
- error('未检测到项目类型。请确保当前目录包含 package.json')
26
- process.exit(1)
27
- }
28
-
29
- // 2. 输出检测结果
30
- log(`项目类型: ${project.type}`)
31
- log(`包管理器: ${project.packageManager.name} (from ${project.packageManager.source})`)
32
-
33
- if (!project.scripts) {
34
- error('无法读取 package.json 的 scripts')
35
- process.exit(1)
36
- }
37
-
38
- // 3. 确定要执行的脚本
39
- const scriptName = findScript(project, scriptType)
40
-
41
- if (!scriptName) {
42
- error(`未找到 ${scriptType} 相关的脚本`)
43
- showAvailableScripts(project)
44
- process.exit(1)
45
- }
46
-
47
- log(`将执行脚本: ${scriptName}`)
48
-
49
- // 4. 检查依赖状态,决定是否需要 install
50
- if (!noInstall && project.dependencies.needsInstall) {
51
- log(`依赖状态: ${project.dependencies.reason || '需要安装'}`)
52
- newline()
53
-
54
- // 执行 install
55
- const installCmd = getInstallCommand(project.packageManager.name)
56
- const installExitCode = await execute(installCmd, { cwd: projectDir })
57
-
58
- if (installExitCode !== 0) {
59
- error('依赖安装失败')
60
- process.exit(installExitCode)
61
- }
62
-
63
- success('依赖安装完成')
64
- newline()
65
- } else if (!noInstall) {
66
- log('依赖状态: 已是最新')
67
- }
68
-
69
- // 5. 执行脚本
70
- const runCmd = getRunCommand(project.packageManager.name, scriptName)
71
- const exitCode = await execute(runCmd, { cwd: projectDir })
72
-
73
- if (exitCode !== 0) {
74
- process.exit(exitCode)
75
- }
76
- }
77
-
78
- /**
79
- * 根据脚本类型找到对应的脚本名
80
- */
81
- function findScript(project: ProjectInfo, scriptType: ScriptType): string | undefined {
82
- const scripts = project.scripts
83
- if (!scripts) return undefined
84
-
85
- // 使用智能检测的结果
86
- const detected = scripts.detected[scriptType]
87
- if (detected) {
88
- return detected
89
- }
90
-
91
- // 如果没有检测到,尝试精确匹配
92
- if (scripts.scripts[scriptType]) {
93
- return scriptType
94
- }
95
-
96
- return undefined
97
- }
98
-
99
- /**
100
- * 显示可用的脚本列表
101
- */
102
- function showAvailableScripts(project: ProjectInfo) {
103
- if (!project.scripts) return
104
-
105
- const scriptNames = Object.keys(project.scripts.scripts)
106
- if (scriptNames.length === 0) {
107
- warn('package.json 中没有定义任何 scripts')
108
- return
109
- }
110
-
111
- info('可用的脚本:')
112
- for (const name of scriptNames) {
113
- console.log(` - ${name}`)
114
- }
115
- console.log()
116
- info('使用 qy <script> 运行任意脚本')
117
- }
package/src/cli/script.ts DELETED
@@ -1,59 +0,0 @@
1
- import { analyzeProject } from '../analyzer'
2
- import { getRunCommand } from '../analyzer/package-manager'
3
- import { execute } from '../runner/executor'
4
- import { error, log, info } from '../utils/log'
5
-
6
- /**
7
- * 运行任意 package.json 脚本
8
- */
9
- export async function scriptCommand(projectDir: string, scriptName: string) {
10
- // 分析项目
11
- const project = await analyzeProject(projectDir)
12
-
13
- if (project.type === 'unknown') {
14
- error('未检测到项目类型。请确保当前目录包含 package.json')
15
- process.exit(1)
16
- }
17
-
18
- if (!project.scripts) {
19
- error('无法读取 package.json 的 scripts')
20
- process.exit(1)
21
- }
22
-
23
- // 检查脚本是否存在
24
- const scripts = project.scripts.scripts
25
- if (!(scriptName in scripts)) {
26
- error(`脚本 "${scriptName}" 不存在`)
27
- console.log()
28
- showAvailableScripts(scripts)
29
- process.exit(1)
30
- }
31
-
32
- log(`包管理器: ${project.packageManager.name}`)
33
- log(`执行脚本: ${scriptName}`)
34
-
35
- // 执行脚本
36
- const runCmd = getRunCommand(project.packageManager.name, scriptName)
37
- const exitCode = await execute(runCmd, { cwd: projectDir })
38
-
39
- if (exitCode !== 0) {
40
- process.exit(exitCode)
41
- }
42
- }
43
-
44
- /**
45
- * 显示可用脚本列表
46
- */
47
- function showAvailableScripts(scripts: Record<string, string>) {
48
- const scriptNames = Object.keys(scripts)
49
-
50
- if (scriptNames.length === 0) {
51
- info('package.json 中没有定义任何脚本')
52
- return
53
- }
54
-
55
- info('可用的脚本:')
56
- for (const name of scriptNames) {
57
- console.log(` - ${name}`)
58
- }
59
- }