Skip to content

Commit 4864c29

Browse files
fix: improve command output
1 parent 9d326bf commit 4864c29

File tree

6 files changed

+182
-108
lines changed

6 files changed

+182
-108
lines changed

src/bin.ts

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import * as fs from "node:fs";
44
import * as path from "node:path";
55
import { parseArgs } from "node:util";
66
import { install, remove } from "./commands";
7-
import { JsrPackage, setDebug } from "./utils";
7+
import { JsrPackage, JsrPackageNameError, prettyTime, setDebug } from "./utils";
8+
import { PkgManagerName } from "./pkg_manager";
89

910
const args = process.argv.slice(2);
1011

@@ -43,6 +44,9 @@ ${prettyPrintRow([
4344
],
4445
["-D, --save-dev", "Package will be added to devDependencies."],
4546
["-O, --save-optional", "Package will be added to optionalDependencies."],
47+
["--npm", "Use npm to remove and install packages."],
48+
["--yarn", "Use yarn to remove and install packages."],
49+
["--pnpm", "Use pnpm to remove and install packages."],
4650
["--verbose", "Show additional debugging information."],
4751
["-h, --help", "Show this help text."],
4852
["--version", "Print the version number."],
@@ -75,6 +79,9 @@ if (args.length === 0) {
7579
"save-prod": { type: "boolean", default: true, short: "P" },
7680
"save-dev": { type: "boolean", default: false, short: "D" },
7781
"save-optional": { type: "boolean", default: false, short: "O" },
82+
npm: { type: "boolean", default: false },
83+
yarn: { type: "boolean", default: false },
84+
pnpm: { type: "boolean", default: false },
7885
debug: { type: "boolean", default: false },
7986
help: { type: "boolean", default: false, short: "h" },
8087
version: { type: "boolean", default: false },
@@ -99,27 +106,51 @@ if (args.length === 0) {
99106
process.exit(0);
100107
}
101108

109+
const pkgManagerName: PkgManagerName | null = options.values.pnpm
110+
? "pnpm"
111+
: options.values.yarn
112+
? "yarn"
113+
: null;
114+
102115
const cmd = options.positionals[0];
103116
if (cmd === "i" || cmd === "install" || cmd === "add") {
104-
(async () => {
117+
run(async () => {
105118
const packages = getPackages(options.positionals);
106119
await install(packages, {
107120
mode: options.values["save-dev"]
108121
? "dev"
109122
: options.values["save-optional"]
110123
? "optional"
111124
: "prod",
125+
pkgManagerName,
112126
});
113-
})();
127+
});
114128
} else if (cmd === "r" || cmd === "uninstall" || cmd === "remove") {
115-
(async () => {
129+
run(async () => {
116130
const packages = getPackages(options.positionals);
117-
await remove(packages);
118-
})();
131+
await remove(packages, { pkgManagerName });
132+
});
119133
} else {
120134
console.error(kl.red(`Unknown command: ${cmd}`));
121135
console.log();
122136
printHelp();
123137
process.exit(1);
124138
}
125139
}
140+
141+
async function run(fn: () => Promise<void>) {
142+
const start = Date.now();
143+
try {
144+
await fn();
145+
const time = Date.now() - start;
146+
console.log();
147+
console.log(`${kl.green("Completed")} in ${prettyTime(time)}`);
148+
} catch (err) {
149+
if (err instanceof JsrPackageNameError) {
150+
console.log(kl.red(err.message));
151+
process.exit(1);
152+
}
153+
154+
throw err;
155+
}
156+
}

src/commands.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as path from "node:path";
22
import * as fs from "node:fs";
3+
import * as kl from "kolorist";
34
import { JsrPackage } from "./utils";
4-
import { detectPackageManager, getProjectDir } from "./pkg_manager";
5+
import { getPkgManager } from "./pkg_manager";
56

67
const JSR_NPMRC = `@jsr:registry=https://npm.jsr.io\n`;
78

@@ -22,20 +23,24 @@ export async function setupNpmRc(dir: string) {
2223
}
2324
}
2425

25-
export interface InstallOptions {
26+
export interface BaseOptions {
27+
pkgManagerName: "npm" | "yarn" | "pnpm" | null;
28+
}
29+
30+
export interface InstallOptions extends BaseOptions {
2631
mode: "dev" | "prod" | "optional";
2732
}
2833

2934
export async function install(packages: JsrPackage[], options: InstallOptions) {
30-
const { projectDir, lockFilePath } = await getProjectDir(process.cwd());
31-
await setupNpmRc(projectDir);
35+
console.log(`Installing ${kl.cyan(packages.join(", "))}...`);
36+
const pkgManager = await getPkgManager(process.cwd(), options.pkgManagerName);
37+
await setupNpmRc(pkgManager.cwd);
3238

33-
const pkgManager = await detectPackageManager(lockFilePath, projectDir);
3439
await pkgManager.install(packages, options);
3540
}
3641

37-
export async function remove(packages: JsrPackage[]) {
38-
const { projectDir, lockFilePath } = await getProjectDir(process.cwd());
39-
const pkgManager = await detectPackageManager(lockFilePath, projectDir);
42+
export async function remove(packages: JsrPackage[], options: BaseOptions) {
43+
console.log(`Removing ${kl.cyan(packages.join(", "))}...`);
44+
const pkgManager = await getPkgManager(process.cwd(), options.pkgManagerName);
4045
await pkgManager.remove(packages);
4146
}

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { install, remove, InstallOptions } from "./commands";
2-
export { JsrPackage } from "./utils";
1+
export { install, remove, type InstallOptions } from "./commands";
2+
export { JsrPackage, JsrPackageNameError } from "./utils";

src/pkg_manager.ts

Lines changed: 69 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1-
import { promisify } from "node:util";
2-
import * as path from "node:path";
31
import { InstallOptions } from "./commands";
4-
import { JsrPackage, findLockFile, findPackageJson, logDebug } from "./utils";
5-
import * as cp from "node:child_process";
2+
import { JsrPackage, findProjectDir } from "./utils";
3+
import { spawn } from "node:child_process";
4+
import * as kl from "kolorist";
65

7-
const execAsync = promisify(cp.exec);
8-
const exec = (cmd: string, options: cp.ExecOptions) => {
9-
logDebug(`$ ${cmd}`);
10-
return execAsync(cmd, options);
6+
const exec = async (cmd: string, args: string[], cwd: string) => {
7+
console.log(kl.dim(`$ ${cmd} ${args.join(" ")}`));
8+
9+
const cp = spawn(cmd, args, { stdio: "inherit" });
10+
11+
return new Promise<void>((resolve) => {
12+
cp.on("exit", (code) => {
13+
if (code === 0) resolve();
14+
else process.exit(code ?? 1);
15+
});
16+
});
1117
};
1218

1319
function modeToFlag(mode: InstallOptions["mode"]): string {
@@ -18,106 +24,102 @@ function modeToFlag(mode: InstallOptions["mode"]): string {
1824
: "";
1925
}
2026

21-
function toPackageArgs(pkgs: JsrPackage[]): string {
22-
return pkgs
23-
.map((pkg) => `@${pkg.scope}/${pkg.name}@npm:${pkg.toNpmPackage()}`)
24-
.join(" ");
25-
}
26-
27-
function toMappedArg(pkgs: JsrPackage[]): string {
28-
return pkgs.map((pkg) => pkg.toString()).join(" ");
27+
function toPackageArgs(pkgs: JsrPackage[]): string[] {
28+
return pkgs.map(
29+
(pkg) => `@${pkg.scope}/${pkg.name}@npm:${pkg.toNpmPackage()}`
30+
);
2931
}
3032

3133
export interface PackageManager {
34+
cwd: string;
3235
install(packages: JsrPackage[], options: InstallOptions): Promise<void>;
3336
remove(packages: JsrPackage[]): Promise<void>;
3437
}
3538

3639
class Npm implements PackageManager {
37-
constructor(private cwd: string) {}
40+
constructor(public cwd: string) {}
3841

3942
async install(packages: JsrPackage[], options: InstallOptions) {
43+
const args = ["install"];
4044
const mode = modeToFlag(options.mode);
41-
await exec(`npm install ${mode}${toPackageArgs(packages)}`, {
42-
cwd: this.cwd,
43-
});
45+
if (mode !== "") {
46+
args.push(mode);
47+
}
48+
args.push(...toPackageArgs(packages));
49+
50+
await exec("npm", args, this.cwd);
4451
}
4552

4653
async remove(packages: JsrPackage[]) {
47-
await exec(`npm remove ${toMappedArg(packages)}`, {
48-
cwd: this.cwd,
49-
});
54+
await exec(
55+
"npm",
56+
["remove", ...packages.map((pkg) => pkg.toString())],
57+
this.cwd
58+
);
5059
}
5160
}
5261

5362
class Yarn implements PackageManager {
54-
constructor(private cwd: string) {}
63+
constructor(public cwd: string) {}
5564

5665
async install(packages: JsrPackage[], options: InstallOptions) {
66+
const args = ["add"];
5767
const mode = modeToFlag(options.mode);
58-
await exec(`yarn add ${mode}${toPackageArgs(packages)}`, {
59-
cwd: this.cwd,
60-
});
68+
if (mode !== "") {
69+
args.push(mode);
70+
}
71+
args.push(...toPackageArgs(packages));
72+
await exec("yarn", args, this.cwd);
6173
}
6274

6375
async remove(packages: JsrPackage[]) {
64-
await exec(`yarn remove ${toMappedArg(packages)}`, {
65-
cwd: this.cwd,
66-
});
76+
await exec(
77+
"yarn",
78+
["remove", ...packages.map((pkg) => pkg.toString())],
79+
this.cwd
80+
);
6781
}
6882
}
6983

7084
class Pnpm implements PackageManager {
71-
constructor(private cwd: string) {}
85+
constructor(public cwd: string) {}
7286

7387
async install(packages: JsrPackage[], options: InstallOptions) {
88+
const args = ["add"];
7489
const mode = modeToFlag(options.mode);
75-
await exec(`pnpm add ${mode}${toPackageArgs(packages)}`, {
76-
cwd: this.cwd,
77-
});
90+
if (mode !== "") {
91+
args.push(mode);
92+
}
93+
args.push(...toPackageArgs(packages));
94+
await exec("pnpm", args, this.cwd);
7895
}
7996

8097
async remove(packages: JsrPackage[]) {
81-
cp.execSync(`pnpm remove ${toMappedArg(packages)}`, {
82-
cwd: this.cwd,
83-
});
98+
await exec(
99+
"yarn",
100+
["remove", ...packages.map((pkg) => pkg.toString())],
101+
this.cwd
102+
);
84103
}
85104
}
86105

87-
export async function getProjectDir(cwd: string): Promise<{
88-
projectDir: string;
89-
lockFilePath: string | null;
90-
}> {
91-
const lockFilePath = await findLockFile(cwd);
92-
if (lockFilePath !== null) {
93-
const projectDir = path.dirname(lockFilePath);
94-
return { lockFilePath, projectDir };
95-
}
106+
export type PkgManagerName = "npm" | "yarn" | "pnpm";
96107

97-
const pkgJsonPath = await findPackageJson(cwd);
98-
if (pkgJsonPath !== null) {
99-
const projectDir = path.dirname(pkgJsonPath);
100-
return { lockFilePath: null, projectDir };
101-
}
108+
export async function getPkgManager(
109+
cwd: string,
110+
pkgManagerName: PkgManagerName | null
111+
) {
112+
const { projectDir, pkgManagerName: foundPkgManager } = await findProjectDir(
113+
cwd
114+
);
102115

103-
return { lockFilePath: null, projectDir: cwd };
104-
}
116+
const result = pkgManagerName || foundPkgManager || "npm";
105117

106-
export function detectPackageManager(
107-
lockFilePath: string | null,
108-
projectDir: string
109-
): PackageManager {
110-
if (lockFilePath !== null) {
111-
const filename = path.basename(lockFilePath);
112-
if (filename === "package-lock.json") {
113-
return new Npm(projectDir);
114-
} else if (filename === "yarn.lock") {
115-
return new Yarn(projectDir);
116-
} else if (filename === "pnpm-lock.yml") {
117-
return new Pnpm(projectDir);
118-
}
118+
if (result === "yarn") {
119+
return new Yarn(projectDir);
120+
} else if (result === "pnpm") {
121+
return new Pnpm(projectDir);
119122
}
120123

121-
// Fall back to npm if no lockfile is present.
122124
return new Npm(projectDir);
123125
}

0 commit comments

Comments
 (0)