Skip to content

Commit ba52e1e

Browse files
committed
feat: add PackageManager and update extractPackageApiEffect
1 parent 2988f04 commit ba52e1e

File tree

8 files changed

+99
-75
lines changed

8 files changed

+99
-75
lines changed

src/bun-package-manager.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Effect } from "effect";
2+
import { temporaryDirectoryTask } from "tempy";
3+
import { expect, test } from "vitest";
4+
import { bunPackageManager } from "./bun-package-manager";
5+
import type { InstallPackageOptions } from "./package-manager";
6+
7+
const bun = bunPackageManager();
8+
9+
const _installPackage = (options: InstallPackageOptions) =>
10+
Effect.runPromise(bun.installPackage(options));
11+
12+
test("invalid package", async () => {
13+
await temporaryDirectoryTask(async (cwd) => {
14+
await expect(_installPackage({ pkg: "", cwd })).rejects.toThrow();
15+
});
16+
});
17+
18+
test("package with no production dependencies", async () => {
19+
await temporaryDirectoryTask(async (cwd) => {
20+
await expect(_installPackage({ pkg: "verify-hcaptcha@1.0.0", cwd })).resolves.toStrictEqual([
21+
"verify-hcaptcha@1.0.0",
22+
]);
23+
});
24+
});
25+
26+
test("package with some production dependencies", async () => {
27+
await temporaryDirectoryTask(async (cwd) => {
28+
await expect(_installPackage({ pkg: "query-registry@2.6.0", cwd })).resolves.toContain(
29+
"query-registry@2.6.0",
30+
);
31+
});
32+
});

src/bun-package-manager.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Effect } from "effect";
2+
import { execa } from "execa";
3+
import { InstallPackageError, PackageManager } from "./package-manager";
4+
5+
/** @internal */
6+
export const bunPackageManager = (bunPath = "bun") =>
7+
PackageManager.of({
8+
installPackage: ({ pkg, cwd }) =>
9+
Effect.gen(function* (_) {
10+
// Run `bun add <pkg> --verbose`.
11+
// See https://bun.sh/docs/cli/add.
12+
const { stdout } = yield* _(
13+
Effect.tryPromise({
14+
try: () => execa(bunPath, ["add", pkg, "--verbose"], { cwd }),
15+
catch: (e) => new InstallPackageError({ cause: e }),
16+
}),
17+
);
18+
19+
// With verbose output on, bun prints one line per installed package
20+
// (e.g., "foo@1.0.0"), including all installed dependencies.
21+
// These lines are between the two delimiting lines found here:
22+
// https://github.com/oven-sh/bun/blob/972a7b7080bd3066b54dcb43e9c91c5dfa26a69c/src/install/lockfile.zig#L5369-L5370.
23+
const lines = stdout.split("\n");
24+
const beginHash = lines.findIndex((line) => line.startsWith("-- BEGIN SHA512/256"));
25+
const endHash = lines.findIndex((line) => line.startsWith("-- END HASH"));
26+
const installedPackages = lines.slice(beginHash + 1, endHash);
27+
return installedPackages;
28+
}),
29+
});

