|
import * as fs from "fs"; |
|
import * as path from "path"; |
|
import * as util from "util"; |
|
import * as os from "os"; |
|
import { Command } from "commander"; |
|
import * as glob from "glob"; |
|
import { exec } from "child_process"; |
|
|
|
const readFile = util.promisify(fs.readFile); |
|
const symlink = util.promisify(fs.symlink); |
|
const mkdir = util.promisify(fs.mkdir); |
|
const lstat = util.promisify(fs.lstat); |
|
const rm = util.promisify(fs.rm); |
|
const unlink = util.promisify(fs.unlink); |
|
const execPromise = util.promisify(exec); |
|
|
|
interface PackageInfo { |
|
name: string; |
|
path: string; |
|
dependencies: Record<string, string>; |
|
} |
|
|
|
async function getPackageInfo(packageDir: string): Promise<PackageInfo> { |
|
const packageJsonPath = path.join(packageDir, "package.json"); |
|
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")); |
|
return { |
|
name: packageJson.name, |
|
path: packageDir, |
|
dependencies: packageJson.dependencies || {}, |
|
}; |
|
} |
|
|
|
async function ensureSymlink( |
|
target: string, |
|
linkPath: string, |
|
type: fs.symlink.Type |
|
): Promise<void> { |
|
try { |
|
const stats = await lstat(linkPath); |
|
if (stats.isSymbolicLink()) { |
|
await unlink(linkPath); |
|
} else if (stats.isDirectory()) { |
|
await rm(linkPath, { recursive: true }); |
|
} else { |
|
await unlink(linkPath); |
|
} |
|
} catch (error: any) { |
|
if (error.code !== "ENOENT") { |
|
console.error(`Error accessing ${linkPath}:`, error); |
|
return; |
|
} |
|
} |
|
|
|
try { |
|
await symlink(target, linkPath, type); |
|
console.log(`Linked ${target} to ${linkPath}`); |
|
} catch (error) { |
|
console.error(`Failed to link ${target} to ${linkPath}:`, error); |
|
} |
|
} |
|
|
|
async function interlink(packageDirs: string[]): Promise<void> { |
|
const expandedDirs = packageDirs.flatMap((dir) => glob.sync(dir)); |
|
const packageInfos = await Promise.all(expandedDirs.map(getPackageInfo)); |
|
const packageMap = new Map<string, PackageInfo>(); |
|
|
|
for (const pkg of packageInfos) { |
|
packageMap.set(pkg.name, pkg); |
|
} |
|
|
|
for (const pkg of packageInfos) { |
|
const nodeModulesPath = path.join(pkg.path, "node_modules"); |
|
await mkdir(nodeModulesPath, { recursive: true }); |
|
|
|
for (const depName of Object.keys(pkg.dependencies)) { |
|
if (packageMap.has(depName)) { |
|
const depPackageInfo = packageMap.get(depName)!; |
|
const targetPath = path.join(nodeModulesPath, depName); |
|
const relativeTargetPath = path.relative( |
|
nodeModulesPath, |
|
depPackageInfo.path |
|
); |
|
|
|
if (os.platform() === "win32") { |
|
// For Windows, create a directory junction |
|
await ensureSymlink(relativeTargetPath, targetPath, "junction"); |
|
} else { |
|
// For Linux and MacOS, create a symbolic link |
|
await ensureSymlink(relativeTargetPath, targetPath, "dir"); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
async function reset(packageDirs: string[]): Promise<void> { |
|
const expandedDirs = packageDirs.flatMap((dir) => glob.sync(dir)); |
|
|
|
for (const dir of expandedDirs) { |
|
console.log(`Running npm install in ${dir}`); |
|
try { |
|
await execPromise("npm install", { cwd: dir }); |
|
console.log(`npm install completed in ${dir}`); |
|
} catch (error) { |
|
console.error(`Failed to run npm install in ${dir}:`, error); |
|
} |
|
} |
|
} |
|
|
|
const program = new Command(); |
|
program |
|
.name("interlink") |
|
.command("link <directories...>") |
|
.description("Link local npm packages") |
|
.action((directories: string[]) => { |
|
interlink(directories) |
|
.then(() => console.log("Interlinking complete")) |
|
.catch((error) => console.error("Error during interlinking:", error)); |
|
}); |
|
|
|
program |
|
.command("reset <directories...>") |
|
.description("Reset symlinks by running npm install") |
|
.action((directories: string[]) => { |
|
reset(directories) |
|
.then(() => console.log("Reset complete")) |
|
.catch((error) => console.error("Error during reset:", error)); |
|
}); |
|
|
|
program.parse(process.argv); |