@@ -6,12 +6,14 @@ import npa from "npm-package-arg"
66import semver from "semver"
77import Config from "@npmcli/config"
88import { definitions , flatten , nerfDarts , shorthands } from "@npmcli/config/lib/definitions/index.js"
9- import { Effect , Schema , Context , Layer , Option , FileSystem } from "effect"
9+ import { Effect , Schema , Context , Layer , Option , FileSystem , Stream } from "effect"
1010import { NodeFileSystem } from "@effect/platform-node"
1111import { AppFileSystem } from "@opencode-ai/shared/filesystem"
1212import { Global } from "@opencode-ai/shared/global"
1313import { EffectFlock } from "@opencode-ai/shared/util/effect-flock"
14+ import { ChildProcess , ChildProcessSpawner } from "effect/unstable/process"
1415
16+ import * as CrossSpawnSpawner from "../effect/cross-spawn-spawner"
1517import { makeRuntime } from "../effect/runtime"
1618
1719export class InstallFailedError extends Schema . TaggedErrorClass < InstallFailedError > ( ) ( "NpmInstallFailedError" , {
@@ -106,7 +108,36 @@ export const layer = Layer.effect(
106108 const global = yield * Global . Service
107109 const fs = yield * FileSystem . FileSystem
108110 const flock = yield * EffectFlock . Service
111+ const spawner = yield * ChildProcessSpawner . ChildProcessSpawner
109112 const directory = ( pkg : string ) => path . join ( global . cache , "packages" , sanitize ( pkg ) )
113+ const runView = Effect . fnUntraced (
114+ function * ( cmd : string [ ] ) {
115+ const handle = yield * spawner . spawn (
116+ ChildProcess . make ( cmd [ 0 ] , cmd . slice ( 1 ) , {
117+ extendEnv : true ,
118+ } ) ,
119+ )
120+ const [ stdout , stderr ] = yield * Effect . all (
121+ [ Stream . mkString ( Stream . decodeText ( handle . stdout ) ) , Stream . mkString ( Stream . decodeText ( handle . stderr ) ) ] ,
122+ { concurrency : 2 } ,
123+ )
124+ const code = yield * handle . exitCode
125+ if ( code !== 0 || ! stdout . trim ( ) ) {
126+ return yield * Effect . fail ( stderr || stdout || `Failed to run ${ cmd . join ( " " ) } ` )
127+ }
128+ return yield * Schema . decodeUnknownEffect ( Schema . fromJsonString ( Schema . String ) ) ( stdout )
129+ } ,
130+ Effect . scoped ,
131+ )
132+ const viewLatestVersion = Effect . fnUntraced ( function * ( pkg : string ) {
133+ return yield * runView ( [ "npm" , "view" , pkg , "dist-tags.latest" , "--json" ] ) . pipe (
134+ Effect . catch ( ( ) =>
135+ runView ( [ "pnpm" , "view" , pkg , "dist-tags.latest" , "--json" ] ) . pipe (
136+ Effect . catch ( ( ) => runView ( [ "bun" , "pm" , "view" , pkg , "dist-tags.latest" , "--json" ] ) ) ,
137+ ) ,
138+ ) ,
139+ )
140+ } )
110141 const reify = ( input : { dir : string ; add ?: string [ ] } ) =>
111142 Effect . gen ( function * ( ) {
112143 yield * flock . acquire ( `npm-install:${ input . dir } ` )
@@ -143,29 +174,15 @@ export const layer = Layer.effect(
143174 )
144175
145176 const outdated = Effect . fn ( "Npm.outdated" ) ( function * ( pkg : string , cachedVersion : string ) {
146- const response = yield * Effect . tryPromise ( {
147- try : ( ) => fetch ( `https://registry.npmjs.org/${ pkg } ` ) ,
148- catch : ( ) => undefined ,
149- } ) . pipe ( Effect . orElseSucceed ( ( ) => undefined ) )
150-
151- if ( ! response || ! response . ok ) {
152- return false
153- }
154-
155- const data = yield * Effect . tryPromise ( {
156- try : ( ) => response . json ( ) as Promise < { "dist-tags" ?: { latest ?: string } } > ,
157- catch : ( ) => undefined ,
158- } ) . pipe ( Effect . orElseSucceed ( ( ) => undefined ) )
159-
160- const latestVersion = data ?. [ "dist-tags" ] ?. latest
161- if ( ! latestVersion ) {
177+ const latestVersion = yield * viewLatestVersion ( pkg ) . pipe ( Effect . option )
178+ if ( Option . isNone ( latestVersion ) ) {
162179 return false
163180 }
164181
165182 const range = / [ \s ^ ~ * x X < > | = ] / . test ( cachedVersion )
166- if ( range ) return ! semver . satisfies ( latestVersion , cachedVersion )
183+ if ( range ) return ! semver . satisfies ( latestVersion . value , cachedVersion )
167184
168- return semver . lt ( cachedVersion , latestVersion )
185+ return semver . lt ( cachedVersion , latestVersion . value )
169186 } )
170187
171188 const add = Effect . fn ( "Npm.add" ) ( function * ( pkg : string ) {
@@ -304,6 +321,7 @@ export const defaultLayer = layer.pipe(
304321 Layer . provide ( AppFileSystem . layer ) ,
305322 Layer . provide ( Global . layer ) ,
306323 Layer . provide ( NodeFileSystem . layer ) ,
324+ Layer . provide ( CrossSpawnSpawner . defaultLayer ) ,
307325)
308326
309327const { runPromise } = makeRuntime ( Service , defaultLayer )
0 commit comments