src/extract-package-api-effect.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { performance } from "node:perf_hooks";
33
import { join } from "pathe";
44
import { createProject } from "./create-project";
55
import type { ExtractPackageApiOptions, PackageApi } from "./extract-package-api";
6-
import { installPackage } from "./install-package";
76
import { packageDeclarations } from "./package-declarations";
87
import { packageJson } from "./package-json";
8+
import { PackageManager } from "./package-manager";
99
import { packageName } from "./package-name";
1010
import { packageOverview } from "./package-overview";
1111
import { packageTypes } from "./package-types";
@@ -16,21 +16,21 @@ export const extractPackageApiEffect = ({
1616
pkg,
1717
subpath = ".",
1818
maxDepth = 5,
19-
bunPath = "bun",
2019
}: ExtractPackageApiOptions) =>
2120
Effect.gen(function* (_) {
2221
const startTime = performance.now();
2322
const pkgName = yield* _(packageName(pkg));
2423
const { path: cwd } = yield* _(workDir);
25-
const packages = yield* _(installPackage({ pkg, cwd, bunPath }));
24+
const pm = yield* _(PackageManager);
25+
const packages = yield* _(pm.installPackage({ pkg, cwd }));
2626
const pkgDir = join(cwd, "node_modules", pkgName);
2727
const pkgJson = yield* _(packageJson(pkgDir));
2828
const types = yield* _(packageTypes(pkgJson, subpath));
2929
const indexFilePath = join(pkgDir, types);
3030
const { project, indexFile } = yield* _(createProject({ indexFilePath, cwd }));
3131
const overview = packageOverview(indexFile);
3232
const declarations = yield* _(packageDeclarations({ pkgName, project, indexFile, maxDepth }));
33-
return {
33+
const pkgApi: PackageApi = {
3434
name: pkgJson.name,
3535
version: pkgJson.version,
3636
subpath,
@@ -40,5 +40,6 @@ export const extractPackageApiEffect = ({
4040
packages,
4141
analyzedAt: new Date().toISOString(),
4242
analyzedIn: Math.round(performance.now() - startTime),
43-
} satisfies PackageApi;
43+
};
44+
return pkgApi;
4445
});

src/extract-package-api.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Effect } from "effect";
2+
import { bunPackageManager } from "./bun-package-manager";
23
import type { ExtractedDeclaration } from "./extract-declarations";
34
import { extractPackageApiEffect } from "./extract-package-api-effect";
5+
import { PackageManager } from "./package-manager";
46

57
/**
68
`ExtractPackageApiOptions` contains all the options
@@ -129,4 +131,8 @@ export const extractPackageApi = ({
129131
maxDepth = 5,
130132
bunPath = "bun",
131133
}: ExtractPackageApiOptions): Promise<PackageApi> =>
132-
Effect.runPromise(Effect.scoped(extractPackageApiEffect({ pkg, subpath, maxDepth, bunPath })));
134+
extractPackageApiEffect({ pkg, subpath, maxDepth }).pipe(
135+
Effect.scoped,
136+
Effect.provideService(PackageManager, bunPackageManager(bunPath)),
137+
Effect.runPromise,
138+
);

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export type {
22
AllExtractedDeclaration,
33
AllExtractedDeclarationKind,
44
} from "./all-extracted-declaration";
5+
export { bunPackageManager } from "./bun-package-manager";
56
export { ProjectError } from "./create-project";
67
export type {
78
ExtractedClass,
@@ -36,9 +37,9 @@ export {
3637
export { extractPackageApiEffect } from "./extract-package-api-effect";
3738
export type { ExtractedTypeAlias } from "./extract-type-alias";
3839
export type { ExtractedVariable } from "./extract-variable";
39-
export { InstallPackageError, installPackage, type InstallPackageOptions } from "./install-package";
4040
export { PackageDeclarationsError } from "./package-declarations";
4141
export { PackageJsonError, packageJson } from "./package-json";
42+
export { InstallPackageError, PackageManager, type InstallPackageOptions } from "./package-manager";
4243
export { PackageNameError, packageName } from "./package-name";
4344
export { PackageTypesError, packageTypes } from "./package-types";
4445
export { parseDocComment } from "./parse-doc-comment";

src/install-package.test.ts

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/install-package.ts

Lines changed: 0 additions & 39 deletions
This file was deleted.

src/package-manager.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Context, Data, Effect } from "effect";
2+
3+
/** @internal */
4+
export type InstallPackageOptions = {
5+
pkg: string;
6+
cwd: string;
7+
};
8+
9+
/** @internal */
10+
export class InstallPackageError extends Data.TaggedError("InstallPackageError")<{
11+
cause?: unknown;
12+
}> {}
13+
14+
/** @internal */
15+
export class PackageManager extends Context.Tag("PackageManager")<
16+
PackageManager,
17+
{
18+
readonly installPackage: ({
19+
pkg,
20+
cwd,
21+
}: InstallPackageOptions) => Effect.Effect<string[], InstallPackageError>;
22+
}
23+
>() {}

0 commit comments

Comments
 (0)