* Cross-platform helpers for npm install / dev environment checks.
*/
import { existsSync } from "node:fs";
import { join } from "node:path";
export const MIN_NODE_MAJOR = 22;
const NATIVE_CSS_BY_PLATFORM = {
win32: {
x64: ["@tailwindcss/oxide-win32-x64-msvc", "lightningcss-win32-x64-msvc"],
arm64: ["@tailwindcss/oxide-win32-arm64-msvc", "lightningcss-win32-arm64-msvc"],
},
linux: {
x64: ["@tailwindcss/oxide-linux-x64-gnu", "lightningcss-linux-x64-gnu"],
arm64: ["@tailwindcss/oxide-linux-arm64-gnu", "lightningcss-linux-arm64-gnu"],
},
darwin: {
x64: ["@tailwindcss/oxide-darwin-x64", "lightningcss-darwin-x64"],
arm64: ["@tailwindcss/oxide-darwin-arm64", "lightningcss-darwin-arm64"],
},
};
const LINUX_MUSL_ALTERNATES = {
x64: ["@tailwindcss/oxide-linux-x64-musl", "lightningcss-linux-x64-musl"],
arm64: ["@tailwindcss/oxide-linux-arm64-musl", "lightningcss-linux-arm64-musl"],
};
const MIXED_INSTALL_MARKERS = {
win32: ["@tailwindcss/oxide-win32-x64-msvc", "lightningcss-win32-x64-msvc"],
linux: ["@tailwindcss/oxide-linux-x64-gnu", "lightningcss-linux-x64-gnu"],
};
export function localBinExists(root, name) {
const binDir = join(root, "node_modules", ".bin");
if (process.platform === "win32") {
return existsSync(join(binDir, `${name}.cmd`)) || existsSync(join(binDir, name));
}
return existsSync(join(binDir, name));
}
export function packageInstalled(root, packageName) {
const segments = packageName.split("/");
const direct = join(root, "node_modules", ...segments);
if (existsSync(direct)) {
return true;
}
const nestedCandidates = [];
if (packageName.startsWith("@tailwindcss/oxide-")) {
nestedCandidates.push(
join(root, "node_modules", "@tailwindcss", "oxide", "node_modules", ...segments),
);
}
if (packageName.startsWith("lightningcss-") && packageName !== "lightningcss") {
nestedCandidates.push(join(root, "node_modules", "lightningcss", "node_modules", packageName));
}
return nestedCandidates.some((candidate) => existsSync(candidate));
}
export function expectedNativeCssPackages(platform = process.platform, arch = process.arch) {
const byArch = NATIVE_CSS_BY_PLATFORM[platform];
if (!byArch) return [];
return byArch[arch] ?? byArch.x64 ?? [];
}
function nativePackageSatisfied(root, packageName, platform, arch) {
if (packageInstalled(root, packageName)) {
return true;
}
if (platform !== "linux") {
return false;
}
const alternates = LINUX_MUSL_ALTERNATES[arch] ?? LINUX_MUSL_ALTERNATES.x64 ?? [];
const index = (NATIVE_CSS_BY_PLATFORM.linux[arch] ?? NATIVE_CSS_BY_PLATFORM.linux.x64 ?? []).indexOf(
packageName,
);
if (index < 0) {
return false;
}
const alternate = alternates[index];
return alternate ? packageInstalled(root, alternate) : false;
}
export function missingNativeCssPackages(root, platform = process.platform, arch = process.arch) {
const expected = expectedNativeCssPackages(platform, arch);
return expected.filter((pkg) => !nativePackageSatisfied(root, pkg, platform, arch));
}
export function detectMixedPlatformInstall(root, platform = process.platform) {
const hasWin = MIXED_INSTALL_MARKERS.win32.some((pkg) => packageInstalled(root, pkg));
const hasLinux = MIXED_INSTALL_MARKERS.linux.some((pkg) => packageInstalled(root, pkg));
if (hasWin && hasLinux) {
return (
"Both Windows and Linux native CSS modules are present. " +
"Remove node_modules and run npm install once on your current OS."
);
}
if (platform === "win32" && hasLinux && !hasWin) {
return (
"Linux-native modules found while running on Windows. " +
"Do not share node_modules with WSL — delete node_modules and run npm install on Windows."
);
}
if (platform === "linux" && hasWin && !hasLinux) {
return (
"Windows-native modules found while running on Linux/WSL. " +
"Do not run npm install from Windows against this tree — delete node_modules and run npm install on Linux."
);
}
return null;
}
export function nodeVersionMessage(currentVersion) {
return (
`Node.js ${MIN_NODE_MAJOR}+ required (current: ${currentVersion}). ` +
"Install Node 22+ for your platform (nvm, fnm, or nodejs.org)."
);
}