11import fs from "fs/promises"
22import path from "path"
33import { describe , expect , test } from "bun:test"
4+ import { NodeFileSystem } from "@effect/platform-node"
5+ import { Effect , Layer , Option } from "effect"
6+ import { AppFileSystem } from "@opencode-ai/core/filesystem"
7+ import { Global } from "@opencode-ai/core/global"
48import { Npm } from "@opencode-ai/core/npm"
9+ import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
510import { tmpdir } from "./fixture/tmpdir"
611
712const win = process . platform === "win32"
@@ -15,6 +20,14 @@ const writePackage = (dir: string, pkg: Record<string, unknown>) =>
1520 } ) ,
1621 )
1722
23+ const npmLayer = ( cache : string ) =>
24+ Npm . layer . pipe (
25+ Layer . provide ( EffectFlock . layer ) ,
26+ Layer . provide ( AppFileSystem . layer ) ,
27+ Layer . provide ( Global . layerWith ( { cache, state : path . join ( cache , "state" ) } ) ) ,
28+ Layer . provide ( NodeFileSystem . layer ) ,
29+ )
30+
1831describe ( "Npm.sanitize" , ( ) => {
1932 test ( "keeps normal scoped package specs unchanged" , ( ) => {
2033 expect ( Npm . sanitize ( "@opencode/acme" ) ) . toBe ( "@opencode/acme" )
@@ -29,6 +42,28 @@ describe("Npm.sanitize", () => {
2942 } )
3043} )
3144
45+ describe ( "Npm.add" , ( ) => {
46+ test ( "reifies when package cache directory exists without the package installed" , async ( ) => {
47+ await using tmp = await tmpdir ( )
48+ await fs . mkdir ( path . join ( tmp . path , "fixture-provider" ) )
49+ await writePackage ( path . join ( tmp . path , "fixture-provider" ) , {
50+ name : "fixture-provider" ,
51+ main : "index.js" ,
52+ } )
53+ await Bun . write ( path . join ( tmp . path , "fixture-provider" , "index.js" ) , "export const fixture = true\n" )
54+
55+ const spec = `fixture-provider@file:${ path . join ( tmp . path , "fixture-provider" ) } `
56+ await fs . mkdir ( path . join ( tmp . path , "cache" , "packages" , Npm . sanitize ( spec ) ) , { recursive : true } )
57+
58+ const entry = await Effect . gen ( function * ( ) {
59+ const npm = yield * Npm . Service
60+ return yield * npm . add ( spec )
61+ } ) . pipe ( Effect . scoped , Effect . provide ( npmLayer ( path . join ( tmp . path , "cache" ) ) ) , Effect . runPromise )
62+
63+ expect ( Option . isSome ( entry . entrypoint ) ) . toBe ( true )
64+ } )
65+ } )
66+
3267describe ( "Npm.install" , ( ) => {
3368 test ( "respects omit from project .npmrc" , async ( ) => {
3469 await using tmp = await tmpdir ( )
0 commit comments