@visulima/vis 1.0.0-alpha.1 → 1.0.0-alpha.11
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 +403 -12
- package/LICENSE.md +283 -0
- package/README.md +254 -9
- package/dist/bin.js +9 -146
- package/dist/config/index.d.ts +1818 -0
- package/dist/config/index.js +2 -0
- package/dist/generate/index.d.ts +157 -0
- package/dist/generate/index.js +3 -0
- package/dist/packem_chunks/applyDefaults.js +336 -0
- package/dist/packem_chunks/bin.js +9577 -0
- package/dist/packem_chunks/doctor-probe.js +112 -0
- package/dist/packem_chunks/fix.js +234 -0
- package/dist/packem_chunks/handler.js +99 -0
- package/dist/packem_chunks/handler10.js +53 -0
- package/dist/packem_chunks/handler11.js +32 -0
- package/dist/packem_chunks/handler12.js +100 -0
- package/dist/packem_chunks/handler13.js +25 -0
- package/dist/packem_chunks/handler14.js +916 -0
- package/dist/packem_chunks/handler15.js +206 -0
- package/dist/packem_chunks/handler16.js +124 -0
- package/dist/packem_chunks/handler17.js +13 -0
- package/dist/packem_chunks/handler18.js +106 -0
- package/dist/packem_chunks/handler19.js +19 -0
- package/dist/packem_chunks/handler2.js +75 -0
- package/dist/packem_chunks/handler20.js +29 -0
- package/dist/packem_chunks/handler21.js +222 -0
- package/dist/packem_chunks/handler22.js +237 -0
- package/dist/packem_chunks/handler23.js +101 -0
- package/dist/packem_chunks/handler24.js +110 -0
- package/dist/packem_chunks/handler25.js +402 -0
- package/dist/packem_chunks/handler26.js +13 -0
- package/dist/packem_chunks/handler27.js +63 -0
- package/dist/packem_chunks/handler28.js +34 -0
- package/dist/packem_chunks/handler29.js +458 -0
- package/dist/packem_chunks/handler3.js +95 -0
- package/dist/packem_chunks/handler30.js +170 -0
- package/dist/packem_chunks/handler31.js +530 -0
- package/dist/packem_chunks/handler32.js +214 -0
- package/dist/packem_chunks/handler33.js +119 -0
- package/dist/packem_chunks/handler34.js +630 -0
- package/dist/packem_chunks/handler35.js +283 -0
- package/dist/packem_chunks/handler36.js +542 -0
- package/dist/packem_chunks/handler37.js +762 -0
- package/dist/packem_chunks/handler38.js +989 -0
- package/dist/packem_chunks/handler39.js +574 -0
- package/dist/packem_chunks/handler4.js +90 -0
- package/dist/packem_chunks/handler40.js +1685 -0
- package/dist/packem_chunks/handler41.js +1088 -0
- package/dist/packem_chunks/handler42.js +797 -0
- package/dist/packem_chunks/handler43.js +2658 -0
- package/dist/packem_chunks/handler44.js +3886 -0
- package/dist/packem_chunks/handler45.js +2574 -0
- package/dist/packem_chunks/handler46.js +3769 -0
- package/dist/packem_chunks/handler47.js +1491 -0
- package/dist/packem_chunks/handler5.js +174 -0
- package/dist/packem_chunks/handler6.js +95 -0
- package/dist/packem_chunks/handler7.js +115 -0
- package/dist/packem_chunks/handler8.js +12 -0
- package/dist/packem_chunks/handler9.js +29 -0
- package/dist/packem_chunks/heal-accept.js +522 -0
- package/dist/packem_chunks/heal.js +673 -0
- package/dist/packem_chunks/index.js +873 -0
- package/dist/packem_chunks/loader.js +23 -0
- package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +1316 -0
- package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +5 -0
- package/dist/packem_shared/ai-analysis-CHeB1joD.js +367 -0
- package/dist/packem_shared/ai-cache-Be_jexe4.js +142 -0
- package/dist/packem_shared/ai-fix-B9iQVcD2.js +379 -0
- package/dist/packem_shared/cache-directory-2qvs4goY.js +98 -0
- package/dist/packem_shared/catalog-BJTtyi-O.js +1371 -0
- package/dist/packem_shared/dependency-scan-A0KSklpG.js +188 -0
- package/dist/packem_shared/docker-2iZzc280.js +181 -0
- package/dist/packem_shared/failure-log-Cz3Z4SKL.js +100 -0
- package/dist/packem_shared/flakiness-goTxXuCX.js +180 -0
- package/dist/packem_shared/otel-DCvqCTz_.js +158 -0
- package/dist/packem_shared/otelPlugin-DFaLDvJf.js +3 -0
- package/dist/packem_shared/registry-CbqXI0rc.js +272 -0
- package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +130 -0
- package/dist/packem_shared/runtime-check-Cobi3p6l.js +127 -0
- package/dist/packem_shared/selectors-SM69TfqC.js +194 -0
- package/dist/packem_shared/symbols-Ta7g2nU-.js +14 -0
- package/dist/packem_shared/toolchain-BdZd9eBi.js +975 -0
- package/dist/packem_shared/typosquats-C-bCh3PX.js +1210 -0
- package/dist/packem_shared/use-measured-height-CNP0vT4M.js +20 -0
- package/dist/packem_shared/utils-CthVdBPS.js +40 -0
- package/dist/packem_shared/xxh3-Ck8mXNg1.js +239 -0
- package/index.js +773 -0
- package/package.json +82 -21
- package/schemas/project.schema.json +420 -0
- package/schemas/vis-config.schema.json +501 -0
- package/skills/vis/SKILL.md +96 -0
- package/templates/buildkite-ci/.buildkite/pipeline.yml.tera +85 -0
- package/templates/buildkite-ci/template.yml +20 -0
- package/dist/ai-analysis.d.ts +0 -40
- package/dist/ai-cache.d.ts +0 -21
- package/dist/bin.d.ts +0 -1
- package/dist/catalog.d.ts +0 -110
- package/dist/commands/affected.d.ts +0 -3
- package/dist/commands/ai.d.ts +0 -3
- package/dist/commands/analyze.d.ts +0 -3
- package/dist/commands/check.d.ts +0 -3
- package/dist/commands/graph.d.ts +0 -3
- package/dist/commands/hook/constants.d.ts +0 -8
- package/dist/commands/hook/index.d.ts +0 -3
- package/dist/commands/hook/install.d.ts +0 -7
- package/dist/commands/hook/migrate.d.ts +0 -27
- package/dist/commands/hook/uninstall.d.ts +0 -3
- package/dist/commands/migrate/constants.d.ts +0 -12
- package/dist/commands/migrate/deps.d.ts +0 -32
- package/dist/commands/migrate/index.d.ts +0 -3
- package/dist/commands/migrate/json.d.ts +0 -20
- package/dist/commands/migrate/lint-staged.d.ts +0 -62
- package/dist/commands/migrate/types.d.ts +0 -20
- package/dist/commands/run.d.ts +0 -3
- package/dist/commands/staged.d.ts +0 -3
- package/dist/commands/update.d.ts +0 -3
- package/dist/config.d.ts +0 -40
- package/dist/config.js +0 -1
- package/dist/package-manager.d.ts +0 -23
- package/dist/workspace.d.ts +0 -58
|
@@ -0,0 +1,975 @@
|
|
|
1
|
+
import { createRequire as __cjs_createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
const __cjs_require = __cjs_createRequire(import.meta.url);
|
|
4
|
+
|
|
5
|
+
const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
|
|
6
|
+
|
|
7
|
+
const __cjs_getBuiltinModule = (module) => {
|
|
8
|
+
// Check if we're in Node.js and version supports getBuiltinModule
|
|
9
|
+
if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
|
|
10
|
+
const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
|
|
11
|
+
// Node.js 20.16.0+ and 22.3.0+
|
|
12
|
+
if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
|
|
13
|
+
return __cjs_getProcess.getBuiltinModule(module);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
// Fallback to createRequire
|
|
17
|
+
return __cjs_require(module);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
execFileSync
|
|
22
|
+
} = __cjs_getBuiltinModule("node:child_process");
|
|
23
|
+
const {
|
|
24
|
+
writeFileSync,
|
|
25
|
+
renameSync,
|
|
26
|
+
unlinkSync
|
|
27
|
+
} = __cjs_getBuiltinModule("node:fs");
|
|
28
|
+
import { isAccessibleSync, readFileSync, readJsonSync } from '@visulima/fs';
|
|
29
|
+
import { join, delimiter, sep } from '@visulima/path';
|
|
30
|
+
|
|
31
|
+
const SUPPORTED_MANAGERS = ["proto", "mise", "fnm", "volta", "asdf", "nvm", "corepack"];
|
|
32
|
+
const MANAGER_CAPABILITIES = {
|
|
33
|
+
// npm / pnpm / yarn are available via community asdf plugins; users
|
|
34
|
+
// who haven't installed those plugins won't have asdf reach for
|
|
35
|
+
// them in resolveManagerFor (the manager just won't have a config
|
|
36
|
+
// file referencing them), so listing them here is safe and matches
|
|
37
|
+
// the "asdf is catch-all" docstring above.
|
|
38
|
+
asdf: ["bun", "deno", "go", "node", "npm", "pnpm", "python", "ruby", "rust", "yarn"],
|
|
39
|
+
corepack: ["npm", "pnpm", "yarn"],
|
|
40
|
+
fnm: ["node"],
|
|
41
|
+
mise: ["bun", "deno", "go", "node", "npm", "pnpm", "python", "ruby", "rust", "yarn"],
|
|
42
|
+
nvm: ["node"],
|
|
43
|
+
proto: ["bun", "deno", "go", "node", "npm", "pnpm", "python", "ruby", "rust", "yarn"],
|
|
44
|
+
volta: ["node", "npm", "pnpm", "yarn"]
|
|
45
|
+
};
|
|
46
|
+
const MANAGER_ORDER = ["proto", "mise", "fnm", "volta", "asdf", "nvm", "corepack"];
|
|
47
|
+
const MANAGER_CONFIG_FILES = {
|
|
48
|
+
asdf: [".tool-versions"],
|
|
49
|
+
// Corepack's config is the `packageManager` field in package.json; we
|
|
50
|
+
// detect that separately in `corepackConfigFiles`.
|
|
51
|
+
corepack: [],
|
|
52
|
+
fnm: [".nvmrc", ".node-version"],
|
|
53
|
+
mise: [".mise.toml", ".config/mise.toml", "mise.toml"],
|
|
54
|
+
nvm: [".nvmrc"],
|
|
55
|
+
proto: [".prototools"],
|
|
56
|
+
volta: []
|
|
57
|
+
};
|
|
58
|
+
const onPathCache = /* @__PURE__ */ new Map();
|
|
59
|
+
const isOnPath = (binary) => {
|
|
60
|
+
const cached = onPathCache.get(binary);
|
|
61
|
+
if (cached !== void 0 || onPathCache.has(binary)) {
|
|
62
|
+
return cached;
|
|
63
|
+
}
|
|
64
|
+
const pathEnv = process.env["PATH"];
|
|
65
|
+
if (!pathEnv) {
|
|
66
|
+
onPathCache.set(binary, void 0);
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
const isWindows = process.platform === "win32";
|
|
70
|
+
const exts = isWindows ? ["", ...(process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD").split(";")] : [""];
|
|
71
|
+
for (const rawDir of pathEnv.split(delimiter)) {
|
|
72
|
+
const dir = rawDir.replaceAll(/^["']|["']$/g, "").trim();
|
|
73
|
+
if (dir === "") {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
for (const ext of exts) {
|
|
77
|
+
const candidate = `${dir}${sep}${binary}${ext}`;
|
|
78
|
+
if (isAccessibleSync(candidate)) {
|
|
79
|
+
onPathCache.set(binary, candidate);
|
|
80
|
+
return candidate;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
onPathCache.set(binary, void 0);
|
|
85
|
+
return void 0;
|
|
86
|
+
};
|
|
87
|
+
const queryManagerVersion = (binary, args = ["--version"]) => {
|
|
88
|
+
try {
|
|
89
|
+
const output = execFileSync(binary, args, {
|
|
90
|
+
encoding: "utf8",
|
|
91
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
92
|
+
timeout: 2e3
|
|
93
|
+
});
|
|
94
|
+
const match = /\d+\.\d+(\.\d+)?/.exec(output);
|
|
95
|
+
return match ? match[0] : output.trim() || void 0;
|
|
96
|
+
} catch {
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const pkgFieldConfigFiles = (workspaceRoot, field) => {
|
|
101
|
+
const pkgPath = join(workspaceRoot, "package.json");
|
|
102
|
+
if (!isAccessibleSync(pkgPath)) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const pkg = readJsonSync(pkgPath);
|
|
107
|
+
const value = pkg[field];
|
|
108
|
+
if (field === "volta" && typeof value === "object" && value !== null && Object.keys(value).length > 0) {
|
|
109
|
+
return ["package.json"];
|
|
110
|
+
}
|
|
111
|
+
if (field === "packageManager" && typeof value === "string" && value.length > 0) {
|
|
112
|
+
return ["package.json"];
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
return [];
|
|
117
|
+
};
|
|
118
|
+
const configFilesFor = (name, workspaceRoot) => {
|
|
119
|
+
if (name === "volta") {
|
|
120
|
+
return pkgFieldConfigFiles(workspaceRoot, "volta");
|
|
121
|
+
}
|
|
122
|
+
if (name === "corepack") {
|
|
123
|
+
return pkgFieldConfigFiles(workspaceRoot, "packageManager");
|
|
124
|
+
}
|
|
125
|
+
return MANAGER_CONFIG_FILES[name].filter((file) => isAccessibleSync(join(workspaceRoot, file)));
|
|
126
|
+
};
|
|
127
|
+
const detectionCache = /* @__PURE__ */ new Map();
|
|
128
|
+
const clearToolchainCache = () => {
|
|
129
|
+
detectionCache.clear();
|
|
130
|
+
onPathCache.clear();
|
|
131
|
+
};
|
|
132
|
+
const findInstalledManagers = (workspaceRoot, options) => {
|
|
133
|
+
{
|
|
134
|
+
const cached = detectionCache.get(workspaceRoot);
|
|
135
|
+
if (cached) {
|
|
136
|
+
return cached;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const results = [];
|
|
140
|
+
const pnpmOrYarnOnPath = Boolean(isOnPath("pnpm")) || Boolean(isOnPath("yarn"));
|
|
141
|
+
for (const name of MANAGER_ORDER) {
|
|
142
|
+
const binary = name === "nvm" ? void 0 : isOnPath(name);
|
|
143
|
+
const nvmInstalled = name === "nvm" && Boolean(process.env["NVM_DIR"]);
|
|
144
|
+
const configFiles = configFilesFor(name, workspaceRoot);
|
|
145
|
+
const installed = Boolean(binary) || nvmInstalled;
|
|
146
|
+
if (name === "corepack" && !installed) {
|
|
147
|
+
const hasPackageManagerPin = configFiles.length > 0;
|
|
148
|
+
if (!hasPackageManagerPin || pnpmOrYarnOnPath) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (!installed && configFiles.length === 0) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
results.push({
|
|
156
|
+
binPath: binary,
|
|
157
|
+
configFiles,
|
|
158
|
+
installed,
|
|
159
|
+
name,
|
|
160
|
+
version: binary ? queryManagerVersion(binary) : void 0
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const frozen = Object.freeze(results);
|
|
164
|
+
detectionCache.set(workspaceRoot, frozen);
|
|
165
|
+
return frozen;
|
|
166
|
+
};
|
|
167
|
+
const pickPrimaryManager = (workspaceRoot, config, detected) => {
|
|
168
|
+
const found = findInstalledManagers(workspaceRoot);
|
|
169
|
+
if (config?.preferredManager && config.preferredManager !== "none") {
|
|
170
|
+
return found.find((d) => d.name === config.preferredManager) ?? { configFiles: [], installed: false, name: config.preferredManager };
|
|
171
|
+
}
|
|
172
|
+
return found.find((d) => d.installed && d.configFiles.length > 0) ?? found.find((d) => d.installed) ?? found.find((d) => d.configFiles.length > 0) ?? { configFiles: [], installed: false, name: "none" };
|
|
173
|
+
};
|
|
174
|
+
const readVersionFile = (workspaceRoot, names) => {
|
|
175
|
+
for (const name of names) {
|
|
176
|
+
const path = join(workspaceRoot, name);
|
|
177
|
+
if (!isAccessibleSync(path)) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const content = readFileSync(path).trim();
|
|
182
|
+
if (content !== "") {
|
|
183
|
+
return { name, value: content.replace(/^v/, "") };
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return void 0;
|
|
189
|
+
};
|
|
190
|
+
const PROTO_LINE_RE = /^([a-z][\w-]*)\s*=\s*"?([^"\n#]+?)"?\s*(?:#.*)?$/i;
|
|
191
|
+
const parsePrototools = (workspaceRoot) => {
|
|
192
|
+
const path = join(workspaceRoot, ".prototools");
|
|
193
|
+
if (!isAccessibleSync(path)) {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
const content = readFileSync(path);
|
|
197
|
+
const specs = [];
|
|
198
|
+
let inSection = false;
|
|
199
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
200
|
+
const line = rawLine.trim();
|
|
201
|
+
if (line === "" || line.startsWith("#")) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (line.startsWith("[")) {
|
|
205
|
+
inSection = true;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (inSection) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const match = PROTO_LINE_RE.exec(line);
|
|
212
|
+
if (!match) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const [, rawTool, version] = match;
|
|
216
|
+
const normalizedTool = normalizeToolName(rawTool);
|
|
217
|
+
if (normalizedTool) {
|
|
218
|
+
specs.push({ source: ".prototools", tool: normalizedTool, version: version.trim() });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return specs;
|
|
222
|
+
};
|
|
223
|
+
const MISE_TOOL_HEADER_RE = /^\[tools\]\s*$/i;
|
|
224
|
+
const MISE_LINE_RE = /^([a-z][\w-]*)\s*=\s*"?([^"\n#]+?)"?\s*(?:#.*)?$/i;
|
|
225
|
+
const parseMiseToml = (workspaceRoot) => {
|
|
226
|
+
for (const candidate of MANAGER_CONFIG_FILES.mise) {
|
|
227
|
+
const path = join(workspaceRoot, candidate);
|
|
228
|
+
if (!isAccessibleSync(path)) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const specs = [];
|
|
232
|
+
const content = readFileSync(path);
|
|
233
|
+
let inToolsSection = false;
|
|
234
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
235
|
+
const line = rawLine.trim();
|
|
236
|
+
if (line === "" || line.startsWith("#")) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (line.startsWith("[")) {
|
|
240
|
+
inToolsSection = MISE_TOOL_HEADER_RE.test(line);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (!inToolsSection) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const match = MISE_LINE_RE.exec(line);
|
|
247
|
+
if (!match) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const [, rawTool, version] = match;
|
|
251
|
+
const normalizedTool = normalizeToolName(rawTool);
|
|
252
|
+
if (normalizedTool) {
|
|
253
|
+
specs.push({ source: ".mise.toml", tool: normalizedTool, version: version.trim() });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (specs.length > 0) {
|
|
257
|
+
return specs;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return [];
|
|
261
|
+
};
|
|
262
|
+
const parseToolVersions = (workspaceRoot) => {
|
|
263
|
+
const path = join(workspaceRoot, ".tool-versions");
|
|
264
|
+
if (!isAccessibleSync(path)) {
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
const content = readFileSync(path);
|
|
268
|
+
const specs = [];
|
|
269
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
270
|
+
const line = rawLine.trim();
|
|
271
|
+
if (line === "" || line.startsWith("#")) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const parts = line.split(/\s+/);
|
|
275
|
+
if (parts.length < 2) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const [rawTool, ...versions] = parts;
|
|
279
|
+
const normalizedTool = normalizeToolName(rawTool);
|
|
280
|
+
if (normalizedTool && versions[0]) {
|
|
281
|
+
specs.push({ source: ".tool-versions", tool: normalizedTool, version: versions[0] });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return specs;
|
|
285
|
+
};
|
|
286
|
+
const normalizeToolName = (raw) => {
|
|
287
|
+
const lower = raw.toLowerCase();
|
|
288
|
+
switch (lower) {
|
|
289
|
+
case "bun": {
|
|
290
|
+
return "bun";
|
|
291
|
+
}
|
|
292
|
+
case "deno": {
|
|
293
|
+
return "deno";
|
|
294
|
+
}
|
|
295
|
+
case "go":
|
|
296
|
+
case "golang": {
|
|
297
|
+
return "go";
|
|
298
|
+
}
|
|
299
|
+
case "node":
|
|
300
|
+
case "nodejs": {
|
|
301
|
+
return "node";
|
|
302
|
+
}
|
|
303
|
+
case "npm": {
|
|
304
|
+
return "npm";
|
|
305
|
+
}
|
|
306
|
+
case "pnpm": {
|
|
307
|
+
return "pnpm";
|
|
308
|
+
}
|
|
309
|
+
case "python":
|
|
310
|
+
case "python3": {
|
|
311
|
+
return "python";
|
|
312
|
+
}
|
|
313
|
+
case "ruby": {
|
|
314
|
+
return "ruby";
|
|
315
|
+
}
|
|
316
|
+
case "rust":
|
|
317
|
+
case "rustc": {
|
|
318
|
+
return "rust";
|
|
319
|
+
}
|
|
320
|
+
case "yarn": {
|
|
321
|
+
return "yarn";
|
|
322
|
+
}
|
|
323
|
+
default: {
|
|
324
|
+
return void 0;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
const parsePackageManagerField = (field) => {
|
|
329
|
+
const [nameAndVersion] = field.split("+", 1);
|
|
330
|
+
const match = /^(pnpm|yarn|npm|bun)@(.+)$/.exec(nameAndVersion ?? "");
|
|
331
|
+
if (!match) {
|
|
332
|
+
return void 0;
|
|
333
|
+
}
|
|
334
|
+
const normalizedTool = normalizeToolName(match[1]);
|
|
335
|
+
if (!normalizedTool) {
|
|
336
|
+
return void 0;
|
|
337
|
+
}
|
|
338
|
+
return { source: "packageManager", tool: normalizedTool, version: match[2] };
|
|
339
|
+
};
|
|
340
|
+
const parseExpectedTools = (workspaceRoot, config) => {
|
|
341
|
+
const merged = /* @__PURE__ */ new Map();
|
|
342
|
+
const add = (spec) => {
|
|
343
|
+
merged.set(spec.tool, spec);
|
|
344
|
+
};
|
|
345
|
+
const pkgPath = join(workspaceRoot, "package.json");
|
|
346
|
+
let rootPkg = {};
|
|
347
|
+
try {
|
|
348
|
+
if (isAccessibleSync(pkgPath)) {
|
|
349
|
+
rootPkg = readJsonSync(pkgPath);
|
|
350
|
+
}
|
|
351
|
+
} catch {
|
|
352
|
+
}
|
|
353
|
+
if (rootPkg.engines) {
|
|
354
|
+
for (const [rawTool, version] of Object.entries(rootPkg.engines)) {
|
|
355
|
+
const normalizedTool = normalizeToolName(rawTool);
|
|
356
|
+
if (normalizedTool && typeof version === "string") {
|
|
357
|
+
add({ source: "engines", tool: normalizedTool, version });
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (rootPkg.packageManager) {
|
|
362
|
+
const pmSpec = parsePackageManagerField(rootPkg.packageManager);
|
|
363
|
+
if (pmSpec) {
|
|
364
|
+
add(pmSpec);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (rootPkg.volta) {
|
|
368
|
+
for (const [rawTool, version] of Object.entries(rootPkg.volta)) {
|
|
369
|
+
const normalizedTool = normalizeToolName(rawTool);
|
|
370
|
+
if (normalizedTool && typeof version === "string") {
|
|
371
|
+
add({ source: "volta", tool: normalizedTool, version });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const nodeFile = readVersionFile(workspaceRoot, [".nvmrc", ".node-version"]);
|
|
376
|
+
if (nodeFile) {
|
|
377
|
+
add({
|
|
378
|
+
source: nodeFile.name === ".nvmrc" ? ".nvmrc" : ".node-version",
|
|
379
|
+
tool: "node",
|
|
380
|
+
version: nodeFile.value
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
for (const spec of parseToolVersions(workspaceRoot)) {
|
|
384
|
+
add(spec);
|
|
385
|
+
}
|
|
386
|
+
for (const spec of parseMiseToml(workspaceRoot)) {
|
|
387
|
+
add(spec);
|
|
388
|
+
}
|
|
389
|
+
for (const spec of parsePrototools(workspaceRoot)) {
|
|
390
|
+
add(spec);
|
|
391
|
+
}
|
|
392
|
+
if (config?.tools) {
|
|
393
|
+
for (const [rawTool, version] of Object.entries(config.tools)) {
|
|
394
|
+
const normalizedTool = normalizeToolName(rawTool);
|
|
395
|
+
if (normalizedTool && typeof version === "string") {
|
|
396
|
+
add({ source: "vis.config.ts", tool: normalizedTool, version });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return [...merged.values()];
|
|
401
|
+
};
|
|
402
|
+
const TOOL_VERSION_QUERY = {
|
|
403
|
+
bun: { args: ["--version"], binaries: ["bun"] },
|
|
404
|
+
deno: { args: ["--version"], binaries: ["deno"] },
|
|
405
|
+
go: { args: ["version"], binaries: ["go"] },
|
|
406
|
+
node: { args: ["--version"], binaries: ["node"] },
|
|
407
|
+
npm: { args: ["--version"], binaries: ["npm"] },
|
|
408
|
+
pnpm: { args: ["--version"], binaries: ["pnpm"] },
|
|
409
|
+
python: { args: ["--version"], binaries: ["python", "python3"] },
|
|
410
|
+
ruby: { args: ["--version"], binaries: ["ruby"] },
|
|
411
|
+
rust: { args: ["--version"], binaries: ["rustc"] },
|
|
412
|
+
yarn: { args: ["--version"], binaries: ["yarn"] }
|
|
413
|
+
};
|
|
414
|
+
const queryToolVersion = (tool) => {
|
|
415
|
+
const lookup = TOOL_VERSION_QUERY[tool];
|
|
416
|
+
for (const candidate of lookup.binaries) {
|
|
417
|
+
const binary = isOnPath(candidate);
|
|
418
|
+
if (binary) {
|
|
419
|
+
return queryManagerVersion(binary, lookup.args);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (tool === "node") {
|
|
423
|
+
return process.versions.node;
|
|
424
|
+
}
|
|
425
|
+
return void 0;
|
|
426
|
+
};
|
|
427
|
+
const satisfies = (actual, range) => {
|
|
428
|
+
const normalized = range.trim();
|
|
429
|
+
if (normalized === "" || normalized === "*" || normalized === "latest") {
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
if (/^\d[\d.]*$/.test(normalized)) {
|
|
433
|
+
return actual === normalized || actual.startsWith(`${normalized}.`);
|
|
434
|
+
}
|
|
435
|
+
const parse = (version) => version.split(/[.\-+]/).map((part) => Number.parseInt(part, 10) || 0);
|
|
436
|
+
const compare = (a, b) => {
|
|
437
|
+
const aParts = parse(a);
|
|
438
|
+
const bParts = parse(b);
|
|
439
|
+
const len = Math.max(aParts.length, bParts.length);
|
|
440
|
+
for (let index = 0; index < len; index++) {
|
|
441
|
+
const ai = aParts[index] ?? 0;
|
|
442
|
+
const bi = bParts[index] ?? 0;
|
|
443
|
+
if (ai !== bi) {
|
|
444
|
+
return ai - bi;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return 0;
|
|
448
|
+
};
|
|
449
|
+
const matchesAll = (clauses) => {
|
|
450
|
+
for (const clause of clauses) {
|
|
451
|
+
if (clause.startsWith(">=")) {
|
|
452
|
+
if (compare(actual, clause.slice(2).trim()) < 0) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
} else if (clause.startsWith("<=")) {
|
|
456
|
+
if (compare(actual, clause.slice(2).trim()) > 0) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
} else if (clause.startsWith(">")) {
|
|
460
|
+
if (compare(actual, clause.slice(1).trim()) <= 0) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
} else if (clause.startsWith("<")) {
|
|
464
|
+
if (compare(actual, clause.slice(1).trim()) >= 0) {
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
} else if (clause.startsWith("^") || clause.startsWith("~")) {
|
|
468
|
+
const target = clause.slice(1).trim();
|
|
469
|
+
const [targetMajor, targetMinor] = parse(target);
|
|
470
|
+
const [actualMajor, actualMinor] = parse(actual);
|
|
471
|
+
if (actualMajor !== targetMajor) {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
if (clause.startsWith("~") && actualMinor !== targetMinor) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
if (compare(actual, target) < 0) {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return true;
|
|
483
|
+
};
|
|
484
|
+
const alternatives = normalized.split("||").map((alternative) => alternative.trim().split(/\s+/).filter(Boolean)).filter((clauses) => clauses.length > 0);
|
|
485
|
+
if (alternatives.length === 0) {
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
return alternatives.some((clauses) => matchesAll(clauses));
|
|
489
|
+
};
|
|
490
|
+
const preferenceFor = (source, tool) => {
|
|
491
|
+
switch (source) {
|
|
492
|
+
case ".mise.toml": {
|
|
493
|
+
return ["mise"];
|
|
494
|
+
}
|
|
495
|
+
case ".node-version":
|
|
496
|
+
case ".nvmrc": {
|
|
497
|
+
return ["fnm", "nvm", "volta", "proto", "mise", "asdf"];
|
|
498
|
+
}
|
|
499
|
+
case ".prototools": {
|
|
500
|
+
return ["proto"];
|
|
501
|
+
}
|
|
502
|
+
case ".tool-versions": {
|
|
503
|
+
return ["asdf", "mise"];
|
|
504
|
+
}
|
|
505
|
+
case "packageManager": {
|
|
506
|
+
if (tool === "pnpm" || tool === "yarn") {
|
|
507
|
+
return ["self-activate", "volta", "proto", "mise", "corepack"];
|
|
508
|
+
}
|
|
509
|
+
if (tool === "npm") {
|
|
510
|
+
return ["volta", "proto", "mise", "asdf", "corepack"];
|
|
511
|
+
}
|
|
512
|
+
if (tool === "bun") {
|
|
513
|
+
return ["proto", "mise", "asdf"];
|
|
514
|
+
}
|
|
515
|
+
return ["volta", "proto", "mise"];
|
|
516
|
+
}
|
|
517
|
+
case "volta": {
|
|
518
|
+
return ["volta"];
|
|
519
|
+
}
|
|
520
|
+
// Catch-all pins (`engines`, `vis.config.ts`, anything else) —
|
|
521
|
+
// walk every capable manager.
|
|
522
|
+
default: {
|
|
523
|
+
return ["proto", "mise", "fnm", "volta", "asdf", "nvm", "corepack"];
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
const resolveManagerFor = (spec, detected, config) => {
|
|
528
|
+
if (config?.preferredManager && config.preferredManager !== "none" && canHandle(config.preferredManager, spec.tool)) {
|
|
529
|
+
const override = detected.find((d) => d.name === config.preferredManager);
|
|
530
|
+
return override ? { installed: override.installed, name: override.name } : {
|
|
531
|
+
installed: false,
|
|
532
|
+
name: config.preferredManager,
|
|
533
|
+
note: `${config.preferredManager} is the preferred manager but isn't on PATH`
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
const preference = preferenceFor(spec.source, spec.tool);
|
|
537
|
+
for (const name of preference) {
|
|
538
|
+
if (name === "self-activate") {
|
|
539
|
+
if ((spec.tool === "pnpm" || spec.tool === "yarn") && isOnPath(spec.tool)) {
|
|
540
|
+
return {
|
|
541
|
+
installed: true,
|
|
542
|
+
name: "self-activate",
|
|
543
|
+
note: `${spec.tool} will activate ${spec.version} from the packageManager field on next invocation`
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (!canHandle(name, spec.tool)) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
const match = detected.find((d) => d.name === name);
|
|
552
|
+
if (match?.installed) {
|
|
553
|
+
return { installed: true, name };
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
for (const name of preference) {
|
|
557
|
+
if (name === "self-activate" || !canHandle(name, spec.tool)) {
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
return { installed: false, name, note: `${name} can install ${spec.tool} — run \`vis toolchain install\` after adding it to PATH` };
|
|
561
|
+
}
|
|
562
|
+
return { installed: false, name: "none", note: "No manager knows how to install this tool" };
|
|
563
|
+
};
|
|
564
|
+
const canHandle = (name, tool) => {
|
|
565
|
+
if (name === "none") {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
if (name === "self-activate") {
|
|
569
|
+
return tool === "pnpm" || tool === "yarn";
|
|
570
|
+
}
|
|
571
|
+
return MANAGER_CAPABILITIES[name].includes(tool);
|
|
572
|
+
};
|
|
573
|
+
const getToolchainStatus = (workspaceRoot, config) => {
|
|
574
|
+
const detected = findInstalledManagers(workspaceRoot);
|
|
575
|
+
const expectedTools = parseExpectedTools(workspaceRoot, config);
|
|
576
|
+
const tools = expectedTools.map((expected) => {
|
|
577
|
+
const actual = queryToolVersion(expected.tool);
|
|
578
|
+
const matches = actual !== void 0 && satisfies(actual, expected.version);
|
|
579
|
+
const manager = resolveManagerFor(expected, detected, config);
|
|
580
|
+
return { actual, expected, manager, matches };
|
|
581
|
+
});
|
|
582
|
+
return { detected, tools };
|
|
583
|
+
};
|
|
584
|
+
const buildInstallInvocation = (manager, spec) => {
|
|
585
|
+
switch (manager) {
|
|
586
|
+
case "asdf": {
|
|
587
|
+
if (!spec) {
|
|
588
|
+
return void 0;
|
|
589
|
+
}
|
|
590
|
+
return { args: ["install", spec.tool, spec.version], bin: "asdf" };
|
|
591
|
+
}
|
|
592
|
+
case "corepack": {
|
|
593
|
+
if (!spec) {
|
|
594
|
+
return { args: ["prepare", "--activate"], bin: "corepack", hint: "reads the packageManager field in package.json" };
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
args: ["prepare", `${spec.tool}@${spec.version}`, "--activate"],
|
|
598
|
+
bin: "corepack"
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
case "fnm": {
|
|
602
|
+
if (spec?.tool !== "node") {
|
|
603
|
+
return void 0;
|
|
604
|
+
}
|
|
605
|
+
return { args: ["install", spec.version], bin: "fnm" };
|
|
606
|
+
}
|
|
607
|
+
case "mise": {
|
|
608
|
+
if (!spec) {
|
|
609
|
+
return void 0;
|
|
610
|
+
}
|
|
611
|
+
return { args: ["install", `${spec.tool}@${spec.version}`], bin: "mise" };
|
|
612
|
+
}
|
|
613
|
+
case "none": {
|
|
614
|
+
return void 0;
|
|
615
|
+
}
|
|
616
|
+
case "nvm": {
|
|
617
|
+
return {
|
|
618
|
+
args: [],
|
|
619
|
+
bin: "nvm",
|
|
620
|
+
hint: "nvm is a shell function — run `nvm install` / `nvm use` from your shell"
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
case "proto": {
|
|
624
|
+
if (!spec) {
|
|
625
|
+
return void 0;
|
|
626
|
+
}
|
|
627
|
+
return { args: ["install", spec.tool, spec.version], bin: "proto" };
|
|
628
|
+
}
|
|
629
|
+
case "self-activate": {
|
|
630
|
+
return {
|
|
631
|
+
args: [],
|
|
632
|
+
bin: spec?.tool ?? "pnpm",
|
|
633
|
+
hint: `${spec?.tool ?? "pnpm"} will self-activate on next invocation — no install needed`
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
case "volta": {
|
|
637
|
+
if (!spec) {
|
|
638
|
+
return { args: ["install", "node@lts"], bin: "volta", hint: "volta pins per-tool; specify <tool>@<version>" };
|
|
639
|
+
}
|
|
640
|
+
return { args: ["install", `${spec.tool}@${spec.version}`], bin: "volta" };
|
|
641
|
+
}
|
|
642
|
+
default: {
|
|
643
|
+
const exhaustive = manager;
|
|
644
|
+
throw new Error(`Unknown manager: ${exhaustive}`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
const buildUseInvocation = (manager, spec) => {
|
|
649
|
+
switch (manager) {
|
|
650
|
+
case "asdf": {
|
|
651
|
+
return {
|
|
652
|
+
args: ["local", spec.tool, spec.version],
|
|
653
|
+
bin: "asdf",
|
|
654
|
+
configChange: { file: ".tool-versions", hint: `Pins ${spec.tool} ${spec.version}` }
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
case "corepack": {
|
|
658
|
+
if (spec.tool !== "npm" && spec.tool !== "pnpm" && spec.tool !== "yarn") {
|
|
659
|
+
return void 0;
|
|
660
|
+
}
|
|
661
|
+
return {
|
|
662
|
+
args: ["use", `${spec.tool}@${spec.version}`],
|
|
663
|
+
bin: "corepack",
|
|
664
|
+
configChange: { file: "package.json", hint: `Writes packageManager: "${spec.tool}@${spec.version}"` }
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
case "fnm": {
|
|
668
|
+
if (spec.tool === "node") {
|
|
669
|
+
return { args: ["use", spec.version], bin: "fnm" };
|
|
670
|
+
}
|
|
671
|
+
return void 0;
|
|
672
|
+
}
|
|
673
|
+
case "mise": {
|
|
674
|
+
return {
|
|
675
|
+
args: ["use", "--", `${spec.tool}@${spec.version}`],
|
|
676
|
+
bin: "mise",
|
|
677
|
+
configChange: { file: ".mise.toml", hint: `Pins ${spec.tool} ${spec.version}` }
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
case "none": {
|
|
681
|
+
return void 0;
|
|
682
|
+
}
|
|
683
|
+
case "nvm": {
|
|
684
|
+
if (spec.tool === "node") {
|
|
685
|
+
return {
|
|
686
|
+
args: [],
|
|
687
|
+
bin: "nvm",
|
|
688
|
+
configChange: { file: ".nvmrc", hint: "Write version to .nvmrc manually (nvm doesn't persist)." }
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
return void 0;
|
|
692
|
+
}
|
|
693
|
+
case "proto": {
|
|
694
|
+
return {
|
|
695
|
+
args: ["pin", spec.tool, spec.version],
|
|
696
|
+
bin: "proto",
|
|
697
|
+
configChange: { file: ".prototools", hint: `Pins ${spec.tool} ${spec.version}` }
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
case "self-activate": {
|
|
701
|
+
return {
|
|
702
|
+
args: [],
|
|
703
|
+
bin: spec.tool,
|
|
704
|
+
configChange: {
|
|
705
|
+
file: "package.json",
|
|
706
|
+
hint: `Set packageManager: "${spec.tool}@${spec.version}" — ${spec.tool} will self-activate on next invocation`
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
case "volta": {
|
|
711
|
+
return {
|
|
712
|
+
args: ["pin", `${spec.tool}@${spec.version}`],
|
|
713
|
+
bin: "volta",
|
|
714
|
+
configChange: { file: "package.json", hint: `Writes volta.${spec.tool}` }
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
default: {
|
|
718
|
+
const exhaustive = manager;
|
|
719
|
+
throw new Error(`Unknown manager: ${exhaustive}`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
const findOnPathByAlias = (tool) => {
|
|
724
|
+
for (const alias of TOOL_VERSION_QUERY[tool].binaries) {
|
|
725
|
+
const binary = isOnPath(alias);
|
|
726
|
+
if (binary) {
|
|
727
|
+
return binary;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return void 0;
|
|
731
|
+
};
|
|
732
|
+
const resolveToolBinary = (manager, tool) => {
|
|
733
|
+
const aliases = TOOL_VERSION_QUERY[tool].binaries;
|
|
734
|
+
if (manager.installed && manager.binPath && // proto/mise/asdf expose `which`; volta has `volta which`; fnm
|
|
735
|
+
// prints to stdout from `fnm which`. Try each alias in order so
|
|
736
|
+
// `mise which rust` (which doesn't know "rust") falls back to
|
|
737
|
+
// `mise which rustc`.
|
|
738
|
+
(manager.name === "proto" || manager.name === "mise" || manager.name === "asdf" || manager.name === "volta" || manager.name === "fnm")) {
|
|
739
|
+
for (const alias of aliases) {
|
|
740
|
+
try {
|
|
741
|
+
const output = execFileSync(manager.binPath, ["which", alias], {
|
|
742
|
+
encoding: "utf8",
|
|
743
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
744
|
+
timeout: 2e3
|
|
745
|
+
});
|
|
746
|
+
const trimmed = output.trim();
|
|
747
|
+
if (trimmed) {
|
|
748
|
+
return trimmed;
|
|
749
|
+
}
|
|
750
|
+
} catch {
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
for (const alias of aliases) {
|
|
755
|
+
const binary = isOnPath(alias);
|
|
756
|
+
if (binary) {
|
|
757
|
+
return binary;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return void 0;
|
|
761
|
+
};
|
|
762
|
+
const atomicWrite = (path, body) => {
|
|
763
|
+
const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
764
|
+
writeFileSync(tmp, body);
|
|
765
|
+
try {
|
|
766
|
+
renameSync(tmp, path);
|
|
767
|
+
} catch (error) {
|
|
768
|
+
try {
|
|
769
|
+
unlinkSync(tmp);
|
|
770
|
+
} catch {
|
|
771
|
+
}
|
|
772
|
+
throw error;
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
const insertPackageManagerKey = (pkg, value) => {
|
|
776
|
+
if ("packageManager" in pkg) {
|
|
777
|
+
pkg.packageManager = value;
|
|
778
|
+
return pkg;
|
|
779
|
+
}
|
|
780
|
+
const ordered = {};
|
|
781
|
+
let inserted = false;
|
|
782
|
+
for (const [key, fieldValue] of Object.entries(pkg)) {
|
|
783
|
+
if (!inserted && (key === "dependencies" || key === "devDependencies" || key === "peerDependencies" || key === "optionalDependencies")) {
|
|
784
|
+
ordered.packageManager = value;
|
|
785
|
+
inserted = true;
|
|
786
|
+
}
|
|
787
|
+
ordered[key] = fieldValue;
|
|
788
|
+
}
|
|
789
|
+
if (!inserted) {
|
|
790
|
+
ordered.packageManager = value;
|
|
791
|
+
}
|
|
792
|
+
return ordered;
|
|
793
|
+
};
|
|
794
|
+
const writePackageManagerField = (workspaceRoot, spec) => {
|
|
795
|
+
if (spec.tool !== "pnpm" && spec.tool !== "yarn" && spec.tool !== "npm" && spec.tool !== "bun") {
|
|
796
|
+
return void 0;
|
|
797
|
+
}
|
|
798
|
+
const pkgPath = join(workspaceRoot, "package.json");
|
|
799
|
+
if (!isAccessibleSync(pkgPath)) {
|
|
800
|
+
throw new Error(`Cannot pin ${spec.tool}: ${pkgPath} does not exist.`);
|
|
801
|
+
}
|
|
802
|
+
const raw = readFileSync(pkgPath);
|
|
803
|
+
const indentMatch = /\n([ \t]+)/.exec(raw);
|
|
804
|
+
const indent = indentMatch?.[1] ?? " ";
|
|
805
|
+
let pkg;
|
|
806
|
+
try {
|
|
807
|
+
pkg = JSON.parse(raw);
|
|
808
|
+
} catch (error) {
|
|
809
|
+
throw new Error(`${pkgPath} is not valid JSON — fix it before running \`vis toolchain use\`. Underlying error: ${error.message}`);
|
|
810
|
+
}
|
|
811
|
+
const value = `${spec.tool}@${spec.version}`;
|
|
812
|
+
const updated = insertPackageManagerKey(pkg, value);
|
|
813
|
+
const trailingNewline = raw.endsWith("\n") ? "\n" : "";
|
|
814
|
+
atomicWrite(pkgPath, `${JSON.stringify(updated, void 0, indent)}${trailingNewline}`);
|
|
815
|
+
return value;
|
|
816
|
+
};
|
|
817
|
+
const updateEnginesField = (workspaceRoot, spec) => {
|
|
818
|
+
const pkgPath = join(workspaceRoot, "package.json");
|
|
819
|
+
if (!isAccessibleSync(pkgPath)) {
|
|
820
|
+
return void 0;
|
|
821
|
+
}
|
|
822
|
+
const raw = readFileSync(pkgPath);
|
|
823
|
+
let pkg;
|
|
824
|
+
try {
|
|
825
|
+
pkg = JSON.parse(raw);
|
|
826
|
+
} catch (error) {
|
|
827
|
+
throw new Error(`${pkgPath} is not valid JSON — fix it before running \`vis toolchain use\`. Underlying error: ${error.message}`);
|
|
828
|
+
}
|
|
829
|
+
if (pkg.engines?.[spec.tool] === void 0) {
|
|
830
|
+
return void 0;
|
|
831
|
+
}
|
|
832
|
+
if (pkg.engines[spec.tool] === spec.version) {
|
|
833
|
+
return void 0;
|
|
834
|
+
}
|
|
835
|
+
pkg.engines[spec.tool] = spec.version;
|
|
836
|
+
const indentMatch = /\n([ \t]+)/.exec(raw);
|
|
837
|
+
const indent = indentMatch?.[1] ?? " ";
|
|
838
|
+
const trailingNewline = raw.endsWith("\n") ? "\n" : "";
|
|
839
|
+
atomicWrite(pkgPath, `${JSON.stringify(pkg, void 0, indent)}${trailingNewline}`);
|
|
840
|
+
return spec.version;
|
|
841
|
+
};
|
|
842
|
+
const parseUseArgument = (raw) => {
|
|
843
|
+
const match = /^([a-z][\w-]*)@(.+)$/i.exec(raw.trim());
|
|
844
|
+
if (!match) {
|
|
845
|
+
return void 0;
|
|
846
|
+
}
|
|
847
|
+
const normalizedTool = normalizeToolName(match[1]);
|
|
848
|
+
if (!normalizedTool) {
|
|
849
|
+
return void 0;
|
|
850
|
+
}
|
|
851
|
+
return { source: "vis.config.ts", tool: normalizedTool, version: match[2] };
|
|
852
|
+
};
|
|
853
|
+
const ensureToolchain = async (workspaceRoot, config, logger) => {
|
|
854
|
+
const status = getToolchainStatus(workspaceRoot, config);
|
|
855
|
+
const mismatches = status.tools.filter((t) => !t.matches);
|
|
856
|
+
if (mismatches.length === 0) {
|
|
857
|
+
return { attempted: [], failed: [], upToDate: true };
|
|
858
|
+
}
|
|
859
|
+
const hasManager = status.detected.some((d) => d.installed);
|
|
860
|
+
const autoInstall = config?.autoInstall ?? hasManager;
|
|
861
|
+
if (!autoInstall) {
|
|
862
|
+
return { attempted: [], failed: [], upToDate: false };
|
|
863
|
+
}
|
|
864
|
+
const attempted = [];
|
|
865
|
+
const failed = [];
|
|
866
|
+
const byManager = /* @__PURE__ */ new Map();
|
|
867
|
+
for (const tool of mismatches) {
|
|
868
|
+
const bucket = byManager.get(tool.manager.name);
|
|
869
|
+
if (bucket) {
|
|
870
|
+
bucket.push(tool);
|
|
871
|
+
} else {
|
|
872
|
+
byManager.set(tool.manager.name, [tool]);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
for (const [managerName, tools] of byManager) {
|
|
876
|
+
if (managerName === "self-activate") {
|
|
877
|
+
for (const { expected } of tools) {
|
|
878
|
+
logger.info(`toolchain: ${expected.tool} ${expected.version} will self-activate on next ${expected.tool} invocation`);
|
|
879
|
+
attempted.push(expected);
|
|
880
|
+
}
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
if (managerName === "none") {
|
|
884
|
+
for (const { expected } of tools) {
|
|
885
|
+
failed.push({
|
|
886
|
+
error: `no manager can install ${expected.tool} — install one of ${SUPPORTED_MANAGERS.join(", ")}`,
|
|
887
|
+
spec: expected
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
const manager = status.detected.find((d) => d.name === managerName);
|
|
893
|
+
if (!manager?.installed) {
|
|
894
|
+
for (const { expected } of tools) {
|
|
895
|
+
failed.push({ error: `${managerName} is not on PATH`, spec: expected });
|
|
896
|
+
}
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
const pairs = tools.map((tool) => {
|
|
900
|
+
return { invocation: buildInstallInvocation(managerName, tool.expected), tool };
|
|
901
|
+
}).filter((pair) => pair.invocation !== void 0);
|
|
902
|
+
for (const { invocation, tool } of pairs) {
|
|
903
|
+
const { expected } = tool;
|
|
904
|
+
if (invocation.bin === "nvm" && invocation.args.length === 0) {
|
|
905
|
+
logger.warn(
|
|
906
|
+
`toolchain: nvm requires a shell-side activation for ${expected.tool} ${expected.version}. Run \`nvm install\` / \`nvm use\` manually.`
|
|
907
|
+
);
|
|
908
|
+
failed.push({ error: "nvm requires shell-side activation", spec: expected });
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
logger.info(`toolchain: $ ${invocation.bin} ${invocation.args.join(" ")}`);
|
|
912
|
+
try {
|
|
913
|
+
execFileSync(invocation.bin, invocation.args, {
|
|
914
|
+
cwd: workspaceRoot,
|
|
915
|
+
stdio: "inherit"
|
|
916
|
+
});
|
|
917
|
+
attempted.push(expected);
|
|
918
|
+
if (managerName === "fnm") {
|
|
919
|
+
activateFnmEnv(invocation.bin, logger);
|
|
920
|
+
}
|
|
921
|
+
} catch (error) {
|
|
922
|
+
failed.push({ error: error.message, spec: expected });
|
|
923
|
+
break;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
clearToolchainCache();
|
|
928
|
+
return { attempted, failed, upToDate: false };
|
|
929
|
+
};
|
|
930
|
+
const runToolchainPreflight = async (workspaceRoot, config, logger, skip = false) => {
|
|
931
|
+
if (skip) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
const result = await ensureToolchain(workspaceRoot, config, logger);
|
|
935
|
+
for (const failure of result.failed) {
|
|
936
|
+
logger.warn(`toolchain: ${failure.spec.tool} ${failure.spec.version} — ${failure.error}`);
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
const activateFnmEnv = (fnmBin, logger) => {
|
|
940
|
+
const shell = process.platform === "win32" ? "powershell" : "bash";
|
|
941
|
+
try {
|
|
942
|
+
const output = execFileSync(fnmBin, ["env", "--shell", shell], {
|
|
943
|
+
encoding: "utf8",
|
|
944
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
945
|
+
timeout: 2e3
|
|
946
|
+
});
|
|
947
|
+
for (const line of output.split(/\r?\n/)) {
|
|
948
|
+
const trimmed = line.trim();
|
|
949
|
+
if (trimmed === "") {
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
const psMatch = /^\$env:([A-Z_]\w*)\s*=\s*(.+)$/i.exec(trimmed);
|
|
953
|
+
if (psMatch) {
|
|
954
|
+
const [, name, rawValue] = psMatch;
|
|
955
|
+
process.env[name] = rawValue.replaceAll(/^["']|["']$/g, "");
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
const cmdMatch = /^set\s+"?([A-Z_]\w*)=(.*?)"?$/i.exec(trimmed);
|
|
959
|
+
if (cmdMatch) {
|
|
960
|
+
const [, name, value] = cmdMatch;
|
|
961
|
+
process.env[name] = value;
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
const bashMatch = /^(?:export\s+)?([A-Z_]\w*)=(.+)$/i.exec(trimmed);
|
|
965
|
+
if (bashMatch) {
|
|
966
|
+
const [, name, rawValue] = bashMatch;
|
|
967
|
+
process.env[name] = rawValue.replaceAll(/^["']|["']$/g, "");
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
} catch (error) {
|
|
971
|
+
logger.warn(`toolchain: could not activate fnm env (${error.message}). Subsequent tasks may use the previous Node version.`);
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
export { SUPPORTED_MANAGERS as S, resolveManagerFor as a, resolveToolBinary as b, findOnPathByAlias as c, buildUseInvocation as d, buildInstallInvocation as e, findInstalledManagers as f, getToolchainStatus as g, pickPrimaryManager as h, parseUseArgument as p, runToolchainPreflight as r, updateEnginesField as u, writePackageManagerField as w };
|