Skip to content

Commit

Permalink
Add modules: install, node, npm (#4)
Browse files Browse the repository at this point in the history
Co-authored-by: Grant Gurvis <[email protected]>
  • Loading branch information
brendanfalk and grant0417 authored Apr 28, 2023
1 parent d191413 commit 3409304
Show file tree
Hide file tree
Showing 14 changed files with 331 additions and 229 deletions.
3 changes: 3 additions & 0 deletions src/aws/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { aws } from "../install/mod.ts";

export { aws as install };
3 changes: 3 additions & 0 deletions src/github/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { github } from "../install/mod.ts";

export { github as install };
157 changes: 157 additions & 0 deletions src/install/installPackageBasedOnPackageManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
const PACKAGE_MANAGERS_MAP = [
{
name: "apt-get",
installArgs: ["install", "-y"],
updateArgs: ["update"],
},
{
name: "pacman",
installArgs: ["-S", "--noconfirm"],
updateArgs: ["-Sy"],
},
{
name: "yum",
installArgs: ["install", "-y"],
updateArgs: ["update"],
},
{
name: "dnf",
installArgs: ["install", "-y"],
updateArgs: ["update"],
},
{
name: "apk",
installArgs: ["add"],
updateArgs: ["update"],
},
{
name: "pkg",
installArgs: ["install"],
updateArgs: ["update"],
},
{
name: "brew",
installArgs: ["install"],
updateArgs: ["update"],
},
] as const;

const PACKAGE_MANAGER = PACKAGE_MANAGERS_MAP.map((pm) => pm.name);
export type PackageManager = typeof PACKAGE_MANAGER[number];

export type PackageManagerPackage = {
name: string;
version?: string;
};

export type PackageManagerInput = string | PackageManagerPackage;

async function findExecutable(name: string): Promise<string | null> {
const pathEnv = Deno.env.get("PATH");
if (!pathEnv) {
return null;
}
const paths = pathEnv.split(Deno.build.os === "windows" ? ";" : ":");
for (const path of paths) {
const fullPath = `${path}/${name}`;
try {
const fileInfo = await Deno.stat(fullPath);
if (fileInfo.isFile && fileInfo.mode && fileInfo.mode & 0o111) {
// The file exists and is executable
return fullPath;
}
} catch (_error) {
// Ignore errors (file not found, permission denied, etc.)
}
}
return null;
}

async function getPackageManager(): Promise<PackageManager | null> {
for (const pm of PACKAGE_MANAGERS_MAP) {
if (await findExecutable(pm.name)) {
return pm.name;
}
}
return null;
}

/**
* Install packages using the package manager
*
* @example
* ```ts
* await installPackageBasedOnPackageManager({
* "apt-get": [{ name: "curl", version: "7.68.0-1ubuntu2.6" }],
* "pacman": ["curl"],
* "yum": [{ name: "curl", version: "7.61.1-18.el8" }],
* });
* ```
*
* @param map a map of package manager to packages to install
*/
export async function installPackageBasedOnPackageManager(
map: Partial<Record<PackageManager, PackageManagerInput[]>>,
) {
const packageManager = await getPackageManager();

if (!packageManager) {
throw new Error("No package manager found");
}

const packages = map[packageManager];
if (!packages) {
throw new Error(`No package manager found for ${packageManager}`);
}

const updateArgs = [
...PACKAGE_MANAGERS_MAP.find((p) => p.name === packageManager)!
.updateArgs,
];

const updateCmd = new Deno.Command(
packageManager,
{
args: updateArgs,
},
);

const updateStatus = await updateCmd.spawn().status;
if (!updateStatus.success) {
throw new Error(
`Failed to update package manager index for ${packageManager}`,
);
}

const installArgs = [
...PACKAGE_MANAGERS_MAP.find((p) => p.name === packageManager)!.installArgs,
...packages.map((input) => {
const pkg = typeof input === "string" ? { name: input } : input;

if (!pkg.version) return pkg.name;
else if (pkg.version === "latest") return pkg.name;
else if (
packageManager === "apt-get" || packageManager === "yum" ||
packageManager === "dnf"
) {
return `${pkg.name}${pkg.version ? `=${pkg.version}` : ""}`;
} else if (packageManager === "brew") {
return `${pkg.name}@${pkg.version}`;
} else {
return pkg.name;
}
}),
];

const cmd = new Deno.Command(
packageManager,
{
args: installArgs,
},
);

const status = await cmd.spawn().status;
if (!status.success) {
throw new Error(`Failed to install packages with ${packageManager}`);
}
}
191 changes: 37 additions & 154 deletions src/install/mod.ts
Original file line number Diff line number Diff line change
@@ -1,154 +1,37 @@
const PACKAGE_MANAGERS_MAP = [
{
name: "apt-get",
installArgs: ["install", "-y"],
updateArgs: ["update"],
},
{
name: "pacman",
installArgs: ["-S", "--noconfirm"],
updateArgs: ["-Sy"],
},
{
name: "yum",
installArgs: ["install", "-y"],
updateArgs: ["update"],
},
{
name: "dnf",
installArgs: ["install", "-y"],
updateArgs: ["update"],
},
{
name: "apk",
installArgs: ["add"],
updateArgs: ["update"],
},
{
name: "pkg",
installArgs: ["install"],
updateArgs: ["update"],
},
{
name: "brew",
installArgs: ["install"],
updateArgs: ["update"],
},
] as const;

const PACKAGE_MANAGER = PACKAGE_MANAGERS_MAP.map((pm) => pm.name);
export type PackageManager = typeof PACKAGE_MANAGER[number];

export type PackageManagerPackage = {
name: string;
version?: string;
};

export type PackageManagerInput = string | PackageManagerPackage;

async function findExecutable(name: string): Promise<string | null> {
const pathEnv = Deno.env.get("PATH");
if (!pathEnv) {
return null;
}
const paths = pathEnv.split(Deno.build.os === "windows" ? ";" : ":");
for (const path of paths) {
const fullPath = `${path}/${name}`;
try {
const fileInfo = await Deno.stat(fullPath);
if (fileInfo.isFile && fileInfo.mode && fileInfo.mode & 0o111) {
// The file exists and is executable
return fullPath;
}
} catch (_error) {
// Ignore errors (file not found, permission denied, etc.)
}
}
return null;
}

async function getPackageManager(): Promise<PackageManager | null> {
for (const pm of PACKAGE_MANAGERS_MAP) {
if (await findExecutable(pm.name)) {
return pm.name;
}
}
return null;
}

/**
* Install packages using the package manager
*
* @example
* ```ts
* await installPackages({
* "apt-get": [{ name: "curl", version: "7.68.0-1ubuntu2.6" }],
* "pacman": ["curl"],
* "yum": [{ name: "curl", version: "7.61.1-18.el8" }],
* });
* ```
*
* @param map a map of package manager to packages to install
*/
export async function installPackages(
map: Record<PackageManager, PackageManagerInput[]>,
) {
const packageManager = await getPackageManager();

if (!packageManager) {
throw new Error("No package manager found");
}

const packages = map[packageManager];
if (!packages) {
throw new Error(`No package manager found for ${packageManager}`);
}

const updateArgs = [
...PACKAGE_MANAGERS_MAP.find((p) => p.name === packageManager)!
.updateArgs,
];

const updateCmd = new Deno.Command(
packageManager,
{
args: updateArgs,
},
);

const updateStatus = await updateCmd.spawn().status;
if (!updateStatus.success) {
throw new Error(
`Failed to update package manager index for ${packageManager}`,
);
}

const installArgs = [
...PACKAGE_MANAGERS_MAP.find((p) => p.name === packageManager)!.installArgs,
...packages.map((input) => {
const pkg = typeof input === "string" ? { name: input } : input;
if (
packageManager === "apt-get" || packageManager === "yum" ||
packageManager === "dnf"
) {
return `${pkg.name}${pkg.version ? `=${pkg.version}` : ""}`;
} else if (packageManager === "brew" && pkg.version) {
return `${pkg.name}@${pkg.version}`;
} else {
return pkg.name;
}
}),
];

const cmd = new Deno.Command(
packageManager,
{
args: installArgs,
},
);

const status = await cmd.spawn().status;
if (!status.success) {
throw new Error(`Failed to install packages with ${packageManager}`);
}
}
import {
installPackageBasedOnPackageManager,
} from "./installPackageBasedOnPackageManager.ts";

import { createPackageInstallStep } from "./utils.ts";

export { installPackageBasedOnPackageManager };

export const node = createPackageInstallStep("node", {
"apt-get": "nodejs",
"pacman": "nodejs",
"yum": "nodejs",
"dnf": "nodejs",
"apk": "nodejs",
"pkg": "node",
"brew": "node",
});

export const github = createPackageInstallStep("github", {
"apt-get": "gh",
"pacman": "github-cli",
"yum": "gh",
"dnf": "gh",
"apk": "gh",
"pkg": "gh",
"brew": "gh",
});

export const aws = createPackageInstallStep("aws", {
"apt-get": "awscli",
"pacman": "aws-cli",
"yum": "awscli",
"dnf": "awscli",
"apk": "aws-cli",
"pkg": "aws-cli",
"brew": "awscli",
});
34 changes: 34 additions & 0 deletions src/install/packages-to-install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// import { installPackages } from "./mod.ts";

// // Yarn
// await installPackages({
// "apt-get": ["yarn"],
// "pacman": ["yarn"],
// "yum": ["yarn"],
// "dnf": ["yarn"],
// "apk": ["yarn"],
// "pkg": ["yarn"],
// "brew": ["yarn"],
// });

// // Sentry CLI
// await installPackages({
// "apt-get": [{ name: "sentry-cli", version: "1.71.0" }], // Replace with the desired version
// "pacman": ["sentry-cli"],
// "yum": ["sentry-cli"],
// "dnf": ["sentry-cli"],
// "apk": ["sentry-cli"],
// "pkg": ["sentry-cli"],
// "brew": [{ name: "sentry-cli", version: "1.71.0" }], // Replace with the desired version
// });

// // Next.js CLI
// await installPackages({
// "apt-get": [{ name: "next-cli", version: "12.0.7" }], // Replace with the desired version
// "pacman": ["next-cli"],
// "yum": ["next-cli"],
// "dnf": ["next-cli"],
// "apk": ["next-cli"],
// "pkg": ["next-cli"],
// "brew": [{ name: "next-cli", version: "12.0.7" }], // Replace with the desired version
// });
Loading

0 comments on commit 3409304

Please sign in to comment.