@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
package/dist/io.js ADDED
@@ -0,0 +1,150 @@
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 { existsSync, realpathSync } from 'node:fs';
9
+ import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
10
+ /**
11
+ * Resolve a path's longest EXISTING ancestor through `realpath`, then rejoin the
12
+ * non-existing tail. This makes containment checks symlink-aware even for
13
+ * destinations that do not exist yet (e.g. a move target): if any existing
14
+ * ancestor is a symlink escaping `--root`, the canonical path reflects it, while
15
+ * a not-yet-created leaf cannot be `realpath`'d directly (it would throw).
16
+ *
17
+ * Walks up to a fixed-point so a chain like `root/link -> /tmp/ext` + `ext/sub`
18
+ * is fully canonicalized via the first existing ancestor.
19
+ */
20
+ function canonicalize(absPath) {
21
+ let existing = absPath;
22
+ const tail = [];
23
+ while (!existsSync(existing)) {
24
+ const parent = dirname(existing);
25
+ if (parent === existing)
26
+ break; // reached filesystem root
27
+ tail.unshift(existing.slice(parent.length + 1));
28
+ existing = parent;
29
+ }
30
+ try {
31
+ const real = realpathSync(existing);
32
+ return tail.length ? resolve(real, ...tail) : real;
33
+ }
34
+ catch {
35
+ // realpath can still fail on permission errors; fall back to the lexical path.
36
+ return absPath;
37
+ }
38
+ }
39
+ /**
40
+ * Test whether an absolute path is contained within `root`, resolving symlinks
41
+ * in both the candidate and the root before comparing. A lexical-only check
42
+ * accepts `root/link/x` where `root/link -> /tmp/external`; canonicalizing the
43
+ * existing ancestors closes that escape.
44
+ */
45
+ export function isInsideRoot(absPath, root) {
46
+ const realRoot = canonicalize(resolve(root));
47
+ const realPath = canonicalize(absPath);
48
+ return realPath === realRoot || realPath.startsWith(realRoot + sep);
49
+ }
50
+ /**
51
+ * Resolve a file-path argument against the project `--root` and enforce that it
52
+ * stays inside that root.
53
+ *
54
+ * This is the single base both the server (spawn cwd / `rootUri`) and the file
55
+ * arguments share — they MUST agree. Relative args are resolved against `root`
56
+ * (NOT `process.cwd()`), so running the CLI from an unrelated directory (e.g. a
57
+ * different git worktree of the same project) cannot silently operate on the
58
+ * wrong checkout. Absolute args are used as-is.
59
+ *
60
+ * Unless `allowOutsideRoot` is set, any resolved path outside `root` is rejected
61
+ * (error + non-zero exit, no changes) — defense-in-depth against the wrong-repo
62
+ * incident relative-cwd resolution caused. Containment is symlink-aware (see
63
+ * {@link isInsideRoot}).
64
+ */
65
+ export function resolvePathArg(arg, flags) {
66
+ const root = resolve(flags.root);
67
+ const abs = isAbsolute(arg) ? resolve(arg) : resolve(root, arg);
68
+ if (!flags.allowOutsideRoot && !isInsideRoot(abs, root)) {
69
+ fail(`refusing: ${abs} is outside --root ${root}`, flags.json);
70
+ }
71
+ return abs;
72
+ }
73
+ /**
74
+ * Refuse a SERVER-RETURNED set of edit/resource targets that escape `--root`.
75
+ *
76
+ * The input-path guard ({@link resolvePathArg}) only validates CLI arguments;
77
+ * a language server can return a {@link WorkspaceEdit} whose URIs/resource ops
78
+ * point outside the project. Applying that unguarded would let a server
79
+ * modify, rename, or delete an external file — bypassing the `--root` safety
80
+ * guarantee. Every command routes server edits through this pre-flight check
81
+ * (via `apply.ts`), so all three inherit the boundary.
82
+ *
83
+ * Throws (rather than `fail()`/exit) so it composes inside the apply pipeline
84
+ * and is unit-testable; callers surface the message verbatim.
85
+ */
86
+ export function assertTargetsInsideRoot(targets, flags) {
87
+ if (flags.allowOutsideRoot)
88
+ return;
89
+ const root = resolve(flags.root);
90
+ for (const abs of targets) {
91
+ if (!isInsideRoot(abs, root)) {
92
+ throw new Error(`refusing server edit: ${abs} is outside --root ${root} (pass --allow-outside-root to permit)`);
93
+ }
94
+ }
95
+ }
96
+ /** Parse `line:col` (1-based, editor style) into a 1-based position. */
97
+ export function parseLineCol(input) {
98
+ const m = /^(\d+):(\d+)$/.exec(input.trim());
99
+ if (!m)
100
+ throw new Error(`Invalid position "${input}" — expected line:col (1-based), e.g. 12:7`);
101
+ const line = Number(m[1]);
102
+ const character = Number(m[2]);
103
+ if (line < 1 || character < 1)
104
+ throw new Error(`Position is 1-based; got "${input}"`);
105
+ return { line, character };
106
+ }
107
+ /** Convert a 1-based editor position to the 0-based position LSP expects. */
108
+ export function toLspPosition(p) {
109
+ return { line: p.line - 1, character: p.character - 1 };
110
+ }
111
+ export function fail(message, json) {
112
+ if (json)
113
+ process.stdout.write(JSON.stringify({ ok: false, error: message }) + '\n');
114
+ else
115
+ process.stderr.write(`error: ${message}\n`);
116
+ process.exit(1);
117
+ }
118
+ /** Render a list of applied/planned changes for human output. */
119
+ export function summarizeChanges(changes, root, dryRun) {
120
+ // Use path.relative so the root prefix is stripped with the platform
121
+ // separator (a hardcoded '/' left Windows paths un-shortened). Fall back to
122
+ // the absolute path for anything that resolves outside root.
123
+ const resolvedRoot = resolve(root);
124
+ const rel = (p) => relative(resolvedRoot, p) || p;
125
+ const lines = [];
126
+ const verb = dryRun ? 'would change' : 'changed';
127
+ const totalEdits = changes.reduce((n, c) => n + (c.editCount ?? 0), 0);
128
+ lines.push(`${dryRun ? '[dry-run] ' : ''}${changes.length} file(s) ${verb}, ${totalEdits} text edit(s)`);
129
+ for (const c of changes) {
130
+ if (c.kind === 'edit')
131
+ lines.push(` edit ${c.editCount}\t${rel(c.path)}`);
132
+ else if (c.kind === 'create')
133
+ lines.push(` create \t${rel(c.path)}`);
134
+ else if (c.kind === 'delete')
135
+ lines.push(` delete \t${rel(c.path)}`);
136
+ else if (c.kind === 'rename')
137
+ lines.push(` rename \t${rel(c.path)} -> ${rel(c.toPath ?? '')}`);
138
+ }
139
+ return lines.join('\n');
140
+ }
141
+ /** Emit the result, either as JSON (stdout) or a human summary. */
142
+ export function emitResult(op, changes, flags, extra = {}) {
143
+ if (flags.json) {
144
+ process.stdout.write(JSON.stringify({ ok: true, op, dryRun: flags.dryRun, changes, ...extra }) + '\n');
145
+ }
146
+ else {
147
+ process.stdout.write(summarizeChanges(changes, flags.root, flags.dryRun) + '\n');
148
+ }
149
+ }
150
+ //# sourceMappingURL=io.js.map
package/dist/io.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"io.js","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Thin session wrapper around `@lspeasy/client` for one-shot refactor commands.
3
+ *
4
+ * Encapsulates the proven recipe: spawn the language server, connect (which
5
+ * performs the `initialize` / `initialized` handshake with a generous set of
6
+ * client capabilities), open an anchor file, wait for the project to index,
7
+ * then run a request with a single null-result retry.
8
+ *
9
+ * All diagnostic logging goes to **stderr** so that `--json` output on stdout
10
+ * stays machine-parseable.
11
+ */
12
+ import { LSPClient } from '@lspeasy/client';
13
+ import { type ServerCapabilities, type Transport } from '@lspeasy/core';
14
+ import type { WorkspaceEdit } from './apply.js';
15
+ export interface SessionOptions {
16
+ /** Server launch command, e.g. `typescript-language-server --stdio`. Required unless `transport` is supplied. */
17
+ serverCommand?: string;
18
+ /** Absolute project root directory. */
19
+ root: string;
20
+ /** languageId for textDocument/didOpen (e.g. 'typescript', 'rust'). */
21
+ languageId?: string;
22
+ /** Milliseconds to wait for the server to index before the first request. */
23
+ indexWaitMs?: number;
24
+ /** Emit `[lspeasy] …` progress lines to stderr. */
25
+ verbose?: boolean;
26
+ /**
27
+ * Pre-built transport to use instead of spawning a server process.
28
+ *
29
+ * When supplied, `serverCommand` is ignored and no child process is spawned.
30
+ * Used by the proxy path to reuse all downstream session logic unchanged.
31
+ */
32
+ transport?: Transport;
33
+ }
34
+ /**
35
+ * An ordered queue of edits the server pushed via `workspace/applyEdit`.
36
+ *
37
+ * A server implementing `executeCommand` may send MORE THAN ONE sequential
38
+ * applyEdit request for a single command. The old "single field" capture
39
+ * overwrote earlier edits while still acking each `applied: true`, silently
40
+ * dropping every edit but the last. This queue preserves them all in arrival
41
+ * order; {@link drain} returns and clears the batch.
42
+ */
43
+ export declare class CapturedEdits {
44
+ private edits;
45
+ /** Record an edit the server pushed (handler must NOT drop it). */
46
+ push(edit: WorkspaceEdit): void;
47
+ /** Return all captured edits in order and reset the queue. */
48
+ drain(): WorkspaceEdit[];
49
+ }
50
+ export declare class RefactorSession {
51
+ private readonly opts;
52
+ private proc?;
53
+ private client?;
54
+ /**
55
+ * Edits pushed by the server via `workspace/applyEdit`, in arrival order. A
56
+ * server implementing `executeCommand` may send MORE THAN ONE sequential
57
+ * applyEdit; we queue them all (rather than overwriting) so none is dropped
58
+ * after the server was told `applied: true`.
59
+ */
60
+ private readonly capturedEdits;
61
+ constructor(opts: SessionOptions);
62
+ private log;
63
+ /** Spawn the server (or reuse a pre-built transport) and complete the LSP handshake. */
64
+ start(): Promise<void>;
65
+ /** Notify the server that an anchor file is open. */
66
+ open(anchorFile: string): Promise<void>;
67
+ /**
68
+ * Run a request immediately and retry with exponential backoff while the
69
+ * server returns null (i.e. it is still indexing). Gives up after
70
+ * `indexWaitMs` total elapsed time and returns null.
71
+ *
72
+ * Initial retry delay: 250 ms, doubling each round, capped at 5 s per
73
+ * attempt. The first attempt is always immediate so fast servers pay no
74
+ * extra latency at all.
75
+ */
76
+ requestWithRetry<R>(run: () => Promise<R | null | undefined>): Promise<R | null>;
77
+ /**
78
+ * Drain ALL edits pushed by the server via `workspace/applyEdit` since the
79
+ * last drain, in arrival order. Returns an empty array if none were pushed.
80
+ * Callers apply them in order so a multi-applyEdit command is fully honored.
81
+ */
82
+ takeCapturedEdits(): WorkspaceEdit[];
83
+ get lsp(): LSPClient;
84
+ get capabilities(): ServerCapabilities;
85
+ private requireClient;
86
+ /** Shut down the client and kill the server process. */
87
+ stop(): Promise<void>;
88
+ }
89
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAKL,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACf,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,iHAAiH;IACjH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAwFD;;;;;;;;GAQG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,KAAK,CAAuB;IACpC,mEAAmE;IACnE,IAAI,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAE9B;IACD,8DAA8D;IAC9D,KAAK,IAAI,aAAa,EAAE,CAIvB;CACF;AAgCD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAiC;IAC9C,OAAO,CAAC,MAAM,CAAC,CAAY;IAC3B;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAuB;IAErD,YAAY,IAAI,EAAE,cAAc,EAO/B;IAED,OAAO,CAAC,GAAG;IAIX,wFAAwF;IAClF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CA8C3B;IAED,qDAAqD;IAC/C,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW5C;IAED;;;;;;;;OAQG;IACG,gBAAgB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAarF;IAED;;;;OAIG;IACH,iBAAiB,IAAI,aAAa,EAAE,CAEnC;IAED,IAAI,GAAG,IAAI,SAAS,CAEnB;IAED,IAAI,YAAY,IAAI,kBAAkB,CAErC;IAED,OAAO,CAAC,aAAa;IAKrB,wDAAwD;IAClD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAO1B;CACF"}
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Thin session wrapper around `@lspeasy/client` for one-shot refactor commands.
3
+ *
4
+ * Encapsulates the proven recipe: spawn the language server, connect (which
5
+ * performs the `initialize` / `initialized` handshake with a generous set of
6
+ * client capabilities), open an anchor file, wait for the project to index,
7
+ * then run a request with a single null-result retry.
8
+ *
9
+ * All diagnostic logging goes to **stderr** so that `--json` output on stdout
10
+ * stays machine-parseable.
11
+ */
12
+ import { spawn } from 'node:child_process';
13
+ import { readFileSync } from 'node:fs';
14
+ import { basename, resolve } from 'node:path';
15
+ import { pathToFileURL } from 'node:url';
16
+ import { LSPClient } from '@lspeasy/client';
17
+ import { NullLogger, tokenizeCommand } from '@lspeasy/core';
18
+ import { StdioTransport } from '@lspeasy/core/node';
19
+ // Advertise the full set of capabilities the CLI can dispatch so servers do
20
+ // not gate their ServerCapabilities on a narrow client declaration. Each entry
21
+ // corresponds to a ClientCapability path used by getCapabilityForRequestMethod;
22
+ // a missing entry silently suppresses the matching command from the command tree.
23
+ const CLIENT_CAPABILITIES = {
24
+ textDocument: {
25
+ synchronization: { dynamicRegistration: false },
26
+ // Navigation
27
+ definition: { dynamicRegistration: false },
28
+ declaration: { dynamicRegistration: false },
29
+ typeDefinition: { dynamicRegistration: false },
30
+ implementation: { dynamicRegistration: false },
31
+ references: { dynamicRegistration: false },
32
+ documentHighlight: { dynamicRegistration: false },
33
+ documentSymbol: { dynamicRegistration: false },
34
+ // Hover / completion / signature
35
+ hover: { dynamicRegistration: false },
36
+ completion: { dynamicRegistration: false },
37
+ signatureHelp: { dynamicRegistration: false },
38
+ // Refactor / actions / rename
39
+ rename: { dynamicRegistration: false, prepareSupport: true },
40
+ codeAction: {
41
+ dynamicRegistration: false,
42
+ codeActionLiteralSupport: {
43
+ codeActionKind: {
44
+ valueSet: [
45
+ '',
46
+ 'quickfix',
47
+ 'refactor',
48
+ 'refactor.extract',
49
+ 'refactor.inline',
50
+ 'refactor.move',
51
+ 'refactor.rewrite',
52
+ 'source',
53
+ 'source.organizeImports',
54
+ 'source.fixAll'
55
+ ]
56
+ }
57
+ },
58
+ resolveSupport: { properties: ['edit'] },
59
+ dataSupport: true
60
+ },
61
+ codeLens: { dynamicRegistration: false },
62
+ // Formatting
63
+ formatting: { dynamicRegistration: false },
64
+ rangeFormatting: { dynamicRegistration: false },
65
+ onTypeFormatting: { dynamicRegistration: false },
66
+ // Semantic tokens — needed for servers to advertise semanticTokensProvider
67
+ semanticTokens: {
68
+ dynamicRegistration: false,
69
+ requests: { full: { delta: true }, range: true },
70
+ tokenTypes: [],
71
+ tokenModifiers: [],
72
+ formats: ['relative'],
73
+ overlappingTokenSupport: false,
74
+ multilineTokenSupport: false
75
+ },
76
+ // Hierarchy / navigation
77
+ callHierarchy: { dynamicRegistration: false },
78
+ typeHierarchy: { dynamicRegistration: false },
79
+ selectionRange: { dynamicRegistration: false },
80
+ foldingRange: { dynamicRegistration: false },
81
+ linkedEditingRange: { dynamicRegistration: false },
82
+ // Lenses / hints
83
+ inlayHint: { dynamicRegistration: false, resolveSupport: { properties: [] } },
84
+ inlineValue: { dynamicRegistration: false },
85
+ // Diagnostics / misc
86
+ diagnostic: { dynamicRegistration: false },
87
+ colorProvider: { dynamicRegistration: false },
88
+ documentLink: { dynamicRegistration: false },
89
+ moniker: { dynamicRegistration: false }
90
+ },
91
+ workspace: {
92
+ applyEdit: true,
93
+ executeCommand: { dynamicRegistration: false },
94
+ symbol: { dynamicRegistration: false },
95
+ workspaceEdit: {
96
+ documentChanges: true,
97
+ resourceOperations: ['create', 'rename', 'delete']
98
+ },
99
+ fileOperations: { dynamicRegistration: false, willRename: true }
100
+ }
101
+ };
102
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
103
+ /**
104
+ * An ordered queue of edits the server pushed via `workspace/applyEdit`.
105
+ *
106
+ * A server implementing `executeCommand` may send MORE THAN ONE sequential
107
+ * applyEdit request for a single command. The old "single field" capture
108
+ * overwrote earlier edits while still acking each `applied: true`, silently
109
+ * dropping every edit but the last. This queue preserves them all in arrival
110
+ * order; {@link drain} returns and clears the batch.
111
+ */
112
+ export class CapturedEdits {
113
+ edits = [];
114
+ /** Record an edit the server pushed (handler must NOT drop it). */
115
+ push(edit) {
116
+ this.edits.push(edit);
117
+ }
118
+ /** Return all captured edits in order and reset the queue. */
119
+ drain() {
120
+ const out = this.edits;
121
+ this.edits = [];
122
+ return out;
123
+ }
124
+ }
125
+ /**
126
+ * Logger that routes everything to **stderr**, keeping stdout clean for the
127
+ * CLI's own output (critical for `--json`).
128
+ */
129
+ class StderrLogger {
130
+ error(message, ...args) {
131
+ this.write('error', message, args);
132
+ }
133
+ warn(message, ...args) {
134
+ this.write('warn', message, args);
135
+ }
136
+ info(message, ...args) {
137
+ this.write('info', message, args);
138
+ }
139
+ debug(message, ...args) {
140
+ this.write('debug', message, args);
141
+ }
142
+ trace(message, ...args) {
143
+ this.write('trace', message, args);
144
+ }
145
+ write(level, message, args) {
146
+ const extra = args.length ? ' ' + args.map((a) => JSON.stringify(a)).join(' ') : '';
147
+ process.stderr.write(`[lsp:${level}] ${message}${extra}\n`);
148
+ }
149
+ }
150
+ export class RefactorSession {
151
+ opts;
152
+ proc;
153
+ client;
154
+ /**
155
+ * Edits pushed by the server via `workspace/applyEdit`, in arrival order. A
156
+ * server implementing `executeCommand` may send MORE THAN ONE sequential
157
+ * applyEdit; we queue them all (rather than overwriting) so none is dropped
158
+ * after the server was told `applied: true`.
159
+ */
160
+ capturedEdits = new CapturedEdits();
161
+ constructor(opts) {
162
+ this.opts = {
163
+ indexWaitMs: 15000,
164
+ verbose: false,
165
+ languageId: 'plaintext',
166
+ ...opts
167
+ };
168
+ }
169
+ log(msg) {
170
+ if (this.opts.verbose)
171
+ process.stderr.write(`[lspeasy] ${msg}\n`);
172
+ }
173
+ /** Spawn the server (or reuse a pre-built transport) and complete the LSP handshake. */
174
+ async start() {
175
+ // Derive the workspace root URI + folders from --root so the server indexes
176
+ // the right project (rootUri:null is fragile for non-tsserver servers).
177
+ const rootDir = resolve(this.opts.root);
178
+ const rootUri = pathToFileURL(rootDir).href;
179
+ let transport;
180
+ if (this.opts.transport) {
181
+ transport = this.opts.transport;
182
+ this.log(`using pre-built transport (proxy)`);
183
+ }
184
+ else {
185
+ const [cmd, ...args] = tokenizeCommand(this.opts.serverCommand ?? '');
186
+ if (!cmd)
187
+ throw new Error('Empty --server command');
188
+ this.log(`spawning: ${this.opts.serverCommand} (cwd ${this.opts.root})`);
189
+ const proc = spawn(cmd, args, { cwd: this.opts.root });
190
+ this.proc = proc;
191
+ proc.on('error', (e) => {
192
+ process.stderr.write(`[lspeasy] server spawn error: ${e.message}\n`);
193
+ });
194
+ transport = new StdioTransport({ input: proc.stdout, output: proc.stdin });
195
+ }
196
+ const client = new LSPClient({
197
+ name: 'lspeasy-cli',
198
+ version: '0.1.0',
199
+ capabilities: CLIENT_CAPABILITIES,
200
+ rootUri,
201
+ workspaceFolders: [{ uri: rootUri, name: basename(rootDir) }],
202
+ initializationOptions: { languageId: this.opts.languageId },
203
+ // Keep stdout clean; route the SDK's own logging to stderr (or silence it).
204
+ logger: this.opts.verbose ? new StderrLogger() : new NullLogger()
205
+ });
206
+ this.client = client;
207
+ // Intercept server-pushed edits (tsserver's refactor.move applies this way).
208
+ // Queue every pushed edit in order — a server may send several sequential
209
+ // applyEdit requests for one command, and acking `applied:true` for an edit
210
+ // we then dropped would be a lie that loses changes.
211
+ client.onRequest('workspace/applyEdit', (params) => {
212
+ this.capturedEdits.push(params.edit);
213
+ return { applied: true };
214
+ });
215
+ await client.connect(transport);
216
+ this.log('connected (initialize/initialized handshake complete)');
217
+ }
218
+ /** Notify the server that an anchor file is open. */
219
+ async open(anchorFile) {
220
+ const client = this.requireClient();
221
+ await client.sendNotification('textDocument/didOpen', {
222
+ textDocument: {
223
+ uri: pathToFileURL(anchorFile).href,
224
+ languageId: this.opts.languageId,
225
+ version: 1,
226
+ text: readFileSync(anchorFile, 'utf8')
227
+ }
228
+ });
229
+ this.log(`didOpen ${anchorFile}`);
230
+ }
231
+ /**
232
+ * Run a request immediately and retry with exponential backoff while the
233
+ * server returns null (i.e. it is still indexing). Gives up after
234
+ * `indexWaitMs` total elapsed time and returns null.
235
+ *
236
+ * Initial retry delay: 250 ms, doubling each round, capped at 5 s per
237
+ * attempt. The first attempt is always immediate so fast servers pay no
238
+ * extra latency at all.
239
+ */
240
+ async requestWithRetry(run) {
241
+ const deadline = Date.now() + this.opts.indexWaitMs;
242
+ let delay = 250;
243
+ while (true) {
244
+ const res = await run();
245
+ if (res !== null && res !== undefined)
246
+ return res;
247
+ const remaining = deadline - Date.now();
248
+ if (remaining <= 0)
249
+ return null;
250
+ const wait = Math.min(delay, remaining);
251
+ this.log(`null result — retrying in ${wait}ms (${remaining}ms remaining)`);
252
+ await sleep(wait);
253
+ delay = Math.min(delay * 2, 5000);
254
+ }
255
+ }
256
+ /**
257
+ * Drain ALL edits pushed by the server via `workspace/applyEdit` since the
258
+ * last drain, in arrival order. Returns an empty array if none were pushed.
259
+ * Callers apply them in order so a multi-applyEdit command is fully honored.
260
+ */
261
+ takeCapturedEdits() {
262
+ return this.capturedEdits.drain();
263
+ }
264
+ get lsp() {
265
+ return this.requireClient();
266
+ }
267
+ get capabilities() {
268
+ return this.requireClient().getServerCapabilities() ?? {};
269
+ }
270
+ requireClient() {
271
+ if (!this.client)
272
+ throw new Error('Session not started — call start() first');
273
+ return this.client;
274
+ }
275
+ /** Shut down the client and kill the server process. */
276
+ async stop() {
277
+ try {
278
+ await this.client?.disconnect();
279
+ }
280
+ catch {
281
+ /* ignore shutdown races */
282
+ }
283
+ this.proc?.kill();
284
+ }
285
+ }
286
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ import { z } from 'zod';
3
+ import type { GlobalFlags } from './io.js';
4
+ import type { RefactorSession } from './session.js';
5
+ import { type AppliedChange } from './apply.js';
6
+ export type ArgPattern = 'file-position-newname' | 'file-position' | 'file-range' | 'file' | 'query' | 'raw';
7
+ export declare function detectArgPattern(schema: z.ZodType<unknown>): ArgPattern;
8
+ export declare function marshalParams(pattern: ArgPattern, positional: string[], opts: Record<string, unknown>, flags: GlobalFlags): unknown;
9
+ export declare function printAppliedChanges(changes: AppliedChange[], method: string, dryRun: boolean, json: boolean): void;
10
+ export declare function zodToCommander(method: string, schema: z.ZodType<unknown>, session: RefactorSession, flags: GlobalFlags): Command;
11
+ //# sourceMappingURL=zod-to-commander.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod-to-commander.d.ts","sourceRoot":"","sources":["../src/zod-to-commander.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAIL,KAAK,aAAa,EAEnB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,UAAU,GAClB,uBAAuB,GACvB,eAAe,GACf,YAAY,GACZ,MAAM,GACN,OAAO,GACP,KAAK,CAAC;AAsHV,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,UAAU,CAUvE;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,MAAM,EAAE,EACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,WAAW,GACjB,OAAO,CAwCT;AA8BD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,aAAa,EAAE,EACxB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,OAAO,GACZ,IAAI,CAeN;AAoBD,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAC1B,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,WAAW,GACjB,OAAO,CA4JT"}