oh-my-opencode 5.0.0-beta.23 → 5.0.0-beta.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +141 -184
- package/dist/cli-node/index.js +141 -184
- package/dist/index.js +1508 -1567
- package/dist/skills/debugging/SKILL.md +0 -2
- package/dist/skills/debugging/references/methodology/00-setup.md +0 -12
- package/dist/skills/debugging/references/methodology/02-investigate.md +0 -21
- package/dist/skills/debugging/references/runtimes/go.md +1 -15
- package/dist/skills/debugging/references/runtimes/native-binary.md +0 -38
- package/dist/skills/debugging/references/runtimes/node.md +0 -25
- package/dist/skills/debugging/references/runtimes/python.md +0 -13
- package/dist/skills/debugging/references/runtimes/rust.md +0 -41
- package/dist/tools/delegate-task/types.d.ts +0 -3
- package/dist/tui.js +0 -25
- package/package.json +15 -18
- package/packages/lsp-core/src/index.ts +0 -1
- package/packages/lsp-core/src/lsp/client-wrapper.ts +5 -25
- package/packages/lsp-core/src/lsp/client.ts +0 -26
- package/packages/lsp-core/src/lsp/connection.ts +0 -16
- package/packages/lsp-core/src/lsp/constants.ts +0 -1
- package/packages/lsp-core/src/lsp/manager.ts +1 -34
- package/packages/lsp-core/src/lsp/server-definitions.ts +0 -29
- package/packages/lsp-core/src/lsp/server-installation.ts +27 -200
- package/packages/lsp-core/src/lsp/server-resolution.ts +5 -18
- package/packages/lsp-core/src/lsp/transport.ts +0 -9
- package/packages/lsp-core/src/lsp/types.ts +0 -8
- package/packages/lsp-core/src/tool-surface.test.ts +1 -13
- package/packages/lsp-core/src/tools/definitions.ts +0 -14
- package/packages/lsp-core/src/tools/index.ts +0 -1
- package/packages/lsp-core/src/tools/types.ts +0 -10
- package/packages/lsp-daemon/dist/cli.js +144 -432
- package/packages/lsp-daemon/dist/client.d.ts +0 -10
- package/packages/lsp-daemon/dist/client.js +107 -401
- package/packages/lsp-daemon/dist/daemon-client.d.ts +0 -1
- package/packages/lsp-daemon/dist/daemon-client.js +0 -3
- package/packages/lsp-daemon/dist/index.js +107 -395
- package/packages/lsp-tools-mcp/dist/cli.js +99 -384
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +0 -53
- package/packages/lsp-tools-mcp/dist/mcp.js +99 -384
- package/packages/lsp-tools-mcp/dist/tools.js +99 -385
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +2 -2
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +35 -69
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +2 -21
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +121 -404
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/dist/cli.js +3 -3
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +3 -3
- package/packages/omo-codex/plugin/components/rules/test/post-compact-budget.test.ts +4 -4
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +15 -30
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +15 -30
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/model-catalog.json +3 -3
- package/packages/omo-codex/plugin/package-lock.json +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/catalog.mjs +3 -3
- package/packages/omo-codex/plugin/skills/debugging/SKILL.md +0 -2
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/00-setup.md +0 -12
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/02-investigate.md +0 -21
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/go.md +1 -15
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/native-binary.md +0 -38
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/node.md +0 -25
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/python.md +0 -13
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/rust.md +0 -41
- package/packages/omo-codex/plugin/test/aggregate-model-catalog.test.mjs +1 -1
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +2 -2
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +8 -8
- package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +2 -2
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +23 -38
- package/packages/shared-skills/skills/debugging/SKILL.md +0 -2
- package/packages/shared-skills/skills/debugging/references/methodology/00-setup.md +0 -12
- package/packages/shared-skills/skills/debugging/references/methodology/02-investigate.md +0 -21
- package/packages/shared-skills/skills/debugging/references/runtimes/go.md +1 -15
- package/packages/shared-skills/skills/debugging/references/runtimes/native-binary.md +0 -38
- package/packages/shared-skills/skills/debugging/references/runtimes/node.md +0 -25
- package/packages/shared-skills/skills/debugging/references/runtimes/python.md +0 -13
- package/packages/shared-skills/skills/debugging/references/runtimes/rust.md +0 -41
- package/dist/skills/debugging/references/scripts/dap.mjs +0 -267
- package/dist/skills/debugging/references/scripts/dap.test.ts +0 -86
- package/dist/skills/debugging/references/scripts/fixture-adapter.mjs +0 -59
- package/dist/skills/debugging/references/tools/dap.md +0 -103
- package/dist/skills/debugging/references/tools/frida.md +0 -193
- package/packages/lsp-core/src/lsp/format-document.test.ts +0 -143
- package/packages/lsp-core/src/lsp/format-document.ts +0 -90
- package/packages/lsp-core/src/lsp/manager-max-clients.test.ts +0 -151
- package/packages/lsp-core/src/lsp/server-installation.test.ts +0 -376
- package/packages/lsp-core/src/lsp/server-resolution-local-binary.test.ts +0 -170
- package/packages/lsp-core/src/tools/format.test.ts +0 -56
- package/packages/lsp-core/src/tools/format.ts +0 -56
- package/packages/omo-codex/plugin/skills/debugging/references/scripts/dap.mjs +0 -267
- package/packages/omo-codex/plugin/skills/debugging/references/scripts/fixture-adapter.mjs +0 -59
- package/packages/omo-codex/plugin/skills/debugging/references/tools/dap.md +0 -103
- package/packages/omo-codex/plugin/skills/debugging/references/tools/frida.md +0 -193
- package/packages/shared-skills/skills/debugging/references/scripts/dap.mjs +0 -267
- package/packages/shared-skills/skills/debugging/references/scripts/dap.test.ts +0 -86
- package/packages/shared-skills/skills/debugging/references/scripts/fixture-adapter.mjs +0 -59
- package/packages/shared-skills/skills/debugging/references/tools/dap.md +0 -103
- package/packages/shared-skills/skills/debugging/references/tools/frida.md +0 -193
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
5
|
-
|
|
6
|
-
import { runWithRequestContext } from "../request-context.js";
|
|
7
|
-
import { formatServerLookupError } from "./client-wrapper.js";
|
|
8
|
-
import { __resetServerBinaryResolutionCacheForTests } from "./server-installation.js";
|
|
9
|
-
import { findServerForExtension } from "./server-resolution.js";
|
|
10
|
-
|
|
11
|
-
const tempDirectories: string[] = [];
|
|
12
|
-
let previousPath: string | undefined;
|
|
13
|
-
|
|
14
|
-
function makeTempRoot(prefix: string): string {
|
|
15
|
-
const root = mkdtempSync(join(tmpdir(), prefix));
|
|
16
|
-
tempDirectories.push(root);
|
|
17
|
-
return root;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function writeExecutable(path: string): string {
|
|
21
|
-
mkdirSync(join(path, ".."), { recursive: true });
|
|
22
|
-
writeFileSync(path, "#!/bin/sh\nexit 0\n");
|
|
23
|
-
chmodSync(path, 0o755);
|
|
24
|
-
return path;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function contextFor(cwd: string) {
|
|
28
|
-
return {
|
|
29
|
-
cwd,
|
|
30
|
-
projectConfigPaths: [join(cwd, ".codex", "lsp-client.json")],
|
|
31
|
-
userConfigPath: join(cwd, "home-config", "lsp-client.json"),
|
|
32
|
-
installDecisionsPath: join(cwd, "home-config", "lsp-install-decisions.json"),
|
|
33
|
-
capabilities: { installDecisionTool: true },
|
|
34
|
-
} as const;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
beforeEach(() => {
|
|
38
|
-
previousPath = process.env["PATH"];
|
|
39
|
-
__resetServerBinaryResolutionCacheForTests();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
afterEach(() => {
|
|
43
|
-
if (previousPath === undefined) delete process.env["PATH"];
|
|
44
|
-
else process.env["PATH"] = previousPath;
|
|
45
|
-
for (const directory of tempDirectories.splice(0)) {
|
|
46
|
-
rmSync(directory, { recursive: true, force: true });
|
|
47
|
-
}
|
|
48
|
-
__resetServerBinaryResolutionCacheForTests();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe("findServerForExtension local binary substitution", () => {
|
|
52
|
-
test("#given a repo-local biome #when looking up a typescript file #then the command spawns the absolute local path", () => {
|
|
53
|
-
// given
|
|
54
|
-
const root = makeTempRoot("lsp-resolution-local-");
|
|
55
|
-
writeFileSync(join(root, "package.json"), "{}\n");
|
|
56
|
-
const binaryPath = writeExecutable(join(root, "node_modules", ".bin", "biome"));
|
|
57
|
-
process.env["PATH"] = join(root, "definitely-empty");
|
|
58
|
-
|
|
59
|
-
// when
|
|
60
|
-
const result = runWithRequestContext(contextFor(root), () => findServerForExtension(".ts"));
|
|
61
|
-
|
|
62
|
-
// then
|
|
63
|
-
expect(result.status).toBe("found");
|
|
64
|
-
if (result.status !== "found") throw new Error("expected a found server");
|
|
65
|
-
expect(result.server.command[0]).toBe(binaryPath);
|
|
66
|
-
expect(result.server.command.slice(1)).toEqual(["lsp-proxy", "--stdio"]);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("#given a PATH-only server #when looking up #then the resolved PATH entry is substituted", () => {
|
|
70
|
-
// given
|
|
71
|
-
const root = makeTempRoot("lsp-resolution-path-root-");
|
|
72
|
-
writeFileSync(join(root, "package.json"), "{}\n");
|
|
73
|
-
const pathDir = makeTempRoot("lsp-resolution-path-");
|
|
74
|
-
const pathBinary = writeExecutable(join(pathDir, "biome"));
|
|
75
|
-
process.env["PATH"] = pathDir;
|
|
76
|
-
|
|
77
|
-
// when
|
|
78
|
-
const result = runWithRequestContext(contextFor(root), () => findServerForExtension(".ts"));
|
|
79
|
-
|
|
80
|
-
// then
|
|
81
|
-
expect(result.status).toBe("found");
|
|
82
|
-
if (result.status !== "found") throw new Error("expected a found server");
|
|
83
|
-
expect(result.server.command[0]).toBe(pathBinary);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("#given no server anywhere #when looking up #then the not_installed result is preserved", () => {
|
|
87
|
-
// given
|
|
88
|
-
const root = makeTempRoot("lsp-resolution-missing-");
|
|
89
|
-
process.env["PATH"] = join(root, "definitely-empty");
|
|
90
|
-
|
|
91
|
-
// when
|
|
92
|
-
const result = runWithRequestContext(contextFor(root), () => findServerForExtension(".ts"));
|
|
93
|
-
|
|
94
|
-
// then
|
|
95
|
-
expect(result.status).toBe("not_installed");
|
|
96
|
-
if (result.status !== "not_installed") throw new Error("expected not_installed");
|
|
97
|
-
expect(result.installHint.length).toBeGreaterThan(0);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("#given an unknown extension #when looking up #then the not_configured result is preserved", () => {
|
|
101
|
-
// given
|
|
102
|
-
const root = makeTempRoot("lsp-resolution-unknown-");
|
|
103
|
-
process.env["PATH"] = join(root, "definitely-empty");
|
|
104
|
-
|
|
105
|
-
// when
|
|
106
|
-
const result = runWithRequestContext(contextFor(root), () => findServerForExtension(".unknown-ext"));
|
|
107
|
-
|
|
108
|
-
// then
|
|
109
|
-
expect(result.status).toBe("not_configured");
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
describe("not-installed message repo-local guidance", () => {
|
|
114
|
-
test("#given a missing biome #when formatting the message #then the repo-local install is suggested before the global hint", () => {
|
|
115
|
-
// given: .css is served by biome alone, so the lookup cannot fall through to typescript
|
|
116
|
-
const root = makeTempRoot("lsp-message-biome-");
|
|
117
|
-
process.env["PATH"] = join(root, "definitely-empty");
|
|
118
|
-
const result = runWithRequestContext(contextFor(root), () => findServerForExtension(".css"));
|
|
119
|
-
if (result.status !== "not_installed") throw new Error("expected not_installed");
|
|
120
|
-
expect(result.server.id).toBe("biome");
|
|
121
|
-
|
|
122
|
-
// when
|
|
123
|
-
const message = runWithRequestContext(contextFor(root), () => formatServerLookupError(result));
|
|
124
|
-
|
|
125
|
-
// then
|
|
126
|
-
expect(message).toContain("bun add -d @biomejs/biome");
|
|
127
|
-
expect(message).toContain("npm install -g @biomejs/biome");
|
|
128
|
-
expect(message.indexOf("bun add -d @biomejs/biome")).toBeLessThan(message.indexOf("npm install -g @biomejs/biome"));
|
|
129
|
-
expect(message).toContain("ACTION REQUIRED");
|
|
130
|
-
expect(message).toContain("NOT INSTALLED");
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test("#given a server with no repo-local route #when formatting the message #then only the global hint is shown", () => {
|
|
134
|
-
// given
|
|
135
|
-
const root = makeTempRoot("lsp-message-global-");
|
|
136
|
-
process.env["PATH"] = join(root, "definitely-empty");
|
|
137
|
-
const result = runWithRequestContext(contextFor(root), () => findServerForExtension(".rs"));
|
|
138
|
-
if (result.status !== "not_installed") throw new Error("expected not_installed");
|
|
139
|
-
|
|
140
|
-
// when
|
|
141
|
-
const message = runWithRequestContext(contextFor(root), () => formatServerLookupError(result));
|
|
142
|
-
|
|
143
|
-
// then
|
|
144
|
-
expect(message).toContain("To install, run:");
|
|
145
|
-
expect(message).not.toContain("THIS repository");
|
|
146
|
-
expect(message).toContain("ACTION REQUIRED");
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test("#given a pre-authorized install #when formatting the message #then both install routes stay visible", () => {
|
|
150
|
-
// given
|
|
151
|
-
const root = makeTempRoot("lsp-message-allowed-");
|
|
152
|
-
process.env["PATH"] = join(root, "definitely-empty");
|
|
153
|
-
const context = contextFor(root);
|
|
154
|
-
mkdirSync(join(root, "home-config"), { recursive: true });
|
|
155
|
-
writeFileSync(
|
|
156
|
-
context.installDecisionsPath,
|
|
157
|
-
`${JSON.stringify({ biome: { decision: "allowed", decidedAt: new Date().toISOString() } })}\n`,
|
|
158
|
-
);
|
|
159
|
-
const result = runWithRequestContext(context, () => findServerForExtension(".css"));
|
|
160
|
-
if (result.status !== "not_installed") throw new Error("expected not_installed");
|
|
161
|
-
|
|
162
|
-
// when
|
|
163
|
-
const message = runWithRequestContext(context, () => formatServerLookupError(result));
|
|
164
|
-
|
|
165
|
-
// then
|
|
166
|
-
expect(message).toContain("pre-authorized");
|
|
167
|
-
expect(message).toContain("bun add -d @biomejs/biome");
|
|
168
|
-
expect(message).toContain("npm install -g @biomejs/biome");
|
|
169
|
-
});
|
|
170
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
|
|
5
|
-
import { afterEach, describe, expect, it } from "bun:test";
|
|
6
|
-
|
|
7
|
-
import { parseLspRequestContext, runWithRequestContext } from "../request-context.js";
|
|
8
|
-
import { executeLspFormat } from "./format.js";
|
|
9
|
-
import type { LspFormatDetails } from "./types.js";
|
|
10
|
-
|
|
11
|
-
const workspaces: string[] = [];
|
|
12
|
-
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
for (const workspace of workspaces.splice(0)) rmSync(workspace, { recursive: true, force: true });
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
function workspaceWith(fileName: string, content: string): { readonly root: string; readonly filePath: string } {
|
|
18
|
-
const root = mkdtempSync(join(tmpdir(), "lsp-format-tool-"));
|
|
19
|
-
workspaces.push(root);
|
|
20
|
-
const filePath = join(root, fileName);
|
|
21
|
-
writeFileSync(filePath, content, "utf-8");
|
|
22
|
-
return { root, filePath };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function contextFor(root: string) {
|
|
26
|
-
return parseLspRequestContext({
|
|
27
|
-
cwd: root,
|
|
28
|
-
projectConfigPaths: [join(root, "lsp.json")],
|
|
29
|
-
userConfigPath: join(root, "user-lsp.json"),
|
|
30
|
-
installDecisionsPath: join(root, "install-decisions.json"),
|
|
31
|
-
capabilities: { installDecisionTool: false },
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
describe("executeLspFormat", () => {
|
|
36
|
-
it("#given an extension with no configured server #when format runs #then the missing dependency result is returned and the file is untouched", async () => {
|
|
37
|
-
// given
|
|
38
|
-
const original = "value\n";
|
|
39
|
-
const { root, filePath } = workspaceWith("module.wat", original);
|
|
40
|
-
|
|
41
|
-
// when
|
|
42
|
-
const result = await runWithRequestContext(contextFor(root), () => executeLspFormat({ filePath }));
|
|
43
|
-
|
|
44
|
-
// then
|
|
45
|
-
const details = result.details as LspFormatDetails;
|
|
46
|
-
expect(details.errorKind).toBe("missing_dependency");
|
|
47
|
-
expect(details.status).toBe("unavailable");
|
|
48
|
-
expect(result.content[0]?.text).toContain("No LSP server configured for extension: .wat");
|
|
49
|
-
expect(readFileSync(filePath)).toEqual(Buffer.from(original));
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("#given a missing filePath argument #when format runs #then the parameter contract is enforced", async () => {
|
|
53
|
-
// given / when / then
|
|
54
|
-
await expect(executeLspFormat({})).rejects.toThrow(/filePath/);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { withLspClient } from "../lsp/client-wrapper.js";
|
|
2
|
-
import { type FormatDocumentResult, formatDocumentWithClient } from "../lsp/format-document.js";
|
|
3
|
-
import { missingDependencyResult } from "../missing-dependency-result.js";
|
|
4
|
-
import { clientOptions, requireString } from "./parameters.js";
|
|
5
|
-
import { text } from "./result.js";
|
|
6
|
-
import type { LspFormatDetails, ToolExecutionResult } from "./types.js";
|
|
7
|
-
|
|
8
|
-
export async function executeLspFormat(
|
|
9
|
-
params: Record<string, unknown>,
|
|
10
|
-
signal?: AbortSignal,
|
|
11
|
-
): Promise<ToolExecutionResult> {
|
|
12
|
-
const filePath = requireString(params, "filePath");
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const result = await withLspClient(
|
|
16
|
-
filePath,
|
|
17
|
-
async (client, _workspaceRoot, resolvedFilePath) =>
|
|
18
|
-
formatDocumentWithClient(client, resolvedFilePath, signal === undefined ? {} : { signal }),
|
|
19
|
-
"format",
|
|
20
|
-
clientOptions(signal),
|
|
21
|
-
);
|
|
22
|
-
return text(describeResult(filePath, result), formatDetails(filePath, result));
|
|
23
|
-
} catch (error) {
|
|
24
|
-
const missingDependency = missingDependencyResult(error, {
|
|
25
|
-
filePath,
|
|
26
|
-
status: "unavailable",
|
|
27
|
-
reason: "server_unavailable",
|
|
28
|
-
linesAdded: 0,
|
|
29
|
-
linesRemoved: 0,
|
|
30
|
-
} satisfies Omit<LspFormatDetails, "error" | "errorKind">);
|
|
31
|
-
if (missingDependency) return missingDependency;
|
|
32
|
-
throw error;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function formatDetails(filePath: string, result: FormatDocumentResult): LspFormatDetails {
|
|
37
|
-
if (result.status === "unavailable") {
|
|
38
|
-
return { filePath, status: "unavailable", reason: result.reason, linesAdded: 0, linesRemoved: 0 };
|
|
39
|
-
}
|
|
40
|
-
return {
|
|
41
|
-
filePath,
|
|
42
|
-
status: result.status,
|
|
43
|
-
linesAdded: result.linesAdded,
|
|
44
|
-
linesRemoved: result.linesRemoved,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function describeResult(filePath: string, result: FormatDocumentResult): string {
|
|
49
|
-
if (result.status === "unavailable") {
|
|
50
|
-
return `Formatting unavailable for ${filePath}: the language server does not advertise documentFormattingProvider.`;
|
|
51
|
-
}
|
|
52
|
-
if (result.status === "unchanged") {
|
|
53
|
-
return `Already formatted: ${filePath}`;
|
|
54
|
-
}
|
|
55
|
-
return `Formatted ${filePath} (+${result.linesAdded}/-${result.linesRemoved} lines)`;
|
|
56
|
-
}
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Zero-dependency DAP REPL, modeled on oh-my-pi's debug tool. DAP framing is UTF-8 byte based.
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
import { createConnection } from "node:net";
|
|
5
|
-
import { createInterface } from "node:readline";
|
|
6
|
-
import { stdin, stdout, stderr, env } from "node:process";
|
|
7
|
-
|
|
8
|
-
export const MAX_ROWS = 100;
|
|
9
|
-
export const MAX_OUTPUT_BYTES = 32 * 1024;
|
|
10
|
-
|
|
11
|
-
export function isTcpAdapterSpec(spec) {
|
|
12
|
-
const separator = spec.lastIndexOf(":");
|
|
13
|
-
return separator > 0 &&
|
|
14
|
-
!spec.includes("/") &&
|
|
15
|
-
!spec.includes("\\") &&
|
|
16
|
-
/^\d+$/.test(spec.slice(separator + 1));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export class DapFrameParser {
|
|
20
|
-
constructor() { this.buffer = Buffer.alloc(0); }
|
|
21
|
-
push(chunk) {
|
|
22
|
-
this.buffer = Buffer.concat([this.buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
|
|
23
|
-
const messages = [];
|
|
24
|
-
while (true) {
|
|
25
|
-
const marker = this.buffer.indexOf(Buffer.from("\r\n\r\n"));
|
|
26
|
-
if (marker < 0) break;
|
|
27
|
-
const header = this.buffer.subarray(0, marker).toString("ascii");
|
|
28
|
-
const match = header.match(/(?:^|\r\n)Content-Length:\s*(\d+)/i);
|
|
29
|
-
if (!match) { this.buffer = this.buffer.subarray(marker + 4); continue; }
|
|
30
|
-
const length = Number(match[1]);
|
|
31
|
-
const start = marker + 4;
|
|
32
|
-
if (this.buffer.length < start + length) break;
|
|
33
|
-
const body = this.buffer.subarray(start, start + length);
|
|
34
|
-
this.buffer = this.buffer.subarray(start + length);
|
|
35
|
-
try { messages.push(JSON.parse(body.toString("utf8"))); } catch { /* malformed frames are ignored */ }
|
|
36
|
-
}
|
|
37
|
-
return messages;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const timeoutMs = Number(env.DAP_TIMEOUT_MS || 15000);
|
|
42
|
-
let transport = null;
|
|
43
|
-
let parser = null;
|
|
44
|
-
let nextSeq = 1;
|
|
45
|
-
let initialized = false;
|
|
46
|
-
let terminated = false;
|
|
47
|
-
let currentStop = null;
|
|
48
|
-
let topFrame = null;
|
|
49
|
-
const pending = new Map();
|
|
50
|
-
const breakpoints = new Map();
|
|
51
|
-
// Event-driven waits. A tight `await setImmediate` busy-loop starves Bun's socket
|
|
52
|
-
// data callbacks (the `initialized` event then never processes and launch deadlocks),
|
|
53
|
-
// so every wait subscribes to the actual DAP event instead of polling state.
|
|
54
|
-
const eventWaiters = new Map();
|
|
55
|
-
function onEvent(name) {
|
|
56
|
-
return new Promise(resolve => {
|
|
57
|
-
const list = eventWaiters.get(name) || [];
|
|
58
|
-
list.push(resolve);
|
|
59
|
-
eventWaiters.set(name, list);
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
function emitEvent(name, value) {
|
|
63
|
-
const list = eventWaiters.get(name) || [];
|
|
64
|
-
eventWaiters.delete(name);
|
|
65
|
-
for (const resolve of list) resolve(value);
|
|
66
|
-
}
|
|
67
|
-
function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
|
|
68
|
-
|
|
69
|
-
const debugTraffic = Boolean(env.DAP_DEBUG);
|
|
70
|
-
function trace(direction, message) { if (debugTraffic) stderr.write(`DAP ${direction} ${JSON.stringify(message).slice(0, 300)}\n`); }
|
|
71
|
-
function line(text) { stdout.write(`${text}\n`); }
|
|
72
|
-
function error(kind, detail = "") { line(`ERR: ${kind}${detail ? ` ${detail}` : ""}`); }
|
|
73
|
-
function send(message) {
|
|
74
|
-
trace(">", message);
|
|
75
|
-
const body = Buffer.from(JSON.stringify(message));
|
|
76
|
-
transport.write(Buffer.concat([Buffer.from(`Content-Length: ${body.length}\r\n\r\n`), body]));
|
|
77
|
-
}
|
|
78
|
-
function request(command, args = {}) {
|
|
79
|
-
if (!transport || terminated) return Promise.reject(new Error("no-session"));
|
|
80
|
-
const seq = nextSeq++;
|
|
81
|
-
send({ type: "request", seq, command, arguments: args });
|
|
82
|
-
return new Promise((resolve, reject) => {
|
|
83
|
-
const timer = setTimeout(() => { pending.delete(seq); reject(new Error("timeout")); }, timeoutMs);
|
|
84
|
-
pending.set(seq, { resolve: value => { clearTimeout(timer); resolve(value); }, reject: value => { clearTimeout(timer); reject(value); } });
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
function onMessage(message) {
|
|
88
|
-
trace("<", message);
|
|
89
|
-
if (message.type === "response") {
|
|
90
|
-
const wait = pending.get(message.request_seq);
|
|
91
|
-
if (!wait) return;
|
|
92
|
-
pending.delete(message.request_seq);
|
|
93
|
-
if (message.success === false) wait.reject(new Error("adapter-error")); else wait.resolve(message);
|
|
94
|
-
} else if (message.type === "event") {
|
|
95
|
-
if (message.event === "initialized") { initialized = true; emitEvent("initialized"); }
|
|
96
|
-
if (message.event === "stopped") { currentStop = message.body || {}; emitEvent("stopped", currentStop); }
|
|
97
|
-
if (message.event === "terminated" || message.event === "exited") { terminated = true; emitEvent("terminated"); }
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
function connectTransport(write, read, close = read) {
|
|
101
|
-
transport = { write: chunk => write.write(chunk), destroy: () => close.destroy?.() };
|
|
102
|
-
parser = new DapFrameParser();
|
|
103
|
-
read.on("data", chunk => { for (const message of parser.push(chunk)) onMessage(message); });
|
|
104
|
-
read.on("error", () => { if (!terminated) error("adapter-failed"); });
|
|
105
|
-
read.on("close", () => { if (!terminated) error("adapter-failed"); });
|
|
106
|
-
}
|
|
107
|
-
function connectSocket(socket) { connectTransport(socket, socket, socket); }
|
|
108
|
-
async function startAdapter(spec) {
|
|
109
|
-
if (isTcpAdapterSpec(spec)) {
|
|
110
|
-
const separator = spec.lastIndexOf(":");
|
|
111
|
-
const socket = createConnection(Number(spec.slice(separator + 1)), spec.slice(0, separator));
|
|
112
|
-
connectSocket(socket);
|
|
113
|
-
await new Promise((resolve, reject) => { socket.once("connect", resolve); socket.once("error", reject); });
|
|
114
|
-
} else {
|
|
115
|
-
// A .mjs/.cjs/.ts adapter spec runs under the current Bun/Node runtime (e.g. the
|
|
116
|
-
// fixture adapter); any other spec is an adapter executable (lldb-dap, debugpy, dlv)
|
|
117
|
-
// spawned directly with its own argv.
|
|
118
|
-
const isScript = /\.(mjs|cjs|ts)$/.test(spec);
|
|
119
|
-
const [cmd, argv] = isScript ? [process.execPath, [spec]] : [spec, []];
|
|
120
|
-
const child = spawn(cmd, argv, { stdio: ["pipe", "pipe", "inherit"] });
|
|
121
|
-
connectTransport(child.stdin, child.stdout, child);
|
|
122
|
-
child.on("error", () => error("adapter-failed"));
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// Waits for a stopped event. When `required` is false this is a best-effort grace
|
|
126
|
-
// window (e.g. an optional stop-on-entry); it returns on the first stop OR on timeout
|
|
127
|
-
// without failing, so adapters that run straight to a breakpoint are not broken.
|
|
128
|
-
async function waitForStop(required = true, graceMs) {
|
|
129
|
-
if (currentStop || terminated) return Boolean(currentStop);
|
|
130
|
-
const budget = graceMs ?? timeoutMs;
|
|
131
|
-
await Promise.race([onEvent("stopped"), onEvent("terminated"), sleep(budget)]);
|
|
132
|
-
return Boolean(currentStop);
|
|
133
|
-
}
|
|
134
|
-
// Waits for the adapter's `initialized` event, event-driven (never a busy-loop).
|
|
135
|
-
async function waitInitialized() {
|
|
136
|
-
if (initialized) return;
|
|
137
|
-
await Promise.race([onEvent("initialized"), sleep(timeoutMs)]);
|
|
138
|
-
if (!initialized) throw new Error("timeout");
|
|
139
|
-
}
|
|
140
|
-
// Adapters gate the `initialized` event on a recognized adapterID (debugpy withholds
|
|
141
|
-
// it for unknown IDs, hanging the handshake). Infer a known ID from the adapter spec.
|
|
142
|
-
function adapterIdFor(spec) {
|
|
143
|
-
const base = String(spec).split("/").pop().toLowerCase();
|
|
144
|
-
if (base.includes("debugpy") || base.includes("python")) return "debugpy";
|
|
145
|
-
if (base.includes("lldb")) return "lldb-dap";
|
|
146
|
-
if (base.includes("dlv") || base.includes("delve")) return "dlv";
|
|
147
|
-
if (base.includes("js-debug")) return "js-debug";
|
|
148
|
-
if (base.includes("gdb")) return "gdb";
|
|
149
|
-
return "omo";
|
|
150
|
-
}
|
|
151
|
-
async function launch(adapter, program, args) {
|
|
152
|
-
if (transport) return error("adapter-error", "session already exists");
|
|
153
|
-
try {
|
|
154
|
-
await startAdapter(adapter);
|
|
155
|
-
await request("initialize", { clientID: "omo-dap", adapterID: adapterIdFor(adapter), linesStartAt1: true, columnsStartAt1: true, pathFormat: "path" });
|
|
156
|
-
// Send launch BEFORE awaiting `initialized`: debugpy's adapter defers/flushes the
|
|
157
|
-
// initialized event until further client activity, so waiting for it first
|
|
158
|
-
// deadlocks the handshake. DAP allows launch any time after the initialize
|
|
159
|
-
// response; `initialized` is only required before configurationDone.
|
|
160
|
-
// stopOnEntry yields an initial stopped event with a real threadId, so the client
|
|
161
|
-
// can set breakpoints and inspect from a valid stopped state before continuing.
|
|
162
|
-
// `console: "internalConsole"` is REQUIRED by debugpy (without it the launch never
|
|
163
|
-
// spawns the debug server -> "Server is not available"); lldb-dap ignores it.
|
|
164
|
-
// lldb-dap reports the launch response success unreliably (false even when the
|
|
165
|
-
// process started and stopped); the process/stopped EVENTS are the real signal,
|
|
166
|
-
// so a non-success launch response is tolerated, not fatal.
|
|
167
|
-
await request("launch", { program, args, console: "internalConsole", justMyCode: false, stopOnEntry: true }).catch(() => null);
|
|
168
|
-
await waitInitialized();
|
|
169
|
-
await request("configurationDone").catch(() => null);
|
|
170
|
-
// Best-effort: lldb-dap stops at entry, other adapters run to the first breakpoint.
|
|
171
|
-
await waitForStop(false, 2000);
|
|
172
|
-
line("READY: launch");
|
|
173
|
-
} catch (e) { error(e.message === "timeout" ? "timeout" : e.message === "adapter-error" ? "adapter-error" : "adapter-failed"); }
|
|
174
|
-
}
|
|
175
|
-
async function snapshot() {
|
|
176
|
-
if (terminated) { line("EXIT: terminated"); return; }
|
|
177
|
-
try {
|
|
178
|
-
const response = await request("stackTrace", { threadId: currentStop?.threadId || 1, levels: 1 });
|
|
179
|
-
topFrame = response.body?.stackFrames?.[0];
|
|
180
|
-
const f = topFrame;
|
|
181
|
-
if (f) line(`STOP: stopped reason=${currentStop?.reason || "unknown"} threadId=${currentStop?.threadId || ""} ${f.name} at ${f.source?.path || "?"}:${f.line || 0}:${f.column || 0}`);
|
|
182
|
-
else line(`STOP: stopped reason=${currentStop?.reason || "unknown"} threadId=${currentStop?.threadId || ""} ? at ?:0:0`);
|
|
183
|
-
} catch (e) { error(e.message === "timeout" ? "timeout" : "adapter-error"); }
|
|
184
|
-
}
|
|
185
|
-
function boundedTable(header, rows) {
|
|
186
|
-
const all = rows.map(row => row.join("\t"));
|
|
187
|
-
const chosen = all.slice(0, MAX_ROWS);
|
|
188
|
-
const prefix = `${header.join("\t")}\n`;
|
|
189
|
-
let output = prefix;
|
|
190
|
-
let count = 0;
|
|
191
|
-
for (const row of chosen) {
|
|
192
|
-
const candidate = `${row}\n`;
|
|
193
|
-
if (Buffer.byteLength(output + candidate) > MAX_OUTPUT_BYTES) break;
|
|
194
|
-
output += candidate; count++;
|
|
195
|
-
}
|
|
196
|
-
stdout.write(output);
|
|
197
|
-
const rowDropped = all.length - count;
|
|
198
|
-
const byteDropped = Buffer.byteLength(all.slice(count).join("\n") + (all.length > count ? "\n" : ""));
|
|
199
|
-
if (rowDropped > 0 || byteDropped > 0) line(`TRUNCATED: rows dropped=${rowDropped} bytes dropped=${byteDropped}`);
|
|
200
|
-
}
|
|
201
|
-
async function command(input) {
|
|
202
|
-
const parts = input.trim().split(/\s+/);
|
|
203
|
-
const cmd = parts.shift();
|
|
204
|
-
if (!cmd) return;
|
|
205
|
-
if (cmd === "quit") { transport?.destroy?.(); process.exit(0); }
|
|
206
|
-
if (cmd === "launch") return launch(parts.shift(), parts.shift(), parts);
|
|
207
|
-
if (cmd === "attach") {
|
|
208
|
-
const [host, port] = (parts.shift() || "").split(":");
|
|
209
|
-
try {
|
|
210
|
-
await startAdapter(`${host}:${port}`);
|
|
211
|
-
await request("initialize", { clientID: "omo-dap", adapterID: "debugpy", linesStartAt1: true, columnsStartAt1: true, pathFormat: "path" });
|
|
212
|
-
await waitInitialized();
|
|
213
|
-
await request("attach", { justMyCode: false });
|
|
214
|
-
await request("configurationDone");
|
|
215
|
-
line("READY: attach");
|
|
216
|
-
} catch (e) { error(e.message === "timeout" ? "timeout" : "adapter-failed"); }
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
if (!transport) return error("no-session");
|
|
220
|
-
try {
|
|
221
|
-
if (cmd === "break" || cmd === "rmbreak") {
|
|
222
|
-
const [file, lineNo] = (parts[0] || "").split(":");
|
|
223
|
-
if (!file || !lineNo) return error("invalid-args");
|
|
224
|
-
const set = breakpoints.get(file) || [];
|
|
225
|
-
const next = cmd === "break" ? [...set.filter(x => x.line !== Number(lineNo)), { line: Number(lineNo) }] : set.filter(x => x.line !== Number(lineNo));
|
|
226
|
-
const response = await request("setBreakpoints", { source: { path: file }, breakpoints: next });
|
|
227
|
-
breakpoints.set(file, next);
|
|
228
|
-
if (response.body?.breakpoints?.some(bp => bp.verified === false)) error("unverified-breakpoint"); else line(`BREAK: ${file}:${lineNo}`);
|
|
229
|
-
} else if (["continue", "step", "next", "stepin", "stepout", "pause"].includes(cmd)) {
|
|
230
|
-
const dapCmd = { step: "next", stepin: "stepIn", stepout: "stepOut" }[cmd] || cmd;
|
|
231
|
-
const threadId = currentStop?.threadId || 1;
|
|
232
|
-
currentStop = null;
|
|
233
|
-
await request(dapCmd, { threadId });
|
|
234
|
-
// Wait for the resulting stopped event (breakpoint hit / step completed) before
|
|
235
|
-
// snapshotting; without this the stack is read while the debuggee is still running.
|
|
236
|
-
await waitForStop();
|
|
237
|
-
await snapshot();
|
|
238
|
-
} else if (cmd === "stack") {
|
|
239
|
-
const r = await request("stackTrace", { threadId: currentStop?.threadId || 1, levels: Number(parts[0]) || 100 });
|
|
240
|
-
boundedTable(["FRAME", "NAME", "FILE", "LINE", "COLUMN"], (r.body?.stackFrames || []).map(f => [String(f.id), f.name, f.source?.path || "", String(f.line || ""), String(f.column || "")]));
|
|
241
|
-
} else if (cmd === "scopes") {
|
|
242
|
-
const r = await request("scopes", { frameId: topFrame?.id || 42 });
|
|
243
|
-
boundedTable(["NAME", "VARIABLES_REFERENCE"], (r.body?.scopes || []).map(s => [s.name, String(s.variablesReference)]));
|
|
244
|
-
} else if (cmd === "vars") {
|
|
245
|
-
if (!parts[0]) return error("invalid-args");
|
|
246
|
-
const r = await request("variables", { variablesReference: Number(parts[0]) });
|
|
247
|
-
boundedTable(["NAME", "VALUE"], (r.body?.variables || []).map(v => [v.name, v.value ?? ""]));
|
|
248
|
-
} else if (cmd === "eval") {
|
|
249
|
-
if (!parts.length) return error("invalid-args");
|
|
250
|
-
const r = await request("evaluate", { expression: parts.join(" "), frameId: topFrame?.id || 42 });
|
|
251
|
-
line(`EVAL\t${r.body?.result || ""}`);
|
|
252
|
-
} else if (cmd === "threads") {
|
|
253
|
-
const r = await request("threads"); boundedTable(["ID", "NAME"], (r.body?.threads || []).map(t => [String(t.id), t.name]));
|
|
254
|
-
} else if (cmd === "sessions") line("SESSION\tSTATE\n1\tactive");
|
|
255
|
-
else if (cmd === "terminate") { await request("terminate", { restart: false }); line("EXIT: terminated"); terminated = true; }
|
|
256
|
-
else error("invalid-args");
|
|
257
|
-
} catch (e) { error(e.message === "timeout" ? "timeout" : e.message === "adapter-error" ? "adapter-error" : "adapter-failed"); }
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (import.meta.main) {
|
|
261
|
-
// Serialize stdin commands: fire-and-forget (`void command(...)`) lets a `continue`
|
|
262
|
-
// race ahead of the launch handshake's configurationDone, and the adapter then
|
|
263
|
-
// drops or rejects the out-of-order request. Each line awaits the previous command.
|
|
264
|
-
let chain = Promise.resolve();
|
|
265
|
-
const rl = createInterface({ input: stdin, crlfDelay: Infinity });
|
|
266
|
-
rl.on("line", value => { chain = chain.then(() => command(value)).catch(() => {}); });
|
|
267
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Deterministic DAP fixture used by dap.test.ts.
|
|
3
|
-
import { stdin, stdout, argv } from "node:process";
|
|
4
|
-
|
|
5
|
-
let seq = 1;
|
|
6
|
-
let buffer = Buffer.alloc(0);
|
|
7
|
-
const noAnswer = argv.includes("--no-answer") || process.env.DAP_FIXTURE_NO_ANSWER === "1";
|
|
8
|
-
|
|
9
|
-
function send(message) {
|
|
10
|
-
const body = Buffer.from(JSON.stringify(message));
|
|
11
|
-
stdout.write(`Content-Length: ${body.length}\r\n\r\n`);
|
|
12
|
-
stdout.write(body);
|
|
13
|
-
}
|
|
14
|
-
function response(request, body = {}, success = true, message) {
|
|
15
|
-
send({ type: "response", seq: seq++, request_seq: request.seq, success, command: request.command, body, ...(message ? { message } : {}) });
|
|
16
|
-
}
|
|
17
|
-
function event(event, body = {}) { send({ type: "event", seq: seq++, event, body }); }
|
|
18
|
-
function handle(request) {
|
|
19
|
-
if (noAnswer && request.command === "initialize") return;
|
|
20
|
-
switch (request.command) {
|
|
21
|
-
case "initialize":
|
|
22
|
-
response(request, { supportsConfigurationDoneRequest: true, supportsEvaluateForHovers: true });
|
|
23
|
-
event("initialized");
|
|
24
|
-
break;
|
|
25
|
-
case "setBreakpoints": {
|
|
26
|
-
const bps = request.arguments?.breakpoints ?? [];
|
|
27
|
-
response(request, { breakpoints: bps.map((bp, i) => ({ id: i + 1, verified: !request.arguments?.source?.path?.includes("unverified"), line: bp.line, column: bp.column ?? 1, message: "fixture" })) });
|
|
28
|
-
break;
|
|
29
|
-
}
|
|
30
|
-
case "configurationDone": response(request); break;
|
|
31
|
-
case "launch": response(request); break;
|
|
32
|
-
case "continue": response(request, { allThreadsContinued: true }); event("stopped", { reason: "breakpoint", threadId: 1, allThreadsStopped: true }); break;
|
|
33
|
-
case "next": case "stepIn": case "stepOut": response(request); event("stopped", { reason: "step", threadId: 1 }); break;
|
|
34
|
-
case "pause": response(request); event("stopped", { reason: "pause", threadId: 1 }); break;
|
|
35
|
-
case "threads": response(request, { threads: [{ id: 1, name: "main" }] }); break;
|
|
36
|
-
case "stackTrace": response(request, { stackFrames: [{ id: 42, name: "main", source: { path: "/tmp/program.py" }, line: 12, column: 3 }] }); break;
|
|
37
|
-
case "scopes": response(request, { scopes: [{ name: "Locals", variablesReference: 7, expensive: false }] }); break;
|
|
38
|
-
case "variables": response(request, { variables: Array.from({ length: 250 }, (_, i) => ({ name: `v${i + 1}`, value: request.arguments?.variablesReference === 8 ? `${String(i + 1)} ${"x".repeat(500)}` : String(i + 1), variablesReference: 0 })) }); break;
|
|
39
|
-
case "evaluate": response(request, { result: "42", type: "int", variablesReference: 0 }); break;
|
|
40
|
-
case "terminate": response(request); event("terminated", {}); break;
|
|
41
|
-
default: response(request, {}, false, `unsupported ${request.command}`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
stdin.on("data", chunk => {
|
|
45
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
46
|
-
while (true) {
|
|
47
|
-
const marker = buffer.indexOf(Buffer.from("\r\n\r\n"));
|
|
48
|
-
if (marker < 0) return;
|
|
49
|
-
const header = buffer.subarray(0, marker).toString();
|
|
50
|
-
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
51
|
-
if (!match) { buffer = buffer.subarray(marker + 4); continue; }
|
|
52
|
-
const length = Number(match[1]);
|
|
53
|
-
const start = marker + 4;
|
|
54
|
-
if (buffer.length < start + length) return;
|
|
55
|
-
const body = buffer.subarray(start, start + length);
|
|
56
|
-
buffer = buffer.subarray(start + length);
|
|
57
|
-
try { handle(JSON.parse(body.toString("utf8"))); } catch { /* fixture input is controlled */ }
|
|
58
|
-
}
|
|
59
|
-
});
|