aws-runtime-bridge 1.7.7 → 1.7.9
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/config.d.ts +6 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -0
- package/dist/config.test.js +12 -0
- package/dist/routes/git.d.ts +7 -0
- package/dist/routes/git.d.ts.map +1 -1
- package/dist/routes/git.js +15 -1
- package/dist/routes/git.test.js +18 -3
- package/dist/routes/instance.d.ts.map +1 -1
- package/dist/routes/instance.js +41 -6
- package/dist/routes/instance.test.js +70 -0
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
export declare const port: number;
|
|
8
8
|
/** 默认调度器基础 URL */
|
|
9
9
|
export declare const DEFAULT_SCHEDULER_BASE_URL = "http://localhost:8080";
|
|
10
|
-
export type SchedulerBaseUrlSource = "env" | "single-auto-register-target" | "default" | "ambiguous-auto-register-targets";
|
|
10
|
+
export type SchedulerBaseUrlSource = "request" | "env" | "single-auto-register-target" | "default" | "ambiguous-auto-register-targets";
|
|
11
11
|
export interface SchedulerBaseUrlResolution {
|
|
12
12
|
url: string;
|
|
13
13
|
source: SchedulerBaseUrlSource;
|
|
@@ -18,6 +18,11 @@ export interface SchedulerBaseUrlResolution {
|
|
|
18
18
|
* 主流程:显式环境变量优先;未配置时,单个自动注册目标可作为宿主机部署的零额外配置回退;多目标不静默取第一个。
|
|
19
19
|
*/
|
|
20
20
|
export declare function resolveSchedulerBaseUrl(): SchedulerBaseUrlResolution;
|
|
21
|
+
/**
|
|
22
|
+
* 解析调度中心基础 URL。
|
|
23
|
+
* 主流程:服务端请求携带的 scheduler 地址优先;再读显式环境变量;最后回退到单个自动注册目标或默认值。
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveSchedulerBaseUrlFrom(requestSchedulerBaseUrl: unknown): SchedulerBaseUrlResolution;
|
|
21
26
|
/** 调度器基础 URL(兼容旧导入;请求处理应优先调用 resolveSchedulerBaseUrl 获取最新值) */
|
|
22
27
|
export declare const schedulerBaseUrl: string;
|
|
23
28
|
/** Node 环境 */
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,WAAW;AACX,eAAO,MAAM,IAAI,EAAE,MAElB,CAAC;AAEF,kBAAkB;AAClB,eAAO,MAAM,0BAA0B,0BAA0B,CAAC;AAElE,MAAM,MAAM,sBAAsB,GAC9B,KAAK,GACL,6BAA6B,GAC7B,SAAS,GACT,iCAAiC,CAAC;AAEtC,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,sBAAsB,CAAC;IAC/B,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AA+ED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,0BAA0B,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,WAAW;AACX,eAAO,MAAM,IAAI,EAAE,MAElB,CAAC;AAEF,kBAAkB;AAClB,eAAO,MAAM,0BAA0B,0BAA0B,CAAC;AAElE,MAAM,MAAM,sBAAsB,GAC9B,SAAS,GACT,KAAK,GACL,6BAA6B,GAC7B,SAAS,GACT,iCAAiC,CAAC;AAEtC,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,sBAAsB,CAAC;IAC/B,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AA+ED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,0BAA0B,CAEpE;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,uBAAuB,EAAE,OAAO,GAC/B,0BAA0B,CA4C5B;AAED,+DAA+D;AAC/D,eAAO,MAAM,gBAAgB,EAAE,MAEH,CAAC;AAE7B,cAAc;AACd,eAAO,MAAM,OAAO,EAAE,MAA8C,CAAC;AAErE,oCAAoC;AACpC,eAAO,MAAM,oBAAoB,EAAE,OACiB,CAAC;AAErD,8BAA8B;AAC9B,eAAO,MAAM,kBAAkB,EAAE,MAAM,EAMC,CAAC;AAEzC,gEAAgE;AAChE,wBAAgB,uBAAuB,IAAI,IAAI,CAAG;AAElD,eAAe;AACf,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,8CAA8C;AAC9C,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,2BAA2B;AAC3B,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKlE;AAED,0BAA0B;AAC1B,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,yBAAyB;AACzB,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKjE;AAED,sBAAsB;AACtB,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,yBAAyB;AACzB,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK7D;AAED,4BAA4B;AAC5B,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKhE;AAED;;GAEG;AAEH,yBAAyB;AACzB,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,sBAAsB,EACrB,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE1C,eAAe;AACf,eAAO,MAAM,mBAAmB,SACiB,CAAC;AAElD,iCAAiC;AACjC,eAAO,MAAM,2BAA2B,QAEvC,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAEF,qCAAqC;AACrC,eAAO,MAAM,uBAAuB,QAEnC,CAAC;AAEF;;GAEG;AAEH,eAAe;AACf,eAAO,MAAM,qBAAqB,SAA2C,CAAC;AAE9E,YAAY;AACZ,eAAO,MAAM,uBAAuB,QAAkC,CAAC;AAEvE,iBAAiB;AACjB,eAAO,MAAM,sBAAsB,QAAiC,CAAC;AAErE,oBAAoB;AACpB,eAAO,MAAM,2BAA2B,QAAsC,CAAC;AAE/E,WAAW;AACX,eAAO,MAAM,0BAA0B,QAAqC,CAAC;AAE7E,WAAW;AACX,eAAO,MAAM,uBAAuB,QAAkC,CAAC;AAEvE,aAAa;AACb,eAAO,MAAM,4BAA4B,QACH,CAAC;AAEvC,kCAAkC;AAClC,eAAO,MAAM,wBAAwB,QAC4B,CAAC;AAElE,aAAa;AACb,eAAO,MAAM,yBAAyB,QAErC,CAAC;AAEF,iBAAiB;AACjB,eAAO,MAAM,4BAA4B,QAExC,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -76,6 +76,21 @@ function readAutoRegisterTargetUrls() {
|
|
|
76
76
|
* 主流程:显式环境变量优先;未配置时,单个自动注册目标可作为宿主机部署的零额外配置回退;多目标不静默取第一个。
|
|
77
77
|
*/
|
|
78
78
|
export function resolveSchedulerBaseUrl() {
|
|
79
|
+
return resolveSchedulerBaseUrlFrom(undefined);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 解析调度中心基础 URL。
|
|
83
|
+
* 主流程:服务端请求携带的 scheduler 地址优先;再读显式环境变量;最后回退到单个自动注册目标或默认值。
|
|
84
|
+
*/
|
|
85
|
+
export function resolveSchedulerBaseUrlFrom(requestSchedulerBaseUrl) {
|
|
86
|
+
const normalizedRequestSchedulerBaseUrl = normalizeSchedulerHttpBaseUrl(requestSchedulerBaseUrl);
|
|
87
|
+
if (normalizedRequestSchedulerBaseUrl) {
|
|
88
|
+
return {
|
|
89
|
+
url: normalizedRequestSchedulerBaseUrl,
|
|
90
|
+
source: "request",
|
|
91
|
+
ambiguousTargetUrls: [],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
79
94
|
const envSchedulerBaseUrl = normalizeSchedulerHttpBaseUrl(process.env.AWS_RUNTIME_SCHEDULER_BASE_URL);
|
|
80
95
|
if (envSchedulerBaseUrl) {
|
|
81
96
|
return {
|
package/dist/config.test.js
CHANGED
|
@@ -42,6 +42,18 @@ afterEach(() => {
|
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
describe("scheduler base URL resolution", () => {
|
|
45
|
+
it("keeps scheduler URL supplied by the server request as the highest priority", async () => {
|
|
46
|
+
const runtimeHome = createRuntimeHome();
|
|
47
|
+
useRuntimeHome(runtimeHome);
|
|
48
|
+
process.env.AWS_RUNTIME_SCHEDULER_BASE_URL = "http://env.local:7380";
|
|
49
|
+
writeBridgeConfig(runtimeHome, [{ serverUrl: "http://target.local:7380" }]);
|
|
50
|
+
const { resolveSchedulerBaseUrlFrom } = await import("./config.js");
|
|
51
|
+
expect(resolveSchedulerBaseUrlFrom("http://server.local:7380/api")).toEqual({
|
|
52
|
+
url: "http://server.local:7380",
|
|
53
|
+
source: "request",
|
|
54
|
+
ambiguousTargetUrls: [],
|
|
55
|
+
});
|
|
56
|
+
});
|
|
45
57
|
it("keeps explicit AWS_RUNTIME_SCHEDULER_BASE_URL as the highest priority", async () => {
|
|
46
58
|
const runtimeHome = createRuntimeHome();
|
|
47
59
|
useRuntimeHome(runtimeHome);
|
package/dist/routes/git.d.ts
CHANGED
|
@@ -5,4 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export declare const gitRouter: import("express-serve-static-core").Router;
|
|
7
7
|
export declare function assertGitWorkspacePathAccessible(workspacePath: string): Promise<void>;
|
|
8
|
+
type GitDiffFileStatus = 'modified' | 'added' | 'deleted' | 'renamed';
|
|
9
|
+
/**
|
|
10
|
+
* 判断 Git diff 汇总行是否应该展示在差异树中。
|
|
11
|
+
* 普通 modified 且文本增删均为 0 通常是 filemode/元数据噪声,避免显示为“无差异内容”。
|
|
12
|
+
*/
|
|
13
|
+
export declare function shouldIncludeGitDiffSummaryFile(status: GitDiffFileStatus, additions: number, deletions: number, isBinaryDiff: boolean): boolean;
|
|
14
|
+
export {};
|
|
8
15
|
//# sourceMappingURL=git.d.ts.map
|
package/dist/routes/git.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/routes/git.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,eAAO,MAAM,SAAS,4CAAW,CAAC;AAWlC,wBAAsB,gCAAgC,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3F"}
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/routes/git.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,eAAO,MAAM,SAAS,4CAAW,CAAC;AAWlC,wBAAsB,gCAAgC,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3F;AASD,KAAK,iBAAiB,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtE;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,iBAAiB,EACzB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,OAAO,GACpB,OAAO,CAMT"}
|
package/dist/routes/git.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Router } from 'express';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
|
-
import path from 'node:path';
|
|
9
8
|
import { promises as fs } from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
10
|
import { validateToken } from '../middleware/auth.js';
|
|
11
11
|
export const gitRouter = Router();
|
|
12
12
|
export async function assertGitWorkspacePathAccessible(workspacePath) {
|
|
@@ -25,6 +25,16 @@ export async function assertGitWorkspacePathAccessible(workspacePath) {
|
|
|
25
25
|
throw new Error(`workspace path is not a directory: ${workspacePath}`);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* 判断 Git diff 汇总行是否应该展示在差异树中。
|
|
30
|
+
* 普通 modified 且文本增删均为 0 通常是 filemode/元数据噪声,避免显示为“无差异内容”。
|
|
31
|
+
*/
|
|
32
|
+
export function shouldIncludeGitDiffSummaryFile(status, additions, deletions, isBinaryDiff) {
|
|
33
|
+
if (status === 'added' || status === 'deleted' || status === 'renamed') {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return isBinaryDiff || additions > 0 || deletions > 0;
|
|
37
|
+
}
|
|
28
38
|
/**
|
|
29
39
|
* 执行 git 命令并返回输出
|
|
30
40
|
*/
|
|
@@ -418,10 +428,14 @@ gitRouter.post('/git/diff', validateToken, async (req, res) => {
|
|
|
418
428
|
const parts = line.split('\t');
|
|
419
429
|
if (parts.length < 3)
|
|
420
430
|
continue;
|
|
431
|
+
const isBinaryDiff = parts[0] === '-' || parts[1] === '-';
|
|
421
432
|
const additions = parts[0] === '-' ? 0 : parseInt(parts[0], 10) || 0;
|
|
422
433
|
const deletions = parts[1] === '-' ? 0 : parseInt(parts[1], 10) || 0;
|
|
423
434
|
const filePath = parseRenameDisplayPath(parts.slice(2).join('\t').trim());
|
|
424
435
|
const status = statusMap.get(filePath) || 'modified';
|
|
436
|
+
if (!filePath || !shouldIncludeGitDiffSummaryFile(status, additions, deletions, isBinaryDiff)) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
425
439
|
files.push({ path: filePath, status, additions, deletions });
|
|
426
440
|
}
|
|
427
441
|
res.json({
|
package/dist/routes/git.test.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
-
import { describe,
|
|
8
|
-
import { assertGitWorkspacePathAccessible } from './git.js';
|
|
7
|
+
import { describe, expect, it } from 'vitest';
|
|
8
|
+
import { assertGitWorkspacePathAccessible, shouldIncludeGitDiffSummaryFile } from './git.js';
|
|
9
9
|
describe('git stash operations', () => {
|
|
10
10
|
function parseStashList(output) {
|
|
11
11
|
const stashes = [];
|
|
@@ -98,6 +98,21 @@ describe('git workspace path validation', () => {
|
|
|
98
98
|
}
|
|
99
99
|
});
|
|
100
100
|
});
|
|
101
|
+
describe('git diff summary visibility', () => {
|
|
102
|
+
it('filters ordinary modified files without textual changes', () => {
|
|
103
|
+
expect(shouldIncludeGitDiffSummaryFile('modified', 0, 0, false)).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
it('keeps modified files with text or binary changes', () => {
|
|
106
|
+
expect(shouldIncludeGitDiffSummaryFile('modified', 1, 0, false)).toBe(true);
|
|
107
|
+
expect(shouldIncludeGitDiffSummaryFile('modified', 0, 1, false)).toBe(true);
|
|
108
|
+
expect(shouldIncludeGitDiffSummaryFile('modified', 0, 0, true)).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
it('keeps structural status changes even when line counts are zero', () => {
|
|
111
|
+
expect(shouldIncludeGitDiffSummaryFile('added', 0, 0, false)).toBe(true);
|
|
112
|
+
expect(shouldIncludeGitDiffSummaryFile('deleted', 0, 0, false)).toBe(true);
|
|
113
|
+
expect(shouldIncludeGitDiffSummaryFile('renamed', 0, 0, false)).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
101
116
|
describe('error handling', () => {
|
|
102
117
|
it('detects "no local changes" error', () => {
|
|
103
118
|
const isNoChangesError = (stderr) => stderr.includes('No local changes to save');
|
|
@@ -111,7 +126,7 @@ describe('error handling', () => {
|
|
|
111
126
|
});
|
|
112
127
|
it('detects invalid stash reference', () => {
|
|
113
128
|
const isInvalidRef = (stderr) => stderr.includes('is not a valid reference');
|
|
114
|
-
expect(isInvalidRef(
|
|
129
|
+
expect(isInvalidRef('stash@{99} is not a valid reference')).toBe(true);
|
|
115
130
|
expect(isInvalidRef('other error')).toBe(false);
|
|
116
131
|
});
|
|
117
132
|
});
|
|
@@ -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;
|
|
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"}
|
package/dist/routes/instance.js
CHANGED
|
@@ -2,14 +2,14 @@ import { Router } from "express";
|
|
|
2
2
|
import axios from "axios";
|
|
3
3
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { validateToken } from "../middleware/auth.js";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveSchedulerBaseUrlFrom } from "../config.js";
|
|
6
6
|
import { getRuntimeAccessToken } from "../services/runtime-binding.js";
|
|
7
7
|
import { loadInstanceState, saveInstanceState, } from "../services/instance-state.js";
|
|
8
8
|
import { initInstance } from "../services/instance-init-service.js";
|
|
9
9
|
import { discoverCcSwitchConfiguredItems, loadCcSwitchSdk } from "../services/cc-switch-sdk.js";
|
|
10
10
|
import { syncLegacyStateFromSdk } from "../services/instance-init-service.js";
|
|
11
11
|
import { detectToolStatuses, ensureToolsInstalled, SUPPORTED_INSTALLABLE_TOOLS, SUPPORTED_UNINSTALLABLE_TOOLS, uninstallTools, } from "../services/tool-installer.js";
|
|
12
|
-
import { getConfiguredConnectionKeys } from "../services/auto-register.js";
|
|
12
|
+
import { getConfiguredConnectionKeys, requestRuntimeAccessTokenRefreshForServer } from "../services/auto-register.js";
|
|
13
13
|
import { createLogger } from "../utils/logger.js";
|
|
14
14
|
const log = createLogger("instance");
|
|
15
15
|
const PANEL_TOOL_STATUS_KEYS = ["claude", "opencode", "codex"];
|
|
@@ -92,6 +92,40 @@ export function buildSchedulerPingFailureResponse(error, configuredSchedulerBase
|
|
|
92
92
|
: "aws-runtime-bridge 已连通,但它回连调度中心失败。请确认 AWS_RUNTIME_SCHEDULER_BASE_URL 指向的调度中心地址可从 bridge 机器访问,并且运行时访问令牌仍有效。",
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
|
+
function getAxiosStatusCode(error) {
|
|
96
|
+
if (typeof error !== "object" || error === null) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
const response = Reflect.get(error, "response");
|
|
100
|
+
if (typeof response !== "object" || response === null) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
const status = Reflect.get(response, "status");
|
|
104
|
+
return typeof status === "number" ? status : undefined;
|
|
105
|
+
}
|
|
106
|
+
async function pingSchedulerWithToken(schedulerBaseUrl, runtimeAccessToken) {
|
|
107
|
+
try {
|
|
108
|
+
const schedulerResponse = await axios.get(`${schedulerBaseUrl}/api/runtime/ping`, { headers: { "X-Runtime-Token": runtimeAccessToken } });
|
|
109
|
+
return {
|
|
110
|
+
schedulerReachable: schedulerResponse.status >= 200 && schedulerResponse.status < 300,
|
|
111
|
+
tokenRefreshed: false,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (getAxiosStatusCode(error) !== 401) {
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
const refreshResult = await requestRuntimeAccessTokenRefreshForServer(schedulerBaseUrl);
|
|
119
|
+
if (!refreshResult.success || !refreshResult.runtimeAccessToken) {
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
const retryResponse = await axios.get(`${schedulerBaseUrl}/api/runtime/ping`, { headers: { "X-Runtime-Token": refreshResult.runtimeAccessToken } });
|
|
123
|
+
return {
|
|
124
|
+
schedulerReachable: retryResponse.status >= 200 && retryResponse.status < 300,
|
|
125
|
+
tokenRefreshed: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
95
129
|
/**
|
|
96
130
|
* Build the response for ambiguous scheduler targets.
|
|
97
131
|
* Main flow: never silently pick the first target; require an explicit scheduler URL or future scheduler identity routing.
|
|
@@ -154,8 +188,8 @@ instanceRouter.get("/connection-check", (req, res) => {
|
|
|
154
188
|
const result = buildConnectionCheckResponse(connectionKeyMd5);
|
|
155
189
|
res.status(result.status).json(result.body);
|
|
156
190
|
});
|
|
157
|
-
instanceRouter.get("/ping", validateToken, async (
|
|
158
|
-
const schedulerBaseUrlResolution =
|
|
191
|
+
instanceRouter.get("/ping", validateToken, async (req, res) => {
|
|
192
|
+
const schedulerBaseUrlResolution = resolveSchedulerBaseUrlFrom(req.header("X-Scheduler-Base-Url"));
|
|
159
193
|
if (schedulerBaseUrlResolution.source === "ambiguous-auto-register-targets") {
|
|
160
194
|
res
|
|
161
195
|
.status(400)
|
|
@@ -169,11 +203,12 @@ instanceRouter.get("/ping", validateToken, async (_req, res) => {
|
|
|
169
203
|
res.status(401).json({ ok: false, error: "runtime_access_token_required" });
|
|
170
204
|
return;
|
|
171
205
|
}
|
|
172
|
-
const
|
|
206
|
+
const schedulerPing = await pingSchedulerWithToken(schedulerBaseUrl, runtimeAccessToken);
|
|
173
207
|
res.json({
|
|
174
208
|
ok: true,
|
|
175
209
|
runtimeBridge: "healthy",
|
|
176
|
-
schedulerReachable:
|
|
210
|
+
schedulerReachable: schedulerPing.schedulerReachable,
|
|
211
|
+
tokenRefreshed: schedulerPing.tokenRefreshed,
|
|
177
212
|
schedulerBaseUrl,
|
|
178
213
|
});
|
|
179
214
|
}
|
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
* Instance 路由单元测试
|
|
3
3
|
*/
|
|
4
4
|
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
5
|
+
vi.mock('axios', () => ({
|
|
6
|
+
default: {
|
|
7
|
+
get: vi.fn(),
|
|
8
|
+
},
|
|
9
|
+
}));
|
|
5
10
|
vi.mock('../services/auto-register.js', () => ({
|
|
6
11
|
getConfiguredConnectionKeys: vi.fn(() => []),
|
|
12
|
+
requestRuntimeAccessTokenRefreshForServer: vi.fn(),
|
|
13
|
+
}));
|
|
14
|
+
vi.mock('../services/runtime-binding.js', () => ({
|
|
15
|
+
getRuntimeAccessToken: vi.fn(() => 'stale-runtime-token-123456'),
|
|
7
16
|
}));
|
|
8
17
|
afterEach(() => {
|
|
9
18
|
vi.clearAllMocks();
|
|
@@ -203,6 +212,67 @@ describe('instance route validation', () => {
|
|
|
203
212
|
expect(response.runtimeBridge).toBe('healthy');
|
|
204
213
|
expect(response.hint).toContain('运行时访问令牌仍有效');
|
|
205
214
|
});
|
|
215
|
+
it('refreshes runtime token and retries scheduler ping after 401', async () => {
|
|
216
|
+
process.env.AWS_RUNTIME_SCHEDULER_BASE_URL = 'http://localhost:8080';
|
|
217
|
+
const { default: axios } = await import('axios');
|
|
218
|
+
const { requestRuntimeAccessTokenRefreshForServer } = await import('../services/auto-register.js');
|
|
219
|
+
const { instanceRouter } = await import('./instance.js');
|
|
220
|
+
vi.mocked(requestRuntimeAccessTokenRefreshForServer).mockResolvedValue({
|
|
221
|
+
success: true,
|
|
222
|
+
runtimeAccessToken: 'fresh-runtime-token-123456',
|
|
223
|
+
updated: true,
|
|
224
|
+
});
|
|
225
|
+
vi.mocked(axios.get)
|
|
226
|
+
.mockRejectedValueOnce({ response: { status: 401 }, message: 'Request failed with status code 401' })
|
|
227
|
+
.mockResolvedValueOnce({ status: 200 });
|
|
228
|
+
const handler = instanceRouter.stack.find((layer) => layer.route?.path === '/ping')?.route?.stack[1]?.handle;
|
|
229
|
+
expect(handler).toBeTypeOf('function');
|
|
230
|
+
const json = vi.fn();
|
|
231
|
+
const status = vi.fn(() => ({ json }));
|
|
232
|
+
await handler?.({ header: vi.fn(() => '') }, { json, status }, vi.fn());
|
|
233
|
+
expect(requestRuntimeAccessTokenRefreshForServer).toHaveBeenCalledWith('http://localhost:8080');
|
|
234
|
+
expect(axios.get).toHaveBeenNthCalledWith(1, 'http://localhost:8080/api/runtime/ping', {
|
|
235
|
+
headers: { 'X-Runtime-Token': 'stale-runtime-token-123456' },
|
|
236
|
+
});
|
|
237
|
+
expect(axios.get).toHaveBeenNthCalledWith(2, 'http://localhost:8080/api/runtime/ping', {
|
|
238
|
+
headers: { 'X-Runtime-Token': 'fresh-runtime-token-123456' },
|
|
239
|
+
});
|
|
240
|
+
expect(json).toHaveBeenCalledWith({
|
|
241
|
+
ok: true,
|
|
242
|
+
runtimeBridge: 'healthy',
|
|
243
|
+
schedulerReachable: true,
|
|
244
|
+
tokenRefreshed: true,
|
|
245
|
+
schedulerBaseUrl: 'http://localhost:8080',
|
|
246
|
+
});
|
|
247
|
+
expect(status).not.toHaveBeenCalled();
|
|
248
|
+
});
|
|
249
|
+
it('uses scheduler base URL supplied by server ping request before local bridge config', async () => {
|
|
250
|
+
process.env.AWS_RUNTIME_SCHEDULER_BASE_URL = 'http://localhost:8080';
|
|
251
|
+
const { default: axios } = await import('axios');
|
|
252
|
+
const { getRuntimeAccessToken } = await import('../services/runtime-binding.js');
|
|
253
|
+
const { instanceRouter } = await import('./instance.js');
|
|
254
|
+
vi.mocked(getRuntimeAccessToken).mockReturnValue('stale-runtime-token-123456');
|
|
255
|
+
vi.mocked(axios.get).mockReset();
|
|
256
|
+
vi.mocked(axios.get).mockResolvedValueOnce({ status: 200 });
|
|
257
|
+
const handler = instanceRouter.stack.find((layer) => layer.route?.path === '/ping')?.route?.stack[1]?.handle;
|
|
258
|
+
expect(handler).toBeTypeOf('function');
|
|
259
|
+
const json = vi.fn();
|
|
260
|
+
const status = vi.fn(() => ({ json }));
|
|
261
|
+
const header = vi.fn((name) => (name === 'X-Scheduler-Base-Url' ? 'http://server.local:7380/api' : ''));
|
|
262
|
+
await handler?.({ header }, { json, status }, vi.fn());
|
|
263
|
+
expect(getRuntimeAccessToken).toHaveBeenCalledWith(undefined, 'http://server.local:7380');
|
|
264
|
+
expect(axios.get).toHaveBeenCalledWith('http://server.local:7380/api/runtime/ping', {
|
|
265
|
+
headers: { 'X-Runtime-Token': 'stale-runtime-token-123456' },
|
|
266
|
+
});
|
|
267
|
+
expect(json).toHaveBeenCalledWith({
|
|
268
|
+
ok: true,
|
|
269
|
+
runtimeBridge: 'healthy',
|
|
270
|
+
schedulerReachable: true,
|
|
271
|
+
tokenRefreshed: false,
|
|
272
|
+
schedulerBaseUrl: 'http://server.local:7380',
|
|
273
|
+
});
|
|
274
|
+
expect(status).not.toHaveBeenCalled();
|
|
275
|
+
});
|
|
206
276
|
it('explains ambiguous auto-register scheduler targets without picking the first one', async () => {
|
|
207
277
|
const { buildAmbiguousSchedulerBaseUrlResponse } = await import('./instance.js');
|
|
208
278
|
const response = buildAmbiguousSchedulerBaseUrlResponse([
|