@lsproxy/cli 0.3.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -0
  3. package/dist/apply.d.ts +139 -0
  4. package/dist/apply.d.ts.map +1 -0
  5. package/dist/apply.js +325 -0
  6. package/dist/apply.js.map +1 -0
  7. package/dist/build-commands.d.ts +6 -0
  8. package/dist/build-commands.d.ts.map +1 -0
  9. package/dist/build-commands.js +111 -0
  10. package/dist/build-commands.js.map +1 -0
  11. package/dist/cli.d.ts +30 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +207 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/commands/move-file.d.ts +13 -0
  16. package/dist/commands/move-file.d.ts.map +1 -0
  17. package/dist/commands/move-file.js +84 -0
  18. package/dist/commands/move-file.js.map +1 -0
  19. package/dist/commands/move-symbol.d.ts +70 -0
  20. package/dist/commands/move-symbol.d.ts.map +1 -0
  21. package/dist/commands/move-symbol.js +154 -0
  22. package/dist/commands/move-symbol.js.map +1 -0
  23. package/dist/commands/query.d.ts +14 -0
  24. package/dist/commands/query.d.ts.map +1 -0
  25. package/dist/commands/query.js +46 -0
  26. package/dist/commands/query.js.map +1 -0
  27. package/dist/commands/rename.d.ts +14 -0
  28. package/dist/commands/rename.d.ts.map +1 -0
  29. package/dist/commands/rename.js +42 -0
  30. package/dist/commands/rename.js.map +1 -0
  31. package/dist/connect.d.ts +10 -0
  32. package/dist/connect.d.ts.map +1 -0
  33. package/dist/connect.js +63 -0
  34. package/dist/connect.js.map +1 -0
  35. package/dist/discover.d.ts +17 -0
  36. package/dist/discover.d.ts.map +1 -0
  37. package/dist/discover.js +46 -0
  38. package/dist/discover.js.map +1 -0
  39. package/dist/index.d.ts +15 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +12 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/io.d.ts +76 -0
  44. package/dist/io.d.ts.map +1 -0
  45. package/dist/io.js +150 -0
  46. package/dist/io.js.map +1 -0
  47. package/dist/session.d.ts +89 -0
  48. package/dist/session.d.ts.map +1 -0
  49. package/dist/session.js +286 -0
  50. package/dist/session.js.map +1 -0
  51. package/dist/zod-to-commander.d.ts +11 -0
  52. package/dist/zod-to-commander.d.ts.map +1 -0
  53. package/dist/zod-to-commander.js +351 -0
  54. package/dist/zod-to-commander.js.map +1 -0
  55. package/package.json +65 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * `lspeasy move-symbol <file> <line:col> <targetFile>`
