batch-run-local 1.0.4 → 1.1.1
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/cli/index.js +141 -20
- package/dist/cli/index.js.map +1 -1
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +70 -52
- package/dist/web/main.js +63 -6
- package/dist/web/style.css +100 -17
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -57,6 +57,28 @@ async function addProject(input) {
|
|
|
57
57
|
store.set("projects", [...projects, project]);
|
|
58
58
|
return project;
|
|
59
59
|
}
|
|
60
|
+
async function updateProject(projectId, input) {
|
|
61
|
+
const root = resolve(input.root);
|
|
62
|
+
await access(root);
|
|
63
|
+
const projects = getProjects();
|
|
64
|
+
const projectIndex = projects.findIndex((project) => project.id === projectId);
|
|
65
|
+
if (projectIndex < 0) {
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
if (projects.some((project) => project.id !== projectId && project.root.toLowerCase() === root.toLowerCase())) {
|
|
69
|
+
throw new Error("\u8BE5\u9879\u76EE\u8DEF\u5F84\u5DF2\u7ECF\u5B58\u5728");
|
|
70
|
+
}
|
|
71
|
+
const nextProject = {
|
|
72
|
+
...projects[projectIndex],
|
|
73
|
+
name: input.name.trim(),
|
|
74
|
+
root,
|
|
75
|
+
updatedAt: Date.now()
|
|
76
|
+
};
|
|
77
|
+
const nextProjects = [...projects];
|
|
78
|
+
nextProjects[projectIndex] = nextProject;
|
|
79
|
+
store.set("projects", nextProjects);
|
|
80
|
+
return nextProject;
|
|
81
|
+
}
|
|
60
82
|
function removeProject(projectId) {
|
|
61
83
|
const projects = getProjects();
|
|
62
84
|
const nextProjects = projects.filter((project) => project.id !== projectId);
|
|
@@ -216,17 +238,20 @@ async function readPackageScripts(projectRoot) {
|
|
|
216
238
|
|
|
217
239
|
// src/runner/git-bash.ts
|
|
218
240
|
import { spawn } from "child_process";
|
|
241
|
+
import { mkdtempSync, writeFileSync } from "fs";
|
|
242
|
+
import { tmpdir } from "os";
|
|
243
|
+
import { join as join3 } from "path";
|
|
219
244
|
|
|
220
245
|
// src/runner/path.ts
|
|
221
246
|
import { existsSync } from "fs";
|
|
222
247
|
import { platform } from "os";
|
|
223
|
-
import { dirname, join as join2 } from "path";
|
|
248
|
+
import { basename, dirname, join as join2 } from "path";
|
|
224
249
|
var defaultWindowsGitBashPaths = [
|
|
225
250
|
"C:\\Program Files\\Git\\git-bash.exe",
|
|
226
251
|
"C:\\Program Files (x86)\\Git\\git-bash.exe"
|
|
227
252
|
];
|
|
228
253
|
function findGitBashPath(configuredPath) {
|
|
229
|
-
if (configuredPath && existsSync(configuredPath)) {
|
|
254
|
+
if (configuredPath && existsSync(configuredPath) && isGitBashExecutable(configuredPath)) {
|
|
230
255
|
return configuredPath;
|
|
231
256
|
}
|
|
232
257
|
if (platform() !== "win32") {
|
|
@@ -247,32 +272,71 @@ function shellSingleQuote(value) {
|
|
|
247
272
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
248
273
|
}
|
|
249
274
|
function findMinttyPath(gitBashPath) {
|
|
250
|
-
const
|
|
251
|
-
|
|
275
|
+
const gitBashDir = dirname(gitBashPath);
|
|
276
|
+
const candidates = [
|
|
277
|
+
join2(gitBashDir, "usr", "bin", "mintty.exe"),
|
|
278
|
+
join2(gitBashDir, "mintty.exe"),
|
|
279
|
+
join2(dirname(gitBashDir), "usr", "bin", "mintty.exe")
|
|
280
|
+
];
|
|
281
|
+
return candidates.find((minttyPath) => existsSync(minttyPath));
|
|
282
|
+
}
|
|
283
|
+
function findBashExecutablePath(gitBashPath) {
|
|
284
|
+
const executableName = basename(gitBashPath).toLowerCase();
|
|
285
|
+
if (executableName === "bash.exe" || executableName === "sh.exe") {
|
|
286
|
+
return gitBashPath;
|
|
287
|
+
}
|
|
288
|
+
const gitBashDir = dirname(gitBashPath);
|
|
289
|
+
const candidates = [
|
|
290
|
+
join2(gitBashDir, "bin", "bash.exe"),
|
|
291
|
+
join2(gitBashDir, "usr", "bin", "bash.exe"),
|
|
292
|
+
join2(gitBashDir, "bin", "sh.exe"),
|
|
293
|
+
join2(gitBashDir, "usr", "bin", "sh.exe")
|
|
294
|
+
];
|
|
295
|
+
return candidates.find((bashPath) => existsSync(bashPath)) ?? gitBashPath;
|
|
296
|
+
}
|
|
297
|
+
function isGitBashExecutable(inputPath) {
|
|
298
|
+
const executableName = basename(inputPath).toLowerCase();
|
|
299
|
+
return executableName === "git-bash.exe" || executableName === "bash.exe" || executableName === "sh.exe";
|
|
252
300
|
}
|
|
253
301
|
|
|
254
302
|
// src/runner/git-bash.ts
|
|
303
|
+
var GIT_ENV_KEYS = [
|
|
304
|
+
"GIT_DIR",
|
|
305
|
+
"GIT_WORK_TREE",
|
|
306
|
+
"GIT_INDEX_FILE",
|
|
307
|
+
"GIT_PREFIX",
|
|
308
|
+
"GIT_OBJECT_DIRECTORY",
|
|
309
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES"
|
|
310
|
+
];
|
|
311
|
+
function processEnvWithoutGitOverrides() {
|
|
312
|
+
const env = { ...process.env };
|
|
313
|
+
for (const key of GIT_ENV_KEYS) {
|
|
314
|
+
delete env[key];
|
|
315
|
+
}
|
|
316
|
+
return env;
|
|
317
|
+
}
|
|
255
318
|
function runScriptInGitBash(project, scriptName, settings) {
|
|
256
319
|
const gitBashPath = findGitBashPath(settings.gitBashPath);
|
|
257
320
|
if (!gitBashPath) {
|
|
258
321
|
throw new Error("\u672A\u627E\u5230 Git Bash\uFF0C\u8BF7\u5148\u901A\u8FC7 br config \u8BBE\u7F6E git-bash.exe \u8DEF\u5F84");
|
|
259
322
|
}
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
].join("; ");
|
|
323
|
+
const title = createTerminalTitle(project.name, scriptName);
|
|
324
|
+
const gitBashRoot = toGitBashPath(project.root);
|
|
325
|
+
const scriptPath = writeRunScript(project, scriptName, title, gitBashRoot);
|
|
326
|
+
const gitBashScriptPath = toGitBashPath(scriptPath);
|
|
327
|
+
const bashPath = findBashExecutablePath(gitBashPath);
|
|
328
|
+
const command = `${bashPath} --login ${scriptPath}`;
|
|
267
329
|
const minttyPath = findMinttyPath(gitBashPath);
|
|
268
|
-
const child = minttyPath ? spawn(minttyPath, ["--dir", toGitBashPath(
|
|
330
|
+
const child = minttyPath ? spawn(minttyPath, ["--title", title, "--dir", gitBashRoot, toGitBashPath(bashPath), "--login", gitBashScriptPath], {
|
|
269
331
|
detached: true,
|
|
270
332
|
stdio: "ignore",
|
|
271
|
-
windowsHide: false
|
|
272
|
-
|
|
333
|
+
windowsHide: false,
|
|
334
|
+
env: processEnvWithoutGitOverrides()
|
|
335
|
+
}) : spawn("cmd.exe", ["/d", "/c", "start", title, "/D", project.root, bashPath, "--login", gitBashScriptPath], {
|
|
273
336
|
detached: true,
|
|
274
337
|
stdio: "ignore",
|
|
275
|
-
windowsHide: false
|
|
338
|
+
windowsHide: false,
|
|
339
|
+
env: processEnvWithoutGitOverrides()
|
|
276
340
|
});
|
|
277
341
|
child.unref();
|
|
278
342
|
return {
|
|
@@ -280,6 +344,43 @@ function runScriptInGitBash(project, scriptName, settings) {
|
|
|
280
344
|
command
|
|
281
345
|
};
|
|
282
346
|
}
|
|
347
|
+
function createTerminalTitle(projectName, scriptName) {
|
|
348
|
+
const title = `batch-run - ${projectName} - npm run ${scriptName}`;
|
|
349
|
+
return title.replace(/[\u0000-\u001f\u007f]/g, " ").slice(0, 120);
|
|
350
|
+
}
|
|
351
|
+
function writeRunScript(project, scriptName, title, gitBashRoot) {
|
|
352
|
+
const tempDir = mkdtempSync(join3(tmpdir(), "batch-run-"));
|
|
353
|
+
const scriptPath = join3(tempDir, "run.sh");
|
|
354
|
+
const scriptContent = [
|
|
355
|
+
"#!/usr/bin/env bash",
|
|
356
|
+
"set +e",
|
|
357
|
+
`cd ${shellSingleQuote(gitBashRoot)}`,
|
|
358
|
+
'rm -f "$0" >/dev/null 2>&1 || true',
|
|
359
|
+
`unset ${GIT_ENV_KEYS.join(" ")}`,
|
|
360
|
+
`__batch_run_title=${shellSingleQuote(title)}`,
|
|
361
|
+
`set_batch_run_title() { printf '\\033]0;%s\\007' "$__batch_run_title"; }`,
|
|
362
|
+
"set_batch_run_title",
|
|
363
|
+
"(while true; do set_batch_run_title; sleep 2; done) &",
|
|
364
|
+
"__batch_run_title_pid=$!",
|
|
365
|
+
"cleanup_batch_run_title() {",
|
|
366
|
+
' kill "$__batch_run_title_pid" >/dev/null 2>&1 || true',
|
|
367
|
+
' wait "$__batch_run_title_pid" 2>/dev/null || true',
|
|
368
|
+
" set_batch_run_title",
|
|
369
|
+
"}",
|
|
370
|
+
"trap cleanup_batch_run_title EXIT",
|
|
371
|
+
`echo Running ${shellSingleQuote(project.name)}: npm run ${shellSingleQuote(scriptName)}`,
|
|
372
|
+
`npm run ${shellSingleQuote(scriptName)}`,
|
|
373
|
+
"__batch_run_exit_code=$?",
|
|
374
|
+
"set_batch_run_title",
|
|
375
|
+
"echo",
|
|
376
|
+
'echo "Process exited with code $__batch_run_exit_code. Press Ctrl+D or close this window."',
|
|
377
|
+
"bash --login",
|
|
378
|
+
'exit "$__batch_run_exit_code"',
|
|
379
|
+
""
|
|
380
|
+
].join("\n");
|
|
381
|
+
writeFileSync(scriptPath, scriptContent, "utf8");
|
|
382
|
+
return scriptPath;
|
|
383
|
+
}
|
|
283
384
|
|
|
284
385
|
// src/cli/commands/run.ts
|
|
285
386
|
async function runCommand() {
|
|
@@ -331,7 +432,7 @@ async function runCommand() {
|
|
|
331
432
|
// src/server/index.ts
|
|
332
433
|
import fastify from "fastify";
|
|
333
434
|
import { readFile as readFile2 } from "fs/promises";
|
|
334
|
-
import { dirname as dirname2, join as
|
|
435
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
335
436
|
import { fileURLToPath } from "url";
|
|
336
437
|
import open2 from "open";
|
|
337
438
|
|
|
@@ -362,6 +463,23 @@ async function registerApiRoutes(app, settings) {
|
|
|
362
463
|
return reply.code(400).send({ message });
|
|
363
464
|
}
|
|
364
465
|
});
|
|
466
|
+
app.put("/api/projects/:id", async (request, reply) => {
|
|
467
|
+
const name = request.body.name?.trim();
|
|
468
|
+
const root = request.body.root?.trim();
|
|
469
|
+
if (!name || !root) {
|
|
470
|
+
return reply.code(400).send({ message: "\u9879\u76EE\u540D\u79F0\u548C\u6839\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A" });
|
|
471
|
+
}
|
|
472
|
+
try {
|
|
473
|
+
const project = await updateProject(request.params.id, { name, root });
|
|
474
|
+
if (!project) {
|
|
475
|
+
return reply.code(404).send({ message: "\u9879\u76EE\u4E0D\u5B58\u5728" });
|
|
476
|
+
}
|
|
477
|
+
return { project };
|
|
478
|
+
} catch (error) {
|
|
479
|
+
const message = error instanceof Error ? error.message : "\u66F4\u65B0\u9879\u76EE\u5931\u8D25";
|
|
480
|
+
return reply.code(400).send({ message });
|
|
481
|
+
}
|
|
482
|
+
});
|
|
365
483
|
app.delete("/api/projects/:id", async (request, reply) => {
|
|
366
484
|
const removed = removeProject(request.params.id);
|
|
367
485
|
if (!removed) {
|
|
@@ -408,7 +526,7 @@ async function registerApiRoutes(app, settings) {
|
|
|
408
526
|
|
|
409
527
|
// src/server/index.ts
|
|
410
528
|
function getWebRoot() {
|
|
411
|
-
return
|
|
529
|
+
return join4(dirname2(dirname2(fileURLToPath(import.meta.url))), "web");
|
|
412
530
|
}
|
|
413
531
|
async function startServer(options = {}) {
|
|
414
532
|
const settings = getSettings();
|
|
@@ -418,13 +536,16 @@ async function startServer(options = {}) {
|
|
|
418
536
|
const webRoot = getWebRoot();
|
|
419
537
|
await registerApiRoutes(app, settings);
|
|
420
538
|
app.get("/", async (_request, reply) => {
|
|
421
|
-
return reply.type("text/html").send(await readFile2(
|
|
539
|
+
return reply.type("text/html").send(await readFile2(join4(webRoot, "index.html"), "utf8"));
|
|
422
540
|
});
|
|
423
541
|
app.get("/main.js", async (_request, reply) => {
|
|
424
|
-
return reply.type("application/javascript").send(await readFile2(
|
|
542
|
+
return reply.type("application/javascript").send(await readFile2(join4(webRoot, "main.js"), "utf8"));
|
|
425
543
|
});
|
|
426
544
|
app.get("/style.css", async (_request, reply) => {
|
|
427
|
-
return reply.type("text/css").send(await readFile2(
|
|
545
|
+
return reply.type("text/css").send(await readFile2(join4(webRoot, "style.css"), "utf8"));
|
|
546
|
+
});
|
|
547
|
+
app.get("/favicon.ico", async (_request, reply) => {
|
|
548
|
+
return reply.type("image/x-icon").send(await readFile2(join4(webRoot, "favicon.ico")));
|
|
428
549
|
});
|
|
429
550
|
const url = await app.listen({
|
|
430
551
|
host: "127.0.0.1",
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/add.ts","../../src/config/store.ts","../../src/cli/commands/config.ts","../../src/cli/commands/list.ts","../../src/cli/commands/remove.ts","../../src/cli/commands/run.ts","../../src/project/package-json.ts","../../src/runner/git-bash.ts","../../src/runner/path.ts","../../src/server/index.ts","../../src/server/routes.ts","../../src/cli/commands/ui.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport { cac } from 'cac'\r\nimport { readFileSync } from 'node:fs'\r\nimport prompts from 'prompts'\r\nimport { addCommand } from './commands/add.js'\r\nimport { configCommand } from './commands/config.js'\r\nimport { listCommand } from './commands/list.js'\r\nimport { removeCommand } from './commands/remove.js'\r\nimport { runCommand } from './commands/run.js'\r\nimport { uiCommand } from './commands/ui.js'\r\n\r\nconst packageJson = JSON.parse(\r\n readFileSync(new URL('../../package.json', import.meta.url), 'utf8'),\r\n) as { version?: string }\r\nconst packageVersion = packageJson.version ?? '0.0.0'\r\n\r\nconst cli = cac('br')\r\n\r\ncli.command('add', '新增项目配置').action(wrap(addCommand))\r\n\r\ncli.command('list', '查看项目配置').action(wrap(async () => listCommand()))\r\n\r\ncli.command('run', '选择并执行项目脚本').action(wrap(runCommand))\r\n\r\ncli.command('remove', '删除项目配置').action(wrap(removeCommand))\r\n\r\ncli.command('ui', '打开 Web 管理页面').action(wrap(uiCommand))\r\n\r\ncli.command('config', '配置 Git Bash 路径和 Web 端口').action(wrap(configCommand))\r\n\r\ncli.help()\r\ncli.version(packageVersion)\r\n\r\nasync function mainMenu(): Promise<void> {\r\n const answer = await prompts({\r\n type: 'select',\r\n name: 'action',\r\n message: '请选择操作',\r\n choices: [\r\n { title: '打开可视化操作页面', value: 'ui' },\r\n { title: '启动项目', value: 'run' },\r\n { title: '查看项目', value: 'list' },\r\n { title: '新增项目', value: 'add' },\r\n { title: '删除项目', value: 'remove' },\r\n { title: '设置 Git Bash 路径', value: 'config' },\r\n ],\r\n })\r\n\r\n switch (answer.action) {\r\n case 'run':\r\n await runCommand()\r\n break\r\n case 'add':\r\n await addCommand()\r\n break\r\n case 'list':\r\n listCommand()\r\n break\r\n case 'remove':\r\n await removeCommand()\r\n break\r\n case 'ui':\r\n await uiCommand()\r\n break\r\n case 'config':\r\n await configCommand()\r\n break\r\n }\r\n}\r\n\r\nfunction wrap(fn: () => Promise<void>) {\r\n return async () => {\r\n try {\r\n await fn()\r\n } catch (error) {\r\n handleError(error)\r\n }\r\n }\r\n}\r\n\r\nfunction handleError(error: unknown): void {\r\n const message = error instanceof Error ? error.message : String(error)\r\n console.error(`执行失败: ${message}`)\r\n process.exitCode = 1\r\n}\r\n\r\nif (process.argv.length <= 2) {\r\n await wrap(mainMenu)()\r\n} else {\r\n cli.parse()\r\n}\r\n","import prompts from \"prompts\";\r\nimport { addProject } from \"../../config/store.js\";\r\n\r\nexport async function addCommand(): Promise<void> {\r\n const answers = await prompts([\r\n {\r\n type: \"text\",\r\n name: \"name\",\r\n message: \"项目名称\",\r\n validate: (value) => value.trim().length > 0 || \"请输入项目名称\",\r\n },\r\n {\r\n type: \"text\",\r\n name: \"root\",\r\n message: \"项目根路径\",\r\n validate: (value) => value.trim().length > 0 || \"请输入项目根路径\",\r\n },\r\n ]);\r\n\r\n if (!answers.name || !answers.root) {\r\n return;\r\n }\r\n\r\n const project = await addProject({\r\n name: answers.name,\r\n root: answers.root,\r\n });\r\n\r\n console.log(`已添加项目: ${project.name}`);\r\n console.log(project.root);\r\n}\r\n","import Conf from \"conf\";\r\nimport { randomUUID } from \"node:crypto\";\r\nimport { access } from \"node:fs/promises\";\r\nimport { resolve } from \"node:path\";\r\nimport type { AppConfig, AppSettings, ProjectConfig } from \"../shared/types.js\";\r\n\r\nconst defaultSettings: AppSettings = {\r\n shell: \"git-bash\",\r\n port: 1234,\r\n};\r\n\r\nconst store = new Conf<AppConfig>({\r\n projectName: \"batch-run-local\",\r\n defaults: {\r\n projects: [],\r\n settings: defaultSettings,\r\n },\r\n});\r\n\r\nexport function getConfig(): AppConfig {\r\n return {\r\n projects: store.get(\"projects\") ?? [],\r\n settings: {\r\n ...defaultSettings,\r\n ...(store.get(\"settings\") ?? {}),\r\n },\r\n };\r\n}\r\n\r\nexport function getProjects(): ProjectConfig[] {\r\n return getConfig().projects;\r\n}\r\n\r\nexport function getProject(projectId: string): ProjectConfig | undefined {\r\n return getProjects().find((project) => project.id === projectId);\r\n}\r\n\r\nexport async function addProject(input: { name: string; root: string }): Promise<ProjectConfig> {\r\n const root = resolve(input.root);\r\n await access(root);\r\n\r\n const now = Date.now();\r\n const project: ProjectConfig = {\r\n id: randomUUID(),\r\n name: input.name.trim(),\r\n root,\r\n createdAt: now,\r\n updatedAt: now,\r\n };\r\n\r\n const projects = getProjects();\r\n if (projects.some((item) => item.root.toLowerCase() === root.toLowerCase())) {\r\n throw new Error(\"该项目路径已经存在\");\r\n }\r\n\r\n store.set(\"projects\", [...projects, project]);\r\n return project;\r\n}\r\n\r\nexport function removeProject(projectId: string): boolean {\r\n const projects = getProjects();\r\n const nextProjects = projects.filter((project) => project.id !== projectId);\r\n store.set(\"projects\", nextProjects);\r\n return nextProjects.length !== projects.length;\r\n}\r\n\r\nexport function reorderProjects(projectIds: string[]): ProjectConfig[] {\r\n const projects = getProjects();\r\n const projectMap = new Map(projects.map((project) => [project.id, project]));\r\n const uniqueProjectIds = new Set(projectIds);\r\n\r\n if (uniqueProjectIds.size !== projects.length) {\r\n throw new Error(\"项目排序数据不完整\");\r\n }\r\n\r\n const nextProjects = projectIds.map((projectId) => {\r\n const project = projectMap.get(projectId);\r\n if (!project) {\r\n throw new Error(\"项目排序包含不存在的项目\");\r\n }\r\n return project;\r\n });\r\n\r\n store.set(\"projects\", nextProjects);\r\n return nextProjects;\r\n}\r\n\r\nexport function getSettings(): AppSettings {\r\n return getConfig().settings;\r\n}\r\n\r\nexport function updateSettings(settings: Partial<AppSettings>): AppSettings {\r\n const nextSettings = {\r\n ...getSettings(),\r\n ...settings,\r\n };\r\n store.set(\"settings\", nextSettings);\r\n return nextSettings;\r\n}\r\n\r\nexport function getConfigPath(): string {\r\n return store.path;\r\n}\r\n","import prompts from \"prompts\";\r\nimport { getConfigPath, getSettings, updateSettings } from \"../../config/store.js\";\r\n\r\nexport async function configCommand(): Promise<void> {\r\n const settings = getSettings();\r\n\r\n console.log(`当前配置文件: ${getConfigPath()}`);\r\n console.log(`当前 Git Bash 路径: ${settings.gitBashPath ?? \"自动检测\"}`);\r\n console.log(`当前 Web 端口: ${settings.port}`);\r\n\r\n const answers = await prompts([\r\n {\r\n type: \"text\",\r\n name: \"gitBashPath\",\r\n message: \"Git Bash 路径,留空表示自动检测\",\r\n initial: settings.gitBashPath ?? \"\",\r\n },\r\n {\r\n type: \"number\",\r\n name: \"port\",\r\n message: \"Web 端口\",\r\n initial: settings.port,\r\n min: 1,\r\n max: 65535,\r\n },\r\n ]);\r\n\r\n updateSettings({\r\n gitBashPath: answers.gitBashPath?.trim() || undefined,\r\n port: answers.port || settings.port,\r\n });\r\n\r\n console.log(\"配置已更新。\");\r\n}\r\n","import { getConfigPath, getProjects } from \"../../config/store.js\";\r\n\r\nexport function listCommand(): void {\r\n const projects = getProjects();\r\n\r\n if (projects.length === 0) {\r\n console.log(\"暂无项目配置,可运行 br add 新增。\");\r\n console.log(`配置文件: ${getConfigPath()}`);\r\n return;\r\n }\r\n\r\n for (const project of projects) {\r\n console.log(`${project.name}`);\r\n console.log(` id: ${project.id}`);\r\n console.log(` root: ${project.root}`);\r\n }\r\n\r\n console.log(`\\n配置文件: ${getConfigPath()}`);\r\n}\r\n","import prompts from \"prompts\";\r\nimport { getProjects, removeProject } from \"../../config/store.js\";\r\n\r\nexport async function removeCommand(): Promise<void> {\r\n const projects = getProjects();\r\n\r\n if (projects.length === 0) {\r\n console.log(\"暂无项目可删除。\");\r\n return;\r\n }\r\n\r\n const answer = await prompts({\r\n type: \"select\",\r\n name: \"projectId\",\r\n message: \"选择要删除的项目\",\r\n choices: projects.map((project) => ({\r\n title: project.name,\r\n description: project.root,\r\n value: project.id,\r\n })),\r\n });\r\n\r\n if (!answer.projectId) {\r\n return;\r\n }\r\n\r\n const removed = removeProject(answer.projectId);\r\n console.log(removed ? \"已删除项目。\" : \"未找到项目。\");\r\n}\r\n","import prompts from \"prompts\";\r\nimport { getProjects, getSettings } from \"../../config/store.js\";\r\nimport { readPackageScripts } from \"../../project/package-json.js\";\r\nimport { runScriptInGitBash } from \"../../runner/git-bash.js\";\r\n\r\nexport async function runCommand(): Promise<void> {\r\n const projects = getProjects();\r\n\r\n if (projects.length === 0) {\r\n console.log(\"暂无项目配置,可运行 br add 新增。\");\r\n return;\r\n }\r\n\r\n const projectAnswer = await prompts({\r\n type: \"select\",\r\n name: \"projectId\",\r\n message: \"选择项目\",\r\n choices: projects.map((project) => ({\r\n title: project.name,\r\n description: project.root,\r\n value: project.id,\r\n })),\r\n });\r\n\r\n const project = projects.find((item) => item.id === projectAnswer.projectId);\r\n if (!project) {\r\n return;\r\n }\r\n\r\n const scripts = await readPackageScripts(project.root);\r\n const scriptEntries = Object.entries(scripts);\r\n\r\n if (scriptEntries.length === 0) {\r\n console.log(\"该项目 package.json 中没有 scripts。\");\r\n return;\r\n }\r\n\r\n const scriptAnswer = await prompts({\r\n type: \"select\",\r\n name: \"script\",\r\n message: \"选择要执行的脚本\",\r\n choices: scriptEntries.map(([name, command]) => ({\r\n title: name,\r\n description: command,\r\n value: name,\r\n })),\r\n });\r\n\r\n if (!scriptAnswer.script) {\r\n return;\r\n }\r\n\r\n const result = runScriptInGitBash(project, scriptAnswer.script, getSettings());\r\n console.log(`已在 Git Bash 窗口执行: npm run ${scriptAnswer.script}`);\r\n if (result.pid) {\r\n console.log(`进程 PID: ${result.pid}`);\r\n }\r\n}\r\n","import { readFile } from \"node:fs/promises\";\r\nimport { join } from \"node:path\";\r\nimport type { PackageScripts } from \"../shared/types.js\";\r\n\r\ntype PackageJson = {\r\n scripts?: PackageScripts;\r\n};\r\n\r\nexport async function readPackageJson(projectRoot: string): Promise<PackageJson> {\r\n const packageJsonPath = join(projectRoot, \"package.json\");\r\n const content = await readFile(packageJsonPath, \"utf8\");\r\n\r\n try {\r\n return JSON.parse(content) as PackageJson;\r\n } catch {\r\n throw new Error(`package.json 解析失败: ${packageJsonPath}`);\r\n }\r\n}\r\n\r\nexport async function readPackageScripts(projectRoot: string): Promise<PackageScripts> {\r\n const packageJson = await readPackageJson(projectRoot);\r\n return packageJson.scripts ?? {};\r\n}\r\n","import { spawn } from \"node:child_process\";\r\nimport type { AppSettings, ProjectConfig } from \"../shared/types.js\";\r\nimport { findGitBashPath, findMinttyPath, shellSingleQuote, toGitBashPath } from \"./path.js\";\r\n\r\nexport type RunResult = {\r\n pid?: number;\r\n command: string;\r\n};\r\n\r\nexport function runScriptInGitBash(project: ProjectConfig, scriptName: string, settings: AppSettings): RunResult {\r\n const gitBashPath = findGitBashPath(settings.gitBashPath);\r\n\r\n if (!gitBashPath) {\r\n throw new Error(\"未找到 Git Bash,请先通过 br config 设置 git-bash.exe 路径\");\r\n }\r\n\r\n const command = [\r\n `echo Running ${shellSingleQuote(project.name)}: npm run ${shellSingleQuote(scriptName)}`,\r\n `npm run ${shellSingleQuote(scriptName)}`,\r\n \"echo\",\r\n \"echo Process exited. Press Ctrl+D or close this window.\",\r\n \"exec bash\",\r\n ].join(\"; \");\r\n\r\n const minttyPath = findMinttyPath(gitBashPath);\r\n const child = minttyPath\r\n ? spawn(minttyPath, [\"--dir\", toGitBashPath(project.root), \"/usr/bin/bash\", \"-lc\", command], {\r\n detached: true,\r\n stdio: \"ignore\",\r\n windowsHide: false,\r\n })\r\n : spawn(\"cmd.exe\", [\"/d\", \"/c\", \"start\", \"\", gitBashPath, `--cd=${toGitBashPath(project.root)}`, \"-c\", command], {\r\n detached: true,\r\n stdio: \"ignore\",\r\n windowsHide: false,\r\n });\r\n\r\n child.unref();\r\n\r\n return {\r\n pid: child.pid,\r\n command,\r\n };\r\n}\r\n","import { existsSync } from \"node:fs\";\r\nimport { platform } from \"node:os\";\r\nimport { dirname, join } from \"node:path\";\r\n\r\nconst defaultWindowsGitBashPaths = [\r\n \"C:\\\\Program Files\\\\Git\\\\git-bash.exe\",\r\n \"C:\\\\Program Files (x86)\\\\Git\\\\git-bash.exe\",\r\n];\r\n\r\nexport function findGitBashPath(configuredPath?: string): string | undefined {\r\n if (configuredPath && existsSync(configuredPath)) {\r\n return configuredPath;\r\n }\r\n\r\n if (platform() !== \"win32\") {\r\n return undefined;\r\n }\r\n\r\n return defaultWindowsGitBashPaths.find((item) => existsSync(item));\r\n}\r\n\r\nexport function toGitBashPath(inputPath: string): string {\r\n const normalized = inputPath.replaceAll(\"\\\\\", \"/\");\r\n const driveMatch = /^([a-zA-Z]):\\/(.*)$/.exec(normalized);\r\n\r\n if (!driveMatch) {\r\n return normalized;\r\n }\r\n\r\n const [, drive, rest] = driveMatch;\r\n return `/${drive.toLowerCase()}/${rest}`;\r\n}\r\n\r\nexport function shellSingleQuote(value: string): string {\r\n return `'${value.replaceAll(\"'\", \"'\\\\''\")}'`;\r\n}\r\n\r\nexport function findMinttyPath(gitBashPath: string): string | undefined {\r\n const minttyPath = join(dirname(gitBashPath), \"usr\", \"bin\", \"mintty.exe\");\r\n return existsSync(minttyPath) ? minttyPath : undefined;\r\n}\r\n","import fastify from \"fastify\";\r\nimport { readFile } from \"node:fs/promises\";\r\nimport { dirname, join } from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\nimport open from \"open\";\r\nimport { getSettings } from \"../config/store.js\";\r\nimport { registerApiRoutes } from \"./routes.js\";\r\n\r\nfunction getWebRoot(): string {\r\n return join(dirname(dirname(fileURLToPath(import.meta.url))), \"web\");\r\n}\r\n\r\nexport async function startServer(options: { openBrowser?: boolean } = {}): Promise<void> {\r\n const settings = getSettings();\r\n const app = fastify({\r\n logger: false,\r\n });\r\n const webRoot = getWebRoot();\r\n\r\n await registerApiRoutes(app, settings);\r\n\r\n app.get(\"/\", async (_request, reply) => {\r\n return reply.type(\"text/html\").send(await readFile(join(webRoot, \"index.html\"), \"utf8\"));\r\n });\r\n\r\n app.get(\"/main.js\", async (_request, reply) => {\r\n return reply.type(\"application/javascript\").send(await readFile(join(webRoot, \"main.js\"), \"utf8\"));\r\n });\r\n\r\n app.get(\"/style.css\", async (_request, reply) => {\r\n return reply.type(\"text/css\").send(await readFile(join(webRoot, \"style.css\"), \"utf8\"));\r\n });\r\n\r\n const url = await app.listen({\r\n host: \"127.0.0.1\",\r\n port: settings.port,\r\n });\r\n\r\n console.log(`Batch Run Web 已启动: ${url}`);\r\n\r\n if (options.openBrowser ?? true) {\r\n await open(url);\r\n }\r\n}\r\n","import type { FastifyInstance } from \"fastify\";\r\nimport open from \"open\";\r\nimport { addProject, getProject, getProjects, removeProject, reorderProjects } from \"../config/store.js\";\r\nimport { readPackageScripts } from \"../project/package-json.js\";\r\nimport { runScriptInGitBash } from \"../runner/git-bash.js\";\r\nimport type { AppSettings } from \"../shared/types.js\";\r\n\r\ntype AddProjectBody = {\r\n name?: string;\r\n root?: string;\r\n};\r\n\r\ntype RunScriptBody = {\r\n script?: string;\r\n};\r\n\r\ntype ReorderProjectsBody = {\r\n projectIds?: string[];\r\n};\r\n\r\nexport async function registerApiRoutes(app: FastifyInstance, settings: AppSettings): Promise<void> {\r\n app.get(\"/api/projects\", async () => ({\r\n projects: getProjects(),\r\n }));\r\n\r\n app.post<{ Body: AddProjectBody }>(\"/api/projects\", async (request, reply) => {\r\n const name = request.body.name?.trim();\r\n const root = request.body.root?.trim();\r\n\r\n if (!name || !root) {\r\n return reply.code(400).send({ message: \"项目名称和根路径不能为空\" });\r\n }\r\n\r\n const project = await addProject({ name, root });\r\n return { project };\r\n });\r\n\r\n app.put<{ Body: ReorderProjectsBody }>(\"/api/projects/order\", async (request, reply) => {\r\n const projectIds = request.body.projectIds;\r\n\r\n if (!Array.isArray(projectIds) || !projectIds.every((projectId) => typeof projectId === \"string\")) {\r\n return reply.code(400).send({ message: \"项目排序数据无效\" });\r\n }\r\n\r\n try {\r\n return { projects: reorderProjects(projectIds) };\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : \"保存项目排序失败\";\r\n return reply.code(400).send({ message });\r\n }\r\n });\r\n\r\n app.delete<{ Params: { id: string } }>(\"/api/projects/:id\", async (request, reply) => {\r\n const removed = removeProject(request.params.id);\r\n\r\n if (!removed) {\r\n return reply.code(404).send({ message: \"项目不存在\" });\r\n }\r\n\r\n return { ok: true };\r\n });\r\n\r\n app.get<{ Params: { id: string } }>(\"/api/projects/:id/scripts\", async (request, reply) => {\r\n const project = getProject(request.params.id);\r\n\r\n if (!project) {\r\n return reply.code(404).send({ message: \"项目不存在\" });\r\n }\r\n\r\n const scripts = await readPackageScripts(project.root);\r\n return { scripts };\r\n });\r\n\r\n app.post<{ Params: { id: string } }>(\"/api/projects/:id/open\", async (request, reply) => {\r\n const project = getProject(request.params.id);\r\n\r\n if (!project) {\r\n return reply.code(404).send({ message: \"项目不存在\" });\r\n }\r\n\r\n await open(project.root);\r\n return { ok: true };\r\n });\r\n\r\n app.post<{ Params: { id: string }; Body: RunScriptBody }>(\"/api/projects/:id/run\", async (request, reply) => {\r\n const project = getProject(request.params.id);\r\n const script = request.body.script?.trim();\r\n\r\n if (!project) {\r\n return reply.code(404).send({ message: \"项目不存在\" });\r\n }\r\n\r\n if (!script) {\r\n return reply.code(400).send({ message: \"脚本名称不能为空\" });\r\n }\r\n\r\n const scripts = await readPackageScripts(project.root);\r\n if (!scripts[script]) {\r\n return reply.code(404).send({ message: \"脚本不存在\" });\r\n }\r\n\r\n const result = runScriptInGitBash(project, script, settings);\r\n return {\r\n ok: true,\r\n pid: result.pid,\r\n };\r\n });\r\n}\r\n","import { startServer } from \"../../server/index.js\";\r\n\r\nexport async function uiCommand(): Promise<void> {\r\n await startServer({ openBrowser: true });\r\n}\r\n"],"mappings":"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","names":["prompts","prompts","prompts","prompts","prompts","prompts","packageJson","join","prompts","project","readFile","dirname","join","open","join","dirname","readFile","open","prompts"]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/add.ts","../../src/config/store.ts","../../src/cli/commands/config.ts","../../src/cli/commands/list.ts","../../src/cli/commands/remove.ts","../../src/cli/commands/run.ts","../../src/project/package-json.ts","../../src/runner/git-bash.ts","../../src/runner/path.ts","../../src/server/index.ts","../../src/server/routes.ts","../../src/cli/commands/ui.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport { cac } from 'cac'\r\nimport { readFileSync } from 'node:fs'\r\nimport prompts from 'prompts'\r\nimport { addCommand } from './commands/add.js'\r\nimport { configCommand } from './commands/config.js'\r\nimport { listCommand } from './commands/list.js'\r\nimport { removeCommand } from './commands/remove.js'\r\nimport { runCommand } from './commands/run.js'\r\nimport { uiCommand } from './commands/ui.js'\r\n\r\nconst packageJson = JSON.parse(\r\n readFileSync(new URL('../../package.json', import.meta.url), 'utf8'),\r\n) as { version?: string }\r\nconst packageVersion = packageJson.version ?? '0.0.0'\r\n\r\nconst cli = cac('br')\r\n\r\ncli.command('add', '新增项目配置').action(wrap(addCommand))\r\n\r\ncli.command('list', '查看项目配置').action(wrap(async () => listCommand()))\r\n\r\ncli.command('run', '选择并执行项目脚本').action(wrap(runCommand))\r\n\r\ncli.command('remove', '删除项目配置').action(wrap(removeCommand))\r\n\r\ncli.command('ui', '打开 Web 管理页面').action(wrap(uiCommand))\r\n\r\ncli.command('config', '配置 Git Bash 路径和 Web 端口').action(wrap(configCommand))\r\n\r\ncli.help()\r\ncli.version(packageVersion)\r\n\r\nasync function mainMenu(): Promise<void> {\r\n const answer = await prompts({\r\n type: 'select',\r\n name: 'action',\r\n message: '请选择操作',\r\n choices: [\r\n { title: '打开可视化操作页面', value: 'ui' },\r\n { title: '启动项目', value: 'run' },\r\n { title: '查看项目', value: 'list' },\r\n { title: '新增项目', value: 'add' },\r\n { title: '删除项目', value: 'remove' },\r\n { title: '设置 Git Bash 路径', value: 'config' },\r\n ],\r\n })\r\n\r\n switch (answer.action) {\r\n case 'run':\r\n await runCommand()\r\n break\r\n case 'add':\r\n await addCommand()\r\n break\r\n case 'list':\r\n listCommand()\r\n break\r\n case 'remove':\r\n await removeCommand()\r\n break\r\n case 'ui':\r\n await uiCommand()\r\n break\r\n case 'config':\r\n await configCommand()\r\n break\r\n }\r\n}\r\n\r\nfunction wrap(fn: () => Promise<void>) {\r\n return async () => {\r\n try {\r\n await fn()\r\n } catch (error) {\r\n handleError(error)\r\n }\r\n }\r\n}\r\n\r\nfunction handleError(error: unknown): void {\r\n const message = error instanceof Error ? error.message : String(error)\r\n console.error(`执行失败: ${message}`)\r\n process.exitCode = 1\r\n}\r\n\r\nif (process.argv.length <= 2) {\r\n await wrap(mainMenu)()\r\n} else {\r\n cli.parse()\r\n}\r\n","import prompts from \"prompts\";\r\nimport { addProject } from \"../../config/store.js\";\r\n\r\nexport async function addCommand(): Promise<void> {\r\n const answers = await prompts([\r\n {\r\n type: \"text\",\r\n name: \"name\",\r\n message: \"项目名称\",\r\n validate: (value) => value.trim().length > 0 || \"请输入项目名称\",\r\n },\r\n {\r\n type: \"text\",\r\n name: \"root\",\r\n message: \"项目根路径\",\r\n validate: (value) => value.trim().length > 0 || \"请输入项目根路径\",\r\n },\r\n ]);\r\n\r\n if (!answers.name || !answers.root) {\r\n return;\r\n }\r\n\r\n const project = await addProject({\r\n name: answers.name,\r\n root: answers.root,\r\n });\r\n\r\n console.log(`已添加项目: ${project.name}`);\r\n console.log(project.root);\r\n}\r\n","import Conf from \"conf\";\r\nimport { randomUUID } from \"node:crypto\";\r\nimport { access } from \"node:fs/promises\";\r\nimport { resolve } from \"node:path\";\r\nimport type { AppConfig, AppSettings, ProjectConfig } from \"../shared/types.js\";\r\n\r\nconst defaultSettings: AppSettings = {\r\n shell: \"git-bash\",\r\n port: 1234,\r\n};\r\n\r\nconst store = new Conf<AppConfig>({\r\n projectName: \"batch-run-local\",\r\n defaults: {\r\n projects: [],\r\n settings: defaultSettings,\r\n },\r\n});\r\n\r\nexport function getConfig(): AppConfig {\r\n return {\r\n projects: store.get(\"projects\") ?? [],\r\n settings: {\r\n ...defaultSettings,\r\n ...(store.get(\"settings\") ?? {}),\r\n },\r\n };\r\n}\r\n\r\nexport function getProjects(): ProjectConfig[] {\r\n return getConfig().projects;\r\n}\r\n\r\nexport function getProject(projectId: string): ProjectConfig | undefined {\r\n return getProjects().find((project) => project.id === projectId);\r\n}\r\n\r\nexport async function addProject(input: { name: string; root: string }): Promise<ProjectConfig> {\r\n const root = resolve(input.root);\r\n await access(root);\r\n\r\n const now = Date.now();\r\n const project: ProjectConfig = {\r\n id: randomUUID(),\r\n name: input.name.trim(),\r\n root,\r\n createdAt: now,\r\n updatedAt: now,\r\n };\r\n\r\n const projects = getProjects();\r\n if (projects.some((item) => item.root.toLowerCase() === root.toLowerCase())) {\r\n throw new Error(\"该项目路径已经存在\");\r\n }\r\n\r\n store.set(\"projects\", [...projects, project]);\r\n return project;\r\n}\r\n\r\nexport async function updateProject(\r\n projectId: string,\r\n input: { name: string; root: string },\r\n): Promise<ProjectConfig | undefined> {\r\n const root = resolve(input.root);\r\n await access(root);\r\n\r\n const projects = getProjects();\r\n const projectIndex = projects.findIndex((project) => project.id === projectId);\r\n if (projectIndex < 0) {\r\n return undefined;\r\n }\r\n\r\n if (projects.some((project) => project.id !== projectId && project.root.toLowerCase() === root.toLowerCase())) {\r\n throw new Error(\"该项目路径已经存在\");\r\n }\r\n\r\n const nextProject: ProjectConfig = {\r\n ...projects[projectIndex],\r\n name: input.name.trim(),\r\n root,\r\n updatedAt: Date.now(),\r\n };\r\n const nextProjects = [...projects];\r\n nextProjects[projectIndex] = nextProject;\r\n store.set(\"projects\", nextProjects);\r\n return nextProject;\r\n}\r\n\r\nexport function removeProject(projectId: string): boolean {\r\n const projects = getProjects();\r\n const nextProjects = projects.filter((project) => project.id !== projectId);\r\n store.set(\"projects\", nextProjects);\r\n return nextProjects.length !== projects.length;\r\n}\r\n\r\nexport function reorderProjects(projectIds: string[]): ProjectConfig[] {\r\n const projects = getProjects();\r\n const projectMap = new Map(projects.map((project) => [project.id, project]));\r\n const uniqueProjectIds = new Set(projectIds);\r\n\r\n if (uniqueProjectIds.size !== projects.length) {\r\n throw new Error(\"项目排序数据不完整\");\r\n }\r\n\r\n const nextProjects = projectIds.map((projectId) => {\r\n const project = projectMap.get(projectId);\r\n if (!project) {\r\n throw new Error(\"项目排序包含不存在的项目\");\r\n }\r\n return project;\r\n });\r\n\r\n store.set(\"projects\", nextProjects);\r\n return nextProjects;\r\n}\r\n\r\nexport function getSettings(): AppSettings {\r\n return getConfig().settings;\r\n}\r\n\r\nexport function updateSettings(settings: Partial<AppSettings>): AppSettings {\r\n const nextSettings = {\r\n ...getSettings(),\r\n ...settings,\r\n };\r\n store.set(\"settings\", nextSettings);\r\n return nextSettings;\r\n}\r\n\r\nexport function getConfigPath(): string {\r\n return store.path;\r\n}\r\n","import prompts from \"prompts\";\r\nimport { getConfigPath, getSettings, updateSettings } from \"../../config/store.js\";\r\n\r\nexport async function configCommand(): Promise<void> {\r\n const settings = getSettings();\r\n\r\n console.log(`当前配置文件: ${getConfigPath()}`);\r\n console.log(`当前 Git Bash 路径: ${settings.gitBashPath ?? \"自动检测\"}`);\r\n console.log(`当前 Web 端口: ${settings.port}`);\r\n\r\n const answers = await prompts([\r\n {\r\n type: \"text\",\r\n name: \"gitBashPath\",\r\n message: \"Git Bash 路径,留空表示自动检测\",\r\n initial: settings.gitBashPath ?? \"\",\r\n },\r\n {\r\n type: \"number\",\r\n name: \"port\",\r\n message: \"Web 端口\",\r\n initial: settings.port,\r\n min: 1,\r\n max: 65535,\r\n },\r\n ]);\r\n\r\n updateSettings({\r\n gitBashPath: answers.gitBashPath?.trim() || undefined,\r\n port: answers.port || settings.port,\r\n });\r\n\r\n console.log(\"配置已更新。\");\r\n}\r\n","import { getConfigPath, getProjects } from \"../../config/store.js\";\r\n\r\nexport function listCommand(): void {\r\n const projects = getProjects();\r\n\r\n if (projects.length === 0) {\r\n console.log(\"暂无项目配置,可运行 br add 新增。\");\r\n console.log(`配置文件: ${getConfigPath()}`);\r\n return;\r\n }\r\n\r\n for (const project of projects) {\r\n console.log(`${project.name}`);\r\n console.log(` id: ${project.id}`);\r\n console.log(` root: ${project.root}`);\r\n }\r\n\r\n console.log(`\\n配置文件: ${getConfigPath()}`);\r\n}\r\n","import prompts from \"prompts\";\r\nimport { getProjects, removeProject } from \"../../config/store.js\";\r\n\r\nexport async function removeCommand(): Promise<void> {\r\n const projects = getProjects();\r\n\r\n if (projects.length === 0) {\r\n console.log(\"暂无项目可删除。\");\r\n return;\r\n }\r\n\r\n const answer = await prompts({\r\n type: \"select\",\r\n name: \"projectId\",\r\n message: \"选择要删除的项目\",\r\n choices: projects.map((project) => ({\r\n title: project.name,\r\n description: project.root,\r\n value: project.id,\r\n })),\r\n });\r\n\r\n if (!answer.projectId) {\r\n return;\r\n }\r\n\r\n const removed = removeProject(answer.projectId);\r\n console.log(removed ? \"已删除项目。\" : \"未找到项目。\");\r\n}\r\n","import prompts from \"prompts\";\r\nimport { getProjects, getSettings } from \"../../config/store.js\";\r\nimport { readPackageScripts } from \"../../project/package-json.js\";\r\nimport { runScriptInGitBash } from \"../../runner/git-bash.js\";\r\n\r\nexport async function runCommand(): Promise<void> {\r\n const projects = getProjects();\r\n\r\n if (projects.length === 0) {\r\n console.log(\"暂无项目配置,可运行 br add 新增。\");\r\n return;\r\n }\r\n\r\n const projectAnswer = await prompts({\r\n type: \"select\",\r\n name: \"projectId\",\r\n message: \"选择项目\",\r\n choices: projects.map((project) => ({\r\n title: project.name,\r\n description: project.root,\r\n value: project.id,\r\n })),\r\n });\r\n\r\n const project = projects.find((item) => item.id === projectAnswer.projectId);\r\n if (!project) {\r\n return;\r\n }\r\n\r\n const scripts = await readPackageScripts(project.root);\r\n const scriptEntries = Object.entries(scripts);\r\n\r\n if (scriptEntries.length === 0) {\r\n console.log(\"该项目 package.json 中没有 scripts。\");\r\n return;\r\n }\r\n\r\n const scriptAnswer = await prompts({\r\n type: \"select\",\r\n name: \"script\",\r\n message: \"选择要执行的脚本\",\r\n choices: scriptEntries.map(([name, command]) => ({\r\n title: name,\r\n description: command,\r\n value: name,\r\n })),\r\n });\r\n\r\n if (!scriptAnswer.script) {\r\n return;\r\n }\r\n\r\n const result = runScriptInGitBash(project, scriptAnswer.script, getSettings());\r\n console.log(`已在 Git Bash 窗口执行: npm run ${scriptAnswer.script}`);\r\n if (result.pid) {\r\n console.log(`进程 PID: ${result.pid}`);\r\n }\r\n}\r\n","import { readFile } from \"node:fs/promises\";\r\nimport { join } from \"node:path\";\r\nimport type { PackageScripts } from \"../shared/types.js\";\r\n\r\ntype PackageJson = {\r\n scripts?: PackageScripts;\r\n};\r\n\r\nexport async function readPackageJson(projectRoot: string): Promise<PackageJson> {\r\n const packageJsonPath = join(projectRoot, \"package.json\");\r\n const content = await readFile(packageJsonPath, \"utf8\");\r\n\r\n try {\r\n return JSON.parse(content) as PackageJson;\r\n } catch {\r\n throw new Error(`package.json 解析失败: ${packageJsonPath}`);\r\n }\r\n}\r\n\r\nexport async function readPackageScripts(projectRoot: string): Promise<PackageScripts> {\r\n const packageJson = await readPackageJson(projectRoot);\r\n return packageJson.scripts ?? {};\r\n}\r\n","import { spawn } from \"node:child_process\";\nimport { mkdtempSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AppSettings, ProjectConfig } from \"../shared/types.js\";\nimport { findBashExecutablePath, findGitBashPath, findMinttyPath, shellSingleQuote, toGitBashPath } from \"./path.js\";\n\n/** Strip Git repo overrides so child processes (e.g. Vite calling `git`) use cwd discovery, not a stale inherited GIT_DIR. */\nconst GIT_ENV_KEYS = [\n \"GIT_DIR\",\n \"GIT_WORK_TREE\",\n \"GIT_INDEX_FILE\",\n \"GIT_PREFIX\",\n \"GIT_OBJECT_DIRECTORY\",\n \"GIT_ALTERNATE_OBJECT_DIRECTORIES\",\n] as const;\n\nfunction processEnvWithoutGitOverrides(): NodeJS.ProcessEnv {\n const env = { ...process.env };\n for (const key of GIT_ENV_KEYS) {\n delete env[key];\n }\n return env;\n}\n\nexport type RunResult = {\n pid?: number;\n command: string;\n};\n\nexport function runScriptInGitBash(project: ProjectConfig, scriptName: string, settings: AppSettings): RunResult {\n const gitBashPath = findGitBashPath(settings.gitBashPath);\n\n if (!gitBashPath) {\n throw new Error(\"未找到 Git Bash,请先通过 br config 设置 git-bash.exe 路径\");\n }\n\n const title = createTerminalTitle(project.name, scriptName);\n const gitBashRoot = toGitBashPath(project.root);\n const scriptPath = writeRunScript(project, scriptName, title, gitBashRoot);\n const gitBashScriptPath = toGitBashPath(scriptPath);\n const bashPath = findBashExecutablePath(gitBashPath);\n const command = `${bashPath} --login ${scriptPath}`;\n\n const minttyPath = findMinttyPath(gitBashPath);\n const child = minttyPath\n ? spawn(minttyPath, [\"--title\", title, \"--dir\", gitBashRoot, toGitBashPath(bashPath), \"--login\", gitBashScriptPath], {\n detached: true,\n stdio: \"ignore\",\n windowsHide: false,\n env: processEnvWithoutGitOverrides(),\n })\n : spawn(\"cmd.exe\", [\"/d\", \"/c\", \"start\", title, \"/D\", project.root, bashPath, \"--login\", gitBashScriptPath], {\n detached: true,\n stdio: \"ignore\",\n windowsHide: false,\n env: processEnvWithoutGitOverrides(),\n });\n\n child.unref();\n\n return {\n pid: child.pid,\n command,\n };\n}\n\nfunction createTerminalTitle(projectName: string, scriptName: string): string {\n const title = `batch-run - ${projectName} - npm run ${scriptName}`;\n return title.replace(/[\\u0000-\\u001f\\u007f]/g, \" \").slice(0, 120);\n}\n\nfunction writeRunScript(project: ProjectConfig, scriptName: string, title: string, gitBashRoot: string): string {\n const tempDir = mkdtempSync(join(tmpdir(), \"batch-run-\"));\n const scriptPath = join(tempDir, \"run.sh\");\n const scriptContent = [\n \"#!/usr/bin/env bash\",\n \"set +e\",\n `cd ${shellSingleQuote(gitBashRoot)}`,\n 'rm -f \"$0\" >/dev/null 2>&1 || true',\n `unset ${GIT_ENV_KEYS.join(\" \")}`,\n `__batch_run_title=${shellSingleQuote(title)}`,\n \"set_batch_run_title() { printf '\\\\033]0;%s\\\\007' \\\"$__batch_run_title\\\"; }\",\n \"set_batch_run_title\",\n \"(while true; do set_batch_run_title; sleep 2; done) &\",\n \"__batch_run_title_pid=$!\",\n \"cleanup_batch_run_title() {\",\n ' kill \"$__batch_run_title_pid\" >/dev/null 2>&1 || true',\n ' wait \"$__batch_run_title_pid\" 2>/dev/null || true',\n \" set_batch_run_title\",\n \"}\",\n \"trap cleanup_batch_run_title EXIT\",\n `echo Running ${shellSingleQuote(project.name)}: npm run ${shellSingleQuote(scriptName)}`,\n `npm run ${shellSingleQuote(scriptName)}`,\n \"__batch_run_exit_code=$?\",\n \"set_batch_run_title\",\n \"echo\",\n 'echo \"Process exited with code $__batch_run_exit_code. Press Ctrl+D or close this window.\"',\n \"bash --login\",\n 'exit \"$__batch_run_exit_code\"',\n \"\",\n ].join(\"\\n\");\n\n writeFileSync(scriptPath, scriptContent, \"utf8\");\n return scriptPath;\n}\n","import { existsSync } from \"node:fs\";\r\nimport { platform } from \"node:os\";\r\nimport { basename, dirname, join } from \"node:path\";\r\n\r\nconst defaultWindowsGitBashPaths = [\r\n \"C:\\\\Program Files\\\\Git\\\\git-bash.exe\",\r\n \"C:\\\\Program Files (x86)\\\\Git\\\\git-bash.exe\",\r\n];\r\n\r\nexport function findGitBashPath(configuredPath?: string): string | undefined {\r\n if (configuredPath && existsSync(configuredPath) && isGitBashExecutable(configuredPath)) {\r\n return configuredPath;\r\n }\r\n\r\n if (platform() !== \"win32\") {\r\n return undefined;\r\n }\r\n\r\n return defaultWindowsGitBashPaths.find((item) => existsSync(item));\r\n}\r\n\r\nexport function toGitBashPath(inputPath: string): string {\r\n const normalized = inputPath.replaceAll(\"\\\\\", \"/\");\r\n const driveMatch = /^([a-zA-Z]):\\/(.*)$/.exec(normalized);\r\n\r\n if (!driveMatch) {\r\n return normalized;\r\n }\r\n\r\n const [, drive, rest] = driveMatch;\r\n return `/${drive.toLowerCase()}/${rest}`;\r\n}\r\n\r\nexport function shellSingleQuote(value: string): string {\r\n return `'${value.replaceAll(\"'\", \"'\\\\''\")}'`;\r\n}\r\n\r\nexport function findMinttyPath(gitBashPath: string): string | undefined {\r\n const gitBashDir = dirname(gitBashPath);\r\n const candidates = [\r\n join(gitBashDir, \"usr\", \"bin\", \"mintty.exe\"),\r\n join(gitBashDir, \"mintty.exe\"),\r\n join(dirname(gitBashDir), \"usr\", \"bin\", \"mintty.exe\"),\r\n ];\r\n\r\n return candidates.find((minttyPath) => existsSync(minttyPath));\r\n}\r\n\r\nexport function findBashExecutablePath(gitBashPath: string): string {\r\n const executableName = basename(gitBashPath).toLowerCase();\r\n if (executableName === \"bash.exe\" || executableName === \"sh.exe\") {\r\n return gitBashPath;\r\n }\r\n\r\n const gitBashDir = dirname(gitBashPath);\r\n const candidates = [\r\n join(gitBashDir, \"bin\", \"bash.exe\"),\r\n join(gitBashDir, \"usr\", \"bin\", \"bash.exe\"),\r\n join(gitBashDir, \"bin\", \"sh.exe\"),\r\n join(gitBashDir, \"usr\", \"bin\", \"sh.exe\"),\r\n ];\r\n\r\n return candidates.find((bashPath) => existsSync(bashPath)) ?? gitBashPath;\r\n}\r\n\r\nfunction isGitBashExecutable(inputPath: string): boolean {\r\n const executableName = basename(inputPath).toLowerCase();\r\n return executableName === \"git-bash.exe\" || executableName === \"bash.exe\" || executableName === \"sh.exe\";\r\n}\r\n","import fastify from \"fastify\";\r\nimport { readFile } from \"node:fs/promises\";\r\nimport { dirname, join } from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\nimport open from \"open\";\r\nimport { getSettings } from \"../config/store.js\";\r\nimport { registerApiRoutes } from \"./routes.js\";\r\n\r\nfunction getWebRoot(): string {\r\n return join(dirname(dirname(fileURLToPath(import.meta.url))), \"web\");\r\n}\r\n\r\nexport async function startServer(options: { openBrowser?: boolean } = {}): Promise<void> {\r\n const settings = getSettings();\r\n const app = fastify({\r\n logger: false,\r\n });\r\n const webRoot = getWebRoot();\r\n\r\n await registerApiRoutes(app, settings);\r\n\r\n app.get(\"/\", async (_request, reply) => {\r\n return reply.type(\"text/html\").send(await readFile(join(webRoot, \"index.html\"), \"utf8\"));\r\n });\r\n\r\n app.get(\"/main.js\", async (_request, reply) => {\r\n return reply.type(\"application/javascript\").send(await readFile(join(webRoot, \"main.js\"), \"utf8\"));\r\n });\r\n\r\n app.get(\"/style.css\", async (_request, reply) => {\r\n return reply.type(\"text/css\").send(await readFile(join(webRoot, \"style.css\"), \"utf8\"));\r\n });\r\n\r\n app.get(\"/favicon.ico\", async (_request, reply) => {\r\n return reply.type(\"image/x-icon\").send(await readFile(join(webRoot, \"favicon.ico\")));\r\n });\r\n\r\n const url = await app.listen({\r\n host: \"127.0.0.1\",\r\n port: settings.port,\r\n });\r\n\r\n console.log(`Batch Run Web 已启动: ${url}`);\r\n\r\n if (options.openBrowser ?? true) {\r\n await open(url);\r\n }\r\n}\r\n","import type { FastifyInstance } from \"fastify\";\r\nimport open from \"open\";\r\nimport { addProject, getProject, getProjects, removeProject, reorderProjects, updateProject } from \"../config/store.js\";\r\nimport { readPackageScripts } from \"../project/package-json.js\";\r\nimport { runScriptInGitBash } from \"../runner/git-bash.js\";\r\nimport type { AppSettings } from \"../shared/types.js\";\r\n\r\ntype AddProjectBody = {\r\n name?: string;\r\n root?: string;\r\n};\r\n\r\ntype UpdateProjectBody = AddProjectBody;\r\n\r\ntype RunScriptBody = {\r\n script?: string;\r\n};\r\n\r\ntype ReorderProjectsBody = {\r\n projectIds?: string[];\r\n};\r\n\r\nexport async function registerApiRoutes(app: FastifyInstance, settings: AppSettings): Promise<void> {\r\n app.get(\"/api/projects\", async () => ({\r\n projects: getProjects(),\r\n }));\r\n\r\n app.post<{ Body: AddProjectBody }>(\"/api/projects\", async (request, reply) => {\r\n const name = request.body.name?.trim();\r\n const root = request.body.root?.trim();\r\n\r\n if (!name || !root) {\r\n return reply.code(400).send({ message: \"项目名称和根路径不能为空\" });\r\n }\r\n\r\n const project = await addProject({ name, root });\r\n return { project };\r\n });\r\n\r\n app.put<{ Body: ReorderProjectsBody }>(\"/api/projects/order\", async (request, reply) => {\r\n const projectIds = request.body.projectIds;\r\n\r\n if (!Array.isArray(projectIds) || !projectIds.every((projectId) => typeof projectId === \"string\")) {\r\n return reply.code(400).send({ message: \"项目排序数据无效\" });\r\n }\r\n\r\n try {\r\n return { projects: reorderProjects(projectIds) };\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : \"保存项目排序失败\";\r\n return reply.code(400).send({ message });\r\n }\r\n });\r\n\r\n app.put<{ Params: { id: string }; Body: UpdateProjectBody }>(\"/api/projects/:id\", async (request, reply) => {\r\n const name = request.body.name?.trim();\r\n const root = request.body.root?.trim();\r\n\r\n if (!name || !root) {\r\n return reply.code(400).send({ message: \"项目名称和根路径不能为空\" });\r\n }\r\n\r\n try {\r\n const project = await updateProject(request.params.id, { name, root });\r\n if (!project) {\r\n return reply.code(404).send({ message: \"项目不存在\" });\r\n }\r\n\r\n return { project };\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : \"更新项目失败\";\r\n return reply.code(400).send({ message });\r\n }\r\n });\r\n\r\n app.delete<{ Params: { id: string } }>(\"/api/projects/:id\", async (request, reply) => {\r\n const removed = removeProject(request.params.id);\r\n\r\n if (!removed) {\r\n return reply.code(404).send({ message: \"项目不存在\" });\r\n }\r\n\r\n return { ok: true };\r\n });\r\n\r\n app.get<{ Params: { id: string } }>(\"/api/projects/:id/scripts\", async (request, reply) => {\r\n const project = getProject(request.params.id);\r\n\r\n if (!project) {\r\n return reply.code(404).send({ message: \"项目不存在\" });\r\n }\r\n\r\n const scripts = await readPackageScripts(project.root);\r\n return { scripts };\r\n });\r\n\r\n app.post<{ Params: { id: string } }>(\"/api/projects/:id/open\", async (request, reply) => {\r\n const project = getProject(request.params.id);\r\n\r\n if (!project) {\r\n return reply.code(404).send({ message: \"项目不存在\" });\r\n }\r\n\r\n await open(project.root);\r\n return { ok: true };\r\n });\r\n\r\n app.post<{ Params: { id: string }; Body: RunScriptBody }>(\"/api/projects/:id/run\", async (request, reply) => {\r\n const project = getProject(request.params.id);\r\n const script = request.body.script?.trim();\r\n\r\n if (!project) {\r\n return reply.code(404).send({ message: \"项目不存在\" });\r\n }\r\n\r\n if (!script) {\r\n return reply.code(400).send({ message: \"脚本名称不能为空\" });\r\n }\r\n\r\n const scripts = await readPackageScripts(project.root);\r\n if (!scripts[script]) {\r\n return reply.code(404).send({ message: \"脚本不存在\" });\r\n }\r\n\r\n const result = runScriptInGitBash(project, script, settings);\r\n return {\r\n ok: true,\r\n pid: result.pid,\r\n };\r\n });\r\n}\r\n","import { startServer } from \"../../server/index.js\";\r\n\r\nexport async function uiCommand(): Promise<void> {\r\n await startServer({ openBrowser: true });\r\n}\r\n"],"mappings":"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","names":["prompts","prompts","prompts","prompts","prompts","prompts","packageJson","join","join","join","prompts","project","readFile","dirname","join","open","join","dirname","readFile","open","prompts"]}
|
|
Binary file
|
package/dist/web/index.html
CHANGED
|
@@ -1,52 +1,70 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>Batch Run</title>
|
|
7
|
-
<link rel="stylesheet" href="/style.css" />
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<div class="
|
|
38
|
-
<div>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
</
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<meta>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Batch Run</title>
|
|
7
|
+
<link rel="stylesheet" href="/style.css" />
|
|
8
|
+
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
|
9
|
+
</meta>
|
|
10
|
+
<body>
|
|
11
|
+
<main class="app">
|
|
12
|
+
<section class="panel sidebar">
|
|
13
|
+
<div class="panel-header">
|
|
14
|
+
<div>
|
|
15
|
+
<p class="eyebrow">Batch Run</p>
|
|
16
|
+
<h2>项目列表</h2>
|
|
17
|
+
<p id="projectCount" class="project-count">共 0 个项目</p>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="header-actions">
|
|
20
|
+
<button id="openProjectConfig" type="button" class="secondary">新增项目</button>
|
|
21
|
+
<button id="refreshProjects" type="button">刷新</button>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div id="projectList" class="project-list"></div>
|
|
26
|
+
</section>
|
|
27
|
+
|
|
28
|
+
<section class="panel content">
|
|
29
|
+
<div class="panel-header">
|
|
30
|
+
<div>
|
|
31
|
+
<p class="eyebrow">package.json</p>
|
|
32
|
+
<h2 id="currentProjectTitle">请选择项目</h2>
|
|
33
|
+
</div>
|
|
34
|
+
<button id="openProjectRoot" type="button" disabled>打开项目目录</button>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="content-body">
|
|
38
|
+
<div id="scriptList" class="script-list empty">选择左侧项目后读取 scripts</div>
|
|
39
|
+
<pre id="status" class="status"></pre>
|
|
40
|
+
</div>
|
|
41
|
+
</section>
|
|
42
|
+
</main>
|
|
43
|
+
|
|
44
|
+
<div id="projectConfigModal" class="modal-backdrop" hidden>
|
|
45
|
+
<section class="modal" role="dialog" aria-modal="true" aria-labelledby="projectConfigTitle">
|
|
46
|
+
<div class="panel-header">
|
|
47
|
+
<div>
|
|
48
|
+
<p class="eyebrow">Project Settings</p>
|
|
49
|
+
<h2 id="projectConfigTitle">项目配置</h2>
|
|
50
|
+
</div>
|
|
51
|
+
<button id="closeProjectConfig" type="button">关闭</button>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<form id="projectForm" class="form">
|
|
55
|
+
<label>
|
|
56
|
+
项目名称
|
|
57
|
+
<input id="projectName" name="name" placeholder="例如:后台服务" required />
|
|
58
|
+
</label>
|
|
59
|
+
<label>
|
|
60
|
+
项目根路径
|
|
61
|
+
<input id="projectRoot" name="root" placeholder="C:\wang\project\server" required />
|
|
62
|
+
</label>
|
|
63
|
+
<button id="projectSubmit" type="submit" class="secondary">新增项目</button>
|
|
64
|
+
</form>
|
|
65
|
+
</section>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<script type="module" src="/main.js"></script>
|
|
69
|
+
</body>
|
|
70
|
+
</html>
|
package/dist/web/main.js
CHANGED
|
@@ -3,6 +3,7 @@ const state = {
|
|
|
3
3
|
currentProjectId: undefined,
|
|
4
4
|
draggedProjectId: undefined,
|
|
5
5
|
suppressProjectClick: false,
|
|
6
|
+
editingProjectId: undefined,
|
|
6
7
|
};
|
|
7
8
|
|
|
8
9
|
const projectList = document.querySelector("#projectList");
|
|
@@ -13,6 +14,29 @@ const projectForm = document.querySelector("#projectForm");
|
|
|
13
14
|
const projectCount = document.querySelector("#projectCount");
|
|
14
15
|
const refreshProjects = document.querySelector("#refreshProjects");
|
|
15
16
|
const openProjectRoot = document.querySelector("#openProjectRoot");
|
|
17
|
+
const openProjectConfig = document.querySelector("#openProjectConfig");
|
|
18
|
+
const closeProjectConfig = document.querySelector("#closeProjectConfig");
|
|
19
|
+
const projectConfigModal = document.querySelector("#projectConfigModal");
|
|
20
|
+
const projectNameInput = document.querySelector("#projectName");
|
|
21
|
+
const projectRootInput = document.querySelector("#projectRoot");
|
|
22
|
+
const projectConfigTitle = document.querySelector("#projectConfigTitle");
|
|
23
|
+
const projectSubmit = document.querySelector("#projectSubmit");
|
|
24
|
+
|
|
25
|
+
openProjectConfig.addEventListener("click", openCreateProjectModal);
|
|
26
|
+
|
|
27
|
+
closeProjectConfig.addEventListener("click", closeConfigModal);
|
|
28
|
+
|
|
29
|
+
projectConfigModal.addEventListener("click", (event) => {
|
|
30
|
+
if (event.target === projectConfigModal) {
|
|
31
|
+
closeConfigModal();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
document.addEventListener("keydown", (event) => {
|
|
36
|
+
if (event.key === "Escape" && !projectConfigModal.hidden) {
|
|
37
|
+
closeConfigModal();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
16
40
|
|
|
17
41
|
projectForm.addEventListener("submit", async (event) => {
|
|
18
42
|
event.preventDefault();
|
|
@@ -20,9 +44,10 @@ projectForm.addEventListener("submit", async (event) => {
|
|
|
20
44
|
const formData = new FormData(projectForm);
|
|
21
45
|
const name = String(formData.get("name") ?? "");
|
|
22
46
|
const root = String(formData.get("root") ?? "");
|
|
47
|
+
const editingProjectId = state.editingProjectId;
|
|
23
48
|
|
|
24
|
-
await request("/api/projects", {
|
|
25
|
-
method: "POST",
|
|
49
|
+
await request(editingProjectId ? `/api/projects/${editingProjectId}` : "/api/projects", {
|
|
50
|
+
method: editingProjectId ? "PUT" : "POST",
|
|
26
51
|
headers: {
|
|
27
52
|
"Content-Type": "application/json",
|
|
28
53
|
},
|
|
@@ -30,7 +55,8 @@ projectForm.addEventListener("submit", async (event) => {
|
|
|
30
55
|
});
|
|
31
56
|
|
|
32
57
|
projectForm.reset();
|
|
33
|
-
|
|
58
|
+
closeConfigModal();
|
|
59
|
+
setStatus(editingProjectId ? `已更新项目:${name}` : `已新增项目:${name}`);
|
|
34
60
|
await loadProjects();
|
|
35
61
|
});
|
|
36
62
|
|
|
@@ -62,7 +88,7 @@ function renderProjects() {
|
|
|
62
88
|
projectCount.textContent = `共 ${state.projects.length} 个项目`;
|
|
63
89
|
|
|
64
90
|
if (state.projects.length === 0) {
|
|
65
|
-
projectList.innerHTML = `<div class="empty"
|
|
91
|
+
projectList.innerHTML = `<div class="empty">暂无项目,请点击“项目配置”进行配置。</div>`;
|
|
66
92
|
currentProjectTitle.textContent = "请选择项目";
|
|
67
93
|
openProjectRoot.disabled = true;
|
|
68
94
|
scriptList.textContent = "选择左侧项目后读取 scripts";
|
|
@@ -81,7 +107,10 @@ function renderProjects() {
|
|
|
81
107
|
<h3>${escapeHtml(project.name)}</h3>
|
|
82
108
|
<div class="path">${escapeHtml(project.root)}</div>
|
|
83
109
|
</div>
|
|
84
|
-
<
|
|
110
|
+
<div class="project-actions">
|
|
111
|
+
<button class="project-edit" type="button" aria-label="编辑 ${escapeHtml(project.name)}">编辑</button>
|
|
112
|
+
<button class="danger project-delete" type="button" aria-label="删除 ${escapeHtml(project.name)}">删除</button>
|
|
113
|
+
</div>
|
|
85
114
|
`;
|
|
86
115
|
|
|
87
116
|
card.addEventListener("click", () => {
|
|
@@ -130,6 +159,10 @@ function renderProjects() {
|
|
|
130
159
|
event.stopPropagation();
|
|
131
160
|
deleteProject(project);
|
|
132
161
|
});
|
|
162
|
+
card.querySelector(".project-edit").addEventListener("click", (event) => {
|
|
163
|
+
event.stopPropagation();
|
|
164
|
+
openEditProjectModal(project);
|
|
165
|
+
});
|
|
133
166
|
projectList.append(card);
|
|
134
167
|
}
|
|
135
168
|
}
|
|
@@ -165,7 +198,7 @@ function renderScripts(projectId, scripts) {
|
|
|
165
198
|
<h3>${escapeHtml(name)}</h3>
|
|
166
199
|
<div class="command">${escapeHtml(command)}</div>
|
|
167
200
|
</div>
|
|
168
|
-
<button type="button">执行</button>
|
|
201
|
+
<button type="button" class="secondary">执行</button>
|
|
169
202
|
`;
|
|
170
203
|
|
|
171
204
|
card.querySelector("button").addEventListener("click", () => runScript(projectId, name));
|
|
@@ -230,6 +263,30 @@ function shouldDropAfter(event, element) {
|
|
|
230
263
|
return event.clientY > rect.top + rect.height / 2;
|
|
231
264
|
}
|
|
232
265
|
|
|
266
|
+
function openCreateProjectModal() {
|
|
267
|
+
state.editingProjectId = undefined;
|
|
268
|
+
projectForm.reset();
|
|
269
|
+
projectConfigTitle.textContent = "项目配置";
|
|
270
|
+
projectSubmit.textContent = "新增项目";
|
|
271
|
+
projectConfigModal.hidden = false;
|
|
272
|
+
projectNameInput.focus();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function openEditProjectModal(project) {
|
|
276
|
+
state.editingProjectId = project.id;
|
|
277
|
+
projectConfigTitle.textContent = "编辑项目";
|
|
278
|
+
projectSubmit.textContent = "保存修改";
|
|
279
|
+
projectNameInput.value = project.name;
|
|
280
|
+
projectRootInput.value = project.root;
|
|
281
|
+
projectConfigModal.hidden = false;
|
|
282
|
+
projectNameInput.focus();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function closeConfigModal() {
|
|
286
|
+
projectConfigModal.hidden = true;
|
|
287
|
+
state.editingProjectId = undefined;
|
|
288
|
+
}
|
|
289
|
+
|
|
233
290
|
async function deleteProject(project) {
|
|
234
291
|
const confirmed = window.confirm(`确定要删除项目“${project.name}”吗?\n\n${project.root}`);
|
|
235
292
|
if (!confirmed) {
|
package/dist/web/style.css
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
:root {
|
|
2
2
|
color-scheme: light;
|
|
3
|
-
font-family:
|
|
4
|
-
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
3
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
5
4
|
sans-serif;
|
|
6
5
|
color: #172033;
|
|
7
6
|
background: #eef2f7;
|
|
@@ -13,6 +12,7 @@
|
|
|
13
12
|
|
|
14
13
|
body {
|
|
15
14
|
margin: 0;
|
|
15
|
+
overflow: hidden;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
button,
|
|
@@ -21,21 +21,28 @@ input {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
button {
|
|
24
|
+
font-size: 14px;
|
|
24
25
|
border: 0;
|
|
25
26
|
border-radius: 10px;
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
border: 1px solid #2563eb;
|
|
28
|
+
background: #fff;
|
|
29
|
+
color: #2563eb;
|
|
28
30
|
cursor: pointer;
|
|
29
|
-
padding: 10px
|
|
31
|
+
padding: 6px 10px;
|
|
32
|
+
&:hover {
|
|
33
|
+
opacity: 0.8;
|
|
34
|
+
}
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
button.secondary {
|
|
33
|
-
background: #
|
|
34
|
-
color: #
|
|
38
|
+
background: #2563eb;
|
|
39
|
+
color: #fff;
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
button.danger {
|
|
38
|
-
|
|
43
|
+
border: 1px solid #dc2626;
|
|
44
|
+
background: #fff;
|
|
45
|
+
color: #dc2626;
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
button:disabled {
|
|
@@ -54,7 +61,7 @@ input {
|
|
|
54
61
|
display: grid;
|
|
55
62
|
grid-template-columns: 380px minmax(0, 1fr);
|
|
56
63
|
gap: 20px;
|
|
57
|
-
|
|
64
|
+
height: 100vh;
|
|
58
65
|
padding: 20px;
|
|
59
66
|
}
|
|
60
67
|
|
|
@@ -66,6 +73,13 @@ input {
|
|
|
66
73
|
padding: 20px;
|
|
67
74
|
}
|
|
68
75
|
|
|
76
|
+
.sidebar,
|
|
77
|
+
.content {
|
|
78
|
+
display: flex;
|
|
79
|
+
min-height: 0;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
}
|
|
82
|
+
|
|
69
83
|
.panel-header {
|
|
70
84
|
display: flex;
|
|
71
85
|
align-items: flex-start;
|
|
@@ -74,6 +88,12 @@ input {
|
|
|
74
88
|
margin-bottom: 18px;
|
|
75
89
|
}
|
|
76
90
|
|
|
91
|
+
.header-actions {
|
|
92
|
+
display: flex;
|
|
93
|
+
flex: 0 0 auto;
|
|
94
|
+
gap: 10px;
|
|
95
|
+
}
|
|
96
|
+
|
|
77
97
|
.eyebrow {
|
|
78
98
|
margin: 0 0 4px;
|
|
79
99
|
color: #64748b;
|
|
@@ -114,6 +134,22 @@ h2 {
|
|
|
114
134
|
gap: 10px;
|
|
115
135
|
}
|
|
116
136
|
|
|
137
|
+
.project-list,
|
|
138
|
+
.content-body {
|
|
139
|
+
min-height: 0;
|
|
140
|
+
overflow-y: auto;
|
|
141
|
+
padding-right: 4px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.project-list {
|
|
145
|
+
flex: 1;
|
|
146
|
+
align-content: start;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.content-body {
|
|
150
|
+
flex: 1;
|
|
151
|
+
}
|
|
152
|
+
|
|
117
153
|
.project-card,
|
|
118
154
|
.script-card {
|
|
119
155
|
border: 1px solid #e2e8f0;
|
|
@@ -128,10 +164,7 @@ h2 {
|
|
|
128
164
|
align-items: flex-start;
|
|
129
165
|
justify-content: space-between;
|
|
130
166
|
gap: 12px;
|
|
131
|
-
transition:
|
|
132
|
-
border-color 0.15s ease,
|
|
133
|
-
background 0.15s ease,
|
|
134
|
-
opacity 0.15s ease;
|
|
167
|
+
transition: border-color 0.15s ease, background 0.15s ease, opacity 0.15s ease;
|
|
135
168
|
cursor: pointer;
|
|
136
169
|
}
|
|
137
170
|
|
|
@@ -147,16 +180,27 @@ h2 {
|
|
|
147
180
|
height: 3px;
|
|
148
181
|
border-radius: 999px;
|
|
149
182
|
background: #2563eb;
|
|
150
|
-
content:
|
|
183
|
+
content: '';
|
|
151
184
|
}
|
|
152
185
|
|
|
153
186
|
.project-card-main {
|
|
187
|
+
flex: 1;
|
|
154
188
|
min-width: 0;
|
|
155
189
|
}
|
|
156
190
|
|
|
157
|
-
.project-
|
|
191
|
+
.project-actions {
|
|
192
|
+
display: flex;
|
|
158
193
|
flex: 0 0 auto;
|
|
159
|
-
|
|
194
|
+
flex-direction: column;
|
|
195
|
+
gap: 6px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.project-edit,
|
|
199
|
+
.project-delete {
|
|
200
|
+
border-radius: 8px;
|
|
201
|
+
padding: 4px 8px;
|
|
202
|
+
font-size: 12px;
|
|
203
|
+
line-height: 1.3;
|
|
160
204
|
}
|
|
161
205
|
|
|
162
206
|
.project-card.active {
|
|
@@ -173,7 +217,7 @@ h2 {
|
|
|
173
217
|
.command {
|
|
174
218
|
overflow-wrap: anywhere;
|
|
175
219
|
color: #64748b;
|
|
176
|
-
font-family:
|
|
220
|
+
font-family: 'Cascadia Code', Consolas, monospace;
|
|
177
221
|
font-size: 13px;
|
|
178
222
|
}
|
|
179
223
|
|
|
@@ -202,8 +246,47 @@ h2 {
|
|
|
202
246
|
white-space: pre-wrap;
|
|
203
247
|
}
|
|
204
248
|
|
|
249
|
+
.modal-backdrop {
|
|
250
|
+
position: fixed;
|
|
251
|
+
inset: 0;
|
|
252
|
+
display: grid;
|
|
253
|
+
place-items: center;
|
|
254
|
+
background: rgb(15 23 42 / 42%);
|
|
255
|
+
padding: 20px;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.modal-backdrop[hidden] {
|
|
259
|
+
display: none;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.modal {
|
|
263
|
+
width: min(520px, 100%);
|
|
264
|
+
max-height: calc(100vh - 40px);
|
|
265
|
+
overflow-y: auto;
|
|
266
|
+
border: 1px solid #dbe3ef;
|
|
267
|
+
border-radius: 18px;
|
|
268
|
+
background: #fff;
|
|
269
|
+
box-shadow: 0 24px 80px rgb(15 23 42 / 24%);
|
|
270
|
+
padding: 20px;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.modal .form {
|
|
274
|
+
margin-bottom: 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
205
277
|
@media (max-width: 900px) {
|
|
278
|
+
body {
|
|
279
|
+
overflow: auto;
|
|
280
|
+
}
|
|
281
|
+
|
|
206
282
|
.app {
|
|
207
283
|
grid-template-columns: 1fr;
|
|
284
|
+
height: auto;
|
|
285
|
+
min-height: 100vh;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.sidebar,
|
|
289
|
+
.content {
|
|
290
|
+
max-height: calc(100vh - 40px);
|
|
208
291
|
}
|
|
209
292
|
}
|