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.
- package/dist/index.js +648 -0
- package/package.json +8 -8
- package/src/analyzer/dependencies.ts +0 -118
- package/src/analyzer/index.ts +0 -56
- package/src/analyzer/package-manager.ts +0 -119
- package/src/analyzer/scripts.ts +0 -98
- package/src/cli/info.ts +0 -87
- package/src/cli/run.ts +0 -117
- package/src/cli/script.ts +0 -59
- package/src/index.ts +0 -141
- package/src/runner/executor.ts +0 -93
- package/src/utils/log.ts +0 -86
package/dist/index.js
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
|
|
6
|
+
// src/utils/log.ts
|
|
7
|
+
var colors = {
|
|
8
|
+
reset: "\x1B[0m",
|
|
9
|
+
bold: "\x1B[1m",
|
|
10
|
+
dim: "\x1B[2m",
|
|
11
|
+
red: "\x1B[31m",
|
|
12
|
+
green: "\x1B[32m",
|
|
13
|
+
yellow: "\x1B[33m",
|
|
14
|
+
blue: "\x1B[34m",
|
|
15
|
+
magenta: "\x1B[35m",
|
|
16
|
+
cyan: "\x1B[36m",
|
|
17
|
+
white: "\x1B[37m",
|
|
18
|
+
gray: "\x1B[90m"
|
|
19
|
+
};
|
|
20
|
+
var isVerbose = false;
|
|
21
|
+
function setVerbose(verbose) {
|
|
22
|
+
isVerbose = verbose;
|
|
23
|
+
}
|
|
24
|
+
function log(message) {
|
|
25
|
+
if (isVerbose) {
|
|
26
|
+
console.log(`${colors.cyan}[pr]${colors.reset} ${message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function info(message) {
|
|
30
|
+
console.log(`${colors.cyan}[pr]${colors.reset} ${message}`);
|
|
31
|
+
}
|
|
32
|
+
function success(message) {
|
|
33
|
+
console.log(`${colors.green}\u2713${colors.reset} ${message}`);
|
|
34
|
+
}
|
|
35
|
+
function warn(message) {
|
|
36
|
+
console.log(`${colors.yellow}\u26A0${colors.reset} ${message}`);
|
|
37
|
+
}
|
|
38
|
+
function error(message) {
|
|
39
|
+
console.error(`${colors.red}\u2717${colors.reset} ${message}`);
|
|
40
|
+
}
|
|
41
|
+
function execLog(command) {
|
|
42
|
+
console.log(`${colors.dim}>${colors.reset} ${colors.bold}${command}${colors.reset}`);
|
|
43
|
+
}
|
|
44
|
+
function newline() {
|
|
45
|
+
console.log();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/runner/executor.ts
|
|
49
|
+
import { spawn } from "child_process";
|
|
50
|
+
var currentProcess = null;
|
|
51
|
+
async function execute(cmd, options = {}) {
|
|
52
|
+
const { cwd = process.cwd(), env, silent = false } = options;
|
|
53
|
+
if (!silent) {
|
|
54
|
+
execLog(cmd.join(" "));
|
|
55
|
+
}
|
|
56
|
+
return new Promise((resolve2, reject) => {
|
|
57
|
+
const isWindows = process.platform === "win32";
|
|
58
|
+
const command = cmd[0] || "";
|
|
59
|
+
const args = cmd.slice(1);
|
|
60
|
+
currentProcess = spawn(command, args, {
|
|
61
|
+
cwd,
|
|
62
|
+
env: { ...process.env, ...env },
|
|
63
|
+
stdio: "inherit",
|
|
64
|
+
shell: isWindows
|
|
65
|
+
});
|
|
66
|
+
const proc = currentProcess;
|
|
67
|
+
proc.on("close", (code) => {
|
|
68
|
+
currentProcess = null;
|
|
69
|
+
resolve2(code ?? 0);
|
|
70
|
+
});
|
|
71
|
+
proc.on("error", (err) => {
|
|
72
|
+
currentProcess = null;
|
|
73
|
+
reject(err);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function setupSignalHandlers() {
|
|
78
|
+
process.on("SIGINT", () => {
|
|
79
|
+
if (currentProcess) {
|
|
80
|
+
currentProcess.kill();
|
|
81
|
+
}
|
|
82
|
+
process.exit(0);
|
|
83
|
+
});
|
|
84
|
+
process.on("SIGTERM", () => {
|
|
85
|
+
if (currentProcess) {
|
|
86
|
+
currentProcess.kill();
|
|
87
|
+
}
|
|
88
|
+
process.exit(0);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/analyzer/index.ts
|
|
93
|
+
import { readFile as readFile3, stat as stat4 } from "fs/promises";
|
|
94
|
+
import { join as join4 } from "path";
|
|
95
|
+
|
|
96
|
+
// src/analyzer/package-manager.ts
|
|
97
|
+
import { readFile, stat } from "fs/promises";
|
|
98
|
+
import { join } from "path";
|
|
99
|
+
var LOCKFILE_MAP = {
|
|
100
|
+
"bun.lockb": "bun",
|
|
101
|
+
"bun.lock": "bun",
|
|
102
|
+
"pnpm-lock.yaml": "pnpm",
|
|
103
|
+
"yarn.lock": "yarn",
|
|
104
|
+
"package-lock.json": "npm"
|
|
105
|
+
};
|
|
106
|
+
async function detectPackageManager(projectDir) {
|
|
107
|
+
const packageJsonPath = join(projectDir, "package.json");
|
|
108
|
+
const packageJson = await readPackageJson(packageJsonPath);
|
|
109
|
+
if (!packageJson) {
|
|
110
|
+
return { name: "npm", source: "default" };
|
|
111
|
+
}
|
|
112
|
+
if (packageJson.packageManager) {
|
|
113
|
+
const match = packageJson.packageManager.match(/^(npm|yarn|pnpm|bun)@(.+)$/);
|
|
114
|
+
if (match) {
|
|
115
|
+
return {
|
|
116
|
+
name: match[1],
|
|
117
|
+
version: match[2],
|
|
118
|
+
source: "packageManager"
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (packageJson.volta) {
|
|
123
|
+
for (const pm of ["pnpm", "yarn", "npm"]) {
|
|
124
|
+
if (packageJson.volta[pm]) {
|
|
125
|
+
return {
|
|
126
|
+
name: pm,
|
|
127
|
+
version: packageJson.volta[pm],
|
|
128
|
+
source: "volta"
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const [lockfile, pm] of Object.entries(LOCKFILE_MAP)) {
|
|
134
|
+
const exists = await fileExists(join(projectDir, lockfile));
|
|
135
|
+
if (exists) {
|
|
136
|
+
return { name: pm, source: "lockfile" };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { name: "npm", source: "default" };
|
|
140
|
+
}
|
|
141
|
+
function getRunCommand(pm, script) {
|
|
142
|
+
switch (pm) {
|
|
143
|
+
case "bun":
|
|
144
|
+
return ["bun", "run", script];
|
|
145
|
+
case "pnpm":
|
|
146
|
+
return ["pnpm", script];
|
|
147
|
+
case "yarn":
|
|
148
|
+
return ["yarn", script];
|
|
149
|
+
case "npm":
|
|
150
|
+
default:
|
|
151
|
+
return ["npm", "run", script];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function getInstallCommand(pm) {
|
|
155
|
+
switch (pm) {
|
|
156
|
+
case "bun":
|
|
157
|
+
return ["bun", "install"];
|
|
158
|
+
case "pnpm":
|
|
159
|
+
return ["pnpm", "install"];
|
|
160
|
+
case "yarn":
|
|
161
|
+
return ["yarn", "install"];
|
|
162
|
+
case "npm":
|
|
163
|
+
default:
|
|
164
|
+
return ["npm", "install"];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function fileExists(path) {
|
|
168
|
+
try {
|
|
169
|
+
const stats = await stat(path);
|
|
170
|
+
return stats.isFile();
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function readPackageJson(path) {
|
|
176
|
+
try {
|
|
177
|
+
const content = await readFile(path, "utf-8");
|
|
178
|
+
return JSON.parse(content);
|
|
179
|
+
} catch {
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/analyzer/scripts.ts
|
|
185
|
+
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
186
|
+
import { join as join2 } from "path";
|
|
187
|
+
var DEV_PATTERNS = ["dev", "serve", "start:dev", "develop", "watch"];
|
|
188
|
+
var TEST_PATTERNS = ["test", "test:unit", "test:all", "spec"];
|
|
189
|
+
var BUILD_PATTERNS = ["build", "compile", "bundle", "dist"];
|
|
190
|
+
var START_PATTERNS = ["start", "serve", "preview", "production"];
|
|
191
|
+
async function fileExists2(path) {
|
|
192
|
+
try {
|
|
193
|
+
const stats = await stat2(path);
|
|
194
|
+
return stats.isFile();
|
|
195
|
+
} catch {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function analyzeScripts(projectDir) {
|
|
200
|
+
const packageJsonPath = join2(projectDir, "package.json");
|
|
201
|
+
try {
|
|
202
|
+
if (!await fileExists2(packageJsonPath)) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const content = await readFile2(packageJsonPath, "utf-8");
|
|
206
|
+
const packageJson = JSON.parse(content);
|
|
207
|
+
const scripts = packageJson.scripts || {};
|
|
208
|
+
const detected = {
|
|
209
|
+
dev: findMatchingScript(scripts, DEV_PATTERNS),
|
|
210
|
+
test: findMatchingScript(scripts, TEST_PATTERNS),
|
|
211
|
+
build: findMatchingScript(scripts, BUILD_PATTERNS),
|
|
212
|
+
start: findMatchingScript(scripts, START_PATTERNS)
|
|
213
|
+
};
|
|
214
|
+
return { scripts, detected };
|
|
215
|
+
} catch {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function findMatchingScript(scripts, patterns) {
|
|
220
|
+
const scriptNames = Object.keys(scripts);
|
|
221
|
+
for (const pattern of patterns) {
|
|
222
|
+
if (scripts[pattern]) {
|
|
223
|
+
return pattern;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
for (const pattern of patterns) {
|
|
227
|
+
const fuzzyMatch = scriptNames.find((name) => {
|
|
228
|
+
if (!name.toLowerCase().includes(pattern.toLowerCase())) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
const scriptContent = scripts[name].toLowerCase();
|
|
232
|
+
if (scriptContent.includes("npm i") || scriptContent.includes("npm install") || scriptContent.includes("yarn install") || scriptContent.includes("pnpm install") || scriptContent.includes("bun install")) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
});
|
|
237
|
+
if (fuzzyMatch) {
|
|
238
|
+
return fuzzyMatch;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return void 0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/analyzer/dependencies.ts
|
|
245
|
+
import { stat as stat3 } from "fs/promises";
|
|
246
|
+
import { join as join3 } from "path";
|
|
247
|
+
var LOCKFILES = [
|
|
248
|
+
"bun.lockb",
|
|
249
|
+
"bun.lock",
|
|
250
|
+
"pnpm-lock.yaml",
|
|
251
|
+
"yarn.lock",
|
|
252
|
+
"package-lock.json"
|
|
253
|
+
];
|
|
254
|
+
async function checkDependencyStatus(projectDir) {
|
|
255
|
+
const nodeModulesPath = join3(projectDir, "node_modules");
|
|
256
|
+
const nodeModulesExists = await directoryExists(nodeModulesPath);
|
|
257
|
+
if (!nodeModulesExists) {
|
|
258
|
+
return {
|
|
259
|
+
hasNodeModules: false,
|
|
260
|
+
needsInstall: true,
|
|
261
|
+
reason: "node_modules \u4E0D\u5B58\u5728"
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const lockfilePath = await findLockfile(projectDir);
|
|
265
|
+
if (lockfilePath) {
|
|
266
|
+
const lockfileMtime = await getModifiedTime(lockfilePath);
|
|
267
|
+
const nodeModulesMtime2 = await getModifiedTime(nodeModulesPath);
|
|
268
|
+
if (lockfileMtime && nodeModulesMtime2 && lockfileMtime > nodeModulesMtime2) {
|
|
269
|
+
return {
|
|
270
|
+
hasNodeModules: true,
|
|
271
|
+
needsInstall: true,
|
|
272
|
+
reason: "lockfile \u5DF2\u66F4\u65B0"
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const packageJsonPath = join3(projectDir, "package.json");
|
|
277
|
+
const packageJsonMtime = await getModifiedTime(packageJsonPath);
|
|
278
|
+
const nodeModulesMtime = await getModifiedTime(nodeModulesPath);
|
|
279
|
+
if (packageJsonMtime && nodeModulesMtime && packageJsonMtime > nodeModulesMtime) {
|
|
280
|
+
return {
|
|
281
|
+
hasNodeModules: true,
|
|
282
|
+
needsInstall: true,
|
|
283
|
+
reason: "package.json \u5DF2\u66F4\u65B0"
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
hasNodeModules: true,
|
|
288
|
+
needsInstall: false
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
async function findLockfile(projectDir) {
|
|
292
|
+
for (const lockfile of LOCKFILES) {
|
|
293
|
+
const lockfilePath = join3(projectDir, lockfile);
|
|
294
|
+
if (await fileExists3(lockfilePath)) {
|
|
295
|
+
return lockfilePath;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
async function getModifiedTime(path) {
|
|
301
|
+
try {
|
|
302
|
+
const stats = await stat3(path);
|
|
303
|
+
return stats.mtimeMs;
|
|
304
|
+
} catch {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async function directoryExists(path) {
|
|
309
|
+
try {
|
|
310
|
+
const stats = await stat3(path);
|
|
311
|
+
return stats.isDirectory();
|
|
312
|
+
} catch {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async function fileExists3(path) {
|
|
317
|
+
try {
|
|
318
|
+
const stats = await stat3(path);
|
|
319
|
+
return stats.isFile();
|
|
320
|
+
} catch {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/analyzer/index.ts
|
|
326
|
+
async function fileExists4(path) {
|
|
327
|
+
try {
|
|
328
|
+
const stats = await stat4(path);
|
|
329
|
+
return stats.isFile();
|
|
330
|
+
} catch {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function analyzeProject(projectDir) {
|
|
335
|
+
const packageJsonPath = join4(projectDir, "package.json");
|
|
336
|
+
const hasPackageJson = await fileExists4(packageJsonPath);
|
|
337
|
+
if (!hasPackageJson) {
|
|
338
|
+
return {
|
|
339
|
+
type: "unknown",
|
|
340
|
+
packageManager: { name: "npm", source: "default" },
|
|
341
|
+
scripts: null,
|
|
342
|
+
dependencies: { hasNodeModules: false, needsInstall: false }
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
let packageJson = {};
|
|
346
|
+
try {
|
|
347
|
+
const content = await readFile3(packageJsonPath, "utf-8");
|
|
348
|
+
packageJson = JSON.parse(content);
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
351
|
+
const [packageManager, scripts, dependencies] = await Promise.all([
|
|
352
|
+
detectPackageManager(projectDir),
|
|
353
|
+
analyzeScripts(projectDir),
|
|
354
|
+
checkDependencyStatus(projectDir)
|
|
355
|
+
]);
|
|
356
|
+
return {
|
|
357
|
+
type: "nodejs",
|
|
358
|
+
packageManager,
|
|
359
|
+
scripts,
|
|
360
|
+
dependencies,
|
|
361
|
+
name: packageJson.name,
|
|
362
|
+
version: packageJson.version,
|
|
363
|
+
description: packageJson.description
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/cli/run.ts
|
|
368
|
+
async function runCommand(projectDir, options = {}) {
|
|
369
|
+
const { noInstall = false, forceInstall = false, scriptType = "dev" } = options;
|
|
370
|
+
log("\u6B63\u5728\u5206\u6790\u9879\u76EE...");
|
|
371
|
+
const project = await analyzeProject(projectDir);
|
|
372
|
+
if (project.type === "unknown") {
|
|
373
|
+
error("\u672A\u68C0\u6D4B\u5230\u9879\u76EE\u7C7B\u578B\u3002\u8BF7\u786E\u4FDD\u5F53\u524D\u76EE\u5F55\u5305\u542B package.json");
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
log(`\u9879\u76EE\u7C7B\u578B: ${project.type}`);
|
|
377
|
+
log(`\u5305\u7BA1\u7406\u5668: ${project.packageManager.name} (from ${project.packageManager.source})`);
|
|
378
|
+
if (!project.scripts) {
|
|
379
|
+
error("\u65E0\u6CD5\u8BFB\u53D6 package.json \u7684 scripts");
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
382
|
+
const scriptName = findScript(project, scriptType);
|
|
383
|
+
if (!scriptName) {
|
|
384
|
+
error(`\u672A\u627E\u5230 ${scriptType} \u76F8\u5173\u7684\u811A\u672C`);
|
|
385
|
+
showAvailableScripts(project);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
log(`\u5C06\u6267\u884C\u811A\u672C: ${scriptName}`);
|
|
389
|
+
const shouldInstall = !noInstall && (forceInstall || project.dependencies.needsInstall);
|
|
390
|
+
if (shouldInstall) {
|
|
391
|
+
if (forceInstall) {
|
|
392
|
+
log("\u5F3A\u5236\u5B89\u88C5\u4F9D\u8D56...");
|
|
393
|
+
} else {
|
|
394
|
+
log(`\u4F9D\u8D56\u72B6\u6001: ${project.dependencies.reason || "\u9700\u8981\u5B89\u88C5"}`);
|
|
395
|
+
}
|
|
396
|
+
newline();
|
|
397
|
+
const installCmd = getInstallCommand(project.packageManager.name);
|
|
398
|
+
const installExitCode = await execute(installCmd, { cwd: projectDir });
|
|
399
|
+
if (installExitCode !== 0) {
|
|
400
|
+
error("\u4F9D\u8D56\u5B89\u88C5\u5931\u8D25");
|
|
401
|
+
process.exit(installExitCode);
|
|
402
|
+
}
|
|
403
|
+
success("\u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210");
|
|
404
|
+
newline();
|
|
405
|
+
} else if (!noInstall) {
|
|
406
|
+
log("\u4F9D\u8D56\u72B6\u6001: \u5DF2\u662F\u6700\u65B0");
|
|
407
|
+
}
|
|
408
|
+
const runCmd = getRunCommand(project.packageManager.name, scriptName);
|
|
409
|
+
const exitCode = await execute(runCmd, { cwd: projectDir });
|
|
410
|
+
if (exitCode !== 0) {
|
|
411
|
+
process.exit(exitCode);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function findScript(project, scriptType) {
|
|
415
|
+
const scripts = project.scripts;
|
|
416
|
+
if (!scripts)
|
|
417
|
+
return void 0;
|
|
418
|
+
const detected = scripts.detected[scriptType];
|
|
419
|
+
if (detected) {
|
|
420
|
+
return detected;
|
|
421
|
+
}
|
|
422
|
+
if (scripts.scripts[scriptType]) {
|
|
423
|
+
return scriptType;
|
|
424
|
+
}
|
|
425
|
+
return void 0;
|
|
426
|
+
}
|
|
427
|
+
function showAvailableScripts(project) {
|
|
428
|
+
if (!project.scripts)
|
|
429
|
+
return;
|
|
430
|
+
const scriptNames = Object.keys(project.scripts.scripts);
|
|
431
|
+
if (scriptNames.length === 0) {
|
|
432
|
+
warn("package.json \u4E2D\u6CA1\u6709\u5B9A\u4E49\u4EFB\u4F55 scripts");
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
info("\u53EF\u7528\u7684\u811A\u672C:");
|
|
436
|
+
for (const name of scriptNames) {
|
|
437
|
+
console.log(` - ${name}`);
|
|
438
|
+
}
|
|
439
|
+
console.log();
|
|
440
|
+
info("\u4F7F\u7528 qy <script> \u8FD0\u884C\u4EFB\u610F\u811A\u672C");
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/cli/info.ts
|
|
444
|
+
async function infoCommand(projectDir) {
|
|
445
|
+
const project = await analyzeProject(projectDir);
|
|
446
|
+
if (project.type === "unknown") {
|
|
447
|
+
console.log(`${colors.red}\u2717${colors.reset} \u672A\u68C0\u6D4B\u5230\u9879\u76EE\u7C7B\u578B`);
|
|
448
|
+
console.log(" \u8BF7\u786E\u4FDD\u5F53\u524D\u76EE\u5F55\u5305\u542B package.json \u6216\u5176\u4ED6\u9879\u76EE\u914D\u7F6E\u6587\u4EF6");
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
console.log();
|
|
452
|
+
console.log(`${colors.cyan}${colors.bold}pr - \u9879\u76EE\u5206\u6790\u7ED3\u679C${colors.reset}`);
|
|
453
|
+
console.log("\u2500".repeat(40));
|
|
454
|
+
if (project.name) {
|
|
455
|
+
console.log(`${colors.bold}\u9879\u76EE\u540D\u79F0:${colors.reset} ${project.name}`);
|
|
456
|
+
}
|
|
457
|
+
if (project.version) {
|
|
458
|
+
console.log(`${colors.bold}\u7248\u672C:${colors.reset} ${project.version}`);
|
|
459
|
+
}
|
|
460
|
+
if (project.description) {
|
|
461
|
+
console.log(`${colors.bold}\u63CF\u8FF0:${colors.reset} ${project.description}`);
|
|
462
|
+
}
|
|
463
|
+
console.log(`${colors.bold}\u9879\u76EE\u7C7B\u578B:${colors.reset} ${project.type}`);
|
|
464
|
+
const pm = project.packageManager;
|
|
465
|
+
let pmInfo = pm.name;
|
|
466
|
+
if (pm.version) {
|
|
467
|
+
pmInfo += `@${pm.version}`;
|
|
468
|
+
}
|
|
469
|
+
pmInfo += ` ${colors.dim}(${pm.source})${colors.reset}`;
|
|
470
|
+
console.log(`${colors.bold}\u5305\u7BA1\u7406\u5668:${colors.reset} ${pmInfo}`);
|
|
471
|
+
const deps = project.dependencies;
|
|
472
|
+
const depsStatus = deps.needsInstall ? `${colors.yellow}\u9700\u8981\u5B89\u88C5${colors.reset} (${deps.reason})` : `${colors.green}\u5DF2\u5C31\u7EEA${colors.reset}`;
|
|
473
|
+
console.log(`${colors.bold}\u4F9D\u8D56\u72B6\u6001:${colors.reset} ${depsStatus}`);
|
|
474
|
+
console.log();
|
|
475
|
+
if (project.scripts) {
|
|
476
|
+
const { scripts, detected } = project.scripts;
|
|
477
|
+
console.log(`${colors.bold}\u8BC6\u522B\u7684\u547D\u4EE4:${colors.reset}`);
|
|
478
|
+
if (detected.dev) {
|
|
479
|
+
console.log(` ${colors.green}pr run${colors.reset} \u2192 ${pm.name} ${detected.dev}`);
|
|
480
|
+
}
|
|
481
|
+
if (detected.test) {
|
|
482
|
+
console.log(` ${colors.green}pr test${colors.reset} \u2192 ${pm.name} ${detected.test}`);
|
|
483
|
+
}
|
|
484
|
+
if (detected.build) {
|
|
485
|
+
console.log(` ${colors.green}pr build${colors.reset} \u2192 ${pm.name} ${detected.build}`);
|
|
486
|
+
}
|
|
487
|
+
if (detected.start) {
|
|
488
|
+
console.log(` ${colors.green}pr start${colors.reset} \u2192 ${pm.name} ${detected.start}`);
|
|
489
|
+
}
|
|
490
|
+
console.log();
|
|
491
|
+
const allScripts = Object.keys(scripts);
|
|
492
|
+
if (allScripts.length > 0) {
|
|
493
|
+
console.log(`${colors.bold}\u6240\u6709\u811A\u672C:${colors.reset}`);
|
|
494
|
+
for (const name of allScripts) {
|
|
495
|
+
const cmd = scripts[name];
|
|
496
|
+
const displayCmd = cmd.length > 40 ? cmd.slice(0, 40) + "..." : cmd;
|
|
497
|
+
console.log(` ${colors.cyan}${name}${colors.reset} ${colors.dim}\u2192 ${displayCmd}${colors.reset}`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
console.log();
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/cli/script.ts
|
|
505
|
+
async function scriptCommand(projectDir, scriptName) {
|
|
506
|
+
const project = await analyzeProject(projectDir);
|
|
507
|
+
if (project.type === "unknown") {
|
|
508
|
+
error("\u672A\u68C0\u6D4B\u5230\u9879\u76EE\u7C7B\u578B\u3002\u8BF7\u786E\u4FDD\u5F53\u524D\u76EE\u5F55\u5305\u542B package.json");
|
|
509
|
+
process.exit(1);
|
|
510
|
+
}
|
|
511
|
+
if (!project.scripts) {
|
|
512
|
+
error("\u65E0\u6CD5\u8BFB\u53D6 package.json \u7684 scripts");
|
|
513
|
+
process.exit(1);
|
|
514
|
+
}
|
|
515
|
+
const scripts = project.scripts.scripts;
|
|
516
|
+
if (!(scriptName in scripts)) {
|
|
517
|
+
error(`\u811A\u672C "${scriptName}" \u4E0D\u5B58\u5728`);
|
|
518
|
+
console.log();
|
|
519
|
+
showAvailableScripts2(scripts);
|
|
520
|
+
process.exit(1);
|
|
521
|
+
}
|
|
522
|
+
log(`\u5305\u7BA1\u7406\u5668: ${project.packageManager.name}`);
|
|
523
|
+
log(`\u6267\u884C\u811A\u672C: ${scriptName}`);
|
|
524
|
+
const runCmd = getRunCommand(project.packageManager.name, scriptName);
|
|
525
|
+
const exitCode = await execute(runCmd, { cwd: projectDir });
|
|
526
|
+
if (exitCode !== 0) {
|
|
527
|
+
process.exit(exitCode);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function showAvailableScripts2(scripts) {
|
|
531
|
+
const scriptNames = Object.keys(scripts);
|
|
532
|
+
if (scriptNames.length === 0) {
|
|
533
|
+
info("package.json \u4E2D\u6CA1\u6709\u5B9A\u4E49\u4EFB\u4F55\u811A\u672C");
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
info("\u53EF\u7528\u7684\u811A\u672C:");
|
|
537
|
+
for (const name of scriptNames) {
|
|
538
|
+
console.log(` - ${name}`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// src/index.ts
|
|
543
|
+
var VERSION = "0.1.2";
|
|
544
|
+
function parseArgs(args) {
|
|
545
|
+
const options = {
|
|
546
|
+
verbose: false,
|
|
547
|
+
dir: process.cwd(),
|
|
548
|
+
noInstall: false,
|
|
549
|
+
install: false
|
|
550
|
+
};
|
|
551
|
+
let command = "";
|
|
552
|
+
const remainingArgs = [];
|
|
553
|
+
let i = 0;
|
|
554
|
+
while (i < args.length) {
|
|
555
|
+
const arg = args[i];
|
|
556
|
+
if (arg === "-v" || arg === "--verbose") {
|
|
557
|
+
options.verbose = true;
|
|
558
|
+
} else if (arg === "-d" || arg === "--dir") {
|
|
559
|
+
options.dir = resolve(args[++i] || ".");
|
|
560
|
+
} else if (arg === "--no-install") {
|
|
561
|
+
options.noInstall = true;
|
|
562
|
+
} else if (arg === "-i" || arg === "--install") {
|
|
563
|
+
options.install = true;
|
|
564
|
+
} else if (arg === "-h" || arg === "--help") {
|
|
565
|
+
command = "help";
|
|
566
|
+
} else if (arg === "-V" || arg === "--version") {
|
|
567
|
+
command = "version";
|
|
568
|
+
} else if (!arg.startsWith("-")) {
|
|
569
|
+
if (!command) {
|
|
570
|
+
command = arg;
|
|
571
|
+
} else {
|
|
572
|
+
remainingArgs.push(arg);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
i++;
|
|
576
|
+
}
|
|
577
|
+
return { command, options, args: remainingArgs };
|
|
578
|
+
}
|
|
579
|
+
function showHelp() {
|
|
580
|
+
console.log(`
|
|
581
|
+
${"\x1B[36m"}pr${"\x1B[0m"} v${VERSION} - \u96F6\u914D\u7F6E\u667A\u80FD\u9879\u76EE\u8FD0\u884C\u5668 (project-runner)
|
|
582
|
+
|
|
583
|
+
${"\x1B[1m"}\u7528\u6CD5:${"\x1B[0m"} pr <command> [options]
|
|
584
|
+
|
|
585
|
+
${"\x1B[1m"}\u547D\u4EE4:${"\x1B[0m"}
|
|
586
|
+
run \u5B8C\u6574\u6D41\u7A0B\uFF1A\u68C0\u6D4B \u2192 install \u2192 \u542F\u52A8\u5F00\u53D1\u670D\u52A1\u5668
|
|
587
|
+
test \u8FD0\u884C\u6D4B\u8BD5
|
|
588
|
+
build \u6784\u5EFA\u9879\u76EE
|
|
589
|
+
start \u751F\u4EA7\u6A21\u5F0F\u542F\u52A8
|
|
590
|
+
info \u663E\u793A\u9879\u76EE\u5206\u6790\u7ED3\u679C
|
|
591
|
+
<script> \u8FD0\u884C package.json \u4E2D\u7684\u4EFB\u610F\u811A\u672C
|
|
592
|
+
|
|
593
|
+
${"\x1B[1m"}\u9009\u9879:${"\x1B[0m"}
|
|
594
|
+
-v, --verbose \u663E\u793A\u8BE6\u7EC6\u68C0\u6D4B\u8FC7\u7A0B
|
|
595
|
+
-d, --dir <path> \u6307\u5B9A\u9879\u76EE\u76EE\u5F55 (\u9ED8\u8BA4: \u5F53\u524D\u76EE\u5F55)
|
|
596
|
+
-i, --install \u5F3A\u5236\u6267\u884C\u4F9D\u8D56\u5B89\u88C5
|
|
597
|
+
--no-install \u8DF3\u8FC7\u4F9D\u8D56\u5B89\u88C5\u6B65\u9AA4
|
|
598
|
+
-h, --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F
|
|
599
|
+
-V, --version \u663E\u793A\u7248\u672C\u53F7
|
|
600
|
+
|
|
601
|
+
${"\x1B[1m"}\u793A\u4F8B:${"\x1B[0m"}
|
|
602
|
+
pr run \u4E00\u952E\u542F\u52A8\u9879\u76EE
|
|
603
|
+
pr run -i \u5F3A\u5236\u5B89\u88C5\u4F9D\u8D56\u540E\u542F\u52A8
|
|
604
|
+
pr run -v \u663E\u793A\u8BE6\u7EC6\u68C0\u6D4B\u8FC7\u7A0B
|
|
605
|
+
pr test \u8FD0\u884C\u6D4B\u8BD5
|
|
606
|
+
pr lint \u8FD0\u884C lint \u811A\u672C
|
|
607
|
+
pr info \u67E5\u770B\u9879\u76EE\u4FE1\u606F
|
|
608
|
+
`);
|
|
609
|
+
}
|
|
610
|
+
function showVersion() {
|
|
611
|
+
console.log(`pr v${VERSION}`);
|
|
612
|
+
}
|
|
613
|
+
async function main() {
|
|
614
|
+
setupSignalHandlers();
|
|
615
|
+
const { command, options, args } = parseArgs(process.argv.slice(2));
|
|
616
|
+
setVerbose(options.verbose);
|
|
617
|
+
switch (command) {
|
|
618
|
+
case "":
|
|
619
|
+
case "help":
|
|
620
|
+
showHelp();
|
|
621
|
+
break;
|
|
622
|
+
case "version":
|
|
623
|
+
showVersion();
|
|
624
|
+
break;
|
|
625
|
+
case "run":
|
|
626
|
+
await runCommand(options.dir, { noInstall: options.noInstall, forceInstall: options.install, scriptType: "dev" });
|
|
627
|
+
break;
|
|
628
|
+
case "test":
|
|
629
|
+
await runCommand(options.dir, { noInstall: true, scriptType: "test" });
|
|
630
|
+
break;
|
|
631
|
+
case "build":
|
|
632
|
+
await runCommand(options.dir, { noInstall: true, scriptType: "build" });
|
|
633
|
+
break;
|
|
634
|
+
case "start":
|
|
635
|
+
await runCommand(options.dir, { noInstall: true, scriptType: "start" });
|
|
636
|
+
break;
|
|
637
|
+
case "info":
|
|
638
|
+
await infoCommand(options.dir);
|
|
639
|
+
break;
|
|
640
|
+
default:
|
|
641
|
+
await scriptCommand(options.dir, command);
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
main().catch((err) => {
|
|
646
|
+
error(err.message);
|
|
647
|
+
process.exit(1);
|
|
648
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "project-runner",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "零配置智能项目运行器 - 一键运行任意 Node.js 项目",
|
|
5
5
|
"author": "liangzhenqi",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,23 +20,23 @@
|
|
|
20
20
|
"build",
|
|
21
21
|
"test"
|
|
22
22
|
],
|
|
23
|
-
"module": "src/index.ts",
|
|
24
23
|
"type": "module",
|
|
25
24
|
"bin": {
|
|
26
|
-
"pr": "
|
|
25
|
+
"pr": "dist/index.js"
|
|
27
26
|
},
|
|
28
27
|
"files": [
|
|
29
|
-
"
|
|
28
|
+
"dist",
|
|
30
29
|
"README.md"
|
|
31
30
|
],
|
|
32
31
|
"scripts": {
|
|
33
32
|
"dev": "bun run src/index.ts",
|
|
34
|
-
"build": "
|
|
35
|
-
"
|
|
36
|
-
"prepublishOnly": "echo 'Ready to publish'"
|
|
33
|
+
"build": "node build.mjs",
|
|
34
|
+
"prepublishOnly": "npm run build"
|
|
37
35
|
},
|
|
38
36
|
"devDependencies": {
|
|
39
|
-
"@types/bun": "latest"
|
|
37
|
+
"@types/bun": "latest",
|
|
38
|
+
"@types/node": "^20",
|
|
39
|
+
"esbuild": "^0.20.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"typescript": "^5"
|