derw
Version:
An Elm-inspired language that transpiles to TypeScript
274 lines (234 loc) • 7.82 kB
text/typescript
import {
allErrors,
bothFlag,
empty,
help,
longFlag,
parse,
parser,
string,
} from "@eeue56/baner";
import { Err, Ok, Result } from "@eeue56/ts-core/build/main/lib/result";
import { spawnSync } from "child_process";
import { writeFile } from "fs/promises";
import fetch from "node-fetch";
import {
addDependency,
decodePackage,
Dependency,
exportPackage,
loadPackageFile,
Package,
} from "../package";
import { ensureDirectoryExists, fileExists } from "./utils";
const installParser = parser([
longFlag("name", "name of the package e.g derw-lang/stdlib", string()),
longFlag("version", "name of the package e.g main or master", string()),
longFlag("quiet", "Keep it short and sweet", empty()),
bothFlag("h", "help", "This help text", empty()),
]);
function showInstallHelp(): void {
console.log(
"To install a new package run `derw install --name {package name} --version {version}`"
);
console.log(
"Or run me without args inside a package directory to install all packages in derw-package.json"
);
console.log(help(installParser));
}
export async function install(
isInPackageDirectory: boolean,
argv: string[]
): Promise<void> {
if (!isInPackageDirectory) {
console.log(
"No derw-package.json found. Maybe you need to run `derw init` first?"
);
process.exit(1);
}
const program = parse(installParser, argv);
if (program.flags["h/help"].isPresent) {
showInstallHelp();
return;
}
const isQuiet = program.flags.quiet.isPresent;
const isInstallNewPackage = program.flags.name.isPresent;
if (argv.length > 3 && !(isInstallNewPackage || isQuiet)) {
showInstallHelp();
return;
}
const packageFile = await loadPackageFile("derw-package.json");
if (packageFile.kind === "Err") {
console.log("Failed to parse package file due to:");
console.log(packageFile.error);
process.exit(1);
}
const errors = allErrors(program);
if (errors.length > 0) {
console.log("Errors:");
console.log(errors.join("\n"));
process.exit(1);
}
let validPackage = packageFile.value;
if (isInstallNewPackage) {
const name = (program.flags.name.arguments as Ok<string>).value;
let version = "main";
if (program.flags.version.isPresent) {
version = (program.flags.version.arguments as Ok<string>).value;
}
if (!isQuiet) console.log("Reading derw-package.json...");
validPackage = addDependency(Dependency(name, version), validPackage);
if (!isQuiet) {
console.log(
`Writing new package ${name}@${version} to derw-package.json...`
);
}
await writeFile("derw-package.json", exportPackage(validPackage));
if (!isQuiet) console.log("Done!");
}
if (validPackage.dependencies.length === 0) {
console.log("No dependencies to install!");
return;
} else {
if (!isQuiet) console.log("Installing packages...");
await installPackages(validPackage, isQuiet, [ ]);
if (!isQuiet)
console.log(
`Installed ${validPackage.dependencies.length} packages`
);
}
}
function isPackageAlreadyThere(
package_: Package,
packages: Package[]
): boolean {
for (const p of packages) {
if (package_.name === p.name) return true;
}
return false;
}
function isAlreadyInstalled(
dependency: Dependency,
alreadyInstalledDependency: Dependency[]
): boolean {
return (
alreadyInstalledDependency.filter(
(d) =>
d.name === dependency.name && d.version === dependency.version
).length > 0
);
}
async function installPackages(
validPackage: Package,
isQuiet: boolean,
alreadyInstalledDependencies: Dependency[]
): Promise<Package[]> {
await ensureDirectoryExists("derw-packages");
const installedPackages: Package[] = [ ];
for (const dependency of validPackage.dependencies) {
const alreadyInstalled = isAlreadyInstalled(
dependency,
alreadyInstalledDependencies
);
if (alreadyInstalled) continue;
if (!isQuiet)
console.log(`Fetching ${dependency.name}@${dependency.version}...`);
const depPackage = await fetchDependencyPackage(dependency);
if (depPackage.kind === "Ok") {
if (!isPackageAlreadyThere(depPackage.value, installedPackages)) {
const subpackages = await installPackages(
depPackage.value,
isQuiet,
[
...alreadyInstalledDependencies,
...validPackage.dependencies,
]
);
for (const subpackage of subpackages) {
installedPackages.push(subpackage);
}
}
}
await cloneRepo(dependency);
await checkoutRef(dependency);
if (await fileExists(`derw-packages/${dependency.name}/package.json`)) {
if (!isQuiet) console.log("Installing npm packages...");
await npmInstall(dependency);
}
if (depPackage.kind === "Ok") {
installedPackages.push(depPackage.value);
}
}
return installedPackages;
}
async function cloneRepo(dependency: Dependency): Promise<void> {
await ensureDirectoryExists(`derw-packages/${dependency.name}`);
const res = spawnSync(
"git",
[
"clone",
`https://github.com/${dependency.name}.git`,
dependency.name,
],
{ cwd: "derw-packages", encoding: "utf-8" }
);
if (res.error) {
console.log(`Encountered error cloning ${dependency.name}`);
console.log(res.error);
}
}
async function checkoutRef(dependency: Dependency) {
let res = spawnSync("git", [ "fetch", `origin`, `${dependency.version}` ], {
cwd: `derw-packages/${dependency.name}`,
encoding: "utf-8",
});
if (res.error) {
console.log(`Encountered error fetching ${dependency.name}`);
console.log(res.error);
}
res = spawnSync(
"git",
[ "reset", "--hard", `origin/${dependency.version}` ],
{
cwd: `derw-packages/${dependency.name}`,
encoding: "utf-8",
}
);
if (res.error) {
console.log(`Encountered error checkout ${dependency.name}`);
console.log(res.error);
}
res = spawnSync("git", [ "reset", "--hard", `${dependency.version}` ], {
cwd: `derw-packages/${dependency.name}`,
encoding: "utf-8",
});
if (res.error) {
console.log(`Encountered error checkout ${dependency.name}`);
console.log(res.error);
}
}
async function npmInstall(dependency: Dependency): Promise<void> {
const res = spawnSync("npm", [ "install" ], {
cwd: `derw-packages/${dependency.name}`,
encoding: "utf-8",
});
if (res.error) {
console.log(
`Encountered error installing npm packages from ${dependency.name}`
);
console.log(res.error);
}
}
async function fetchDependencyPackage(
dependency: Dependency
): Promise<Result<string, Package>> {
try {
const response = await fetch(
`https://raw.githubusercontent.com/${dependency.name}/${dependency.version}/derw-package.json`
);
return decodePackage(await response.json());
} catch (error) {
console.log(error);
return Err(`Failed to fetch ${dependency.name}@${dependency.version}`);
}
}