Skip to content

Commit 30c8186

Browse files
chore: add tests
1 parent 4864c29 commit 30c8186

File tree

6 files changed

+304
-28
lines changed

6 files changed

+304
-28
lines changed

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,26 @@
1313
}
1414
},
1515
"scripts": {
16+
"test": "node -r ts-node/register --test **/*.test.ts",
1617
"cli": "ts-node src/bin.ts",
1718
"build": "rimraf dist dist-esm && tsc && tsc -p tsconfig.esm.json",
18-
"prepublishOnly": "tsc"
19+
"prepublishOnly": "tsc && npm test"
1920
},
2021
"keywords": [],
2122
"author": "",
2223
"license": "MIT",
23-
"files": ["dist/", "dist-esm/"],
24+
"files": [
25+
"dist/",
26+
"dist-esm/"
27+
],
2428
"devDependencies": {
2529
"@types/node": "^20.11.18",
2630
"rimraf": "^5.0.5",
2731
"ts-node": "^10.9.2",
2832
"typescript": "^5.3.3"
2933
},
3034
"dependencies": {
35+
"@std/encoding": "npm:@jsr/std__encoding@^0.216.0",
3136
"kolorist": "^1.8.0"
3237
}
3338
}

src/pkg_manager.ts

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,17 @@
11
import { InstallOptions } from "./commands";
2-
import { JsrPackage, findProjectDir } from "./utils";
3-
import { spawn } from "node:child_process";
2+
import { JsrPackage, exec, findProjectDir } from "./utils";
43
import * as kl from "kolorist";
54

6-
const exec = async (cmd: string, args: string[], cwd: string) => {
5+
async function execWithLog(cmd: string, args: string[], cwd: string) {
76
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-
});
17-
};
7+
return exec(cmd, args, cwd);
8+
}
189

1910
function modeToFlag(mode: InstallOptions["mode"]): string {
2011
return mode === "dev"
21-
? "--save-dev "
12+
? "--save-dev"
2213
: mode === "optional"
23-
? "--save-optional "
14+
? "--save-optional"
2415
: "";
2516
}
2617

@@ -47,11 +38,11 @@ class Npm implements PackageManager {
4738
}
4839
args.push(...toPackageArgs(packages));
4940

50-
await exec("npm", args, this.cwd);
41+
await execWithLog("npm", args, this.cwd);
5142
}
5243

