project-runner 0.1.2 → 0.3.0

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.
Files changed (3) hide show
  1. package/README.md +25 -0
  2. package/dist/index.js +511 -122
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -104,10 +104,35 @@ pr info
104
104
  |------|------|
105
105
  | `-v, --verbose` | 显示详细检测过程 |
106
106
  | `-d, --dir <path>` | 指定项目目录(默认:当前目录)|
107
+ | `-i, --install` | 强制执行依赖安装 |
107
108
  | `--no-install` | 跳过依赖安装步骤 |
109
+ | `-e, --entry <name>` | 指定 MPA 入口(也可用 `PR_ENTRY`) |
108
110
  | `-h, --help` | 显示帮助信息 |
109
111
  | `-V, --version` | 显示版本号 |
110
112
 
113
+ ## MPA 项目支持
114
+
115
+ `pr run` 会自动检测是否存在 `dev:<entry>` 形式的多入口脚本。
116
+
117
+ - 在交互终端中:自动弹出入口列表并让你选择。
118
+ - 在 CI / 非交互环境中:按优先级选择入口
119
+ 1. `--entry <name>`
120
+ 2. `PR_ENTRY=<name>`
121
+ 3. `.pr.local.json` 中的 `defaultEntry`
122
+ 4. 若仍无法确定则报错并提示可选入口
123
+
124
+ 本地配置文件(推荐):
125
+
126
+ ```json
127
+ {
128
+ "entries": ["main", "formengine", "design", "approve"],
129
+ "defaultEntry": "main"
130
+ }
131
+ ```
132
+
133
+ - 文件名固定:`.pr.local.json`
134
+ - 当该文件存在时,`pr` 会自动确保 `.gitignore` 包含 `.pr.local.json`
135
+
111
136
  ## 示例输出
112
137
 
113
138
  ### `pr info`
package/dist/index.js CHANGED
@@ -17,6 +17,13 @@ var colors = {
17
17
  white: "\x1B[37m",
18
18
  gray: "\x1B[90m"
19
19
  };
20
+ var CliError = class extends Error {
21
+ constructor(message, exitCode = 1) {
22
+ super(message);
23
+ this.exitCode = exitCode;
24
+ this.name = "CliError";
25
+ }
26
+ };
20
27
  var isVerbose = false;
21
28
  function setVerbose(verbose) {
22
29
  isVerbose = verbose;
@@ -74,6 +81,34 @@ async function execute(cmd, options = {}) {
74
81
  });
75
82
  });
76
83
  }
