mono-runner
Version:
A script runner for mono repos that just works. Use Mono to run separate compilers and dev servers for your packages at once, build only dependent packages or format everything at once.
179 lines (147 loc) • 4.99 kB
text/typescript
import { access, readFile, readdir } from "node:fs/promises";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { join } from "path";
export type LocalPackage = {
name: string;
path: string;
localDependencies: LocalPackage[];
dependencies: string[];
scripts: string[];
scriptCommands: Record<string, string>;
};
export type LocalPackageInfo = {
[name: string]: string;
};
export const rootPath = process.env.MONO_ROOT || process.cwd();
export const readFileJSON = async (path: string) => {
const data = await readFile(path, { encoding: "utf-8" });
return JSON.parse(data);
};
export const readFileJSONSync = (path: string) => {
const data = readFileSync(path, { encoding: "utf-8" });
return JSON.parse(data);
};
export const getWorkspaces = async (): Promise<string[]> => {
try {
const pJson = await readFileJSON(join(rootPath, "package.json"));
const base = pJson.workspaces || [];
const workspaces = base.map((w: string) => w.replace("/*", ""));
const existing: string[] = [];
await Promise.all(workspaces.map(async(w: string) => {
const path = join(rootPath, w);
if(await access(path).then(() => true).catch(() => false)){
existing.push(w);
}
}));
return existing;
} catch (e) {
console.error("Please run this command in a directory with a valid package.json file.");
return [];
}
};
export const getWorkspacesSync = (): string[] => {
try {
const pJson = readFileJSONSync(join(rootPath, "package.json"));
const base = pJson.workspaces || [];
const workspaces: string[] = base.map((w: string) => w.replace("/*", ""));
const existing: string[] = [];
workspaces.forEach((w) => {
const path = join(rootPath, w);
if(existsSync(path)){
existing.push(w);
}
});
return existing;
} catch (e) {
console.error("Please run this command in a directory with a valid package.json file.");
return [];
}
};
export const getPackageJson = async (path: string):Promise<any> => {
const pJson = await readFileJSON(join(path, "package.json"));
return pJson;
};
export const getPackageJsonSync = (path: string): any => {
const pJson = readFileJSONSync(join(path, "package.json"));
return pJson;
};
export const listPackages = async (workspace: string) => {
const root = join(rootPath, workspace);
const dirs = await readdir(root, { withFileTypes: true });
const packages = dirs.filter((d) => d.isDirectory()).map((d) => d.name);
const packageJsons = (
await Promise.all(
packages.map((p) =>
getPackageJson(join(root, p))
.then((pJson) => ({ ...pJson, path: p}))
.catch(() => null)
)
)
).filter((p) => p !== null);
const packageInfo: LocalPackageInfo = {};
packageJsons.forEach((p) => {
const { name, path } = p;
packageInfo[name] = `${workspace}/${path}`;
});
return packageInfo;
};
export const listPackagesSync = (workspace: string) => {
const root = join(rootPath, workspace);
const dirs = readdirSync(root, { withFileTypes: true });
const packages = dirs.filter((d) => d.isDirectory()).map((d) => d.name);
const packageJsons = packages.map((p) => {
try{
const pJson = getPackageJsonSync(join(root, p));
return { ...pJson, path: p};
}catch(e){
return null;
}
}).filter((p) => p !== null);
const packageInfo: LocalPackageInfo = {};
packageJsons.forEach((p) => {
const { name, path } = p;
packageInfo[name] = `${workspace}/${path}`;
});
return packageInfo;
};
export const getWorkspacePackages = async (): Promise<LocalPackageInfo> => {
const workspaces = await getWorkspaces();
const packages = await Promise.all(workspaces.map((w) => listPackages(w)));
const info = packages.reduce((acc, cur) => ({ ...acc, ...cur }), {});
return info;
};
export const getWorkspacePackagesSync = (): LocalPackageInfo => {
const workspaces = getWorkspacesSync();
const packages = workspaces.map((w) => listPackagesSync(w));
const info = packages.reduce((acc, cur) => ({ ...acc, ...cur }), {});
return info;
};
const formatPackageInfo = async(info: LocalPackageInfo): Promise<LocalPackage[]> => {
const packages = Object.keys(info).map((name) => ({
name,
path: info[name],
localDependencies: [],
dependencies: [],
scripts: [],
scriptCommands: {}
}));
const prms = packages.map(async(pcg: LocalPackage) => {
const { path } = pcg;
const root = join(rootPath, path);
const pJson = await getPackageJson(root);
const { dependencies = {}, devDependencies = {} } = pJson;
const deps = Object.keys({ ...dependencies, ...devDependencies });
const locals = packages.filter((p) => deps.includes(p.name));
pcg.localDependencies = locals;
pcg.dependencies = deps;
pcg.scriptCommands = pJson.scripts || {};
pcg.scripts = Object.keys(pJson.scripts || {});
});
await Promise.all(prms);
return packages;
};
export const getLocalPackages = async (): Promise<LocalPackage[]> => {
const info = await getWorkspacePackages();
const packages = await formatPackageInfo(info);
return packages;
};