baldart 3.9.0 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +6 -2
- package/VERSION +1 -1
- package/framework/.claude/agents/REGISTRY.md +1 -1
- package/framework/.claude/agents/codebase-architect.md +8 -0
- package/framework/.claude/skills/bug/SKILL.md +5 -1
- package/framework/.claude/skills/context-primer/SKILL.md +4 -0
- package/framework/.claude/skills/lsp-bootstrap/SKILL.md +113 -0
- package/framework/.claude/skills/new/SKILL.md +1 -1
- package/framework/.claude/skills/prd/SKILL.md +3 -1
- package/framework/.claude/skills/simplify/SKILL.md +6 -0
- package/framework/agents/code-search-protocol.md +126 -0
- package/framework/agents/index.md +2 -0
- package/framework/docs/LSP-LAYER.md +405 -0
- package/framework/docs/PROJECT-CONFIGURATION.md +17 -0
- package/framework/templates/baldart.config.template.yml +22 -0
- package/framework/templates/overlays/agents/codebase-architect.lsp-example.md +53 -0
- package/package.json +1 -1
- package/src/commands/configure.js +48 -0
- package/src/commands/doctor.js +55 -0
- package/src/utils/lsp-adapters/go.js +29 -0
- package/src/utils/lsp-adapters/index.js +49 -0
- package/src/utils/lsp-adapters/python.js +42 -0
- package/src/utils/lsp-adapters/ruby.js +30 -0
- package/src/utils/lsp-adapters/rust.js +26 -0
- package/src/utils/lsp-adapters/typescript.js +54 -0
- package/src/utils/lsp-installer.js +118 -0
package/src/commands/doctor.js
CHANGED
|
@@ -30,6 +30,7 @@ const GitUtils = require('../utils/git');
|
|
|
30
30
|
const UI = require('../utils/ui');
|
|
31
31
|
const State = require('../utils/state');
|
|
32
32
|
const Hooks = require('../utils/hooks');
|
|
33
|
+
const LspInstaller = require('../utils/lsp-installer');
|
|
33
34
|
|
|
34
35
|
const FRAMEWORK_DIR = '.framework';
|
|
35
36
|
const REPO_DEFAULT = 'antbald/BALDART';
|
|
@@ -196,6 +197,25 @@ async function detectState(cwd, opts = {}) {
|
|
|
196
197
|
|
|
197
198
|
state.editGateRegistered = false;
|
|
198
199
|
try { state.editGateRegistered = Hooks.isRegistered(cwd); } catch (_) {}
|
|
200
|
+
|
|
201
|
+
// ---- LSP layer (since v3.10.0) -------------------------------------
|
|
202
|
+
state.lspEnabled = false;
|
|
203
|
+
state.lspInstalled = [];
|
|
204
|
+
state.lspBroken = [];
|
|
205
|
+
try {
|
|
206
|
+
// Reuse the `config` already parsed above instead of re-reading the file.
|
|
207
|
+
if (config && !config.__malformed && config.features && config.features.has_lsp_layer === true) {
|
|
208
|
+
state.lspEnabled = true;
|
|
209
|
+
const installed = (config.lsp && config.lsp.installed_servers) || [];
|
|
210
|
+
state.lspInstalled = installed;
|
|
211
|
+
const autoVerify = !(config.lsp && config.lsp.auto_verify === false);
|
|
212
|
+
if (autoVerify && installed.length) {
|
|
213
|
+
const installer = new LspInstaller(cwd);
|
|
214
|
+
const results = installer.verifyServers(installed);
|
|
215
|
+
state.lspBroken = results.filter(r => !r.ok).map(r => r.name);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch (_) { /* never block doctor on LSP probe */ }
|
|
199
219
|
}
|
|
200
220
|
|
|
201
221
|
return state;
|
|
@@ -306,6 +326,27 @@ function planActions(state) {
|
|
|
306
326
|
});
|
|
307
327
|
}
|
|
308
328
|
|
|
329
|
+
if (state.lspEnabled && state.lspBroken && state.lspBroken.length > 0) {
|
|
330
|
+
actions.push({
|
|
331
|
+
key: 'lsp-fix',
|
|
332
|
+
label: `Reinstall ${state.lspBroken.length} broken LSP server(s)`,
|
|
333
|
+
why: `LSP layer is enabled but these language servers are not reachable: ${state.lspBroken.join(', ')}. Symbol search will silently fall back to Grep until they are reinstalled.`,
|
|
334
|
+
autoOk: false, // touches dev-deps and is the kind of action a user may
|
|
335
|
+
// have made deliberate (switching from npm pyright to system pyright);
|
|
336
|
+
// keep the outer prompt + the per-server confirm both alive.
|
|
337
|
+
run: async () => {
|
|
338
|
+
const installer = new LspInstaller(state.cwd);
|
|
339
|
+
const { installed, manual, skipped } = await installer.installServers(state.lspBroken);
|
|
340
|
+
if (installed.length) UI.success(`Reinstalled: ${installed.map(i => i.name).join(', ')}`);
|
|
341
|
+
for (const m of manual) {
|
|
342
|
+
UI.warning(`System-level reinstall required for ${m.label}:`);
|
|
343
|
+
console.log(` ${m.command}`);
|
|
344
|
+
}
|
|
345
|
+
if (skipped.length) UI.warning(`Skipped: ${skipped.map(s => `${s.name} (${s.reason})`).join(', ')}`);
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
309
350
|
const remoteAhead = state.remote.fetched && state.remote.behind > 0;
|
|
310
351
|
const hasLocalWork = state.workingTreeDirty > 0 || state.commitsAhead > 0;
|
|
311
352
|
|
|
@@ -437,6 +478,20 @@ function renderDiagnostic(state) {
|
|
|
437
478
|
state.editGateRegistered ? 'ok' : 'warn'
|
|
438
479
|
));
|
|
439
480
|
|
|
481
|
+
if (state.lspEnabled) {
|
|
482
|
+
const total = state.lspInstalled.length;
|
|
483
|
+
const broken = state.lspBroken.length;
|
|
484
|
+
const value = total === 0
|
|
485
|
+
? 'enabled but no servers installed'
|
|
486
|
+
: broken === 0
|
|
487
|
+
? `${total} server(s) verified (${state.lspInstalled.join(', ')})`
|
|
488
|
+
: `${broken}/${total} broken (${state.lspBroken.join(', ')})`;
|
|
489
|
+
const severity = total === 0 || broken > 0 ? 'warn' : 'ok';
|
|
490
|
+
console.log(statusLine('LSP layer', value, severity));
|
|
491
|
+
} else {
|
|
492
|
+
console.log(statusLine('LSP layer', 'disabled', 'ok'));
|
|
493
|
+
}
|
|
494
|
+
|
|
440
495
|
console.log();
|
|
441
496
|
}
|
|
442
497
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Go LSP adapter — gopls.
|
|
6
|
+
*
|
|
7
|
+
* gopls is a Go binary; we can't install it into node_modules. installMode
|
|
8
|
+
* is 'system' which means the configure flow prints the canonical install
|
|
9
|
+
* command and the user runs it themselves. The verify step still checks
|
|
10
|
+
* that the binary is in PATH.
|
|
11
|
+
*/
|
|
12
|
+
class GoAdapter {
|
|
13
|
+
constructor(cwd = process.cwd()) { this.cwd = cwd; }
|
|
14
|
+
|
|
15
|
+
get name() { return 'go'; }
|
|
16
|
+
get label() { return 'Go (gopls)'; }
|
|
17
|
+
get binary() { return 'gopls'; }
|
|
18
|
+
get installMode() { return 'system'; }
|
|
19
|
+
|
|
20
|
+
installCommand() { return 'go install golang.org/x/tools/gopls@latest'; }
|
|
21
|
+
verifyCommand() { return 'gopls version'; }
|
|
22
|
+
claudePluginId() { return null; }
|
|
23
|
+
|
|
24
|
+
static detect(cwd = process.cwd()) {
|
|
25
|
+
return fs.existsSync(path.join(cwd, 'go.mod'));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = GoAdapter;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const TypeScriptAdapter = require('./typescript');
|
|
2
|
+
const PythonAdapter = require('./python');
|
|
3
|
+
const GoAdapter = require('./go');
|
|
4
|
+
const RustAdapter = require('./rust');
|
|
5
|
+
const RubyAdapter = require('./ruby');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* LSP adapter registry.
|
|
9
|
+
*
|
|
10
|
+
* Adding a new language (Java, C/C++, PHP, Elixir, …):
|
|
11
|
+
* 1. Create `src/utils/lsp-adapters/<lang>.js` exporting a class with the
|
|
12
|
+
* same shape as TypeScriptAdapter (name, label, binary, installMode,
|
|
13
|
+
* installCommand, verifyCommand, claudePluginId, static detect).
|
|
14
|
+
* 2. Add it to the REGISTRY below.
|
|
15
|
+
* 3. (Optional) Wire a Claude Code plugin id if one exists.
|
|
16
|
+
*
|
|
17
|
+
* Adapters describe *how* to install/verify a language server; they do NOT
|
|
18
|
+
* speak LSP themselves. The actual symbol search at runtime happens through
|
|
19
|
+
* whatever Claude Code plugin or MCP server the user has loaded.
|
|
20
|
+
*/
|
|
21
|
+
const REGISTRY = {
|
|
22
|
+
typescript: TypeScriptAdapter,
|
|
23
|
+
python: PythonAdapter,
|
|
24
|
+
go: GoAdapter,
|
|
25
|
+
rust: RustAdapter,
|
|
26
|
+
ruby: RubyAdapter
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function listAdapters() { return Object.keys(REGISTRY); }
|
|
30
|
+
|
|
31
|
+
function getAdapter(name, cwd) {
|
|
32
|
+
const Cls = REGISTRY[name];
|
|
33
|
+
if (!Cls) throw new Error(`Unknown LSP adapter: ${name}. Available: ${listAdapters().join(', ')}`);
|
|
34
|
+
return new Cls(cwd);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Probe the cwd for every known language and return the names of adapters
|
|
39
|
+
* whose static detect() fires. Pure: no install side effects.
|
|
40
|
+
*/
|
|
41
|
+
function detectAll(cwd = process.cwd()) {
|
|
42
|
+
return Object.entries(REGISTRY)
|
|
43
|
+
.filter(([, Cls]) => {
|
|
44
|
+
try { return Cls.detect(cwd); } catch { return false; }
|
|
45
|
+
})
|
|
46
|
+
.map(([name]) => name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { REGISTRY, listAdapters, getAdapter, detectAll };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Python LSP adapter — Pyright.
|
|
6
|
+
*
|
|
7
|
+
* Pyright ships as both an npm package and a pip package. We default to the
|
|
8
|
+
* npm devDep route so installs stay project-local and version-pinned without
|
|
9
|
+
* touching the user's Python env. Users who already drive Python via pip/uv
|
|
10
|
+
* can override by removing pyright from devDeps and installing system-wide.
|
|
11
|
+
*
|
|
12
|
+
* Detection signal: pyproject.toml, setup.py, setup.cfg, requirements.txt,
|
|
13
|
+
* Pipfile, or a top-level .py source tree.
|
|
14
|
+
*/
|
|
15
|
+
class PythonAdapter {
|
|
16
|
+
constructor(cwd = process.cwd()) { this.cwd = cwd; }
|
|
17
|
+
|
|
18
|
+
get name() { return 'python'; }
|
|
19
|
+
get label() { return 'Python (Pyright)'; }
|
|
20
|
+
get binary() { return 'pyright'; }
|
|
21
|
+
get installMode() { return 'npm-dev'; }
|
|
22
|
+
get npmPackage() { return 'pyright'; }
|
|
23
|
+
|
|
24
|
+
installCommand() { return 'npm install --save-dev pyright'; }
|
|
25
|
+
verifyCommand() { return 'npx --no-install pyright --version'; }
|
|
26
|
+
claudePluginId() { return null; }
|
|
27
|
+
|
|
28
|
+
static detect(cwd = process.cwd()) {
|
|
29
|
+
const markers = ['pyproject.toml', 'setup.py', 'setup.cfg', 'requirements.txt', 'Pipfile'];
|
|
30
|
+
for (const m of markers) {
|
|
31
|
+
if (fs.existsSync(path.join(cwd, m))) return true;
|
|
32
|
+
}
|
|
33
|
+
// Fallback: a `src/` or top-level *.py file is a strong enough signal.
|
|
34
|
+
try {
|
|
35
|
+
const entries = fs.readdirSync(cwd);
|
|
36
|
+
if (entries.some(e => e.endsWith('.py'))) return true;
|
|
37
|
+
} catch { /* ignore */ }
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = PythonAdapter;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Ruby LSP adapter — ruby-lsp (Shopify).
|
|
6
|
+
*
|
|
7
|
+
* Installed as a gem; system-mode. Users on Bundler-managed projects may
|
|
8
|
+
* prefer to add `gem 'ruby-lsp', group: :development` to their Gemfile and
|
|
9
|
+
* `bundle install` instead — we print the simple gem install command for
|
|
10
|
+
* portability.
|
|
11
|
+
*/
|
|
12
|
+
class RubyAdapter {
|
|
13
|
+
constructor(cwd = process.cwd()) { this.cwd = cwd; }
|
|
14
|
+
|
|
15
|
+
get name() { return 'ruby'; }
|
|
16
|
+
get label() { return 'Ruby (ruby-lsp)'; }
|
|
17
|
+
get binary() { return 'ruby-lsp'; }
|
|
18
|
+
get installMode() { return 'system'; }
|
|
19
|
+
|
|
20
|
+
installCommand() { return 'gem install ruby-lsp'; }
|
|
21
|
+
verifyCommand() { return 'ruby-lsp --version'; }
|
|
22
|
+
claudePluginId() { return null; }
|
|
23
|
+
|
|
24
|
+
static detect(cwd = process.cwd()) {
|
|
25
|
+
const markers = ['Gemfile', '.ruby-version', 'config.ru'];
|
|
26
|
+
return markers.some(m => fs.existsSync(path.join(cwd, m)));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = RubyAdapter;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Rust LSP adapter — rust-analyzer.
|
|
6
|
+
*
|
|
7
|
+
* Installed through rustup as a toolchain component. System-mode only.
|
|
8
|
+
*/
|
|
9
|
+
class RustAdapter {
|
|
10
|
+
constructor(cwd = process.cwd()) { this.cwd = cwd; }
|
|
11
|
+
|
|
12
|
+
get name() { return 'rust'; }
|
|
13
|
+
get label() { return 'Rust (rust-analyzer)'; }
|
|
14
|
+
get binary() { return 'rust-analyzer'; }
|
|
15
|
+
get installMode() { return 'system'; }
|
|
16
|
+
|
|
17
|
+
installCommand() { return 'rustup component add rust-analyzer'; }
|
|
18
|
+
verifyCommand() { return 'rust-analyzer --version'; }
|
|
19
|
+
claudePluginId() { return null; }
|
|
20
|
+
|
|
21
|
+
static detect(cwd = process.cwd()) {
|
|
22
|
+
return fs.existsSync(path.join(cwd, 'Cargo.toml'));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = RustAdapter;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* TypeScript / JavaScript LSP adapter.
|
|
6
|
+
*
|
|
7
|
+
* Language server: `typescript-language-server` (Node, npm).
|
|
8
|
+
* Install mode: project-local devDep — keeps the binary inside the consumer
|
|
9
|
+
* repo's node_modules and avoids polluting the user's global env.
|
|
10
|
+
*
|
|
11
|
+
* Detection signal: presence of `tsconfig.json`, `jsconfig.json`, or a
|
|
12
|
+
* `package.json` referencing typescript in deps/devDeps.
|
|
13
|
+
*/
|
|
14
|
+
class TypeScriptAdapter {
|
|
15
|
+
constructor(cwd = process.cwd()) { this.cwd = cwd; }
|
|
16
|
+
|
|
17
|
+
get name() { return 'typescript'; }
|
|
18
|
+
get label() { return 'TypeScript / JavaScript'; }
|
|
19
|
+
get binary() { return 'typescript-language-server'; }
|
|
20
|
+
|
|
21
|
+
/** 'npm-dev' = npm devDependency, 'system' = printed install instructions. */
|
|
22
|
+
get installMode() { return 'npm-dev'; }
|
|
23
|
+
|
|
24
|
+
/** npm package to install when mode='npm-dev'. */
|
|
25
|
+
get npmPackage() { return 'typescript-language-server typescript'; }
|
|
26
|
+
|
|
27
|
+
/** Shell command the user (or installer) should run. */
|
|
28
|
+
installCommand() {
|
|
29
|
+
return 'npm install --save-dev typescript-language-server typescript';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Verify the binary is reachable (no execution side effects). */
|
|
33
|
+
verifyCommand() { return 'npx --no-install typescript-language-server --version'; }
|
|
34
|
+
|
|
35
|
+
/** Claude Code code-intelligence plugin id, if any. Null = use generic LSP. */
|
|
36
|
+
claudePluginId() { return null; }
|
|
37
|
+
|
|
38
|
+
/** Autodetect whether this language is in scope for the project. */
|
|
39
|
+
static detect(cwd = process.cwd()) {
|
|
40
|
+
const markers = ['tsconfig.json', 'jsconfig.json'];
|
|
41
|
+
for (const m of markers) {
|
|
42
|
+
if (fs.existsSync(path.join(cwd, m))) return true;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
46
|
+
if (!fs.existsSync(pkgPath)) return false;
|
|
47
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
48
|
+
const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
49
|
+
return Boolean(allDeps.typescript);
|
|
50
|
+
} catch { return false; }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = TypeScriptAdapter;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const { detectAll, getAdapter, listAdapters } = require('./lsp-adapters');
|
|
3
|
+
const UI = require('./ui');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* High-level LSP installer used by `baldart configure` and the `lsp-bootstrap`
|
|
7
|
+
* skill / doctor step. Owns the install + verify side effects so the command
|
|
8
|
+
* layers stay thin.
|
|
9
|
+
*
|
|
10
|
+
* Contract:
|
|
11
|
+
* - Pure detection (no install) when only `detectLanguages` is called.
|
|
12
|
+
* - All mutating ops respect `--non-interactive` via the `interactive` flag.
|
|
13
|
+
* - Returns plain objects (no throws) so the caller can render results.
|
|
14
|
+
*/
|
|
15
|
+
class LspInstaller {
|
|
16
|
+
constructor(cwd = process.cwd()) { this.cwd = cwd; }
|
|
17
|
+
|
|
18
|
+
/** Languages whose marker files exist under cwd. */
|
|
19
|
+
detectLanguages() { return detectAll(this.cwd); }
|
|
20
|
+
|
|
21
|
+
/** Adapters that BALDART knows about (regardless of detection). */
|
|
22
|
+
knownAdapters() { return listAdapters(); }
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Build a recommendation list from detected languages. Each entry carries
|
|
26
|
+
* the install command and the install mode so the UI can decide whether
|
|
27
|
+
* to run it automatically (npm-dev) or just print it (system).
|
|
28
|
+
*/
|
|
29
|
+
recommend(langs = this.detectLanguages()) {
|
|
30
|
+
return langs.map(name => {
|
|
31
|
+
const a = getAdapter(name, this.cwd);
|
|
32
|
+
return {
|
|
33
|
+
name: a.name,
|
|
34
|
+
label: a.label,
|
|
35
|
+
binary: a.binary,
|
|
36
|
+
installMode: a.installMode,
|
|
37
|
+
installCommand: a.installCommand(),
|
|
38
|
+
verifyCommand: a.verifyCommand(),
|
|
39
|
+
claudePluginId: a.claudePluginId()
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Install one or more language servers. Returns `{ installed, skipped, manual }`.
|
|
46
|
+
* - npm-dev adapters: executed in-process (npm install --save-dev …).
|
|
47
|
+
* - system adapters: never executed automatically; surfaced under `manual`
|
|
48
|
+
* so the caller can print the command for the user to run.
|
|
49
|
+
*
|
|
50
|
+
* `opts.interactive`: when true, asks confirmation per server (default true).
|
|
51
|
+
* `opts.dryRun`: when true, no commands are executed.
|
|
52
|
+
*/
|
|
53
|
+
async installServers(serverNames, opts = {}) {
|
|
54
|
+
const { interactive = true, dryRun = false } = opts;
|
|
55
|
+
const installed = [];
|
|
56
|
+
const skipped = [];
|
|
57
|
+
const manual = [];
|
|
58
|
+
|
|
59
|
+
for (const name of serverNames) {
|
|
60
|
+
let a;
|
|
61
|
+
try { a = getAdapter(name, this.cwd); }
|
|
62
|
+
catch (err) { skipped.push({ name, reason: err.message }); continue; }
|
|
63
|
+
|
|
64
|
+
if (a.installMode === 'system') {
|
|
65
|
+
manual.push({
|
|
66
|
+
name: a.name,
|
|
67
|
+
label: a.label,
|
|
68
|
+
command: a.installCommand()
|
|
69
|
+
});
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (interactive) {
|
|
74
|
+
const ok = await UI.confirm(`Install ${a.label} via \`${a.installCommand()}\`?`, true);
|
|
75
|
+
if (!ok) { skipped.push({ name, reason: 'user declined' }); continue; }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (dryRun) { installed.push({ name, dryRun: true }); continue; }
|
|
79
|
+
|
|
80
|
+
const spinner = UI.spinner(`Installing ${a.label}…`).start();
|
|
81
|
+
try {
|
|
82
|
+
execSync(a.installCommand(), { cwd: this.cwd, stdio: 'pipe' });
|
|
83
|
+
spinner.succeed(`Installed ${a.label}`);
|
|
84
|
+
installed.push({ name });
|
|
85
|
+
} catch (err) {
|
|
86
|
+
spinner.fail(`Failed to install ${a.label}`);
|
|
87
|
+
skipped.push({ name, reason: err.message.split('\n')[0] });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { installed, skipped, manual };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Verify each named server's binary is reachable. Returns
|
|
96
|
+
* `[{ name, ok, output | error }]`. Never throws.
|
|
97
|
+
*/
|
|
98
|
+
verifyServers(serverNames) {
|
|
99
|
+
return serverNames.map(name => {
|
|
100
|
+
let a;
|
|
101
|
+
try { a = getAdapter(name, this.cwd); }
|
|
102
|
+
catch (err) { return { name, ok: false, error: err.message }; }
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const out = execSync(a.verifyCommand(), {
|
|
106
|
+
cwd: this.cwd,
|
|
107
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
108
|
+
timeout: 8000
|
|
109
|
+
}).toString().trim();
|
|
110
|
+
return { name, ok: true, output: out };
|
|
111
|
+
} catch (err) {
|
|
112
|
+
return { name, ok: false, error: (err.message || '').split('\n')[0] };
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = LspInstaller;
|