3
+ *
4
+ * Moves the symbol at the given position into `targetFile`, updating all
5
+ * importers. Driven by `textDocument/codeAction` (kind `refactor.move`).
6
+ *
7
+ * Two server shapes are supported:
8
+ * 1. The code action (or its `codeAction/resolve`) returns a `WorkspaceEdit`
9
+ * directly — applied as-is.
10
+ * 2. The action carries a `command`. We execute it via
11
+ * `workspace/executeCommand`; many servers (notably
12
+ * typescript-language-server's `_typescript.applyRefactoring`) then push
13
+ * the edit back through a `workspace/applyEdit` request, which the session
14
+ * captures. For TS, we inject the destination as
15
+ * `interactiveRefactorArguments.targetFile` so the move targets an existing
16
+ * file rather than prompting for a new one.
17
+ */
18
+ import { existsSync, readFileSync } from 'node:fs';
19
+ import { pathToFileURL } from 'node:url';
20
+ import { applyWorkspaceEdit, planWorkspaceEdit } from '../apply.js';
21
+ import { emitResult, fail, parseLineCol, resolvePathArg, toLspPosition } from '../io.js';
22
+ import { RefactorSession } from '../session.js';
23
+ /**
24
+ * Normalize the `command` of a code action into an {@link LspCommand}, preserving
25
+ * arguments for BOTH shapes:
26
+ * - nested object command → used as-is;
27
+ * - raw `Command` (string `command`) → its top-level `arguments` are retained
28
+ * (dropping them left command-only `_typescript.applyRefactoring` moves with
29
+ * an empty arg list, so the refactor could not run).
30
+ */
31
+ export function toLspCommand(action) {
32
+ if (!action.command)
33
+ return undefined;
34
+ if (typeof action.command === 'string') {
35
+ const cmd = { title: action.title, command: action.command };
36
+ if (action.arguments !== undefined)
37
+ cmd.arguments = action.arguments;
38
+ return cmd;
39
+ }
40
+ return action.command;
41
+ }
42
+ /**
43
+ * Resolve the ordered list of {@link WorkspaceEdit}s a `refactor.move` action
44
+ * produces, honoring the LSP `edit` + `command` contract:
45
+ * - if the action has an inline `edit`, it is applied FIRST;
46
+ * - if it also (or instead) has a `command`, the command is executed AFTER the
47
+ * inline edit, and EVERY edit the server then pushes via `workspace/applyEdit`
48
+ * (possibly several, in sequence) is appended in arrival order.
49
+ *
50
+ * Pure with respect to disk: it only sequences edits. `execute` runs the command
51
+ * (injecting the move target); `drainCapturedEdits` returns all server-pushed
52
+ * edits captured since the last drain.
53
+ */
54
+ export async function resolveMoveEdits(action, targetFile, execute, drainCapturedEdits) {
55
+ const edits = [];
56
+ if (action.edit)
57
+ edits.push(action.edit);
58
+ const cmd = toLspCommand(action);
59
+ if (cmd) {
60
+ await execute(injectTargetFile(cmd, targetFile));
61
+ edits.push(...drainCapturedEdits());
62
+ }
63
+ return edits;
64
+ }
65
+ /** Inject the move destination into a tsserver `_typescript.applyRefactoring`
66
+ * command argument so the symbol lands in an existing target file. */
67
+ function injectTargetFile(cmd, targetFile) {
68
+ if (cmd.command !== '_typescript.applyRefactoring' || !Array.isArray(cmd.arguments))
69
+ return cmd;
70
+ const arg0 = cmd.arguments[0];
71
+ if (arg0 && typeof arg0 === 'object') {
72
+ const obj = arg0;
73
+ return {
74
+ ...cmd,
75
+ arguments: [
76
+ {
77
+ ...obj,
78
+ refactor: 'Move to file',
79
+ action: 'Move to file',
80
+ interactiveRefactorArguments: { targetFile }
81
+ },
82
+ ...cmd.arguments.slice(1)
83
+ ]
84
+ };
85
+ }
86
+ return cmd;
87
+ }
88
+ export async function runMoveSymbol(args, flags) {
89
+ const file = resolvePathArg(args.file, flags);
90
+ const targetFile = resolvePathArg(args.targetFile, flags);
91
+ const oneBased = parseLineCol(args.position);
92
+ const pos = toLspPosition(oneBased);
93
+ if (!existsSync(file))
94
+ fail(`source file not found: ${file}`, flags.json);
95
+ const session = new RefactorSession({
96
+ serverCommand: flags.server,
97
+ root: flags.root,
98
+ indexWaitMs: flags.waitMs,
99
+ verbose: flags.verbose
100
+ });
101
+ try {
102
+ await session.start();
103
+ await session.openAndWait(file);
104
+ // Also open the target so the server knows its insertion context.
105
+ if (existsSync(targetFile)) {
106
+ await session.lsp.sendNotification('textDocument/didOpen', {
107
+ textDocument: {
108
+ uri: pathToFileURL(targetFile).href,
109
+ languageId: 'typescript',
110
+ version: 1,
111
+ text: readFileSync(targetFile, 'utf8')
112
+ }
113
+ });
114
+ }
115
+ const range = { start: pos, end: pos };
116
+ const actions = await session.requestWithRetry(() => session.lsp.sendRequest('textDocument/codeAction', {
117
+ textDocument: { uri: pathToFileURL(file).href },
118
+ range,
119
+ context: { diagnostics: [], only: ['refactor.move'] }
120
+ }));
121
+ if (!actions || actions.length === 0) {
122
+ fail('no refactor.move code action available at that position', flags.json);
123
+ }
124
+ // Prefer the move action; resolve it if it has no inline edit.
125
+ let action = actions.find((a) => a.kind?.startsWith('refactor.move')) ?? actions[0];
126
+ if (!action.edit && action.data !== undefined) {
127
+ const resolved = (await session.lsp.sendRequest('codeAction/resolve', action));
128
+ if (resolved)
129
+ action = resolved;
130
+ }
131
+ // Validate every server-returned edit against --root before applying.
132
+ const guard = { root: flags.root, allowOutsideRoot: flags.allowOutsideRoot };
133
+ const applyEdit = (e) => flags.dryRun ? planWorkspaceEdit(e, guard) : applyWorkspaceEdit(e, guard);
134
+ // Per the LSP spec, a CodeAction may carry BOTH an inline `edit` and a
135
+ // `command`: apply the edit FIRST, then execute the command (not either/or).
136
+ // The command may itself push further edits via workspace/applyEdit, possibly
137
+ // several in sequence — drain them ALL in order. (See resolveMoveEdits.)
138
+ const edits = await resolveMoveEdits(action, targetFile, async (cmd) => {
139
+ await session.lsp.sendRequest('workspace/executeCommand', {
140
+ command: cmd.command,
141
+ arguments: cmd.arguments ?? []
142
+ });
143
+ }, () => session.takeCapturedEdits());
144
+ if (edits.length === 0) {
145
+ fail('move-symbol produced no edit (server did not return or apply one)', flags.json);
146
+ }
147
+ const changes = edits.flatMap(applyEdit);
148
+ emitResult('move-symbol', changes, flags, { targetFile });
149
+ }
150
+ finally {
151
+ await session.stop();
152
+ }
153
+ }
154
+ //# sourceMappingURL=move-symbol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"move-symbol.js","sourceRoot":"","sources":["../../src/commands/move-symbol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `lspeasy query <op> <file> <line:col>`
3
+ *
4
+ * Read-only parity ops (definition / references / hover) exposed for terminals
5
+ * and CI that want the same position-driven interface as the write commands.
6
+ * Always emits JSON on stdout (these are data ops, not edits).
7
+ */
8
+ import { type GlobalFlags } from '../io.js';
9
+ export declare function runQuery(args: {
10
+ op: string;
11
+ file: string;
12
+ position: string;
13
+ }, flags: GlobalFlags): Promise<void>;
14
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/commands/query.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAqD,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAK/F,wBAAsB,QAAQ,CAC5B,IAAI,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EACpD,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CAkCf"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * `lspeasy query <op> <file> <line:col>`
3
+ *
4
+ * Read-only parity ops (definition / references / hover) exposed for terminals
5
+ * and CI that want the same position-driven interface as the write commands.
6
+ * Always emits JSON on stdout (these are data ops, not edits).
7
+ */
8
+ import { pathToFileURL } from 'node:url';
9
+ import { fail, parseLineCol, resolvePathArg, toLspPosition } from '../io.js';
10
+ import { RefactorSession } from '../session.js';
11
+ const OPS = new Set(['definition', 'references', 'hover']);
12
+ export async function runQuery(args, flags) {
13
+ if (!OPS.has(args.op))
14
+ fail(`unknown query op "${args.op}" (definition | references | hover)`, true);
15
+ const file = resolvePathArg(args.file, flags);
16
+ const pos = toLspPosition(parseLineCol(args.position));
17
+ const session = new RefactorSession({
18
+ serverCommand: flags.server,
19
+ root: flags.root,
20
+ indexWaitMs: flags.waitMs,
21
+ verbose: flags.verbose
22
+ });
23
+ try {
24
+ await session.start();
25
+ await session.openAndWait(file);
26
+ const td = { textDocument: { uri: pathToFileURL(file).href }, position: pos };
27
+ let result;
28
+ if (args.op === 'definition') {
29
+ result = await session.lsp.sendRequest('textDocument/definition', td);
30
+ }
31
+ else if (args.op === 'references') {
32
+ result = await session.lsp.sendRequest('textDocument/references', {
33
+ ...td,
34
+ context: { includeDeclaration: true }
35
+ });
36
+ }
37
+ else {
38
+ result = await session.lsp.sendRequest('textDocument/hover', td);
39
+ }
40
+ process.stdout.write(JSON.stringify({ ok: true, op: args.op, result }) + '\n');
41
+ }
42
+ finally {
43
+ await session.stop();
44
+ }
45
+ }
46
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/commands/query.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAoB,MAAM,UAAU,CAAC;AAC/F,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;AAE3D,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAoD,EACpD,KAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACnB,IAAI,CAAC,qBAAqB,IAAI,CAAC,EAAE,qCAAqC,EAAE,IAAI,CAAC,CAAC;IAChF,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC9C,MAAM,GAAG,GAAG,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEvD,MAAM,OAAO,GAAG,IAAI,eAAe,CAAC;QAClC,aAAa,EAAE,KAAK,CAAC,MAAM;QAC3B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,WAAW,EAAE,KAAK,CAAC,MAAM;QACzB,OAAO,EAAE,KAAK,CAAC,OAAO;KACvB,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAEhC,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;QAC9E,IAAI,MAAe,CAAC;QACpB,IAAI,IAAI,CAAC,EAAE,KAAK,YAAY,EAAE,CAAC;YAC7B,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,yBAAyB,EAAE,EAAE,CAAC,CAAC;QACxE,CAAC;aAAM,IAAI,IAAI,CAAC,EAAE,KAAK,YAAY,EAAE,CAAC;YACpC,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,yBAAyB,EAAE;gBAChE,GAAG,EAAE;gBACL,OAAO,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE;aACtC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC;QACnE,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IACjF,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `lspeasy rename <file> <line:col> <newName>`
3
+ *
4
+ * Drives `textDocument/rename` and applies the returned WorkspaceEdit. The LSP
5
+ * server finds every reference — including re-exports, aliased imports,
6
+ * type-only imports, and `{@link}` references — that a text search would miss.
7
+ */
8
+ import { type GlobalFlags } from '../io.js';
9
+ export declare function runRename(args: {
10
+ file: string;
11
+ position: string;
12
+ newName: string;
13
+ }, flags: GlobalFlags): Promise<void>;
14
+ //# sourceMappingURL=rename.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rename.d.ts","sourceRoot":"","sources":["../../src/commands/rename.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAML,KAAK,WAAW,EACjB,MAAM,UAAU,CAAC;AAGlB,wBAAsB,SAAS,CAC7B,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EACzD,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CAoCf"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * `lspeasy rename <file> <line:col> <newName>`
3
+ *
4
+ * Drives `textDocument/rename` and applies the returned WorkspaceEdit. The LSP
5
+ * server finds every reference — including re-exports, aliased imports,
6
+ * type-only imports, and `{@link}` references — that a text search would miss.
7
+ */
8
+ import { pathToFileURL } from 'node:url';
9
+ import { applyWorkspaceEdit, planWorkspaceEdit } from '../apply.js';
10
+ import { emitResult, fail, parseLineCol, resolvePathArg, toLspPosition } from '../io.js';
11
+ import { RefactorSession } from '../session.js';
12
+ export async function runRename(args, flags) {
13
+ const file = resolvePathArg(args.file, flags);
14
+ const pos = toLspPosition(parseLineCol(args.position));
15
+ const session = new RefactorSession({
16
+ serverCommand: flags.server,
17
+ root: flags.root,
18
+ indexWaitMs: flags.waitMs,
19
+ verbose: flags.verbose
20
+ });
21
+ try {
22
+ await session.start();
23
+ await session.openAndWait(file);
24
+ const edit = await session.requestWithRetry(() => session.lsp.sendRequest('textDocument/rename', {
25
+ textDocument: { uri: pathToFileURL(file).href },
26
+ position: pos,
27
+ newName: args.newName
28
+ }));
29
+ if (!edit)
30
+ fail('rename returned no edit (symbol not renamable, or project not indexed)', flags.json);
31
+ // Validate the SERVER-RETURNED edit against --root: a server can return a
32
+ // URI/resource op outside the project; applying it unguarded would bypass
33
+ // the --root safety guarantee. The guard refuses unless --allow-outside-root.
34
+ const guard = { root: flags.root, allowOutsideRoot: flags.allowOutsideRoot };
35
+ const changes = flags.dryRun ? planWorkspaceEdit(edit, guard) : applyWorkspaceEdit(edit, guard);
36
+ emitResult('rename', changes, flags, { newName: args.newName });
37
+ }
38
+ finally {
39
+ await session.stop();
40
+ }
41
+ }
42
+ //# sourceMappingURL=rename.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rename.js","sourceRoot":"","sources":["../../src/commands/rename.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAsB,MAAM,aAAa,CAAC;AACxF,OAAO,EACL,UAAU,EACV,IAAI,EACJ,YAAY,EACZ,cAAc,EACd,aAAa,EAEd,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAyD,EACzD,KAAkB;IAElB,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC9C,MAAM,GAAG,GAAG,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEvD,MAAM,OAAO,GAAG,IAAI,eAAe,CAAC;QAClC,aAAa,EAAE,KAAK,CAAC,MAAM;QAC3B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,WAAW,EAAE,KAAK,CAAC,MAAM;QACzB,OAAO,EAAE,KAAK,CAAC,OAAO;KACvB,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAEhC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,gBAAgB,CACzC,GAAG,EAAE,CACH,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,qBAAqB,EAAE;YAC7C,YAAY,EAAE,EAAE,GAAG,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE;YAC/C,QAAQ,EAAE,GAAG;YACb,OAAO,EAAE,IAAI,CAAC,OAAO;SACtB,CAAkC,CACtC,CAAC;QAEF,IAAI,CAAC,IAAI;YACP,IAAI,CAAC,wEAAwE,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAE7F,0EAA0E;QAC1E,0EAA0E;QAC1E,8EAA8E;QAC9E,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,EAAE,CAAC;QAC7E,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAChG,UAAU,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAClE,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { RefactorSession } from './session.js';
2
+ export interface ConnectOptions {
3
+ root: string;
4
+ languageId: string;
5
+ serverCommand?: string;
6
+ indexWaitMs: number;
7
+ verbose: boolean;
8
+ }
9
+ export declare function connectViaProxy(opts: ConnectOptions): Promise<RefactorSession>;
10
+ //# sourceMappingURL=connect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,eAAe,EAAuB,MAAM,cAAc,CAAC;AAmCpE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CA0BpF"}
@@ -0,0 +1,63 @@
1
+ // apps/cli/src/connect.ts
2
+ import { spawn } from 'node:child_process';
3
+ import { existsSync, mkdirSync } from 'node:fs';
4
+ import { dirname } from 'node:path';
5
+ import { createConnection } from 'node:net';
6
+ import { SocketTransport } from '@lspeasy/core/node';
7
+ import { socketPath } from '@lsproxy/proxy';
8
+ import { RefactorSession } from './session.js';
9
+ const PROXY_BIN = new URL('../../proxy/dist/main.js', import.meta.url).pathname;
10
+ const POLL_INTERVAL_MS = 100;
11
+ const POLL_TIMEOUT_MS = 5000;
12
+ async function tryConnect(sockPath) {
13
+ return new Promise((resolve) => {
14
+ const socket = createConnection({ path: sockPath });
15
+ socket.once('connect', () => {
16
+ socket.destroy();
17
+ resolve(true);
18
+ });
19
+ socket.once('error', () => resolve(false));
20
+ });
21
+ }
22
+ async function pollForSocket(sockPath, timeoutMs) {
23
+ const deadline = Date.now() + timeoutMs;
24
+ while (Date.now() < deadline) {
25
+ if (existsSync(sockPath) && (await tryConnect(sockPath)))
26
+ return;
27
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
28
+ }
29
+ throw new Error(`Proxy daemon did not start within ${timeoutMs}ms (socket: ${sockPath})`);
30
+ }
31
+ function spawnDaemon(root, sockPath) {
32
+ mkdirSync(dirname(sockPath), { recursive: true });
33
+ const child = spawn(process.execPath, [PROXY_BIN, '--root', root, '--socket', sockPath], {
34
+ detached: true,
35
+ stdio: 'ignore'
36
+ });
37
+ child.unref();
38
+ }
39
+ export async function connectViaProxy(opts) {
40
+ const sockPath = socketPath(opts.root);
41
+ const alreadyUp = existsSync(sockPath) && (await tryConnect(sockPath));
42
+ if (!alreadyUp) {
43
+ if (opts.verbose)
44
+ process.stderr.write(`[lspeasy] spawning proxy daemon\n`);
45
+ spawnDaemon(opts.root, sockPath);
46
+ await pollForSocket(sockPath, POLL_TIMEOUT_MS);
47
+ }
48
+ if (opts.verbose)
49
+ process.stderr.write(`[lspeasy] connecting via proxy ${sockPath}\n`);
50
+ const transport = new SocketTransport({ path: sockPath });
51
+ await transport.waitForConnect();
52
+ const sessionOpts = {
53
+ root: opts.root,
54
+ languageId: opts.languageId,
55
+ indexWaitMs: opts.indexWaitMs,
56
+ verbose: opts.verbose,
57
+ transport
58
+ };
59
+ const session = new RefactorSession(sessionOpts);
60
+ await session.start();
61
+ return session;
62
+ }
63
+ //# sourceMappingURL=connect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect.js","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export interface LspServerEntry {
2
+ command: string;
3
+ args?: string[];
4
+ fileExtensions: Record<string, string>;
5
+ }
6
+ export interface LspJson {
7
+ lspServers: Record<string, LspServerEntry>;
8
+ }
9
+ export interface ResolvedServer {
10
+ /** Full spawn command string passed to RefactorSession as serverCommand. */
11
+ serverCommand: string;
12
+ /** languageId for textDocument/didOpen (e.g. 'typescript', 'rust'). */
13
+ languageId: string;
14
+ }
15
+ export declare function selectServer(config: LspJson, fileExt: string): ResolvedServer | null;
16
+ export declare function discoverServer(root: string, fileExt: string): ResolvedServer | null;
17
+ //# sourceMappingURL=discover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,OAAO;IACtB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,aAAa,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;CACpB;AAaD,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAkBpF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAWnF"}
@@ -0,0 +1,46 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ const SEARCH_PATHS = ['lsp.json', '.claude/lsp.json', '.github/lsp.json'];
5
+ function findLspJsonPath(root) {
6
+ for (const rel of SEARCH_PATHS) {
7
+ const full = join(root, rel);
8
+ if (existsSync(full))
9
+ return full;
10
+ }
11
+ const global = join(homedir(), '.claude', 'lsp.json');
12
+ return existsSync(global) ? global : null;
13
+ }
14
+ export function selectServer(config, fileExt) {
15
+ for (const entry of Object.values(config.lspServers)) {
16
+ const languageId = entry.fileExtensions[fileExt];
17
+ if (languageId) {
18
+ const parts = [entry.command, ...(entry.args ?? [])].filter(Boolean);
19
+ // Quote tokens containing spaces so tokenizeCommand in session.ts can
20
+ // round-trip them without splitting on the embedded whitespace.
21
+ // Escape backslashes first, then double-quotes: tokenizeCommand treats \\
22
+ // inside double-quotes as a literal backslash, so a trailing backslash in a
23
+ // Windows path like "C:\Program Files\" must become "C:\Program Files\\"
24
+ // to avoid the closing " being consumed as \" (an escaped quote).
25
+ const quoted = parts.map((t) => t.includes(' ') ? `"${t.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` : t);
26
+ return { serverCommand: quoted.join(' '), languageId };
27
+ }
28
+ }
29
+ return null;
30
+ }
31
+ export function discoverServer(root, fileExt) {
32
+ const lspJsonPath = findLspJsonPath(root);
33
+ if (!lspJsonPath)
34
+ return null;
35
+ let parsed;
36
+ try {
37
+ parsed = JSON.parse(readFileSync(lspJsonPath, 'utf8'));
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ if (!parsed || typeof parsed['lspServers'] !== 'object')
43
+ return null;
44
+ return selectServer(parsed, fileExt);
45
+ }
46
+ //# sourceMappingURL=discover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAmBjC,MAAM,YAAY,GAAG,CAAC,UAAU,EAAE,kBAAkB,EAAE,kBAAkB,CAAC,CAAC;AAE1E,SAAS,eAAe,CAAC,IAAY;IACnC,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC7B,IAAI,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IACpC,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;IACtD,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAe,EAAE,OAAe;IAC3D,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QACrD,MAAM,UAAU,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QACjD,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACrE,sEAAsE;YACtE,gEAAgE;YAChE,0EAA0E;YAC1E,4EAA4E;YAC5E,yEAAyE;YACzE,kEAAkE;YAClE,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC7B,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAC3E,CAAC;YACF,OAAO,EAAE,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,UAAU,EAAE,CAAC;QACzD,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,OAAe;IAC1D,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,MAAM,IAAI,OAAQ,MAAkC,CAAC,YAAY,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAClG,OAAO,YAAY,CAAC,MAAiB,EAAE,OAAO,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@lspeasy/cli` — programmatic entry point.
3
+ *
4
+ * Exposes the reusable refactor internals (session pipeline + WorkspaceEdit
5
+ * applier) so the same machinery can be embedded in scripts, not just invoked
6
+ * through the `lspeasy` bin.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ export { RefactorSession } from './session.js';
11
+ export type { SessionOptions } from './session.js';
12
+ export { applyWorkspaceEdit, applyTextEdits, planWorkspaceEdit } from './apply.js';
13
+ export type { WorkspaceEdit, LspTextEdit, LspRange, LspPosition, AppliedChange } from './apply.js';
14
+ export type { GlobalFlags } from './io.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACnF,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnG,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@lspeasy/cli` — programmatic entry point.
3
+ *
4
+ * Exposes the reusable refactor internals (session pipeline + WorkspaceEdit
5
+ * applier) so the same machinery can be embedded in scripts, not just invoked
6
+ * through the `lspeasy` bin.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ export { RefactorSession } from './session.js';
11
+ export { applyWorkspaceEdit, applyTextEdits, planWorkspaceEdit } from './apply.js';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
package/dist/io.d.ts ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Shared CLI I/O helpers.
3
+ *
4
+ * Convention: **stdout is reserved for command output** (human summary or, with
5
+ * `--json`, a single JSON document). All progress/diagnostic chatter goes to
6
+ * **stderr** so piped `--json` consumers get clean input.
7
+ */
8
+ import type { AppliedChange } from './apply.js';
9
+ export interface GlobalFlags {
10
+ server: string;
11
+ root: string;
12
+ dryRun: boolean;
13
+ json: boolean;
14
+ verbose: boolean;
15
+ waitMs: number;
16
+ allowOutsideRoot: boolean;
17
+ /** Permit move-file to replace an existing destination (default: OFF). */
18
+ overwrite: boolean;
19
+ /** Bypass the proxy daemon and connect directly to the language server. */
20
+ noProxy: boolean;
21
+ }
22
+ /**
23
+ * Test whether an absolute path is contained within `root`, resolving symlinks
24
+ * in both the candidate and the root before comparing. A lexical-only check
25
+ * accepts `root/link/x` where `root/link -> /tmp/external`; canonicalizing the
26
+ * existing ancestors closes that escape.
27
+ */
28
+ export declare function isInsideRoot(absPath: string, root: string): boolean;
29
+ /**
30
+ * Resolve a file-path argument against the project `--root` and enforce that it
31
+ * stays inside that root.
32
+ *
33
+ * This is the single base both the server (spawn cwd / `rootUri`) and the file
34
+ * arguments share — they MUST agree. Relative args are resolved against `root`
35
+ * (NOT `process.cwd()`), so running the CLI from an unrelated directory (e.g. a
36
+ * different git worktree of the same project) cannot silently operate on the
37
+ * wrong checkout. Absolute args are used as-is.
38
+ *
39
+ * Unless `allowOutsideRoot` is set, any resolved path outside `root` is rejected
40
+ * (error + non-zero exit, no changes) — defense-in-depth against the wrong-repo
41
+ * incident relative-cwd resolution caused. Containment is symlink-aware (see
42
+ * {@link isInsideRoot}).
43
+ */
44
+ export declare function resolvePathArg(arg: string, flags: Pick<GlobalFlags, 'root' | 'json' | 'allowOutsideRoot'>): string;
45
+ /**
46
+ * Refuse a SERVER-RETURNED set of edit/resource targets that escape `--root`.
47
+ *
48
+ * The input-path guard ({@link resolvePathArg}) only validates CLI arguments;
49
+ * a language server can return a {@link WorkspaceEdit} whose URIs/resource ops
50
+ * point outside the project. Applying that unguarded would let a server
51
+ * modify, rename, or delete an external file — bypassing the `--root` safety
52
+ * guarantee. Every command routes server edits through this pre-flight check
53
+ * (via `apply.ts`), so all three inherit the boundary.
54
+ *
55
+ * Throws (rather than `fail()`/exit) so it composes inside the apply pipeline
56
+ * and is unit-testable; callers surface the message verbatim.
57
+ */
58
+ export declare function assertTargetsInsideRoot(targets: readonly string[], flags: Pick<GlobalFlags, 'root' | 'allowOutsideRoot'>): void;
59
+ /** Editor-style 1-based `line:col`. */
60
+ export interface OneBasedPosition {
61
+ line: number;
62
+ character: number;
63
+ }
64
+ /** Parse `line:col` (1-based, editor style) into a 1-based position. */
65
+ export declare function parseLineCol(input: string): OneBasedPosition;
66
+ /** Convert a 1-based editor position to the 0-based position LSP expects. */
67
+ export declare function toLspPosition(p: OneBasedPosition): {
68
+ line: number;
69
+ character: number;
70
+ };
71
+ export declare function fail(message: string, json: boolean): never;
72
+ /** Render a list of applied/planned changes for human output. */
73
+ export declare function summarizeChanges(changes: AppliedChange[], root: string, dryRun: boolean): string;
74
+ /** Emit the result, either as JSON (stdout) or a human summary. */
75
+ export declare function emitResult(op: string, changes: AppliedChange[], flags: Pick<GlobalFlags, 'json' | 'root' | 'dryRun'>, extra?: Record<string, unknown>): void;
76
+ //# sourceMappingURL=io.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"io.d.ts","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,SAAS,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAC;CAClB;AA8BD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAInE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,kBAAkB,CAAC,GAC7D,MAAM,CAOR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,CAAC,GACpD,IAAI,CAUN;AAED,uCAAuC;AACvC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CAO5D;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,CAAC,EAAE,gBAAgB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAEtF;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,KAAK,CAI1D;AAED,iEAAiE;AACjE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAoBhG;AAED,mEAAmE;AACnE,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,aAAa,EAAE,EACxB,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC,EACpD,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAClC,IAAI,CAQN"}