@jingyi0605/codingns 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/bin/codingns.mjs +489 -1
- package/dist/public/assets/{TerminalPage-D00S4KM6.js → TerminalPage-6jHZV9Mh.js} +17 -17
- package/dist/public/assets/index-CSVhg7I8.js +123 -0
- package/dist/public/assets/index-Ce1VX19m.css +1 -0
- package/dist/public/index.html +2 -2
- package/dist/server/modules/assistant-capability/assistant-capability-controller.d.ts +173 -0
- package/dist/server/modules/assistant-capability/assistant-capability-controller.js +307 -0
- package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -1
- package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +199 -2
- package/dist/server/modules/assistant-capability/assistant-capability-service.js +565 -3
- package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -1
- package/dist/server/modules/butler/assistant-automation-service.d.ts +110 -0
- package/dist/server/modules/butler/assistant-automation-service.js +786 -0
- package/dist/server/modules/butler/assistant-automation-service.js.map +1 -0
- package/dist/server/modules/butler/assistant-automation-trigger.d.ts +94 -0
- package/dist/server/modules/butler/assistant-automation-trigger.js +400 -0
- package/dist/server/modules/butler/assistant-automation-trigger.js.map +1 -0
- package/dist/server/modules/butler/assistant-sandbox-service.d.ts +55 -0
- package/dist/server/modules/butler/assistant-sandbox-service.js +266 -0
- package/dist/server/modules/butler/assistant-sandbox-service.js.map +1 -0
- package/dist/server/modules/butler/butler-action-context-service.d.ts +4 -1
- package/dist/server/modules/butler/butler-action-context-service.js +8 -2
- package/dist/server/modules/butler/butler-action-context-service.js.map +1 -1
- package/dist/server/modules/butler/butler-control-session-service.d.ts +8 -1
- package/dist/server/modules/butler/butler-control-session-service.js +154 -40
- package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
- package/dist/server/modules/butler/butler-control-timer-scheduler.d.ts +32 -0
- package/dist/server/modules/butler/butler-control-timer-scheduler.js +93 -0
- package/dist/server/modules/butler/butler-control-timer-scheduler.js.map +1 -0
- package/dist/server/modules/butler/butler-control-timer-service.d.ts +42 -0
- package/dist/server/modules/butler/butler-control-timer-service.js +132 -0
- package/dist/server/modules/butler/butler-control-timer-service.js.map +1 -0
- package/dist/server/modules/butler/butler-controller.d.ts +42 -2
- package/dist/server/modules/butler/butler-controller.js +79 -12
- package/dist/server/modules/butler/butler-controller.js.map +1 -1
- package/dist/server/modules/butler/butler-follow-up-service.d.ts +9 -1
- package/dist/server/modules/butler/butler-follow-up-service.js +273 -181
- package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
- package/dist/server/modules/butler/butler-inbox-analysis-service.d.ts +4 -1
- package/dist/server/modules/butler/butler-inbox-analysis-service.js +18 -4
- package/dist/server/modules/butler/butler-inbox-analysis-service.js.map +1 -1
- package/dist/server/modules/butler/butler-profile-service.js +2 -5
- package/dist/server/modules/butler/butler-profile-service.js.map +1 -1
- package/dist/server/modules/butler/butler-project-service.d.ts +3 -1
- package/dist/server/modules/butler/butler-project-service.js +7 -1
- package/dist/server/modules/butler/butler-project-service.js.map +1 -1
- package/dist/server/modules/butler/butler-session-service.d.ts +3 -1
- package/dist/server/modules/butler/butler-session-service.js +12 -1
- package/dist/server/modules/butler/butler-session-service.js.map +1 -1
- package/dist/server/modules/butler/butler-session-summary-service.js +2 -1
- package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
- package/dist/server/modules/butler/butler-workspace-context.d.ts +3 -0
- package/dist/server/modules/butler/butler-workspace-context.js +164 -44
- package/dist/server/modules/butler/butler-workspace-context.js.map +1 -1
- package/dist/server/modules/butler/patrol-execution-service.js +2 -1
- package/dist/server/modules/butler/patrol-execution-service.js.map +1 -1
- package/dist/server/modules/butler/provider-adapter-registry.d.ts +3 -0
- package/dist/server/modules/butler/provider-adapter-registry.js +18 -1
- package/dist/server/modules/butler/provider-adapter-registry.js.map +1 -1
- package/dist/server/modules/butler/verification-run-service.d.ts +9 -2
- package/dist/server/modules/butler/verification-run-service.js +188 -34
- package/dist/server/modules/butler/verification-run-service.js.map +1 -1
- package/dist/server/modules/debug-target/debug-target-controller.js +1 -1
- package/dist/server/modules/debug-target/debug-target-controller.js.map +1 -1
- package/dist/server/modules/debug-target/debug-target-service.d.ts +7 -2
- package/dist/server/modules/debug-target/debug-target-service.js +563 -100
- package/dist/server/modules/debug-target/debug-target-service.js.map +1 -1
- package/dist/server/modules/git/git-command-helper-client.d.ts +1 -0
- package/dist/server/modules/git/git-command-helper-client.js +19 -26
- package/dist/server/modules/git/git-command-helper-client.js.map +1 -1
- package/dist/server/modules/git/git-command-runner.js +19 -1
- package/dist/server/modules/git/git-command-runner.js.map +1 -1
- package/dist/server/modules/preferences/profile-service.d.ts +3 -1
- package/dist/server/modules/preferences/profile-service.js +74 -3
- package/dist/server/modules/preferences/profile-service.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +5 -3
- package/dist/server/modules/provider/provider-discovery-helper-client.js +129 -43
- package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-helper-process.js +44 -0
- package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-runtime.js +83 -3
- package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -1
- package/dist/server/modules/sessions/claude-runtime-helper-client.js +23 -1
- package/dist/server/modules/sessions/claude-runtime-helper-client.js.map +1 -1
- package/dist/server/modules/sessions/session-history-service.d.ts +7 -1
- package/dist/server/modules/sessions/session-history-service.js +251 -41
- package/dist/server/modules/sessions/session-history-service.js.map +1 -1
- package/dist/server/modules/sessions/session-live-runtime-service.d.ts +6 -0
- package/dist/server/modules/sessions/session-live-runtime-service.js +97 -11
- package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
- package/dist/server/modules/sessions/session-message-origin-utils.d.ts +12 -0
- package/dist/server/modules/sessions/session-message-origin-utils.js +45 -0
- package/dist/server/modules/sessions/session-message-origin-utils.js.map +1 -0
- package/dist/server/modules/sessions/session-permission-request-service.js +167 -0
- package/dist/server/modules/sessions/session-permission-request-service.js.map +1 -1
- package/dist/server/modules/skills/builtin-skill-service.js +1 -1
- package/dist/server/modules/skills/builtin-skill-service.js.map +1 -1
- package/dist/server/modules/skills/builtin-skills/codingns-assistant/SKILL.md +19 -12
- package/dist/server/modules/skills/builtin-skills/codingns-assistant/references/cli-workflow.md +9 -3
- package/dist/server/modules/tasks/task-helper-client.d.ts +5 -2
- package/dist/server/modules/tasks/task-helper-client.js +118 -38
- package/dist/server/modules/tasks/task-helper-client.js.map +1 -1
- package/dist/server/modules/tasks/task-helper-process.js +94 -3
- package/dist/server/modules/tasks/task-helper-process.js.map +1 -1
- package/dist/server/modules/tasks/task-types.d.ts +3 -0
- package/dist/server/modules/tasks/task-types.js +4 -1
- package/dist/server/modules/tasks/task-types.js.map +1 -1
- package/dist/server/modules/terminal/command-template-service.d.ts +9 -0
- package/dist/server/modules/terminal/command-template-service.js +87 -5
- package/dist/server/modules/terminal/command-template-service.js.map +1 -1
- package/dist/server/modules/terminal/terminal-controller.d.ts +3 -0
- package/dist/server/modules/terminal/terminal-controller.js +41 -0
- package/dist/server/modules/terminal/terminal-controller.js.map +1 -1
- package/dist/server/modules/workbench/workbench-service.d.ts +3 -0
- package/dist/server/modules/workbench/workbench-service.js +4 -3
- package/dist/server/modules/workbench/workbench-service.js.map +1 -1
- package/dist/server/modules/workbench/workspace-file-watcher.d.ts +14 -6
- package/dist/server/modules/workbench/workspace-file-watcher.js +267 -57
- package/dist/server/modules/workbench/workspace-file-watcher.js.map +1 -1
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +2 -0
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +32 -3
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
- package/dist/server/modules/worktree/worktree-manager.d.ts +9 -1
- package/dist/server/modules/worktree/worktree-manager.js +9 -1
- package/dist/server/modules/worktree/worktree-manager.js.map +1 -1
- package/dist/server/routes/assistant.js +19 -0
- package/dist/server/routes/assistant.js.map +1 -1
- package/dist/server/routes/butler.js +5 -0
- package/dist/server/routes/butler.js.map +1 -1
- package/dist/server/server/create-server.d.ts +8 -0
- package/dist/server/server/create-server.js +36 -13
- package/dist/server/server/create-server.js.map +1 -1
- package/dist/server/storage/repositories/assistant-automation-run-repository.d.ts +12 -0
- package/dist/server/storage/repositories/assistant-automation-run-repository.js +139 -0
- package/dist/server/storage/repositories/assistant-automation-run-repository.js.map +1 -0
- package/dist/server/storage/repositories/assistant-automation-task-repository.d.ts +15 -0
- package/dist/server/storage/repositories/assistant-automation-task-repository.js +173 -0
- package/dist/server/storage/repositories/assistant-automation-task-repository.js.map +1 -0
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.d.ts +17 -0
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js +164 -0
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js.map +1 -0
- package/dist/server/storage/repositories/butler-control-session-repository.js +27 -3
- package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -1
- package/dist/server/storage/repositories/butler-control-timer-repository.d.ts +15 -0
- package/dist/server/storage/repositories/butler-control-timer-repository.js +157 -0
- package/dist/server/storage/repositories/butler-control-timer-repository.js.map +1 -0
- package/dist/server/storage/repositories/user-preference-profile-repository.js +6 -3
- package/dist/server/storage/repositories/user-preference-profile-repository.js.map +1 -1
- package/dist/server/storage/sqlite/client.js +239 -2
- package/dist/server/storage/sqlite/client.js.map +1 -1
- package/dist/server/storage/sqlite/schema.sql +107 -1
- package/dist/server/types/domain.d.ts +89 -2
- package/dist/server/ws/workbench-ws-hub.d.ts +14 -8
- package/dist/server/ws/workbench-ws-hub.js +299 -163
- package/dist/server/ws/workbench-ws-hub.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +4 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +111 -3
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +6 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +306 -31
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.d.ts +5 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +187 -26
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.d.ts +4 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +98 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +2 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +71 -8
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/utils.d.ts +1 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +4 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +44 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +9 -3
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +1 -0
- package/node_modules/@codingns/session-sync-core/dist/services.js +17 -8
- package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/types.d.ts +4 -0
- package/package.json +1 -1
- package/dist/public/assets/index-BlOinYqR.js +0 -122
- package/dist/public/assets/index-Dg_7g6lA.css +0 -1
|
@@ -4,7 +4,9 @@ import path from "node:path";
|
|
|
4
4
|
import { AppError } from "../../shared/errors/app-error.js";
|
|
5
5
|
import { createId } from "../../shared/utils/id.js";
|
|
6
6
|
import { nowIso } from "../../shared/utils/time.js";
|
|
7
|
+
import { DEFAULT_DEBUG_PORT_POOLS } from "../preferences/profile-service.js";
|
|
7
8
|
import { buildTemplateCommandLine, getShellEnterSequence } from "../terminal/terminal-shell.js";
|
|
9
|
+
import { discoverTemplateRuntimeStatuses } from "../terminal/template-port-runtime.js";
|
|
8
10
|
import { createTaskManager } from "../tasks/task-manager.js";
|
|
9
11
|
import { HOST_TASK_TYPES } from "../tasks/task-types.js";
|
|
10
12
|
import { FRAMEWORK_COMPATIBILITY_MATRIX, FRAMEWORK_COMPATIBILITY_MATRIX_VERSION, getFrameworkCompatibilityItem } from "./framework-compatibility-matrix.js";
|
|
@@ -21,10 +23,11 @@ export class DebugTargetService {
|
|
|
21
23
|
runtimeBindingRepository;
|
|
22
24
|
aiFallbackEditRepository;
|
|
23
25
|
terminalCommandTemplateRepository;
|
|
26
|
+
preferenceProfileService;
|
|
24
27
|
terminalService;
|
|
25
28
|
terminalInstanceRepository;
|
|
26
29
|
taskManager;
|
|
27
|
-
constructor(db, workspaceService, workspaceWorktreeRepository, debugTargetRepository, debugServiceRepository, frameworkAnalysisResultRepository, debugRuntimeSessionRepository, portLeaseRepository, runtimeBindingRepository, aiFallbackEditRepository, terminalCommandTemplateRepository, terminalService, terminalInstanceRepository, taskManager = createTaskManager()) {
|
|
30
|
+
constructor(db, workspaceService, workspaceWorktreeRepository, debugTargetRepository, debugServiceRepository, frameworkAnalysisResultRepository, debugRuntimeSessionRepository, portLeaseRepository, runtimeBindingRepository, aiFallbackEditRepository, terminalCommandTemplateRepository, preferenceProfileService, terminalService, terminalInstanceRepository, taskManager = createTaskManager()) {
|
|
28
31
|
this.db = db;
|
|
29
32
|
this.workspaceService = workspaceService;
|
|
30
33
|
this.workspaceWorktreeRepository = workspaceWorktreeRepository;
|
|
@@ -36,6 +39,7 @@ export class DebugTargetService {
|
|
|
36
39
|
this.runtimeBindingRepository = runtimeBindingRepository;
|
|
37
40
|
this.aiFallbackEditRepository = aiFallbackEditRepository;
|
|
38
41
|
this.terminalCommandTemplateRepository = terminalCommandTemplateRepository;
|
|
42
|
+
this.preferenceProfileService = preferenceProfileService;
|
|
39
43
|
this.terminalService = terminalService;
|
|
40
44
|
this.terminalInstanceRepository = terminalInstanceRepository;
|
|
41
45
|
this.taskManager = taskManager;
|
|
@@ -134,7 +138,94 @@ export class DebugTargetService {
|
|
|
134
138
|
items: FRAMEWORK_COMPATIBILITY_MATRIX
|
|
135
139
|
};
|
|
136
140
|
}
|
|
137
|
-
async createLaunchPlan(targetId, portRequests = []) {
|
|
141
|
+
async createLaunchPlan(targetId, portRequests = [], userId = null) {
|
|
142
|
+
return await this.createRegisteredTemplatePreviewPlan(targetId, portRequests, userId);
|
|
143
|
+
}
|
|
144
|
+
async run(input) {
|
|
145
|
+
const launchPlan = await this.createManagedRuntimeLaunchPlan(input.targetId, input.portRequests ?? [], input.userId);
|
|
146
|
+
if (!launchPlan.autoStartAllowed) {
|
|
147
|
+
const aiFallbackItem = launchPlan.services.find((item) => item.aiFallback?.eligible);
|
|
148
|
+
if (aiFallbackItem) {
|
|
149
|
+
await this.failRuntimePlan(launchPlan.runtimeSession.id, aiFallbackItem.failureStage ?? "ai_fallback_required", "当前服务需要进入受限 AI 兜底流程,自动运行已阻止", 409, "DEBUG_TARGET_AI_FALLBACK_REQUIRED");
|
|
150
|
+
}
|
|
151
|
+
await this.failRuntimePlan(launchPlan.runtimeSession.id, launchPlan.services.find((item) => item.failureStage)?.failureStage ?? "launch_requirements", "当前启动计划缺少必要的服务发现、HMR 或 callback 处理,暂不允许自动启动", 409, "DEBUG_TARGET_RUN_NOT_ALLOWED");
|
|
152
|
+
}
|
|
153
|
+
const startedServices = [];
|
|
154
|
+
try {
|
|
155
|
+
for (const item of launchPlan.services) {
|
|
156
|
+
const service = this.getServiceOrThrow(item.serviceId, input.targetId);
|
|
157
|
+
const analysis = this.frameworkAnalysisResultRepository
|
|
158
|
+
.listByTargetId(input.targetId)
|
|
159
|
+
.find((candidate) => candidate.id === item.frameworkAnalysisId) ?? null;
|
|
160
|
+
const terminal = await this.terminalService.createTerminal({
|
|
161
|
+
workspaceId: this.getTargetOrThrow(input.targetId).workspaceId,
|
|
162
|
+
name: `${service.name} 运行`,
|
|
163
|
+
cwd: service.cwd,
|
|
164
|
+
shell: input.shell,
|
|
165
|
+
runtimeType: input.runtimeType,
|
|
166
|
+
createdByUserId: input.userId,
|
|
167
|
+
env: {
|
|
168
|
+
...service.env,
|
|
169
|
+
...item.envPatch
|
|
170
|
+
},
|
|
171
|
+
debugRuntimeSessionId: launchPlan.runtimeSession.id,
|
|
172
|
+
debugTargetId: input.targetId,
|
|
173
|
+
debugServiceId: service.id,
|
|
174
|
+
frameworkAnalysisId: analysis?.id ?? null,
|
|
175
|
+
launcherSourceType: "debug_service",
|
|
176
|
+
launchStage: "command_dispatched",
|
|
177
|
+
failureStage: null,
|
|
178
|
+
adapterKind: item.adapterKind ?? null,
|
|
179
|
+
envPatchSummary: item.envPatch,
|
|
180
|
+
artifactRef: item.artifactRef
|
|
181
|
+
});
|
|
182
|
+
const commandLine = buildTemplateCommandLine(buildEphemeralTemplate(service, item, input.runtimeType ?? terminal.runtimeType), terminal.shell);
|
|
183
|
+
await this.terminalService.writeInput(terminal.id, `${commandLine}${getShellEnterSequence(terminal.shell)}`);
|
|
184
|
+
this.runtimeBindingRepository.update({
|
|
185
|
+
id: item.runtimeBindingId,
|
|
186
|
+
runtimeId: launchPlan.runtimeSession.id,
|
|
187
|
+
serviceId: service.id,
|
|
188
|
+
processInstanceId: terminal.id,
|
|
189
|
+
expectedPort: item.expectedPort,
|
|
190
|
+
leasedPort: item.leasedPort,
|
|
191
|
+
observedPort: null,
|
|
192
|
+
proxyPath: null,
|
|
193
|
+
status: "ALLOCATED",
|
|
194
|
+
updatedAt: nowIso()
|
|
195
|
+
});
|
|
196
|
+
startedServices.push({
|
|
197
|
+
serviceId: service.id,
|
|
198
|
+
processInstanceId: terminal.id,
|
|
199
|
+
terminalId: terminal.id,
|
|
200
|
+
leasedPort: item.leasedPort,
|
|
201
|
+
runtimeBindingId: item.runtimeBindingId
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
for (const service of startedServices) {
|
|
207
|
+
try {
|
|
208
|
+
await this.terminalService.closeTerminal(service.terminalId);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// 失败回滚阶段只做尽力而为,避免覆盖原始错误。
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
await this.failRuntimePlan(launchPlan.runtimeSession.id, "command_execution", error instanceof Error ? error.message : "调试目标启动失败", 500, "DEBUG_TARGET_RUN_FAILED");
|
|
215
|
+
}
|
|
216
|
+
const updatedRuntimeSession = this.debugRuntimeSessionRepository.update({
|
|
217
|
+
...launchPlan.runtimeSession,
|
|
218
|
+
status: "RUNNING",
|
|
219
|
+
failureStage: null,
|
|
220
|
+
startedAt: nowIso(),
|
|
221
|
+
updatedAt: nowIso()
|
|
222
|
+
});
|
|
223
|
+
return {
|
|
224
|
+
runtimeSession: updatedRuntimeSession ?? launchPlan.runtimeSession,
|
|
225
|
+
services: startedServices
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
async createManagedRuntimeLaunchPlan(targetId, portRequests, userId) {
|
|
138
229
|
const target = this.getTargetOrThrow(targetId);
|
|
139
230
|
const services = this.debugServiceRepository.listByTargetId(targetId);
|
|
140
231
|
const analyses = this.frameworkAnalysisResultRepository.listByTargetId(targetId);
|
|
@@ -166,6 +257,7 @@ export class DebugTargetService {
|
|
|
166
257
|
portRequests,
|
|
167
258
|
portLeaseRepository: this.portLeaseRepository
|
|
168
259
|
});
|
|
260
|
+
const portPoolConfig = this.resolvePortPoolConfig(userId);
|
|
169
261
|
for (const service of services) {
|
|
170
262
|
const analysis = analysisByServiceId.get(service.id) ?? analyses[0] ?? null;
|
|
171
263
|
if (!analysis) {
|
|
@@ -176,7 +268,7 @@ export class DebugTargetService {
|
|
|
176
268
|
});
|
|
177
269
|
}
|
|
178
270
|
const allocatedPort = requestedPorts.get(service.id)
|
|
179
|
-
?? await allocateManagedPort(service.role, this.portLeaseRepository);
|
|
271
|
+
?? await allocateManagedPort(service.role, this.portLeaseRepository, portPoolConfig);
|
|
180
272
|
const injectionPlan = resolveLaunchPlan({
|
|
181
273
|
targetRootPath: target.rootPath,
|
|
182
274
|
service,
|
|
@@ -269,90 +361,60 @@ export class DebugTargetService {
|
|
|
269
361
|
services: planItems
|
|
270
362
|
};
|
|
271
363
|
}
|
|
272
|
-
async
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const startedServices = [];
|
|
282
|
-
try {
|
|
283
|
-
for (const item of launchPlan.services) {
|
|
284
|
-
const service = this.getServiceOrThrow(item.serviceId, input.targetId);
|
|
285
|
-
const analysis = this.frameworkAnalysisResultRepository
|
|
286
|
-
.listByTargetId(input.targetId)
|
|
287
|
-
.find((candidate) => candidate.id === item.frameworkAnalysisId) ?? null;
|
|
288
|
-
const terminal = await this.terminalService.createTerminal({
|
|
289
|
-
workspaceId: this.getTargetOrThrow(input.targetId).workspaceId,
|
|
290
|
-
name: `${service.name} 运行`,
|
|
291
|
-
cwd: service.cwd,
|
|
292
|
-
shell: input.shell,
|
|
293
|
-
runtimeType: input.runtimeType,
|
|
294
|
-
createdByUserId: input.userId,
|
|
295
|
-
env: {
|
|
296
|
-
...service.env,
|
|
297
|
-
...item.envPatch
|
|
298
|
-
},
|
|
299
|
-
debugRuntimeSessionId: launchPlan.runtimeSession.id,
|
|
300
|
-
debugTargetId: input.targetId,
|
|
301
|
-
debugServiceId: service.id,
|
|
302
|
-
frameworkAnalysisId: analysis?.id ?? null,
|
|
303
|
-
launcherSourceType: "debug_service",
|
|
304
|
-
launchStage: "command_dispatched",
|
|
305
|
-
failureStage: null,
|
|
306
|
-
adapterKind: item.adapterKind ?? null,
|
|
307
|
-
envPatchSummary: item.envPatch,
|
|
308
|
-
artifactRef: item.artifactRef
|
|
309
|
-
});
|
|
310
|
-
const commandLine = buildTemplateCommandLine(buildEphemeralTemplate(service, item, input.runtimeType ?? terminal.runtimeType), terminal.shell);
|
|
311
|
-
await this.terminalService.writeInput(terminal.id, `${commandLine}${getShellEnterSequence(terminal.shell)}`);
|
|
312
|
-
this.runtimeBindingRepository.update({
|
|
313
|
-
id: item.runtimeBindingId,
|
|
314
|
-
runtimeId: launchPlan.runtimeSession.id,
|
|
315
|
-
serviceId: service.id,
|
|
316
|
-
processInstanceId: terminal.id,
|
|
317
|
-
expectedPort: item.expectedPort,
|
|
318
|
-
leasedPort: item.leasedPort,
|
|
319
|
-
observedPort: null,
|
|
320
|
-
proxyPath: null,
|
|
321
|
-
status: "ALLOCATED",
|
|
322
|
-
updatedAt: nowIso()
|
|
323
|
-
});
|
|
324
|
-
startedServices.push({
|
|
325
|
-
serviceId: service.id,
|
|
326
|
-
processInstanceId: terminal.id,
|
|
327
|
-
terminalId: terminal.id,
|
|
328
|
-
leasedPort: item.leasedPort,
|
|
329
|
-
runtimeBindingId: item.runtimeBindingId
|
|
330
|
-
});
|
|
331
|
-
}
|
|
364
|
+
async createRegisteredTemplatePreviewPlan(targetId, portRequests, userId) {
|
|
365
|
+
const target = this.getTargetOrThrow(targetId);
|
|
366
|
+
const analyses = this.frameworkAnalysisResultRepository.listByTargetId(targetId);
|
|
367
|
+
if (analyses.length === 0) {
|
|
368
|
+
throw new AppError({
|
|
369
|
+
statusCode: 409,
|
|
370
|
+
errorCode: "DEBUG_TARGET_ANALYSIS_REQUIRED",
|
|
371
|
+
detail: "当前调试目标还没有可用的框架分析结果,请先执行分析"
|
|
372
|
+
});
|
|
332
373
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
catch {
|
|
339
|
-
// 失败回滚阶段只做尽力而为,避免覆盖原始错误。
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
await this.failRuntimePlan(launchPlan.runtimeSession.id, "command_execution", error instanceof Error ? error.message : "调试目标启动失败", 500, "DEBUG_TARGET_RUN_FAILED");
|
|
374
|
+
const timestamp = nowIso();
|
|
375
|
+
const runtimeSession = buildPreviewRuntimeSession(target.id, timestamp);
|
|
376
|
+
const templates = listRegisteredTemplatesForTarget(this.terminalCommandTemplateRepository.listByWorkspace(target.workspaceId), target.rootPath);
|
|
377
|
+
if (templates.length === 0) {
|
|
378
|
+
return await this.createManagedRuntimeLaunchPlan(targetId, portRequests, userId);
|
|
343
379
|
}
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
380
|
+
const services = this.debugServiceRepository.listByTargetId(targetId);
|
|
381
|
+
const runtimeStatuses = await discoverTemplateRuntimeStatuses(templates
|
|
382
|
+
.filter((template) => template.port !== null)
|
|
383
|
+
.map((template) => ({
|
|
384
|
+
templateId: template.id,
|
|
385
|
+
port: template.port
|
|
386
|
+
})));
|
|
387
|
+
const runtimeStatusByTemplateId = new Map(runtimeStatuses.map((item) => [item.templateId, item]));
|
|
388
|
+
const portPoolConfig = this.resolvePortPoolConfig(userId);
|
|
389
|
+
const requestedPorts = await resolveTemplateRequestedPorts({
|
|
390
|
+
targetRootPath: target.rootPath,
|
|
391
|
+
templates,
|
|
392
|
+
portRequests,
|
|
393
|
+
portLeaseRepository: this.portLeaseRepository
|
|
394
|
+
});
|
|
395
|
+
const templatePlans = await buildRegisteredTemplatePlanItems({
|
|
396
|
+
target,
|
|
397
|
+
templates,
|
|
398
|
+
services,
|
|
399
|
+
analyses,
|
|
400
|
+
runtimeStatusByTemplateId,
|
|
401
|
+
requestedPorts,
|
|
402
|
+
portPoolConfig,
|
|
403
|
+
portLeaseRepository: this.portLeaseRepository
|
|
350
404
|
});
|
|
351
405
|
return {
|
|
352
|
-
runtimeSession
|
|
353
|
-
|
|
406
|
+
runtimeSession,
|
|
407
|
+
targetId: target.id,
|
|
408
|
+
autoStartAllowed: templatePlans.length > 0 && templatePlans.every((item) => item.autoStartAllowed),
|
|
409
|
+
services: templatePlans
|
|
354
410
|
};
|
|
355
411
|
}
|
|
412
|
+
resolvePortPoolConfig(userId) {
|
|
413
|
+
if (!userId) {
|
|
414
|
+
return clonePortPoolConfig(DEFAULT_DEBUG_PORT_POOLS);
|
|
415
|
+
}
|
|
416
|
+
return clonePortPoolConfig(this.preferenceProfileService.getProfile(userId).debugPortPools);
|
|
417
|
+
}
|
|
356
418
|
async handleTerminalExit(event) {
|
|
357
419
|
const runtimeId = event.terminal.debugRuntimeSessionId ?? null;
|
|
358
420
|
if (!runtimeId) {
|
|
@@ -1558,6 +1620,394 @@ function resolveMissingRequirements(analysis) {
|
|
|
1558
1620
|
function isFrameworkEligible(level) {
|
|
1559
1621
|
return level === "supported" || level === "conditional";
|
|
1560
1622
|
}
|
|
1623
|
+
async function buildRegisteredTemplatePlanItems(input) {
|
|
1624
|
+
const matchedContexts = input.templates.map((template) => matchTemplateAnalysisContext(template, input.services, input.analyses));
|
|
1625
|
+
const configuredPortCount = new Map();
|
|
1626
|
+
const plannedPortByTemplateId = new Map();
|
|
1627
|
+
const usedPorts = new Set();
|
|
1628
|
+
for (const template of input.templates) {
|
|
1629
|
+
if (template.port !== null) {
|
|
1630
|
+
configuredPortCount.set(template.port, (configuredPortCount.get(template.port) ?? 0) + 1);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
for (const context of matchedContexts) {
|
|
1634
|
+
const requestedPort = input.requestedPorts.get(context.template.id);
|
|
1635
|
+
if (requestedPort !== undefined) {
|
|
1636
|
+
plannedPortByTemplateId.set(context.template.id, requestedPort);
|
|
1637
|
+
usedPorts.add(requestedPort);
|
|
1638
|
+
continue;
|
|
1639
|
+
}
|
|
1640
|
+
if (context.template.port === null) {
|
|
1641
|
+
plannedPortByTemplateId.set(context.template.id, null);
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
const runtimeStatus = input.runtimeStatusByTemplateId.get(context.template.id) ?? null;
|
|
1645
|
+
const needsOrchestration = input.target.sourceType === "worktree"
|
|
1646
|
+
|| (configuredPortCount.get(context.template.port) ?? 0) > 1
|
|
1647
|
+
|| runtimeStatus?.occupied === true
|
|
1648
|
+
|| input.portLeaseRepository.findActiveByPort(context.template.port, "tcp") !== null
|
|
1649
|
+
|| usedPorts.has(context.template.port);
|
|
1650
|
+
if (!needsOrchestration) {
|
|
1651
|
+
plannedPortByTemplateId.set(context.template.id, context.template.port);
|
|
1652
|
+
usedPorts.add(context.template.port);
|
|
1653
|
+
continue;
|
|
1654
|
+
}
|
|
1655
|
+
const allocatedPort = await allocateManagedPort(context.role, input.portLeaseRepository, input.portPoolConfig, usedPorts);
|
|
1656
|
+
plannedPortByTemplateId.set(context.template.id, allocatedPort);
|
|
1657
|
+
usedPorts.add(allocatedPort);
|
|
1658
|
+
}
|
|
1659
|
+
const backendEndpoint = resolveRegisteredTemplateBackendEndpoint(matchedContexts, plannedPortByTemplateId);
|
|
1660
|
+
return matchedContexts.map((context) => {
|
|
1661
|
+
const plannedPort = plannedPortByTemplateId.get(context.template.id) ?? null;
|
|
1662
|
+
const runtimeBindingId = createId();
|
|
1663
|
+
const analysis = context.analysis;
|
|
1664
|
+
if (!analysis) {
|
|
1665
|
+
return {
|
|
1666
|
+
serviceId: context.template.id,
|
|
1667
|
+
role: context.role,
|
|
1668
|
+
frameworkAnalysisId: null,
|
|
1669
|
+
primaryFramework: null,
|
|
1670
|
+
compatibilityLevel: "unknown",
|
|
1671
|
+
adapterKind: context.template.adapterKind ?? null,
|
|
1672
|
+
injectionMode: context.template.injectionMode ?? null,
|
|
1673
|
+
command: context.template.command,
|
|
1674
|
+
args: context.template.args,
|
|
1675
|
+
envPatch: {},
|
|
1676
|
+
expectedPort: context.template.port,
|
|
1677
|
+
leasedPort: plannedPort,
|
|
1678
|
+
artifactRef: context.template.generatedArtifactRef ?? null,
|
|
1679
|
+
runtimeBindingId,
|
|
1680
|
+
portLeaseId: null,
|
|
1681
|
+
requiresServiceDiscoveryHandling: false,
|
|
1682
|
+
requiresHmrHandling: false,
|
|
1683
|
+
requiresCallbackHandling: false,
|
|
1684
|
+
failureStage: "framework_analysis_missing",
|
|
1685
|
+
adapterAttempts: [],
|
|
1686
|
+
aiFallback: null,
|
|
1687
|
+
missingRequirements: ["analysis"],
|
|
1688
|
+
autoStartAllowed: false
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
if (plannedPort === null) {
|
|
1692
|
+
return {
|
|
1693
|
+
serviceId: context.template.id,
|
|
1694
|
+
role: context.role,
|
|
1695
|
+
frameworkAnalysisId: analysis.id,
|
|
1696
|
+
primaryFramework: analysis.primaryFramework ?? null,
|
|
1697
|
+
compatibilityLevel: analysis.compatibilityLevel,
|
|
1698
|
+
adapterKind: context.template.adapterKind ?? null,
|
|
1699
|
+
injectionMode: context.template.injectionMode ?? null,
|
|
1700
|
+
command: context.template.command,
|
|
1701
|
+
args: context.template.args,
|
|
1702
|
+
envPatch: {},
|
|
1703
|
+
expectedPort: context.template.port,
|
|
1704
|
+
leasedPort: null,
|
|
1705
|
+
artifactRef: context.template.generatedArtifactRef ?? null,
|
|
1706
|
+
runtimeBindingId,
|
|
1707
|
+
portLeaseId: null,
|
|
1708
|
+
requiresServiceDiscoveryHandling: analysis.requiresServiceDiscoveryHandling,
|
|
1709
|
+
requiresHmrHandling: analysis.requiresHmrHandling,
|
|
1710
|
+
requiresCallbackHandling: analysis.requiresCallbackHandling,
|
|
1711
|
+
failureStage: "port_unconfigured",
|
|
1712
|
+
adapterAttempts: [],
|
|
1713
|
+
aiFallback: null,
|
|
1714
|
+
missingRequirements: ["port"],
|
|
1715
|
+
autoStartAllowed: false
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
const injectionPlan = resolveLaunchPlan({
|
|
1719
|
+
targetRootPath: input.target.rootPath,
|
|
1720
|
+
service: buildTemplateBackedDebugService(context.template, context.role, analysis.id),
|
|
1721
|
+
analysis,
|
|
1722
|
+
leasedPort: plannedPort
|
|
1723
|
+
});
|
|
1724
|
+
const serviceDiscoveryEnvPatch = buildServiceDiscoveryEnvPatch({
|
|
1725
|
+
role: context.role,
|
|
1726
|
+
analysis,
|
|
1727
|
+
backendEndpoint
|
|
1728
|
+
});
|
|
1729
|
+
const missingRequirements = resolveRegisteredTemplateMissingRequirements(analysis, {
|
|
1730
|
+
role: context.role,
|
|
1731
|
+
backendEndpoint
|
|
1732
|
+
});
|
|
1733
|
+
const envPatch = {
|
|
1734
|
+
...injectionPlan.envPatch,
|
|
1735
|
+
...serviceDiscoveryEnvPatch
|
|
1736
|
+
};
|
|
1737
|
+
const autoStartAllowed = isFrameworkEligible(analysis.compatibilityLevel)
|
|
1738
|
+
&& missingRequirements.length === 0
|
|
1739
|
+
&& injectionPlan.adapterKind !== "ai_fallback"
|
|
1740
|
+
&& injectionPlan.adapterKind !== null;
|
|
1741
|
+
return {
|
|
1742
|
+
serviceId: context.template.id,
|
|
1743
|
+
role: context.role,
|
|
1744
|
+
frameworkAnalysisId: analysis.id,
|
|
1745
|
+
primaryFramework: analysis.primaryFramework ?? null,
|
|
1746
|
+
compatibilityLevel: analysis.compatibilityLevel,
|
|
1747
|
+
adapterKind: injectionPlan.adapterKind ?? null,
|
|
1748
|
+
injectionMode: injectionPlan.injectionMode ?? null,
|
|
1749
|
+
command: context.template.command,
|
|
1750
|
+
args: injectionPlan.args,
|
|
1751
|
+
envPatch,
|
|
1752
|
+
expectedPort: context.template.port,
|
|
1753
|
+
leasedPort: plannedPort,
|
|
1754
|
+
artifactRef: injectionPlan.artifactRef ?? context.template.generatedArtifactRef ?? null,
|
|
1755
|
+
runtimeBindingId,
|
|
1756
|
+
portLeaseId: null,
|
|
1757
|
+
requiresServiceDiscoveryHandling: analysis.requiresServiceDiscoveryHandling,
|
|
1758
|
+
requiresHmrHandling: analysis.requiresHmrHandling,
|
|
1759
|
+
requiresCallbackHandling: analysis.requiresCallbackHandling,
|
|
1760
|
+
failureStage: injectionPlan.failureStage,
|
|
1761
|
+
adapterAttempts: injectionPlan.adapterAttempts,
|
|
1762
|
+
aiFallback: injectionPlan.aiFallback,
|
|
1763
|
+
missingRequirements,
|
|
1764
|
+
autoStartAllowed
|
|
1765
|
+
};
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
function buildPreviewRuntimeSession(targetId, timestamp) {
|
|
1769
|
+
return {
|
|
1770
|
+
id: `preview-${createId()}`,
|
|
1771
|
+
targetId,
|
|
1772
|
+
status: "PREPARING",
|
|
1773
|
+
failureStage: null,
|
|
1774
|
+
startedAt: null,
|
|
1775
|
+
stoppedAt: null,
|
|
1776
|
+
createdAt: timestamp,
|
|
1777
|
+
updatedAt: timestamp
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
function listRegisteredTemplatesForTarget(templates, rootPath) {
|
|
1781
|
+
return templates
|
|
1782
|
+
.filter((template) => template.managedBySystem !== true)
|
|
1783
|
+
.filter((template) => isTemplateRelevantToRootPath(template, rootPath))
|
|
1784
|
+
.sort((left, right) => compareTemplatePriority(left, right, rootPath));
|
|
1785
|
+
}
|
|
1786
|
+
function matchTemplateAnalysisContext(template, services, analyses) {
|
|
1787
|
+
const analysisByServiceId = new Map(analyses
|
|
1788
|
+
.filter((analysis) => analysis.serviceId)
|
|
1789
|
+
.map((analysis) => [analysis.serviceId, analysis]));
|
|
1790
|
+
const normalizedTemplateCwd = path.resolve(template.cwd);
|
|
1791
|
+
const templateRole = inferTemplateRole(template);
|
|
1792
|
+
const matchedService = [...services]
|
|
1793
|
+
.sort((left, right) => compareServiceMatchPriority(left, right, normalizedTemplateCwd, templateRole))
|
|
1794
|
+
.find((service) => {
|
|
1795
|
+
const serviceCwd = path.resolve(service.cwd);
|
|
1796
|
+
return serviceCwd === normalizedTemplateCwd
|
|
1797
|
+
|| isPathWithin(normalizedTemplateCwd, serviceCwd)
|
|
1798
|
+
|| isPathWithin(serviceCwd, normalizedTemplateCwd);
|
|
1799
|
+
}) ?? null;
|
|
1800
|
+
const analysis = matchedService ? analysisByServiceId.get(matchedService.id) ?? null : null;
|
|
1801
|
+
return {
|
|
1802
|
+
template,
|
|
1803
|
+
role: matchedService?.role ?? templateRole,
|
|
1804
|
+
service: matchedService,
|
|
1805
|
+
analysis
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
function compareServiceMatchPriority(left, right, templateCwd, templateRole) {
|
|
1809
|
+
const leftExact = Number(path.resolve(left.cwd) === templateCwd);
|
|
1810
|
+
const rightExact = Number(path.resolve(right.cwd) === templateCwd);
|
|
1811
|
+
if (rightExact !== leftExact) {
|
|
1812
|
+
return rightExact - leftExact;
|
|
1813
|
+
}
|
|
1814
|
+
const leftRoleMatch = Number(left.role === templateRole);
|
|
1815
|
+
const rightRoleMatch = Number(right.role === templateRole);
|
|
1816
|
+
if (rightRoleMatch !== leftRoleMatch) {
|
|
1817
|
+
return rightRoleMatch - leftRoleMatch;
|
|
1818
|
+
}
|
|
1819
|
+
const leftDistance = resolveRelativePathDistance(templateCwd, left.cwd);
|
|
1820
|
+
const rightDistance = resolveRelativePathDistance(templateCwd, right.cwd);
|
|
1821
|
+
if (leftDistance !== rightDistance) {
|
|
1822
|
+
return leftDistance - rightDistance;
|
|
1823
|
+
}
|
|
1824
|
+
return left.name.localeCompare(right.name);
|
|
1825
|
+
}
|
|
1826
|
+
function resolveRelativePathDistance(leftPath, rightPath) {
|
|
1827
|
+
const normalizedLeft = withTrailingSeparator(path.resolve(leftPath));
|
|
1828
|
+
const normalizedRight = withTrailingSeparator(path.resolve(rightPath));
|
|
1829
|
+
if (normalizedLeft === normalizedRight) {
|
|
1830
|
+
return 0;
|
|
1831
|
+
}
|
|
1832
|
+
if (normalizedLeft.startsWith(normalizedRight) || normalizedRight.startsWith(normalizedLeft)) {
|
|
1833
|
+
return 1;
|
|
1834
|
+
}
|
|
1835
|
+
return 2;
|
|
1836
|
+
}
|
|
1837
|
+
function inferTemplateRole(template) {
|
|
1838
|
+
const signal = [
|
|
1839
|
+
template.name,
|
|
1840
|
+
template.cwd,
|
|
1841
|
+
template.command,
|
|
1842
|
+
...template.args
|
|
1843
|
+
].join(" ").toLowerCase();
|
|
1844
|
+
if (signal.includes("frontend")
|
|
1845
|
+
|| signal.includes("web")
|
|
1846
|
+
|| signal.includes("ui")
|
|
1847
|
+
|| signal.includes("vite")
|
|
1848
|
+
|| signal.includes("next")
|
|
1849
|
+
|| signal.includes("astro")
|
|
1850
|
+
|| signal.includes("nuxt")
|
|
1851
|
+
|| signal.includes("vue")
|
|
1852
|
+
|| signal.includes("remix")) {
|
|
1853
|
+
return "frontend";
|
|
1854
|
+
}
|
|
1855
|
+
if (signal.includes("worker")) {
|
|
1856
|
+
return "worker";
|
|
1857
|
+
}
|
|
1858
|
+
if (signal.includes("mock")) {
|
|
1859
|
+
return "mock";
|
|
1860
|
+
}
|
|
1861
|
+
if (signal.includes("backend")
|
|
1862
|
+
|| signal.includes("server")
|
|
1863
|
+
|| signal.includes("api")
|
|
1864
|
+
|| signal.includes("host")
|
|
1865
|
+
|| signal.includes("express")
|
|
1866
|
+
|| signal.includes("nestjs")
|
|
1867
|
+
|| signal.includes("koa")
|
|
1868
|
+
|| signal.includes("hono")
|
|
1869
|
+
|| signal.includes("uvicorn")
|
|
1870
|
+
|| signal.includes("flask")
|
|
1871
|
+
|| signal.includes("django")
|
|
1872
|
+
|| signal.includes("spring")
|
|
1873
|
+
|| signal.includes("rails")
|
|
1874
|
+
|| signal.includes("dotnet")) {
|
|
1875
|
+
return "backend";
|
|
1876
|
+
}
|
|
1877
|
+
return "custom";
|
|
1878
|
+
}
|
|
1879
|
+
function buildTemplateBackedDebugService(template, role, frameworkAnalysisId) {
|
|
1880
|
+
return {
|
|
1881
|
+
id: template.id,
|
|
1882
|
+
targetId: template.debugTargetId ?? "",
|
|
1883
|
+
role,
|
|
1884
|
+
name: template.name,
|
|
1885
|
+
cwd: template.cwd,
|
|
1886
|
+
command: template.command,
|
|
1887
|
+
args: template.args,
|
|
1888
|
+
env: template.env,
|
|
1889
|
+
defaultPortHint: template.port,
|
|
1890
|
+
protocol: "http",
|
|
1891
|
+
healthPath: null,
|
|
1892
|
+
adapterKind: template.adapterKind ?? null,
|
|
1893
|
+
frameworkAnalysisId,
|
|
1894
|
+
createdAt: template.createdAt,
|
|
1895
|
+
updatedAt: template.updatedAt
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
function resolveRegisteredTemplateBackendEndpoint(contexts, plannedPortByTemplateId) {
|
|
1899
|
+
const candidate = contexts.find((context) => context.role === "backend" && plannedPortByTemplateId.get(context.template.id) !== null);
|
|
1900
|
+
if (!candidate) {
|
|
1901
|
+
return null;
|
|
1902
|
+
}
|
|
1903
|
+
return `http://127.0.0.1:${plannedPortByTemplateId.get(candidate.template.id)}`;
|
|
1904
|
+
}
|
|
1905
|
+
function buildServiceDiscoveryEnvPatch(input) {
|
|
1906
|
+
if (input.role === "backend" || !input.analysis.requiresServiceDiscoveryHandling || !input.backendEndpoint) {
|
|
1907
|
+
return {};
|
|
1908
|
+
}
|
|
1909
|
+
switch (input.analysis.primaryFramework) {
|
|
1910
|
+
case "vite":
|
|
1911
|
+
return {
|
|
1912
|
+
VITE_API_BASE_URL: input.backendEndpoint,
|
|
1913
|
+
API_BASE_URL: input.backendEndpoint
|
|
1914
|
+
};
|
|
1915
|
+
case "nextjs":
|
|
1916
|
+
return {
|
|
1917
|
+
NEXT_PUBLIC_API_BASE_URL: input.backendEndpoint,
|
|
1918
|
+
API_BASE_URL: input.backendEndpoint
|
|
1919
|
+
};
|
|
1920
|
+
case "cra":
|
|
1921
|
+
return {
|
|
1922
|
+
REACT_APP_API_BASE_URL: input.backendEndpoint,
|
|
1923
|
+
API_BASE_URL: input.backendEndpoint
|
|
1924
|
+
};
|
|
1925
|
+
case "astro":
|
|
1926
|
+
return {
|
|
1927
|
+
PUBLIC_API_BASE_URL: input.backendEndpoint,
|
|
1928
|
+
API_BASE_URL: input.backendEndpoint
|
|
1929
|
+
};
|
|
1930
|
+
case "nuxt":
|
|
1931
|
+
return {
|
|
1932
|
+
NUXT_PUBLIC_API_BASE_URL: input.backendEndpoint,
|
|
1933
|
+
API_BASE_URL: input.backendEndpoint
|
|
1934
|
+
};
|
|
1935
|
+
case "vue-cli":
|
|
1936
|
+
return {
|
|
1937
|
+
VUE_APP_API_BASE_URL: input.backendEndpoint,
|
|
1938
|
+
API_BASE_URL: input.backendEndpoint
|
|
1939
|
+
};
|
|
1940
|
+
case "remix":
|
|
1941
|
+
return {
|
|
1942
|
+
API_BASE_URL: input.backendEndpoint,
|
|
1943
|
+
PUBLIC_API_BASE_URL: input.backendEndpoint
|
|
1944
|
+
};
|
|
1945
|
+
default:
|
|
1946
|
+
return {
|
|
1947
|
+
API_BASE_URL: input.backendEndpoint,
|
|
1948
|
+
BACKEND_BASE_URL: input.backendEndpoint
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
function resolveRegisteredTemplateMissingRequirements(analysis, input) {
|
|
1953
|
+
const missing = [];
|
|
1954
|
+
if (analysis.requiresServiceDiscoveryHandling
|
|
1955
|
+
&& input.role !== "backend"
|
|
1956
|
+
&& !input.backendEndpoint) {
|
|
1957
|
+
missing.push("service_discovery");
|
|
1958
|
+
}
|
|
1959
|
+
if (analysis.requiresCallbackHandling) {
|
|
1960
|
+
missing.push("callback");
|
|
1961
|
+
}
|
|
1962
|
+
return missing;
|
|
1963
|
+
}
|
|
1964
|
+
async function resolveTemplateRequestedPorts(input) {
|
|
1965
|
+
const requestedPorts = new Map();
|
|
1966
|
+
const usedPorts = new Map();
|
|
1967
|
+
for (const request of input.portRequests) {
|
|
1968
|
+
validatePortRequestShape(request);
|
|
1969
|
+
const matches = input.templates.filter((template) => matchesTemplatePortRequest(template, input.targetRootPath, request));
|
|
1970
|
+
if (matches.length === 0) {
|
|
1971
|
+
throw new AppError({
|
|
1972
|
+
statusCode: 400,
|
|
1973
|
+
errorCode: "DEBUG_TARGET_PORT_REQUEST_UNMATCHED",
|
|
1974
|
+
detail: `显式端口请求没有匹配到任何启动项:${describePortRequest(request)}`,
|
|
1975
|
+
field: "portRequests"
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
if (matches.length > 1) {
|
|
1979
|
+
throw new AppError({
|
|
1980
|
+
statusCode: 409,
|
|
1981
|
+
errorCode: "DEBUG_TARGET_PORT_REQUEST_AMBIGUOUS",
|
|
1982
|
+
detail: `显式端口请求匹配到了多个启动项:${describePortRequest(request)}`,
|
|
1983
|
+
field: "portRequests"
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
const matchedTemplate = matches[0];
|
|
1987
|
+
const previousPort = requestedPorts.get(matchedTemplate.id);
|
|
1988
|
+
if (previousPort !== undefined && previousPort !== request.port) {
|
|
1989
|
+
throw new AppError({
|
|
1990
|
+
statusCode: 409,
|
|
1991
|
+
errorCode: "DEBUG_TARGET_PORT_REQUEST_CONFLICT",
|
|
1992
|
+
detail: `同一个启动项收到了多个不同的显式端口请求:${matchedTemplate.name}`,
|
|
1993
|
+
field: "portRequests"
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
const occupiedBy = usedPorts.get(request.port);
|
|
1997
|
+
if (occupiedBy && occupiedBy !== matchedTemplate.id) {
|
|
1998
|
+
throw new AppError({
|
|
1999
|
+
statusCode: 409,
|
|
2000
|
+
errorCode: "DEBUG_TARGET_PORT_REQUEST_CONFLICT",
|
|
2001
|
+
detail: `多个启动项请求了同一个端口:${request.port}`,
|
|
2002
|
+
field: "portRequests"
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
await ensureRequestedPortAvailable(request.port, inferTemplateRole(matchedTemplate), input.portLeaseRepository);
|
|
2006
|
+
requestedPorts.set(matchedTemplate.id, request.port);
|
|
2007
|
+
usedPorts.set(request.port, matchedTemplate.id);
|
|
2008
|
+
}
|
|
2009
|
+
return requestedPorts;
|
|
2010
|
+
}
|
|
1561
2011
|
async function resolveRequestedPorts(input) {
|
|
1562
2012
|
const requestedPorts = new Map();
|
|
1563
2013
|
const usedPorts = new Map();
|
|
@@ -1605,10 +2055,12 @@ async function resolveRequestedPorts(input) {
|
|
|
1605
2055
|
}
|
|
1606
2056
|
return requestedPorts;
|
|
1607
2057
|
}
|
|
1608
|
-
async function allocateManagedPort(role, portLeaseRepository) {
|
|
1609
|
-
const
|
|
1610
|
-
for (let
|
|
1611
|
-
|
|
2058
|
+
async function allocateManagedPort(role, portLeaseRepository, portPoolConfig, usedPorts = new Set()) {
|
|
2059
|
+
const range = portPoolConfig;
|
|
2060
|
+
for (let port = range.start; port <= range.end; port += 1) {
|
|
2061
|
+
if (usedPorts.has(port)) {
|
|
2062
|
+
continue;
|
|
2063
|
+
}
|
|
1612
2064
|
if (portLeaseRepository.findActiveByPort(port, "tcp")) {
|
|
1613
2065
|
continue;
|
|
1614
2066
|
}
|
|
@@ -1620,7 +2072,7 @@ async function allocateManagedPort(role, portLeaseRepository) {
|
|
|
1620
2072
|
throw new AppError({
|
|
1621
2073
|
statusCode: 409,
|
|
1622
2074
|
errorCode: "PORT_LEASE_EXHAUSTED",
|
|
1623
|
-
detail: `当前服务角色没有可分配的空闲端口:${role}
|
|
2075
|
+
detail: `当前服务角色没有可分配的空闲端口:${role}(范围 ${range.start}-${range.end})`
|
|
1624
2076
|
});
|
|
1625
2077
|
}
|
|
1626
2078
|
async function ensureRequestedPortAvailable(port, role, portLeaseRepository) {
|
|
@@ -1641,20 +2093,6 @@ async function ensureRequestedPortAvailable(port, role, portLeaseRepository) {
|
|
|
1641
2093
|
});
|
|
1642
2094
|
}
|
|
1643
2095
|
}
|
|
1644
|
-
function resolvePortRangeStart(role) {
|
|
1645
|
-
switch (role) {
|
|
1646
|
-
case "frontend":
|
|
1647
|
-
return 43000;
|
|
1648
|
-
case "backend":
|
|
1649
|
-
return 44000;
|
|
1650
|
-
case "worker":
|
|
1651
|
-
return 45000;
|
|
1652
|
-
case "mock":
|
|
1653
|
-
return 46000;
|
|
1654
|
-
default:
|
|
1655
|
-
return 47000;
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
2096
|
async function isPortAvailable(port) {
|
|
1659
2097
|
return await new Promise((resolve) => {
|
|
1660
2098
|
const server = net.createServer();
|
|
@@ -1746,6 +2184,31 @@ function matchesPortRequest(service, targetRootPath, request) {
|
|
|
1746
2184
|
}
|
|
1747
2185
|
return true;
|
|
1748
2186
|
}
|
|
2187
|
+
function matchesTemplatePortRequest(template, targetRootPath, request) {
|
|
2188
|
+
if (request.serviceId?.trim() && request.serviceId.trim() !== template.id) {
|
|
2189
|
+
return false;
|
|
2190
|
+
}
|
|
2191
|
+
if (request.role?.trim() && request.role.trim() !== inferTemplateRole(template)) {
|
|
2192
|
+
return false;
|
|
2193
|
+
}
|
|
2194
|
+
if (request.name?.trim() && request.name.trim() !== template.name) {
|
|
2195
|
+
return false;
|
|
2196
|
+
}
|
|
2197
|
+
if (request.command?.trim() && request.command.trim() !== template.command) {
|
|
2198
|
+
return false;
|
|
2199
|
+
}
|
|
2200
|
+
if (request.cwd?.trim()) {
|
|
2201
|
+
const selectorPath = normalizePortRequestCwd(request.cwd, targetRootPath);
|
|
2202
|
+
const templatePath = path.resolve(template.cwd);
|
|
2203
|
+
if (selectorPath !== templatePath) {
|
|
2204
|
+
return false;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
return true;
|
|
2208
|
+
}
|
|
2209
|
+
function clonePortPoolConfig(config) {
|
|
2210
|
+
return { ...config };
|
|
2211
|
+
}
|
|
1749
2212
|
function normalizePortRequestCwd(value, targetRootPath) {
|
|
1750
2213
|
const trimmed = value?.trim() || "";
|
|
1751
2214
|
if (!trimmed) {
|