11export * as Npm from "./npm"
22
33import path from "path"
4- import { fileURLToPath } from "url"
54import npa from "npm-package-arg"
6- import semver from "semver"
7- // @ts -expect-error npm does not publish types for this internal config API.
8- import Config from "@npmcli/config"
9- // @ts -expect-error npm does not publish types for this internal config API.
10- import { definitions , flatten , nerfDarts , shorthands } from "@npmcli/config/lib/definitions/index.js"
11- import { Effect , Schema , Context , Layer , Option , FileSystem , Stream } from "effect"
5+ import { Effect , Schema , Context , Layer , Option , FileSystem } from "effect"
126import { NodeFileSystem } from "@effect/platform-node"
137import { AppFileSystem } from "./filesystem"
148import { Global } from "./global"
159import { EffectFlock } from "./util/effect-flock"
1610import { makeRuntime } from "./effect/runtime"
17- import { ChildProcess , ChildProcessSpawner } from "effect/unstable/process"
18-
19- import { CrossSpawnSpawner } from "./cross-spawn-spawner"
11+ import { NpmConfig } from "./npm-config"
2012
2113export class InstallFailedError extends Schema . TaggedErrorClass < InstallFailedError > ( ) ( "NpmInstallFailedError" , {
2214 add : Schema . Array ( Schema . String ) . pipe ( Schema . optional ) ,
@@ -40,46 +32,18 @@ export interface Interface {
4032 } [ ]
4133 } ,
4234 ) => Effect . Effect < void , EffectFlock . LockError | InstallFailedError >
43- readonly outdated : ( pkg : string , cachedVersion : string ) => Effect . Effect < boolean >
4435 readonly which : ( pkg : string , bin ?: string ) => Effect . Effect < Option . Option < string > >
4536}
4637
4738export class Service extends Context . Service < Service , Interface > ( ) ( "@opencode/Npm" ) { }
4839
4940const illegal = process . platform === "win32" ? new Set ( [ "<" , ">" , ":" , '"' , "|" , "?" , "*" ] ) : undefined
50- const npmPath = fileURLToPath ( new URL ( ".." , import . meta. url ) )
5141
5242export function sanitize ( pkg : string ) {
5343 if ( ! illegal ) return pkg
5444 return Array . from ( pkg , ( char ) => ( illegal . has ( char ) || char . charCodeAt ( 0 ) < 32 ? "_" : char ) ) . join ( "" )
5545}
5646
57- const loadOptions = ( dir : string ) =>
58- Effect . tryPromise ( {
59- try : async ( ) => {
60- const config = new Config ( {
61- npmPath,
62- cwd : dir ,
63- env : { ...process . env } ,
64- argv : [ process . execPath , process . execPath ] ,
65- execPath : process . execPath ,
66- platform : process . platform ,
67- definitions,
68- flatten,
69- nerfDarts,
70- shorthands,
71- warn : false ,
72- } )
73- await config . load ( )
74- return config . flat
75- } ,
76- catch : ( cause ) =>
77- new InstallFailedError ( {
78- cause,
79- dir,
80- } ) ,
81- } )
82-
8347const resolveEntryPoint = ( name : string , dir : string ) : EntryPoint => {
8448 let entrypoint : Option . Option < string >
8549 try {
@@ -110,39 +74,13 @@ export const layer = Layer.effect(
11074 const global = yield * Global . Service
11175 const fs = yield * FileSystem . FileSystem
11276 const flock = yield * EffectFlock . Service
113- const spawner = yield * ChildProcessSpawner . ChildProcessSpawner
11477 const directory = ( pkg : string ) => path . join ( global . cache , "packages" , sanitize ( pkg ) )
115- const runView = Effect . fnUntraced ( function * ( cmd : string [ ] ) {
116- const handle = yield * spawner . spawn (
117- ChildProcess . make ( cmd [ 0 ] , cmd . slice ( 1 ) , {
118- extendEnv : true ,
119- } ) ,
120- )
121- const [ stdout , stderr ] = yield * Effect . all (
122- [ Stream . mkString ( Stream . decodeText ( handle . stdout ) ) , Stream . mkString ( Stream . decodeText ( handle . stderr ) ) ] ,
123- { concurrency : 2 } ,
124- )
125- const code = yield * handle . exitCode
126- if ( code !== 0 || ! stdout . trim ( ) ) {
127- return yield * Effect . fail ( stderr || stdout || `Failed to run ${ cmd . join ( " " ) } ` )
128- }
129- return yield * Schema . decodeUnknownEffect ( Schema . fromJsonString ( Schema . String ) ) ( stdout )
130- } , Effect . scoped )
131- const viewLatestVersion = Effect . fnUntraced ( function * ( pkg : string ) {
132- return yield * runView ( [ "npm" , "view" , pkg , "dist-tags.latest" , "--json" ] ) . pipe (
133- Effect . catch ( ( ) =>
134- runView ( [ "pnpm" , "view" , pkg , "dist-tags.latest" , "--json" ] ) . pipe (
135- Effect . catch ( ( ) => runView ( [ "bun" , "pm" , "view" , pkg , "dist-tags.latest" , "--json" ] ) ) ,
136- ) ,
137- ) ,
138- )
139- } )
14078 const reify = ( input : { dir : string ; add ?: string [ ] } ) =>
14179 Effect . gen ( function * ( ) {
14280 yield * flock . acquire ( `npm-install:${ input . dir } ` )
14381 const { Arborist } = yield * Effect . promise ( ( ) => import ( "@npmcli/arborist" ) )
14482 const add = input . add ?? [ ]
145- const npmOptions = yield * loadOptions ( input . dir )
83+ const npmOptions = yield * NpmConfig . load ( input . dir )
14684 const arborist = new Arborist ( {
14785 ...npmOptions ,
14886 path : input . dir ,
@@ -172,18 +110,6 @@ export const layer = Layer.effect(
172110 } ) ,
173111 )
174112
175- const outdated = Effect . fn ( "Npm.outdated" ) ( function * ( pkg : string , cachedVersion : string ) {
176- const latestVersion = yield * viewLatestVersion ( pkg ) . pipe ( Effect . option )
177- if ( Option . isNone ( latestVersion ) ) {
178- return false
179- }
180-
181- const range = / [ \s ^ ~ * x X < > | = ] / . test ( cachedVersion )
182- if ( range ) return ! semver . satisfies ( latestVersion . value , cachedVersion )
183-
184- return semver . lt ( cachedVersion , latestVersion . value )
185- } )
186-
187113 const add = Effect . fn ( "Npm.add" ) ( function * ( pkg : string ) {
188114 const dir = directory ( pkg )
189115 const name = ( ( ) => {
@@ -309,7 +235,6 @@ export const layer = Layer.effect(
309235 return Service . of ( {
310236 add,
311237 install,
312- outdated,
313238 which,
314239 } )
315240 } ) ,
@@ -320,7 +245,6 @@ export const defaultLayer = layer.pipe(
320245 Layer . provide ( AppFileSystem . layer ) ,
321246 Layer . provide ( Global . layer ) ,
322247 Layer . provide ( NodeFileSystem . layer ) ,
323- Layer . provide ( CrossSpawnSpawner . defaultLayer ) ,
324248)
325249
326250const { runPromise } = makeRuntime ( Service , defaultLayer )
@@ -337,10 +261,6 @@ export async function add(...args: Parameters<Interface["add"]>) {
337261 }
338262}
339263
340- export async function outdated ( ...args : Parameters < Interface [ "outdated" ] > ) {
341- return runPromise ( ( svc ) => svc . outdated ( ...args ) )
342- }
343-
344264export async function which ( ...args : Parameters < Interface [ "which" ] > ) {
345265 const resolved = await runPromise ( ( svc ) => svc . which ( ...args ) )
346266 return Option . getOrUndefined ( resolved )
0 commit comments