5344
async remove(packages: JsrPackage[]) {
54-
await exec(
45+
await execWithLog(
5546
"npm",
5647
["remove", ...packages.map((pkg) => pkg.toString())],
5748
this.cwd
@@ -69,11 +60,11 @@ class Yarn implements PackageManager {
6960
args.push(mode);
7061
}
7162
args.push(...toPackageArgs(packages));
72-
await exec("yarn", args, this.cwd);
63+
await execWithLog("yarn", args, this.cwd);
7364
}
7465

7566
async remove(packages: JsrPackage[]) {
76-
await exec(
67+
await execWithLog(
7768
"yarn",
7869
["remove", ...packages.map((pkg) => pkg.toString())],
7970
this.cwd
@@ -91,11 +82,11 @@ class Pnpm implements PackageManager {
9182
args.push(mode);
9283
}
9384
args.push(...toPackageArgs(packages));
94-
await exec("pnpm", args, this.cwd);
85+
await execWithLog("pnpm", args, this.cwd);
9586
}
9687

9788
async remove(packages: JsrPackage[]) {
98-
await exec(
89+
await execWithLog(
9990
"yarn",
10091
["remove", ...packages.map((pkg) => pkg.toString())],
10192
this.cwd

src/utils.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as path from "node:path";
22
import * as fs from "node:fs";
33
import { PkgManagerName } from "./pkg_manager";
4+
import { spawn } from "node:child_process";
45

56
export let DEBUG = false;
67
export function setDebug(enabled: boolean) {
@@ -12,8 +13,8 @@ export function logDebug(msg: string) {
1213
}
1314
}
1415

15-
const EXTRACT_REG = /^@([a-z][a-z0-9-]+)\/([a-z0-9-]+)$/;
16-
const EXTRACT_REG_PROXY = /^@jsr\/([a-z][a-z0-9-]+)__([a-z0-9-]+)$/;
16+
const EXTRACT_REG = /^@([a-z][a-z0-9-]+)\/([a-z0-9-]+)(@.+)?$/;
17+
const EXTRACT_REG_PROXY = /^@jsr\/([a-z][a-z0-9-]+)__([a-z0-9-]+)(@.+)?$/;
1718

1819
export class JsrPackageNameError extends Error {}
1920

@@ -23,22 +24,28 @@ export class JsrPackage {
2324
if (exactMatch !== null) {
2425
const scope = exactMatch[1];
2526
const name = exactMatch[2];
26-
return new JsrPackage(scope, name);
27+
const version = exactMatch[3] ?? "";
28+
return new JsrPackage(scope, name, version);
2729
}
2830

2931
const proxyMatch = input.match(EXTRACT_REG_PROXY);
3032
if (proxyMatch !== null) {
3133
const scope = proxyMatch[1];
3234
const name = proxyMatch[2];
33-
return new JsrPackage(scope, name);
35+
const version = proxyMatch[3] ?? "";
36+
return new JsrPackage(scope, name, version);
3437
}
3538

3639
throw new JsrPackageNameError(
3740
`Invalid jsr package name: A jsr package name must have the format @<scope>/<name>, but got "${input}"`
3841
);
3942
}
4043

41-
private constructor(public scope: string, public name: string) {}
44+
private constructor(
45+
public scope: string,
46+
public name: string,
47+
public version: string
48+
) {}
4249

4350
toNpmPackage(): string {
4451
return `@jsr/${this.scope}__${this.name}`;
@@ -131,3 +138,14 @@ export function prettyTime(diff: number) {
131138

132139
return diff + "ms";
133140
}
141+
142+
export async function exec(cmd: string, args: string[], cwd: string) {
143+
const cp = spawn(cmd, args, { stdio: "inherit", cwd });
144+
145+
return new Promise<void>((resolve) => {
146+
cp.on("exit", (code) => {
147+
if (code === 0) resolve();
148+
else process.exit(code ?? 1);
149+
});
150+
});
151+
}

test/install.test.ts

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import { describe, it } from "node:test";
2+
import * as fs from "fs";
3+
import * as path from "path";
4+
import { isDirectory, isFile, runJsr, withTempEnv } from "./test_utils";
5+
import * as assert from "node:assert/strict";
6+
7+
describe("install", () => {
8+
it("jsr i @std/encoding - resolve latest version", async () => {
9+
await withTempEnv(["i", "@std/encoding"], async (getPkgJson, dir) => {
10+
const pkgJson = await getPkgJson();
11+
assert(
12+
pkgJson.dependencies && "@std/encoding" in pkgJson.dependencies,
13+
"Missing dependency entry"
14+
);
15+
16+
assert.match(
17+
pkgJson.dependencies["@std/encoding"],
18+
/^npm:@jsr\/std__encoding@\^\d+\.\d+\.\d+.*$/
19+
);
20+
21+
const depPath = path.join(dir, "node_modules", "@std", "encoding");
22+
assert(await isDirectory(depPath), "Not installed in node_modules");
23+
});
24+
});
25+
26+
it("jsr i @std/[email protected] - with version", async () => {
27+
await withTempEnv(["i", "@std/[email protected]"], async (getPkgJson) => {
28+
const pkgJson = await getPkgJson();
29+
assert.deepEqual(pkgJson.dependencies, {
30+
"@std/encoding": "npm:@jsr/std__encoding@^0.216.0",
31+
});
32+
});
33+
});
34+
35+
it("jsr install @std/[email protected] - command", async () => {
36+
await withTempEnv(["i", "@std/[email protected]"], async (getPkgJson) => {
37+
const pkgJson = await getPkgJson();
38+
assert.deepEqual(pkgJson.dependencies, {
39+
"@std/encoding": "npm:@jsr/std__encoding@^0.216.0",
40+
});
41+
});
42+
});
43+
44+
it("jsr add @std/[email protected] - command", async () => {
45+
await withTempEnv(["i", "@std/[email protected]"], async (getPkgJson) => {
46+
const pkgJson = await getPkgJson();
47+
assert.deepEqual(pkgJson.dependencies, {
48+
"@std/encoding": "npm:@jsr/std__encoding@^0.216.0",
49+
});
50+
});
51+
});
52+
53+
it("jsr add -D @std/[email protected] - dev dependency", async () => {
54+
await withTempEnv(
55+
["i", "-D", "@std/[email protected]"],
56+
async (getPkgJson) => {
57+
const pkgJson = await getPkgJson();
58+
assert.deepEqual(pkgJson.devDependencies, {
59+
"@std/encoding": "npm:@jsr/std__encoding@^0.216.0",
60+
});
61+
}
62+
);
63+
64+
await withTempEnv(
65+
["i", "--save-dev", "@std/[email protected]"],
66+
async (getPkgJson) => {
67+
const pkgJson = await getPkgJson();
68+
assert.deepEqual(pkgJson.devDependencies, {
69+
"@std/encoding": "npm:@jsr/std__encoding@^0.216.0",
70+
});
71+
}
72+
);
73+
});
74+
75+
it("jsr add -O @std/[email protected] - dev dependency", async () => {
76+
await withTempEnv(
77+
["i", "-O", "@std/[email protected]"],
78+
async (getPkgJson) => {
79+
const pkgJson = await getPkgJson();
80+
assert.deepEqual(pkgJson.optionalDependencies, {
81+
"@std/encoding": "npm:@jsr/std__encoding@^0.216.0",
82+
});
83+
}
84+
);
85+
86+
await withTempEnv(
87+
["i", "--save-optional", "@std/[email protected]"],
88+
async (getPkgJson) => {
89+
const pkgJson = await getPkgJson();
90+
assert.deepEqual(pkgJson.optionalDependencies, {
91+
"@std/encoding": "npm:@jsr/std__encoding@^0.216.0",
92+
});
93+
}
94+
);
95+
});
96+
97+
it("jsr add --npm @std/[email protected] - forces npm", async () => {
98+
await withTempEnv(
99+
["i", "--npm", "@std/[email protected]"],
100+
async (_, dir) => {
101+
assert(
102+
await isFile(path.join(dir, "package-lock.json")),
103+
"npm lockfile not created"
104+
);
105+
}
106+
);
107+
});
108+
109+
it("jsr add --yarn @std/[email protected] - forces yarn", async () => {
110+
await withTempEnv(
111+
["i", "--yarn", "@std/[email protected]"],
112+
async (_, dir) => {
113+
assert(
114+
await isFile(path.join(dir, "yarn.lock")),
115+
"yarn lockfile not created"
116+
);
117+
}
118+
);
119+
});
120+
121+
it("jsr add --pnpm @std/[email protected] - forces pnpm", async () => {
122+
await withTempEnv(
123+
["i", "--pnpm", "@std/[email protected]"],
124+
async (_, dir) => {
125+
assert(
126+
await isFile(path.join(dir, "pnpm-lock.yaml")),
127+
"pnpm lockfile not created"
128+
);
129+
}
130+
);
131+
});
132+
});
133+
134+
describe("remove", () => {
135+
it("jsr r @std/[email protected] - removes node_modules", async () => {
136+
await withTempEnv(
137+
["i", "@std/[email protected]"],
138+
async (getPkgJson, dir) => {
139+
await runJsr(["r", "@std/encoding"], dir);
140+
141+
const pkgJson = await getPkgJson();
142+
assert.equal(pkgJson.dependencies, undefined);
143+
144+
const depPath = path.join(dir, "node_modules", "@std", "encoding");
145+
assert(
146+
!(await isDirectory(depPath)),
147+
"Folder in node_modules not removed"
148+
);
149+
}
150+
);
151+
});
152+
153+
it("jsr r @std/[email protected] - command", async () => {
154+
await withTempEnv(
155+
["i", "@std/[email protected]"],
156+
async (getPkgJson, dir) => {
157+
await runJsr(["r", "@std/encoding"], dir);
158+
159+
const pkgJson = await getPkgJson();
160+
assert.equal(pkgJson.dependencies, undefined);
161+
}
162+
);
163+
});
164+
165+
it("jsr remove @std/[email protected] - command", async () => {
166+
await withTempEnv(
167+
["i", "@std/[email protected]"],
168+
async (getPkgJson, dir) => {
169+
await runJsr(["remove", "@std/encoding"], dir);
170+
171+
const pkgJson = await getPkgJson();
172+
assert.equal(pkgJson.dependencies, undefined);
173+
}
174+
);
175+
});
176+
177+
it("jsr uninstall @std/[email protected] - command", async () => {
178+
await withTempEnv(
179+
["i", "@std/[email protected]"],
180+
async (getPkgJson, dir) => {
181+
await runJsr(["uninstall", "@std/encoding"], dir);
182+
183+
const pkgJson = await getPkgJson();
184+
assert.equal(pkgJson.dependencies, undefined);
185+
}
186+
);
187+
});
188+
});

0 commit comments

Comments
 (0)