84
+ async function executeCapture(cmd, options = {}) {
85
+ const { cwd = process.cwd(), env } = options;
86
+ return new Promise((resolve2, reject) => {
87
+ const isWindows = process.platform === "win32";
88
+ const command = cmd[0] || "";
89
+ const args = cmd.slice(1);
90
+ const proc = spawn(command, args, {
91
+ cwd,
92
+ env: { ...process.env, ...env },
93
+ shell: isWindows,
94
+ stdio: ["pipe", "pipe", "pipe"]
95
+ });
96
+ let stdout = "";
97
+ let stderr = "";
98
+ proc.stdout.on("data", (data) => {
99
+ stdout += data.toString();
100
+ });
101
+ proc.stderr.on("data", (data) => {
102
+ stderr += data.toString();
103
+ });
104
+ proc.on("close", (code) => {
105
+ resolve2({ stdout, stderr, exitCode: code ?? 0 });
106
+ });
107
+ proc.on("error", (err) => {
108
+ reject(err);
109
+ });
110
+ });
111
+ }
77
112
  function setupSignalHandlers() {
78
113
  process.on("SIGINT", () => {
79
114
  if (currentProcess) {
@@ -89,13 +124,36 @@ function setupSignalHandlers() {
89
124
  });
90
125
  }
91
126
 
127
+ // src/cli/run.ts
128
+ import { createInterface as createInterface2 } from "node:readline/promises";
129
+
92
130
  // src/analyzer/index.ts
93
- import { readFile as readFile3, stat as stat4 } from "fs/promises";
131
+ import { readFile as readFile2 } from "fs/promises";
94
132
  import { join as join4 } from "path";
95
133
 
96
134
  // src/analyzer/package-manager.ts
97
- import { readFile, stat } from "fs/promises";
98
135
  import { join } from "path";
136
+
137
+ // src/utils/fs.ts
138
+ import { stat } from "fs/promises";
139
+ async function fileExists(path) {
140
+ try {
141
+ const stats = await stat(path);
142
+ return stats.isFile();
143
+ } catch {
144
+ return false;
145
+ }
146
+ }
147
+ async function directoryExists(path) {
148
+ try {
149
+ const stats = await stat(path);
150
+ return stats.isDirectory();
151
+ } catch {
152
+ return false;
153
+ }
154
+ }
155
+
156
+ // src/analyzer/package-manager.ts
99
157
  var LOCKFILE_MAP = {
100
158
  "bun.lockb": "bun",
101
159
  "bun.lock": "bun",
@@ -103,9 +161,7 @@ var LOCKFILE_MAP = {
103
161
  "yarn.lock": "yarn",
104
162
  "package-lock.json": "npm"
105
163
  };
106
- async function detectPackageManager(projectDir) {
107
- const packageJsonPath = join(projectDir, "package.json");
108
- const packageJson = await readPackageJson(packageJsonPath);
164
+ async function detectPackageManager(projectDir, packageJson) {
109
165
  if (!packageJson) {
110
166
  return { name: "npm", source: "default" };
111
167
  }
@@ -164,46 +220,18 @@ function getInstallCommand(pm) {
164
220
  return ["npm", "install"];
165
221
  }
166
222
  }
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
223
 
184
224
  // src/analyzer/scripts.ts
185
- import { readFile as readFile2, stat as stat2 } from "fs/promises";
186
- import { join as join2 } from "path";
187
225
  var DEV_PATTERNS = ["dev", "serve", "start:dev", "develop", "watch"];
188
226
  var TEST_PATTERNS = ["test", "test:unit", "test:all", "spec"];
189
227
  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;
228
+ var START_PATTERNS = ["start", "preview", "production"];
229
+ var STANDARD_TYPES = ["dev", "test", "build", "start"];
230
+ function analyzeScripts(packageJson, localConfig) {
231
+ if (!packageJson) {
232
+ return null;
197
233
  }
198
- }
199
- async function analyzeScripts(projectDir) {
200
- const packageJsonPath = join2(projectDir, "package.json");
201
234
  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
235
  const scripts = packageJson.scripts || {};
208
236
  const detected = {
209
237
  dev: findMatchingScript(scripts, DEV_PATTERNS),
@@ -211,11 +239,81 @@ async function analyzeScripts(projectDir) {
211
239
  build: findMatchingScript(scripts, BUILD_PATTERNS),
212
240
  start: findMatchingScript(scripts, START_PATTERNS)
213
241
  };
214
- return { scripts, detected };
242
+ const mpa = analyzeMpa(packageJson, scripts, localConfig);
243
+ return { scripts, detected, mpa };
215
244
  } catch {
216
245
  return null;
217
246
  }
218
247
  }
248
+ function analyzeMpa(packageJson, scripts, localConfig) {
249
+ const configuredEntriesFromLocal = normalizeEntryList(localConfig?.entries);
250
+ const configuredEntriesFromPackage = normalizeEntryList(packageJson?.pr?.entries);
251
+ const configuredEntries = configuredEntriesFromLocal.length > 0 ? configuredEntriesFromLocal : configuredEntriesFromPackage;
252
+ const scriptEntries = collectEntriesByPrefix(scripts, "dev");
253
+ let entries = configuredEntries.length > 0 ? configuredEntries : scriptEntries;
254
+ let source = "scripts";
255
+ if (configuredEntriesFromLocal.length > 0) {
256
+ source = "local-config";
257
+ } else if (configuredEntriesFromPackage.length > 0) {
258
+ source = "package-json";
259
+ }
260
+ const defaultEntry = normalizeEntryName(localConfig?.defaultEntry ?? packageJson?.pr?.defaultEntry);
261
+ if (defaultEntry && !entries.includes(defaultEntry) && scripts[`dev:${defaultEntry}`]) {
262
+ entries = [...entries, defaultEntry];
263
+ }
264
+ entries = dedupe(entries);
265
+ if (entries.length === 0) {
266
+ source = "none";
267
+ }
268
+ const scriptsByType = {};
269
+ for (const type of STANDARD_TYPES) {
270
+ const mapping = {};
271
+ for (const entry of entries) {
272
+ const scriptName = `${type}:${entry}`;
273
+ if (scripts[scriptName]) {
274
+ mapping[entry] = scriptName;
275
+ }
276
+ }
277
+ if (Object.keys(mapping).length > 0) {
278
+ scriptsByType[type] = mapping;
279
+ }
280
+ }
281
+ return {
282
+ isMpa: entries.length > 0,
283
+ entries,
284
+ defaultEntry: defaultEntry && entries.includes(defaultEntry) ? defaultEntry : void 0,
285
+ source,
286
+ scriptsByType
287
+ };
288
+ }
289
+ function normalizeEntryList(value) {
290
+ if (!Array.isArray(value)) {
291
+ return [];
292
+ }
293
+ return dedupe(value.map(normalizeEntryName).filter((item) => Boolean(item)));
294
+ }
295
+ function normalizeEntryName(value) {
296
+ if (typeof value !== "string") {
297
+ return void 0;
298
+ }
299
+ const trimmed = value.trim();
300
+ return trimmed.length > 0 ? trimmed : void 0;
301
+ }
302
+ function collectEntriesByPrefix(scripts, prefix) {
303
+ const prefixWithColon = `${prefix}:`;
304
+ const entries = [];
305
+ for (const name of Object.keys(scripts)) {
306
+ if (!name.startsWith(prefixWithColon)) {
307
+ continue;
308
+ }
309
+ const entry = name.slice(prefixWithColon.length).trim();
310
+ if (!entry) {
311
+ continue;
312
+ }
313
+ entries.push(entry);
314
+ }
315
+ return dedupe(entries);
316
+ }
219
317
  function findMatchingScript(scripts, patterns) {
220
318
  const scriptNames = Object.keys(scripts);
221
319
  for (const pattern of patterns) {
@@ -228,8 +326,12 @@ function findMatchingScript(scripts, patterns) {
228
326
  if (!name.toLowerCase().includes(pattern.toLowerCase())) {
229
327
  return false;
230
328
  }
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")) {
329
+ const scriptContent = scripts[name];
330
+ if (!scriptContent) {
331
+ return false;
332
+ }
333
+ const content = scriptContent.toLowerCase();
334
+ if (content.includes("npm i") || content.includes("npm install") || content.includes("yarn install") || content.includes("pnpm install") || content.includes("bun install")) {
233
335
  return false;
234
336
  }
235
337
  return true;
@@ -240,10 +342,13 @@ function findMatchingScript(scripts, patterns) {
240
342
  }
241
343
  return void 0;
242
344
  }
345
+ function dedupe(items) {
346
+ return [...new Set(items)];
347
+ }
243
348
 
244
349
  // src/analyzer/dependencies.ts
245
- import { stat as stat3 } from "fs/promises";
246
- import { join as join3 } from "path";
350
+ import { stat as stat2 } from "fs/promises";
351
+ import { join as join2 } from "path";
247
352
  var LOCKFILES = [
248
353
  "bun.lockb",
249
354
  "bun.lock",
@@ -252,7 +357,7 @@ var LOCKFILES = [
252
357
  "package-lock.json"
253
358
  ];
254
359
  async function checkDependencyStatus(projectDir) {
255
- const nodeModulesPath = join3(projectDir, "node_modules");
360
+ const nodeModulesPath = join2(projectDir, "node_modules");
256
361
  const nodeModulesExists = await directoryExists(nodeModulesPath);
257
362
  if (!nodeModulesExists) {
258
363
  return {
@@ -261,11 +366,11 @@ async function checkDependencyStatus(projectDir) {
261
366
  reason: "node_modules \u4E0D\u5B58\u5728"
262
367
  };
263
368
  }
369
+ const nodeModulesMtime = await getModifiedTime(nodeModulesPath);
264
370
  const lockfilePath = await findLockfile(projectDir);
265
371
  if (lockfilePath) {
266
372
  const lockfileMtime = await getModifiedTime(lockfilePath);
267
- const nodeModulesMtime2 = await getModifiedTime(nodeModulesPath);
268
- if (lockfileMtime && nodeModulesMtime2 && lockfileMtime > nodeModulesMtime2) {
373
+ if (lockfileMtime && nodeModulesMtime && lockfileMtime > nodeModulesMtime) {
269
374
  return {
270
375
  hasNodeModules: true,
271
376
  needsInstall: true,
@@ -273,9 +378,8 @@ async function checkDependencyStatus(projectDir) {
273
378
  };
274
379
  }
275
380
  }
276
- const packageJsonPath = join3(projectDir, "package.json");
381
+ const packageJsonPath = join2(projectDir, "package.json");
277
382
  const packageJsonMtime = await getModifiedTime(packageJsonPath);
278
- const nodeModulesMtime = await getModifiedTime(nodeModulesPath);
279
383
  if (packageJsonMtime && nodeModulesMtime && packageJsonMtime > nodeModulesMtime) {
280
384
  return {
281
385
  hasNodeModules: true,
@@ -290,8 +394,8 @@ async function checkDependencyStatus(projectDir) {
290
394
  }
291
395
  async function findLockfile(projectDir) {
292
396
  for (const lockfile of LOCKFILES) {
293
- const lockfilePath = join3(projectDir, lockfile);
294
- if (await fileExists3(lockfilePath)) {
397
+ const lockfilePath = join2(projectDir, lockfile);
398
+ if (await fileExists(lockfilePath)) {
295
399
  return lockfilePath;
296
400
  }
297
401
  }
@@ -299,93 +403,210 @@ async function findLockfile(projectDir) {
299
403
  }
300
404
  async function getModifiedTime(path) {
301
405
  try {
302
- const stats = await stat3(path);
406
+ const stats = await stat2(path);
303
407
  return stats.mtimeMs;
304
408
  } catch {
305
409
  return null;
306
410
  }
307
411
  }
308
- async function directoryExists(path) {
412
+
413
+ // src/analyzer/local-config.ts
414
+ import { appendFile, readFile, writeFile } from "fs/promises";
415
+ import { join as join3 } from "path";
416
+ var LOCAL_CONFIG_FILENAME = ".pr.local.json";
417
+ async function loadPrLocalConfig(projectDir) {
418
+ const configPath = join3(projectDir, LOCAL_CONFIG_FILENAME);
419
+ if (!await fileExists(configPath)) {
420
+ return null;
421
+ }
422
+ let parsed;
309
423
  try {
310
- const stats = await stat3(path);
311
- return stats.isDirectory();
424
+ const content = await readFile(configPath, "utf-8");
425
+ parsed = JSON.parse(content.replace(/^\uFEFF/, ""));
312
426
  } catch {
313
- return false;
427
+ return null;
428
+ }
429
+ await ensureLocalConfigGitignored(projectDir);
430
+ if (!parsed || typeof parsed !== "object") {
431
+ return null;
432
+ }
433
+ const config = {};
434
+ if (Array.isArray(parsed.entries)) {
435
+ config.entries = parsed.entries.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length > 0);
314
436
  }
437
+ if (typeof parsed.defaultEntry === "string" && parsed.defaultEntry.trim()) {
438
+ config.defaultEntry = parsed.defaultEntry.trim();
439
+ }
440
+ return config;
315
441
  }
316
- async function fileExists3(path) {
317
- try {
318
- const stats = await stat3(path);
319
- return stats.isFile();
320
- } catch {
321
- return false;
442
+ async function ensureLocalConfigGitignored(projectDir) {
443
+ const gitignorePath = join3(projectDir, ".gitignore");
444
+ const ignoreEntry = LOCAL_CONFIG_FILENAME;
445
+ if (!await fileExists(gitignorePath)) {
446
+ await writeFile(gitignorePath, `${ignoreEntry}
447
+ `, "utf-8");
448
+ return;
322
449
  }
450
+ const content = await readFile(gitignorePath, "utf-8");
451
+ const lines = content.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
452
+ if (lines.includes(ignoreEntry) || lines.includes(`/${ignoreEntry}`)) {
453
+ return;
454
+ }
455
+ const suffix = content.endsWith("\n") || content.length === 0 ? "" : "\n";
456
+ await appendFile(gitignorePath, `${suffix}${ignoreEntry}
457
+ `, "utf-8");
323
458
  }
324
459
 
325
460
  // 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
461
  async function analyzeProject(projectDir) {
335
462
  const packageJsonPath = join4(projectDir, "package.json");
336
- const hasPackageJson = await fileExists4(packageJsonPath);
463
+ const hasPackageJson = await fileExists(packageJsonPath);
337
464
  if (!hasPackageJson) {
338
465
  return {
339
466
  type: "unknown",
340
467
  packageManager: { name: "npm", source: "default" },
341
468
  scripts: null,
342
- dependencies: { hasNodeModules: false, needsInstall: false }
469
+ dependencies: { hasNodeModules: false, needsInstall: false },
470
+ localConfig: null
343
471
  };
344
472
  }
345
473
  let packageJson = {};
346
474
  try {
347
- const content = await readFile3(packageJsonPath, "utf-8");
348
- packageJson = JSON.parse(content);
475
+ const content = await readFile2(packageJsonPath, "utf-8");
476
+ packageJson = JSON.parse(content.replace(/^\uFEFF/, ""));
349
477
  } catch {
350
478
  }
351
- const [packageManager, scripts, dependencies] = await Promise.all([
352
- detectPackageManager(projectDir),
353
- analyzeScripts(projectDir),
354
- checkDependencyStatus(projectDir)
479
+ const [packageManager, dependencies, localConfig] = await Promise.all([
480
+ detectPackageManager(projectDir, packageJson),
481
+ checkDependencyStatus(projectDir),
482
+ loadPrLocalConfig(projectDir)
355
483
  ]);
484
+ const scripts = analyzeScripts(packageJson, localConfig);
356
485
  return {
357
486
  type: "nodejs",
358
487
  packageManager,
359
488
  scripts,
360
489
  dependencies,
490
+ localConfig,
361
491
  name: packageJson.name,
362
492
  version: packageJson.version,
363
493
  description: packageJson.description
364
494
  };
365
495
  }
366
496
 
497
+ // src/utils/pm-availability.ts
498
+ import { createInterface } from "node:readline/promises";
499
+ var availabilityCache = /* @__PURE__ */ new Map();
500
+ async function isPmAvailable(pm) {
501
+ if (availabilityCache.has(pm)) {
502
+ return availabilityCache.get(pm);
503
+ }
504
+ try {
505
+ const result = await executeCapture([pm, "--version"]);
506
+ const available = result.exitCode === 0 && result.stdout.trim().length > 0;
507
+ availabilityCache.set(pm, available);
508
+ return available;
509
+ } catch {
510
+ availabilityCache.set(pm, false);
511
+ return false;
512
+ }
513
+ }
514
+ async function ensurePmAvailable(pm) {
515
+ if (pm === "npm") {
516
+ return pm;
517
+ }
518
+ const available = await isPmAvailable(pm);
519
+ if (available) {
520
+ return pm;
521
+ }
522
+ warn(`\u5305\u7BA1\u7406\u5668 ${pm} \u672A\u5B89\u88C5`);
523
+ if (!process.stdin.isTTY) {
524
+ warn("\u975E\u4EA4\u4E92\u73AF\u5883\uFF0C\u81EA\u52A8\u56DE\u9000\u5230 npm");
525
+ return "npm";
526
+ }
527
+ return await promptPmResolution(pm);
528
+ }
529
+ async function promptPmResolution(pm) {
530
+ console.log();
531
+ console.log(` \u68C0\u6D4B\u5230\u9879\u76EE\u4F7F\u7528 ${colors.cyan}${pm}${colors.reset}\uFF0C\u4F46\u7CFB\u7EDF\u672A\u5B89\u88C5\u3002`);
532
+ console.log();
533
+ console.log(` ${colors.bold}\u8BF7\u9009\u62E9\u64CD\u4F5C:${colors.reset}`);
534
+ console.log(` ${colors.cyan}1${colors.reset}) \u81EA\u52A8\u5B89\u88C5 ${pm} ${colors.dim}(npm install -g ${pm})${colors.reset}`);
535
+ console.log(` ${colors.cyan}2${colors.reset}) \u4F7F\u7528 npm \u4EE3\u66FF`);
536
+ console.log(` ${colors.cyan}3${colors.reset}) \u9000\u51FA\uFF0C\u624B\u52A8\u5904\u7406`);
537
+ console.log();
538
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
539
+ try {
540
+ const answer = await rl.question(` \u8BF7\u8F93\u5165\u9009\u9879 ${colors.dim}[1/2/3]${colors.reset}: `);
541
+ const choice = answer.trim();
542
+ switch (choice) {
543
+ case "1":
544
+ return await autoInstallPm(pm);
545
+ case "2":
546
+ warn(`\u4F7F\u7528 npm \u4EE3\u66FF ${pm}`);
547
+ return "npm";
548
+ case "3":
549
+ default:
550
+ throw new CliError(`\u8BF7\u5148\u5B89\u88C5 ${pm}: npm install -g ${pm}`);
551
+ }
552
+ } finally {
553
+ rl.close();
554
+ }
555
+ }
556
+ async function autoInstallPm(pm) {
557
+ info(`\u6B63\u5728\u5B89\u88C5 ${pm}...`);
558
+ console.log();
559
+ try {
560
+ const result = await executeCapture(["npm", "install", "-g", pm]);
561
+ if (result.exitCode !== 0) {
562
+ error(`\u5B89\u88C5 ${pm} \u5931\u8D25`);
563
+ if (result.stderr.trim()) {
564
+ console.error(result.stderr.trim());
565
+ }
566
+ throw new CliError(`\u65E0\u6CD5\u81EA\u52A8\u5B89\u88C5 ${pm}\uFF0C\u8BF7\u624B\u52A8\u8FD0\u884C: npm install -g ${pm}`);
567
+ }
568
+ } catch (err) {
569
+ if (err instanceof CliError)
570
+ throw err;
571
+ throw new CliError(`\u5B89\u88C5 ${pm} \u65F6\u51FA\u9519\uFF0C\u8BF7\u624B\u52A8\u8FD0\u884C: npm install -g ${pm}`);
572
+ }
573
+ availabilityCache.delete(pm);
574
+ const nowAvailable = await isPmAvailable(pm);
575
+ if (!nowAvailable) {
576
+ throw new CliError(`${pm} \u5DF2\u5B89\u88C5\u4F46\u65E0\u6CD5\u6267\u884C\u3002\u8BF7\u68C0\u67E5 PATH \u6216\u91CD\u65B0\u6253\u5F00\u7EC8\u7AEF\u540E\u91CD\u8BD5\u3002`);
577
+ }
578
+ success(`${pm} \u5B89\u88C5\u5B8C\u6210`);
579
+ console.log();
580
+ return pm;
581
+ }
582
+
367
583
  // src/cli/run.ts
368
584
  async function runCommand(projectDir, options = {}) {
369
- const { noInstall = false, forceInstall = false, scriptType = "dev" } = options;
585
+ const { noInstall = false, forceInstall = false, scriptType = "dev", entry } = options;
370
586
  log("\u6B63\u5728\u5206\u6790\u9879\u76EE...");
371
587
  const project = await analyzeProject(projectDir);
372
588
  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);
589
+ throw new CliError("\u672A\u68C0\u6D4B\u5230\u9879\u76EE\u7C7B\u578B\u3002\u8BF7\u786E\u4FDD\u5F53\u524D\u76EE\u5F55\u5305\u542B package.json");
375
590
  }
376
591
  log(`\u9879\u76EE\u7C7B\u578B: ${project.type}`);
377
592
  log(`\u5305\u7BA1\u7406\u5668: ${project.packageManager.name} (from ${project.packageManager.source})`);
378
593
  if (!project.scripts) {
379
- error("\u65E0\u6CD5\u8BFB\u53D6 package.json \u7684 scripts");
380
- process.exit(1);
594
+ throw new CliError("\u65E0\u6CD5\u8BFB\u53D6 package.json \u7684 scripts");
381
595
  }
382
- const scriptName = findScript(project, scriptType);
596
+ const resolvedEntry = await resolveEntry(project, scriptType, entry);
597
+ const scriptName = findScript(project, scriptType, resolvedEntry);
383
598
  if (!scriptName) {
384
- error(`\u672A\u627E\u5230 ${scriptType} \u76F8\u5173\u7684\u811A\u672C`);
385
- showAvailableScripts(project);
386
- process.exit(1);
599
+ showAvailableScripts(project, scriptType, resolvedEntry);
600
+ throw new CliError(`\u672A\u627E\u5230 ${scriptType}${resolvedEntry ? `:${resolvedEntry}` : ""} \u76F8\u5173\u7684\u811A\u672C`);
601
+ }
602
+ if (resolvedEntry) {
603
+ log(`\u5165\u53E3: ${resolvedEntry}`);
387
604
  }
388
605
  log(`\u5C06\u6267\u884C\u811A\u672C: ${scriptName}`);
606
+ const resolvedPm = await ensurePmAvailable(project.packageManager.name);
607
+ if (resolvedPm !== project.packageManager.name) {
608
+ log(`\u4F7F\u7528 ${resolvedPm} \u4EE3\u66FF ${project.packageManager.name}`);
609
+ }
389
610
  const shouldInstall = !noInstall && (forceInstall || project.dependencies.needsInstall);
390
611
  if (shouldInstall) {
391
612
  if (forceInstall) {
@@ -394,27 +615,32 @@ async function runCommand(projectDir, options = {}) {
394
615
  log(`\u4F9D\u8D56\u72B6\u6001: ${project.dependencies.reason || "\u9700\u8981\u5B89\u88C5"}`);
395
616
  }
396
617
  newline();
397
- const installCmd = getInstallCommand(project.packageManager.name);
618
+ const installCmd = getInstallCommand(resolvedPm);
398
619
  const installExitCode = await execute(installCmd, { cwd: projectDir });
399
620
  if (installExitCode !== 0) {
400
- error("\u4F9D\u8D56\u5B89\u88C5\u5931\u8D25");
401
- process.exit(installExitCode);
621
+ throw new CliError("\u4F9D\u8D56\u5B89\u88C5\u5931\u8D25", installExitCode);
402
622
  }
403
623
  success("\u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210");
404
624
  newline();
405
625
  } else if (!noInstall) {
406
626
  log("\u4F9D\u8D56\u72B6\u6001: \u5DF2\u662F\u6700\u65B0");
407
627
  }
408
- const runCmd = getRunCommand(project.packageManager.name, scriptName);
628
+ const runCmd = getRunCommand(resolvedPm, scriptName);
409
629
  const exitCode = await execute(runCmd, { cwd: projectDir });
410
630
  if (exitCode !== 0) {
411
- process.exit(exitCode);
631
+ throw new CliError("\u811A\u672C\u6267\u884C\u5931\u8D25", exitCode);
412
632
  }
413
633
  }
414
- function findScript(project, scriptType) {
634
+ function findScript(project, scriptType, entry) {
415
635
  const scripts = project.scripts;
416
636
  if (!scripts)
417
637
  return void 0;
638
+ if (entry) {
639
+ const entryScript = `${scriptType}:${entry}`;
640
+ if (scripts.scripts[entryScript]) {
641
+ return entryScript;
642
+ }
643
+ }
418
644
  const detected = scripts.detected[scriptType];
419
645
  if (detected) {
420
646
  return detected;
@@ -424,9 +650,115 @@ function findScript(project, scriptType) {
424
650
  }
425
651
  return void 0;
426
652
  }
427
- function showAvailableScripts(project) {
653
+ async function resolveEntry(project, scriptType, entry) {
654
+ if (!project.scripts) {
655
+ return entry;
656
+ }
657
+ if (entry) {
658
+ validateEntry(project, scriptType, entry);
659
+ return entry;
660
+ }
661
+ if (scriptType !== "dev") {
662
+ return void 0;
663
+ }
664
+ const { mpa } = project.scripts;
665
+ if (!mpa.isMpa || mpa.entries.length === 0) {
666
+ return void 0;
667
+ }
668
+ if (mpa.entries.length === 1) {
669
+ const onlyEntry = mpa.entries[0];
670
+ if (onlyEntry) {
671
+ return onlyEntry;
672
+ }
673
+ return void 0;
674
+ }
675
+ const envEntry = process.env.PR_ENTRY?.trim();
676
+ if (envEntry) {
677
+ validateEntry(project, scriptType, envEntry);
678
+ return envEntry;
679
+ }
680
+ const defaultEntry = mpa.defaultEntry && hasEntryScript(project, scriptType, mpa.defaultEntry) ? mpa.defaultEntry : void 0;
681
+ if (!process.stdin.isTTY) {
682
+ if (defaultEntry) {
683
+ return defaultEntry;
684
+ }
685
+ throw new CliError(
686
+ `\u68C0\u6D4B\u5230 MPA \u5165\u53E3: ${mpa.entries.join(", ")}\u3002\u975E\u4EA4\u4E92\u73AF\u5883\u8BF7\u4F7F\u7528 --entry <name> \u6216\u8BBE\u7F6E PR_ENTRY\u3002`
687
+ );
688
+ }
689
+ return promptSelectEntry(project, scriptType, defaultEntry);
690
+ }
691
+ function validateEntry(project, scriptType, entry) {
692
+ if (!hasEntryScript(project, scriptType, entry)) {
693
+ const available = getAvailableEntriesByType(project, scriptType);
694
+ if (available.length > 0) {
695
+ throw new CliError(`\u5165\u53E3 "${entry}" \u4E0D\u5B58\u5728\u3002\u53EF\u9009\u5165\u53E3: ${available.join(", ")}`);
696
+ }
697
+ throw new CliError(`\u5165\u53E3 "${entry}" \u4E0D\u5B58\u5728\uFF0C\u4E14\u672A\u627E\u5230 ${scriptType}:<entry> \u811A\u672C`);
698
+ }
699
+ }
700
+ function hasEntryScript(project, scriptType, entry) {
701
+ const scripts = project.scripts;
702
+ if (!scripts)
703
+ return false;
704
+ return Boolean(scripts.scripts[`${scriptType}:${entry}`]);
705
+ }
706
+ function getAvailableEntriesByType(project, scriptType) {
707
+ const scripts = project.scripts;
708
+ if (!scripts)
709
+ return [];
710
+ const mapping = scripts.mpa.scriptsByType[scriptType];
711
+ if (!mapping)
712
+ return [];
713
+ return Object.keys(mapping);
714
+ }
715
+ async function promptSelectEntry(project, scriptType, defaultEntry) {
716
+ const entries = getAvailableEntriesByType(project, scriptType);
717
+ if (entries.length === 0) {
718
+ throw new CliError(`\u672A\u627E\u5230 ${scriptType}:<entry> \u811A\u672C\uFF0C\u65E0\u6CD5\u9009\u62E9 MPA \u5165\u53E3`);
719
+ }
720
+ const defaultResolved = defaultEntry && entries.includes(defaultEntry) ? defaultEntry : entries[0];
721
+ if (!defaultResolved) {
722
+ throw new CliError("\u672A\u627E\u5230\u53EF\u7528\u5165\u53E3\uFF0C\u65E0\u6CD5\u7EE7\u7EED\u6267\u884C");
723
+ }
724
+ info("\u68C0\u6D4B\u5230 MPA \u9879\u76EE\uFF0C\u8BF7\u9009\u62E9\u542F\u52A8\u5165\u53E3\uFF1A");
725
+ entries.forEach((item, index) => {
726
+ const marker = item === defaultResolved ? " (\u9ED8\u8BA4)" : "";
727
+ console.log(` ${index + 1}) ${item}${marker}`);
728
+ });
729
+ console.log();
730
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
731
+ try {
732
+ const answer = await rl.question(`\u8BF7\u8F93\u5165\u5E8F\u53F7 [1-${entries.length}]\uFF0C\u56DE\u8F66\u9ED8\u8BA4 ${defaultResolved}: `);
733
+ const trimmed = answer.trim();
734
+ if (!trimmed) {
735
+ return defaultResolved;
736
+ }
737
+ const number = Number(trimmed);
738
+ if (Number.isInteger(number) && number >= 1 && number <= entries.length) {
739
+ const selectedByNumber = entries[number - 1];
740
+ if (selectedByNumber) {
741
+ return selectedByNumber;
742
+ }
743
+ }
744
+ if (entries.includes(trimmed)) {
745
+ return trimmed;
746
+ }
747
+ } finally {
748
+ rl.close();
749
+ }
750
+ throw new CliError("\u65E0\u6548\u7684\u5165\u53E3\u9009\u62E9\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C\u5E76\u8F93\u5165\u6B63\u786E\u5E8F\u53F7");
751
+ }
752
+ function showAvailableScripts(project, scriptType, entry) {
428
753
  if (!project.scripts)
429
754
  return;
755
+ if (entry) {
756
+ const availableEntries = getAvailableEntriesByType(project, scriptType);
757
+ if (availableEntries.length > 0) {
758
+ info(`\u53EF\u9009 ${scriptType} \u5165\u53E3: ${availableEntries.join(", ")}`);
759
+ return;
760
+ }
761
+ }
430
762
  const scriptNames = Object.keys(project.scripts.scripts);
431
763
  if (scriptNames.length === 0) {
432
764
  warn("package.json \u4E2D\u6CA1\u6709\u5B9A\u4E49\u4EFB\u4F55 scripts");
@@ -437,15 +769,15 @@ function showAvailableScripts(project) {
437
769
  console.log(` - ${name}`);
438
770
  }
439
771
  console.log();
440
- info("\u4F7F\u7528 qy <script> \u8FD0\u884C\u4EFB\u610F\u811A\u672C");
772
+ info("\u4F7F\u7528 pr <script> \u8FD0\u884C\u4EFB\u610F\u811A\u672C");
441
773
  }
442
774
 
443
775
  // src/cli/info.ts
444
776
  async function infoCommand(projectDir) {
445
777
  const project = await analyzeProject(projectDir);
446
778
  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");
779
+ console.log(`${colors.red}\xD7${colors.reset} \u672A\u68C0\u6D4B\u5230\u9879\u76EE\u7C7B\u578B`);
780
+ console.log(" \u8BF7\u786E\u8BA4\u5F53\u524D\u76EE\u5F55\u5305\u542B package.json");
449
781
  return;
450
782
  }
451
783
  console.log();
@@ -467,10 +799,28 @@ async function infoCommand(projectDir) {
467
799
  pmInfo += `@${pm.version}`;
468
800
  }
469
801
  pmInfo += ` ${colors.dim}(${pm.source})${colors.reset}`;
470
- console.log(`${colors.bold}\u5305\u7BA1\u7406\u5668:${colors.reset} ${pmInfo}`);
802
+ const pmAvailable = await isPmAvailable(pm.name);
803
+ const pmStatus = pmAvailable ? `${colors.green}\u5DF2\u5B89\u88C5${colors.reset}` : `${colors.red}\u672A\u5B89\u88C5${colors.reset}`;
804
+ console.log(`${colors.bold}\u5305\u7BA1\u7406\u5668:${colors.reset} ${pmInfo} [${pmStatus}]`);
471
805
  const deps = project.dependencies;
472
806
  const depsStatus = deps.needsInstall ? `${colors.yellow}\u9700\u8981\u5B89\u88C5${colors.reset} (${deps.reason})` : `${colors.green}\u5DF2\u5C31\u7EEA${colors.reset}`;
473
807
  console.log(`${colors.bold}\u4F9D\u8D56\u72B6\u6001:${colors.reset} ${depsStatus}`);
808
+ if (project.scripts) {
809
+ const { mpa } = project.scripts;
810
+ if (mpa.isMpa) {
811
+ const sourceLabel = mpa.source === "local-config" ? ".pr.local.json" : mpa.source === "package-json" ? "package.json#pr" : "scripts";
812
+ console.log(`${colors.bold}MPA \u6A21\u5F0F:${colors.reset} \u662F ${colors.dim}(${sourceLabel})${colors.reset}`);
813
+ console.log(`${colors.bold}\u53EF\u9009\u5165\u53E3:${colors.reset} ${mpa.entries.join(", ")}`);
814
+ if (mpa.defaultEntry) {
815
+ console.log(`${colors.bold}\u9ED8\u8BA4\u5165\u53E3:${colors.reset} ${mpa.defaultEntry}`);
816
+ }
817
+ if (project.localConfig) {
818
+ console.log(`${colors.bold}\u672C\u5730\u914D\u7F6E:${colors.reset} .pr.local.json ${colors.dim}(\u81EA\u52A8\u52A0\u5165 .gitignore)${colors.reset}`);
819
+ }
820
+ } else {
821
+ console.log(`${colors.bold}MPA \u6A21\u5F0F:${colors.reset} \u5426`);
822
+ }
823
+ }
474
824
  console.log();
475
825
  if (project.scripts) {
476
826
  const { scripts, detected } = project.scripts;
@@ -493,7 +843,10 @@ async function infoCommand(projectDir) {
493
843
  console.log(`${colors.bold}\u6240\u6709\u811A\u672C:${colors.reset}`);
494
844
  for (const name of allScripts) {
495
845
  const cmd = scripts[name];
496
- const displayCmd = cmd.length > 40 ? cmd.slice(0, 40) + "..." : cmd;
846
+ if (!cmd) {
847
+ continue;
848
+ }
849
+ const displayCmd = cmd.length > 60 ? cmd.slice(0, 60) + "..." : cmd;
497
850
  console.log(` ${colors.cyan}${name}${colors.reset} ${colors.dim}\u2192 ${displayCmd}${colors.reset}`);
498
851
  }
499
852
  }
@@ -505,26 +858,23 @@ async function infoCommand(projectDir) {
505
858
  async function scriptCommand(projectDir, scriptName) {
506
859
  const project = await analyzeProject(projectDir);
507
860
  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);
861
+ throw new CliError("\u672A\u68C0\u6D4B\u5230\u9879\u76EE\u7C7B\u578B\u3002\u8BF7\u786E\u4FDD\u5F53\u524D\u76EE\u5F55\u5305\u542B package.json");
510
862
  }
511
863
  if (!project.scripts) {
512
- error("\u65E0\u6CD5\u8BFB\u53D6 package.json \u7684 scripts");
513
- process.exit(1);
864
+ throw new CliError("\u65E0\u6CD5\u8BFB\u53D6 package.json \u7684 scripts");
514
865
  }
515
866
  const scripts = project.scripts.scripts;
516
867
  if (!(scriptName in scripts)) {
517
- error(`\u811A\u672C "${scriptName}" \u4E0D\u5B58\u5728`);
518
- console.log();
519
868
  showAvailableScripts2(scripts);
520
- process.exit(1);
869
+ throw new CliError(`\u811A\u672C "${scriptName}" \u4E0D\u5B58\u5728`);
521
870
  }
522
871
  log(`\u5305\u7BA1\u7406\u5668: ${project.packageManager.name}`);
523
872
  log(`\u6267\u884C\u811A\u672C: ${scriptName}`);
524
- const runCmd = getRunCommand(project.packageManager.name, scriptName);
873
+ const resolvedPm = await ensurePmAvailable(project.packageManager.name);
874
+ const runCmd = getRunCommand(resolvedPm, scriptName);
525
875
  const exitCode = await execute(runCmd, { cwd: projectDir });
526
876
  if (exitCode !== 0) {
527
- process.exit(exitCode);
877
+ throw new CliError("\u811A\u672C\u6267\u884C\u5931\u8D25", exitCode);
528
878
  }
529
879
  }
530
880
  function showAvailableScripts2(scripts) {
@@ -540,7 +890,7 @@ function showAvailableScripts2(scripts) {
540
890
  }
541
891
 
542
892
  // src/index.ts
543
- var VERSION = "0.1.2";
893
+ var VERSION = true ? "0.3.0" : "0.0.0-dev";
544
894
  function parseArgs(args) {
545
895
  const options = {
546
896
  verbose: false,
@@ -553,14 +903,30 @@ function parseArgs(args) {
553
903
  let i = 0;
554
904
  while (i < args.length) {
555
905
  const arg = args[i];
906
+ if (!arg) {
907
+ i++;
908
+ continue;
909
+ }
556
910
  if (arg === "-v" || arg === "--verbose") {
557
911
  options.verbose = true;
558
912
  } else if (arg === "-d" || arg === "--dir") {
559
- options.dir = resolve(args[++i] || ".");
913
+ const dirArg = args[i + 1];
914
+ if (!dirArg || dirArg.startsWith("-")) {
915
+ throw new CliError("--dir \u9700\u8981\u4E00\u4E2A\u76EE\u5F55\u53C2\u6570");
916
+ }
917
+ options.dir = resolve(dirArg);
918
+ i++;
560
919
  } else if (arg === "--no-install") {
561
920
  options.noInstall = true;
562
921
  } else if (arg === "-i" || arg === "--install") {
563
922
  options.install = true;
923
+ } else if (arg === "-e" || arg === "--entry") {
924
+ const entryArg = args[i + 1];
925
+ if (!entryArg || entryArg.startsWith("-")) {
926
+ throw new CliError("--entry \u9700\u8981\u4E00\u4E2A\u5165\u53E3\u540D\u53C2\u6570");
927
+ }
928
+ options.entry = entryArg.trim();
929
+ i++;
564
930
  } else if (arg === "-h" || arg === "--help") {
565
931
  command = "help";
566
932
  } else if (arg === "-V" || arg === "--version") {
@@ -571,6 +937,8 @@ function parseArgs(args) {
571
937
  } else {
572
938
  remainingArgs.push(arg);
573
939
  }
940
+ } else {
941
+ warn(`\u672A\u77E5\u9009\u9879: ${arg}`);
574
942
  }
575
943
  i++;
576
944
  }
@@ -583,7 +951,7 @@ ${"\x1B[36m"}pr${"\x1B[0m"} v${VERSION} - \u96F6\u914D\u7F6E\u667A\u80FD\u9879\u
583
951
  ${"\x1B[1m"}\u7528\u6CD5:${"\x1B[0m"} pr <command> [options]
584
952
 
585
953
  ${"\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
954
+ run \u5B8C\u6574\u6D41\u7A0B\uFF1A\u68C0\u6D4B -> install -> \u542F\u52A8\u5F00\u53D1\u811A\u672C
587
955
  test \u8FD0\u884C\u6D4B\u8BD5
588
956
  build \u6784\u5EFA\u9879\u76EE
589
957
  start \u751F\u4EA7\u6A21\u5F0F\u542F\u52A8
@@ -595,16 +963,16 @@ ${"\x1B[1m"}\u9009\u9879:${"\x1B[0m"}
595
963
  -d, --dir <path> \u6307\u5B9A\u9879\u76EE\u76EE\u5F55 (\u9ED8\u8BA4: \u5F53\u524D\u76EE\u5F55)
596
964
  -i, --install \u5F3A\u5236\u6267\u884C\u4F9D\u8D56\u5B89\u88C5
597
965
  --no-install \u8DF3\u8FC7\u4F9D\u8D56\u5B89\u88C5\u6B65\u9AA4
966
+ -e, --entry \u6307\u5B9A MPA \u5165\u53E3 (\u4E5F\u53EF\u7528\u73AF\u5883\u53D8\u91CF PR_ENTRY)
598
967
  -h, --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F
599
968
  -V, --version \u663E\u793A\u7248\u672C\u53F7
600
969
 
601
970
  ${"\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
971
+ pr run
972
+ pr run --entry main
973
+ PR_ENTRY=formengine pr run
974
+ pr build --entry approve
975
+ pr info
608
976
  `);
609
977
  }
610
978
  function showVersion() {
@@ -612,7 +980,7 @@ function showVersion() {
612
980
  }
613
981
  async function main() {
614
982
  setupSignalHandlers();
615
- const { command, options, args } = parseArgs(process.argv.slice(2));
983
+ const { command, options } = parseArgs(process.argv.slice(2));
616
984
  setVerbose(options.verbose);
617
985
  switch (command) {
618
986
  case "":
@@ -623,16 +991,33 @@ async function main() {
623
991
  showVersion();
624
992
  break;
625
993
  case "run":
626
- await runCommand(options.dir, { noInstall: options.noInstall, forceInstall: options.install, scriptType: "dev" });
994
+ await runCommand(options.dir, {
995
+ noInstall: options.noInstall,
996
+ forceInstall: options.install,
997
+ scriptType: "dev",
998
+ entry: options.entry
999
+ });
627
1000
  break;
628
1001
  case "test":
629
- await runCommand(options.dir, { noInstall: true, scriptType: "test" });
1002
+ await runCommand(options.dir, {
1003
+ noInstall: true,
1004
+ scriptType: "test",
1005
+ entry: options.entry
1006
+ });
630
1007
  break;
631
1008
  case "build":
632
- await runCommand(options.dir, { noInstall: true, scriptType: "build" });
1009
+ await runCommand(options.dir, {
1010
+ noInstall: true,
1011
+ scriptType: "build",
1012
+ entry: options.entry
1013
+ });
633
1014
  break;
634
1015
  case "start":
635
- await runCommand(options.dir, { noInstall: true, scriptType: "start" });
1016
+ await runCommand(options.dir, {
1017
+ noInstall: true,
1018
+ scriptType: "start",
1019
+ entry: options.entry
1020
+ });
636
1021
  break;
637
1022
  case "info":
638
1023
  await infoCommand(options.dir);
@@ -643,6 +1028,10 @@ async function main() {
643
1028
  }
644
1029
  }
645
1030
  main().catch((err) => {
646
- error(err.message);
1031
+ if (err instanceof CliError) {
1032
+ error(err.message);
1033
+ process.exit(err.exitCode);
1034
+ }
1035
+ error(err.message || "\u672A\u77E5\u9519\u8BEF");
647
1036
  process.exit(1);
648
1037
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-runner",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "零配置智能项目运行器 - 一键运行任意 Node.js 项目",
5
5
  "author": "liangzhenqi",
6
6
  "license": "MIT",