aws-runtime-bridge 1.7.30 → 1.7.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,156 +1,23 @@
1
- /**
2
- * Git 路由单元测试
3
- */
4
- import { mkdtemp, rm, writeFile } from 'node:fs/promises';
5
- import { tmpdir } from 'node:os';
6
- import path from 'node:path';
7
1
  import { describe, expect, it } from 'vitest';
8
- import { assertGitWorkspacePathAccessible, createMissingInitialCommitSnapshotError, isGitStashNoChangesOutput, isGitStashMissingInitialCommitError, parseLatestGitStash, shouldIncludeGitDiffSummaryFile, } from './git.js';
9
- describe('git stash operations', () => {
10
- function parseStashList(output) {
11
- const stashes = [];
12
- const lines = output.split('\n').filter(line => line.trim());
13
- for (const line of lines) {
14
- const refMatch = line.match(/^(stash@\{\d+\})/);
15
- if (!refMatch)
16
- continue;
17
- const ref = refMatch[1];
18
- const restOfLine = line.slice(ref.length + 2);
19
- let branch = '';
20
- let message = restOfLine;
21
- const branchMatch = restOfLine.match(/^(?:On|WIP on)\s+([^:]+):\s*(.*)$/);
22
- if (branchMatch) {
23
- branch = branchMatch[1].trim();
24
- message = branchMatch[2].trim();
25
- }
26
- stashes.push({ ref, branch, message });
27
- }
28
- return stashes;
29
- }
30
- it('parses simple stash list output', () => {
31
- const output = 'stash@{0}: On main: 快照: 添加新功能';
32
- const result = parseStashList(output);
33
- expect(result.length).toBe(1);
34
- expect(result[0].ref).toBe('stash@{0}');
35
- expect(result[0].branch).toBe('main');
36
- expect(result[0].message).toBe('快照: 添加新功能');
37
- });
38
- it('parses WIP stash format', () => {
39
- const output = 'stash@{0}: WIP on feature: test changes';
40
- const result = parseStashList(output);
41
- expect(result.length).toBe(1);
42
- expect(result[0].branch).toBe('feature');
43
- expect(result[0].message).toBe('test changes');
44
- });
45
- it('handles multiple stash entries', () => {
46
- const output = [
47
- 'stash@{0}: On main: 最新快照',
48
- 'stash@{1}: On main: 旧快照',
49
- ].join('\n');
50
- const result = parseStashList(output);
51
- expect(result.length).toBe(2);
52
- });
53
- it('returns empty array for empty output', () => {
54
- expect(parseStashList('')).toEqual([]);
55
- });
56
- it('parses latest stash for no-change snapshot reuse', () => {
57
- const result = parseLatestGitStash('stash@{0}: On master: 快照: 快照: t1\n');
58
- expect(result).toEqual({
59
- ref: 'stash@{0}',
60
- message: 'On master: 快照: 快照: t1',
61
- });
62
- });
63
- it('returns null when there is no latest stash to reuse', () => {
64
- expect(parseLatestGitStash('')).toBeNull();
65
- expect(parseLatestGitStash('not a stash line')).toBeNull();
66
- });
67
- });
68
- describe('git command validation', () => {
69
- it('validates workspacePath requirement', () => {
70
- const validateInput = (body) => {
71
- if (!body.workspacePath || !String(body.workspacePath).trim()) {
72
- return { valid: false, error: 'workspacePath is required' };
73
- }
74
- return { valid: true };
75
- };
76
- expect(validateInput({}).valid).toBe(false);
77
- expect(validateInput({ workspacePath: '/valid/path' }).valid).toBe(true);
78
- });
79
- it('validates stashRef format', () => {
80
- const isValidStashRef = (ref) => /^stash@\{\d+\}$/.test(ref);
81
- expect(isValidStashRef('stash@{0}')).toBe(true);
82
- expect(isValidStashRef('stash@{99}')).toBe(true);
83
- expect(isValidStashRef('invalid')).toBe(false);
84
- });
85
- });
86
- describe('git workspace path validation', () => {
87
- it('accepts existing workspace directories', async () => {
88
- const workspaceDir = await mkdtemp(path.join(tmpdir(), 'aws-git-workspace-'));
89
- try {
90
- await expect(assertGitWorkspacePathAccessible(workspaceDir)).resolves.toBeUndefined();
91
- }
92
- finally {
93
- await rm(workspaceDir, { recursive: true, force: true });
94
- }
95
- });
96
- it('rejects missing workspace paths with a diagnostic error', async () => {
97
- const missingPath = path.join(tmpdir(), `aws-missing-workspace-${Date.now()}`);
98
- await expect(assertGitWorkspacePathAccessible(missingPath)).rejects.toThrow('workspace path does not exist');
99
- });
100
- it('rejects file paths because git cwd must be a directory', async () => {
101
- const workspaceDir = await mkdtemp(path.join(tmpdir(), 'aws-git-workspace-file-'));
102
- const filePath = path.join(workspaceDir, 'workspace.txt');
103
- try {
104
- await writeFile(filePath, 'not a directory');
105
- await expect(assertGitWorkspacePathAccessible(filePath)).rejects.toThrow('workspace path is not a directory');
106
- }
107
- finally {
108
- await rm(workspaceDir, { recursive: true, force: true });
109
- }
110
- });
111
- });
112
- describe('git diff summary visibility', () => {
113
- it('filters ordinary modified files without textual changes', () => {
114
- expect(shouldIncludeGitDiffSummaryFile('modified', 0, 0, false)).toBe(false);
115
- });
116
- it('keeps modified files with text or binary changes', () => {
117
- expect(shouldIncludeGitDiffSummaryFile('modified', 1, 0, false)).toBe(true);
118
- expect(shouldIncludeGitDiffSummaryFile('modified', 0, 1, false)).toBe(true);
119
- expect(shouldIncludeGitDiffSummaryFile('modified', 0, 0, true)).toBe(true);
120
- });
121
- it('keeps structural status changes even when line counts are zero', () => {
122
- expect(shouldIncludeGitDiffSummaryFile('added', 0, 0, false)).toBe(true);
123
- expect(shouldIncludeGitDiffSummaryFile('deleted', 0, 0, false)).toBe(true);
124
- expect(shouldIncludeGitDiffSummaryFile('renamed', 0, 0, false)).toBe(true);
125
- });
126
- });
127
- describe('error handling', () => {
128
- it('detects "no local changes" error', () => {
129
- expect(isGitStashNoChangesOutput('No local changes to save')).toBe(true);
130
- expect(isGitStashNoChangesOutput('stdout: No changes to save')).toBe(true);
131
- expect(isGitStashNoChangesOutput('没有本地变更需要保存')).toBe(true);
132
- expect(isGitStashNoChangesOutput('other error')).toBe(false);
133
- });
134
- it('detects stash failure before initial commit and returns an actionable message', () => {
135
- expect(isGitStashMissingInitialCommitError('You do not have the initial commit yet\n')).toBe(true);
136
- expect(isGitStashMissingInitialCommitError("fatal: bad revision 'HEAD'\n")).toBe(true);
137
- expect(isGitStashMissingInitialCommitError("fatal: ambiguous argument 'HEAD': unknown revision\n")).toBe(true);
138
- expect(isGitStashMissingInitialCommitError('No local changes to save')).toBe(false);
139
- expect(createMissingInitialCommitSnapshotError()).toContain('请先提交一次初始提交');
140
- });
141
- it('detects conflict during pop', () => {
142
- const hasConflict = (stdout, stderr) => stdout.includes('CONFLICT') || stderr.includes('CONFLICT');
143
- expect(hasConflict('CONFLICT (content): Merge conflict', '')).toBe(true);
144
- expect(hasConflict('clean merge', '')).toBe(false);
145
- });
146
- it('uses apply as the non-consuming restore command', () => {
147
- const buildRestoreCommand = (mode, ref) => ['stash', mode, ref];
148
- expect(buildRestoreCommand('apply', 'stash@{0}')).toEqual(['stash', 'apply', 'stash@{0}']);
149
- expect(buildRestoreCommand('pop', 'stash@{0}')).toEqual(['stash', 'pop', 'stash@{0}']);
150
- });
151
- it('detects invalid stash reference', () => {
152
- const isInvalidRef = (stderr) => stderr.includes('is not a valid reference');
153
- expect(isInvalidRef('stash@{99} is not a valid reference')).toBe(true);
154
- expect(isInvalidRef('other error')).toBe(false);
2
+ import { createScopedFilePathspecArgs, createScopedGitPathspecArgs, normalizeGitCommitMessage, parseGitStatusStagedPaths, } from './git.js';
3
+ describe('git commit helpers', () => {
4
+ it('normalizes commit messages before executing git commit', () => {
5
+ expect(normalizeGitCommitMessage(' chore: update dashboard ')).toBe('chore: update dashboard');
6
+ expect(normalizeGitCommitMessage(' ')).toBe('');
7
+ expect(normalizeGitCommitMessage(null)).toBe('');
8
+ });
9
+ it('creates pathspec args scoped to the selected workspace path', () => {
10
+ expect(createScopedGitPathspecArgs({ relativeWorkspacePath: 'packages/app' })).toEqual(['--', 'packages/app']);
11
+ expect(createScopedGitPathspecArgs({ relativeWorkspacePath: '' })).toEqual(['--', '.']);
12
+ });
13
+ it('creates file pathspec args relative to parent git repositories', () => {
14
+ expect(createScopedFilePathspecArgs('src/App.vue', { relativeWorkspacePath: 'packages/app' })).toEqual([
15
+ '--',
16
+ 'packages/app/src/App.vue',
17
+ ]);
18
+ });
19
+ it('parses staged paths from porcelain status output', () => {
20
+ const stagedPaths = parseGitStatusStagedPaths('M staged.ts\n M unstaged.ts\nA added.ts\n?? untracked.ts\nR old.ts -> renamed.ts\n');
21
+ expect([...stagedPaths].sort()).toEqual(['added.ts', 'renamed.ts', 'staged.ts']);
155
22
  });
156
23
  });
@@ -1 +1 @@
1
- {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAYrD,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI,CAEN;AAED,eAAO,MAAM,cAAc,4CAAW,CAAC;AAyBvC,wBAAgB,4BAA4B,CAAC,gBAAgB,EAAE,MAAM,GAAG;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QACJ,EAAE,EAAE,OAAO,CAAC;QACZ,aAAa,CAAC,EAAE,SAAS,CAAC;QAC1B,oBAAoB,EAAE,OAAO,CAAC;QAC9B,qBAAqB,EAAE,OAAO,CAAC;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAqCA;AAED,MAAM,WAAW,4BAA4B;IAC3C,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,iCAAiC;IACzC,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,8BAA8B,CAAC;IACtC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,KAAK,EACZ,0BAA0B,EAAE,MAAM,GACjC,4BAA4B,CAiB9B;AAoDD;;;GAGG;AACH,wBAAgB,sCAAsC,CACpD,UAAU,EAAE,MAAM,EAAE,GACnB,iCAAiC,CAUnC;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAQhF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,cAAc,EAAE,MAAM,EAAE,EACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC5C,MAAM,CAaR;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC5C,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,OAAO,CAAC,CAAC,CAY3F"}
1
+ {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAoBrD,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI,CAEN;AAED,eAAO,MAAM,cAAc,4CAAW,CAAC;AAmGvC,wBAAgB,4BAA4B,CAAC,gBAAgB,EAAE,MAAM,GAAG;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QACJ,EAAE,EAAE,OAAO,CAAC;QACZ,aAAa,CAAC,EAAE,SAAS,CAAC;QAC1B,oBAAoB,EAAE,OAAO,CAAC;QAC9B,qBAAqB,EAAE,OAAO,CAAC;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAqCA;AAED,MAAM,WAAW,4BAA4B;IAC3C,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,iCAAiC;IACzC,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,8BAA8B,CAAC;IACtC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,KAAK,EACZ,0BAA0B,EAAE,MAAM,GACjC,4BAA4B,CAiB9B;AAoDD;;;GAGG;AACH,wBAAgB,sCAAsC,CACpD,UAAU,EAAE,MAAM,EAAE,GACnB,iCAAiC,CAUnC;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAQhF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,cAAc,EAAE,MAAM,EAAE,EACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC5C,MAAM,CAaR;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC5C,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,OAAO,CAAC,CAAC,CAY3F"}
@@ -1,5 +1,6 @@
1
1
  import { Router } from "express";
2
2
  import axios from "axios";
3
+ import { spawn } from "node:child_process";
3
4
  import { createHash, timingSafeEqual } from "node:crypto";
4
5
  import { validateToken } from "../middleware/auth.js";
5
6
  import { resolveSchedulerBaseUrlFrom } from "../config.js";
@@ -20,6 +21,63 @@ export function setGracefulShutdownFn(fn) {
20
21
  gracefulShutdownFn = fn;
21
22
  }
22
23
  export const instanceRouter = Router();
24
+ function resolveNpmExecutable() {
25
+ return process.platform === "win32" ? "npm.cmd" : "npm";
26
+ }
27
+ function truncateCommandOutput(output) {
28
+ const maxLength = 16_000;
29
+ return output.length > maxLength
30
+ ? `${output.slice(output.length - maxLength)}\n[output truncated]`
31
+ : output;
32
+ }
33
+ function runCommand(command, args) {
34
+ return new Promise((resolve, reject) => {
35
+ const child = spawn(command, args, {
36
+ shell: false,
37
+ windowsHide: true,
38
+ });
39
+ let stdout = "";
40
+ let stderr = "";
41
+ child.stdout.on("data", (chunk) => {
42
+ stdout += chunk.toString("utf8");
43
+ });
44
+ child.stderr.on("data", (chunk) => {
45
+ stderr += chunk.toString("utf8");
46
+ });
47
+ child.on("error", reject);
48
+ child.on("close", (exitCode) => {
49
+ resolve({
50
+ command,
51
+ args,
52
+ exitCode,
53
+ stdout: truncateCommandOutput(stdout),
54
+ stderr: truncateCommandOutput(stderr),
55
+ });
56
+ });
57
+ });
58
+ }
59
+ async function writeRestartSessionFlag(preserveSessions) {
60
+ const fs = await import("node:fs/promises");
61
+ const path = await import("node:path");
62
+ const os = await import("node:os");
63
+ const restartFlagFile = path.join(os.tmpdir(), "agentswork-runtime-bridge", ".preserve-sessions");
64
+ if (preserveSessions) {
65
+ await fs.mkdir(path.dirname(restartFlagFile), { recursive: true });
66
+ await fs.writeFile(restartFlagFile, JSON.stringify({
67
+ preserveSessions: true,
68
+ timestamp: new Date().toISOString(),
69
+ }), "utf-8");
70
+ log.info(`[prepare-restart] 已创建保留会话标记文件: ${restartFlagFile}`);
71
+ return;
72
+ }
73
+ try {
74
+ await fs.unlink(restartFlagFile);
75
+ log.info("[prepare-restart] 已删除保留会话标记文件");
76
+ }
77
+ catch {
78
+ // 文件不存在时无需处理。
79
+ }
80
+ }
23
81
  function md5Hex(value) {
24
82
  return createHash("md5").update(value, "utf8").digest("hex");
25
83
  }
@@ -488,30 +546,8 @@ instanceRouter.post("/prepare-restart", validateToken, async (req, res) => {
488
546
  const { preserveSessions = true } = req.body || {};
489
547
  try {
490
548
  log.info(`[prepare-restart] 收到准备重启通知,preserveSessions=${preserveSessions}`);
491
- // 记录重启模式到文件,供下次启动时判断
492
- const fs = await import("fs/promises");
493
- const path = await import("path");
494
- const os = await import("os");
495
- const restartFlagFile = path.join(os.tmpdir(), "agentswork-runtime-bridge", ".preserve-sessions");
496
- if (preserveSessions) {
497
- // 创建标记文件,表示 aws-mcp-server 重启,需要保留会话
498
- await fs.mkdir(path.dirname(restartFlagFile), { recursive: true });
499
- await fs.writeFile(restartFlagFile, JSON.stringify({
500
- preserveSessions: true,
501
- timestamp: new Date().toISOString(),
502
- }), "utf-8");
503
- log.info(`[prepare-restart] 已创建保留会话标记文件: ${restartFlagFile}`);
504
- }
505
- else {
506
- // 删除标记文件,表示 bridge 自己关闭,需要清理会话
507
- try {
508
- await fs.unlink(restartFlagFile);
509
- log.info(`[prepare-restart] 已删除保留会话标记文件`);
510
- }
511
- catch {
512
- // 文件不存在,忽略
513
- }
514
- }
549
+ // 记录重启模式到文件,供下次启动时判断。
550
+ await writeRestartSessionFlag(Boolean(preserveSessions));
515
551
  res.json({
516
552
  ok: true,
517
553
  preserveSessions,
@@ -526,3 +562,69 @@ instanceRouter.post("/prepare-restart", validateToken, async (req, res) => {
526
562
  res.status(500).json({ error: err.message });
527
563
  }
528
564
  });
565
+ /**
566
+ * POST /runtime/update-bridge
567
+ *
568
+ * 通过当前 Bridge 进程执行全局 npm 包更新,等价于 CLI 的
569
+ * `awsb update`,用于面板上的“更新实例 Bridge”。
570
+ */
571
+ instanceRouter.post("/update-bridge", validateToken, async (_req, res) => {
572
+ const command = resolveNpmExecutable();
573
+ const args = ["install", "-g", "aws-runtime-bridge@latest"];
574
+ try {
575
+ log.info("[update-bridge] 正在更新 aws-runtime-bridge 到最新版本");
576
+ const result = await runCommand(command, args);
577
+ if (result.exitCode !== 0) {
578
+ res.status(500).json({
579
+ ok: false,
580
+ error: `更新失败,npm 退出码: ${result.exitCode ?? "unknown"}`,
581
+ result,
582
+ });
583
+ return;
584
+ }
585
+ res.json({
586
+ ok: true,
587
+ message: "Bridge 更新完成,请重启实例 Bridge 以加载新版本。",
588
+ result,
589
+ });
590
+ }
591
+ catch (error) {
592
+ const err = error;
593
+ log.error("[update-bridge] Error:", err);
594
+ res.status(500).json({ error: err.message || "update bridge failed" });
595
+ }
596
+ });
597
+ /**
598
+ * POST /runtime/restart-bridge
599
+ *
600
+ * 写入保留会话标记后优雅关闭当前 Bridge 进程。真正的重新拉起由
601
+ * systemd、PM2、Docker 外部守护或用户手动启动完成。
602
+ */
603
+ instanceRouter.post("/restart-bridge", validateToken, async (req, res) => {
604
+ const { preserveSessions = true } = req.body || {};
605
+ try {
606
+ log.info(`[restart-bridge] 收到实例 Bridge 重启请求,preserveSessions=${preserveSessions}`);
607
+ await writeRestartSessionFlag(Boolean(preserveSessions));
608
+ res.json({
609
+ ok: true,
610
+ preserveSessions: Boolean(preserveSessions),
611
+ message: "Bridge 正在优雅退出;若实例由 systemd/PM2 等守护管理,将自动重启,否则需要手动重新启动。",
612
+ });
613
+ setTimeout(() => {
614
+ if (gracefulShutdownFn) {
615
+ gracefulShutdownFn("PANEL_RESTART", Boolean(preserveSessions)).catch((error) => {
616
+ const err = error;
617
+ log.error("[restart-bridge] graceful shutdown failed:", err);
618
+ process.exit(1);
619
+ });
620
+ return;
621
+ }
622
+ process.exit(0);
623
+ }, 250);
624
+ }
625
+ catch (error) {
626
+ const err = error;
627
+ log.error("[restart-bridge] Error:", err);
628
+ res.status(500).json({ error: err.message || "restart bridge failed" });
629
+ }
630
+ });
@@ -2,6 +2,8 @@
2
2
  * Instance 路由单元测试
3
3
  */
4
4
  import { describe, it, expect, vi, afterEach } from 'vitest';
5
+ import { EventEmitter } from 'node:events';
6
+ const spawnMock = vi.hoisted(() => vi.fn());
5
7
  vi.mock('axios', () => ({
6
8
  default: {
7
9
  get: vi.fn(),
@@ -14,9 +16,36 @@ vi.mock('../services/auto-register.js', () => ({
14
16
  vi.mock('../services/runtime-binding.js', () => ({
15
17
  getRuntimeAccessToken: vi.fn(() => 'stale-runtime-token-123456'),
16
18
  }));
19
+ vi.mock('node:child_process', async (importOriginal) => ({
20
+ ...(await importOriginal()),
21
+ spawn: spawnMock,
22
+ }));
17
23
  afterEach(() => {
24
+ vi.useRealTimers();
18
25
  vi.clearAllMocks();
19
26
  });
27
+ function mockSpawnResult(exitCode, stdoutText = '', stderrText = '') {
28
+ spawnMock.mockImplementationOnce(() => {
29
+ const child = new EventEmitter();
30
+ child.stdout = new EventEmitter();
31
+ child.stderr = new EventEmitter();
32
+ process.nextTick(() => {
33
+ if (stdoutText) {
34
+ child.stdout.emit('data', Buffer.from(stdoutText));
35
+ }
36
+ if (stderrText) {
37
+ child.stderr.emit('data', Buffer.from(stderrText));
38
+ }
39
+ child.emit('close', exitCode);
40
+ });
41
+ return child;
42
+ });
43
+ }
44
+ function createMockResponse() {
45
+ const json = vi.fn();
46
+ const status = vi.fn(() => ({ json }));
47
+ return { json, status };
48
+ }
20
49
  describe('instance route validation', () => {
21
50
  it('builds correct ping response when healthy', () => {
22
51
  const buildPingResponse = (schedulerStatus, schedulerBaseUrl) => ({
@@ -292,4 +321,52 @@ describe('instance route validation', () => {
292
321
  hint: expect.stringContaining('AWS_RUNTIME_SCHEDULER_BASE_URL'),
293
322
  });
294
323
  });
324
+ it('updates bridge package through npm global install route', async () => {
325
+ const { instanceRouter } = await import('./instance.js');
326
+ mockSpawnResult(0, 'updated');
327
+ const handler = instanceRouter.stack.find((layer) => layer.route?.path === '/update-bridge')?.route?.stack[1]?.handle;
328
+ expect(handler).toBeTypeOf('function');
329
+ const { json, status } = createMockResponse();
330
+ await handler?.({}, { json, status }, vi.fn());
331
+ expect(spawnMock).toHaveBeenCalledWith(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['install', '-g', 'aws-runtime-bridge@latest'], { shell: false, windowsHide: true });
332
+ expect(status).not.toHaveBeenCalled();
333
+ expect(json).toHaveBeenCalledWith({
334
+ ok: true,
335
+ message: 'Bridge 更新完成,请重启实例 Bridge 以加载新版本。',
336
+ result: expect.objectContaining({ exitCode: 0, stdout: 'updated' }),
337
+ });
338
+ });
339
+ it('returns update failure details when npm exits non-zero', async () => {
340
+ const { instanceRouter } = await import('./instance.js');
341
+ mockSpawnResult(1, '', 'permission denied');
342
+ const handler = instanceRouter.stack.find((layer) => layer.route?.path === '/update-bridge')?.route?.stack[1]?.handle;
343
+ expect(handler).toBeTypeOf('function');
344
+ const { json, status } = createMockResponse();
345
+ await handler?.({}, { json, status }, vi.fn());
346
+ expect(status).toHaveBeenCalledWith(500);
347
+ expect(json).toHaveBeenCalledWith({
348
+ ok: false,
349
+ error: '更新失败,npm 退出码: 1',
350
+ result: expect.objectContaining({ exitCode: 1, stderr: 'permission denied' }),
351
+ });
352
+ });
353
+ it('responds before triggering graceful bridge restart', async () => {
354
+ vi.useFakeTimers();
355
+ const { instanceRouter, setGracefulShutdownFn } = await import('./instance.js');
356
+ const gracefulShutdown = vi.fn().mockResolvedValue(undefined);
357
+ setGracefulShutdownFn(gracefulShutdown);
358
+ const handler = instanceRouter.stack.find((layer) => layer.route?.path === '/restart-bridge')?.route?.stack[1]?.handle;
359
+ expect(handler).toBeTypeOf('function');
360
+ const { json, status } = createMockResponse();
361
+ await handler?.({ body: { preserveSessions: true } }, { json, status }, vi.fn());
362
+ expect(status).not.toHaveBeenCalled();
363
+ expect(json).toHaveBeenCalledWith({
364
+ ok: true,
365
+ preserveSessions: true,
366
+ message: 'Bridge 正在优雅退出;若实例由 systemd/PM2 等守护管理,将自动重启,否则需要手动重新启动。',
367
+ });
368
+ expect(gracefulShutdown).not.toHaveBeenCalled();
369
+ await vi.advanceTimersByTimeAsync(250);
370
+ expect(gracefulShutdown).toHaveBeenCalledWith('PANEL_RESTART', true);
371
+ });
295
372
  });
@@ -85,6 +85,44 @@ interface ExtractWorkspaceArchiveParams extends WorkspacePathParams {
85
85
  archivePath: string;
86
86
  outputPath?: string;
87
87
  }
88
+ interface ChmodWorkspaceEntryParams extends WorkspacePathParams {
89
+ targetPath: string;
90
+ mode: string;
91
+ recursive?: boolean;
92
+ }
93
+ export interface WorkspaceImageProperties {
94
+ format: 'png' | 'jpeg' | 'gif' | 'webp';
95
+ width: number;
96
+ height: number;
97
+ }
98
+ export interface WorkspaceEntryPropertiesResult {
99
+ workspacePath: string;
100
+ targetPath: string;
101
+ name: string;
102
+ isDirectory: boolean;
103
+ isFile: boolean;
104
+ isSymbolicLink: boolean;
105
+ size: number;
106
+ mode: number;
107
+ modeOctal: string;
108
+ permissions: string;
109
+ uid?: number;
110
+ gid?: number;
111
+ atimeMs: number;
112
+ mtimeMs: number;
113
+ ctimeMs: number;
114
+ birthtimeMs: number;
115
+ image?: WorkspaceImageProperties;
116
+ }
117
+ export interface ChmodWorkspaceEntryResult {
118
+ ok: true;
119
+ workspacePath: string;
120
+ targetPath: string;
121
+ mode: number;
122
+ modeOctal: string;
123
+ recursive: boolean;
124
+ changedCount: number;
125
+ }
88
126
  /**
89
127
  * 列出工作区目录内容。
90
128
  */
@@ -93,6 +131,16 @@ export declare function listWorkspaceDirectory(params: WorkspacePathParams): Pro
93
131
  * 读取工作区中的文本文件。
94
132
  */
95
133
  export declare function readWorkspaceFile(params: WorkspaceFileParams): Promise<ReadWorkspaceFileResult>;
134
+ /**
135
+ * 读取工作区条目的属性信息。
136
+ * 主流程:解析安全路径 -> 读取 stat/lstat -> 文件额外尝试解析图片尺寸。
137
+ */
138
+ export declare function getWorkspaceEntryProperties(params: DeleteWorkspaceEntryParams): Promise<WorkspaceEntryPropertiesResult>;
139
+ /**
140
+ * 修改工作区条目权限。
141
+ * 主流程:校验八进制权限 -> 解析安全路径 -> 非递归只改目标,递归目录先收集所有非符号链接子项后逐一 chmod。
142
+ */
143
+ export declare function chmodWorkspaceEntry(params: ChmodWorkspaceEntryParams): Promise<ChmodWorkspaceEntryResult>;
96
144
  /**
97
145
  * 返回工作区内 Word 文档的预览元数据;docx 由前端下载原文件后渲染,旧版 doc 明确提示不支持。
98
146
  */
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-files.d.ts","sourceRoot":"","sources":["../../src/services/workspace-files.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqDH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,4BAA4B;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,8BAA8B;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,6BAA6B;IAC5C,EAAE,EAAE,IAAI,CAAC;IACT,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,UAAU,mBAAmB;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,mBAAmB;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,wBAAyB,SAAQ,mBAAmB;IAC5D,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,0BAA2B,SAAQ,mBAAmB;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,UAAU,0BAA2B,SAAQ,mBAAmB;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,wBAAyB,SAAQ,mBAAmB;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,0BAA2B,SAAQ,mBAAmB;IAC9D,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,0BAA2B,SAAQ,mBAAmB;IAC9D,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAC7B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,UAAU,4BAA6B,SAAQ,mBAAmB;IAChE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,6BAA8B,SAAQ,mBAAmB;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAkWD;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,4BAA4B,CAAC,CA0B/G;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAgBrG;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,8BAA8B,CAAC,CAkCnH;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAYzI;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,CAkCxF;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAkCtF;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC,CAiD/G;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAelE;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,6BAA6B,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,CA4CnL;AAED;;GAEG;AACH,wBAAsB,8BAA8B,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAa3H;AAED;;GAEG;AACH,wBAAsB,2BAA2B,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAarH;AA+GD;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,6BAA6B,GAAG,OAAO,CAAC,6BAA6B,CAAC,CA8C3H"}
1
+ {"version":3,"file":"workspace-files.d.ts","sourceRoot":"","sources":["../../src/services/workspace-files.ts"],"names":[],"mappings":"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"}