summaryrefslogtreecommitdiffhomepage
path: root/packages/shared/src
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2026-04-17 14:08:23 -0400
committerDax Raad <[email protected]>2026-04-17 14:08:45 -0400
commit2f73e73e9d03262fb59d4e942b3e1e073cb76cb9 (patch)
tree620bd4ab144320057ebf8ed625bf4bd0eb1b3fff /packages/shared/src
parent4c30a78cd9623fe8f3a7c27860a7b8a0cc760e39 (diff)
downloadopencode-2f73e73e9d03262fb59d4e942b3e1e073cb76cb9.tar.gz
opencode-2f73e73e9d03262fb59d4e942b3e1e073cb76cb9.zip
trace npm fully
Diffstat (limited to 'packages/shared/src')
-rw-r--r--packages/shared/src/npm.ts249
1 files changed, 0 insertions, 249 deletions
diff --git a/packages/shared/src/npm.ts b/packages/shared/src/npm.ts
deleted file mode 100644
index 865e827b3..000000000
--- a/packages/shared/src/npm.ts
+++ /dev/null
@@ -1,249 +0,0 @@
-import path from "path"
-import semver from "semver"
-import { Effect, Schema, Context, Layer, Option, FileSystem } from "effect"
-import { NodeFileSystem } from "@effect/platform-node"
-import { AppFileSystem } from "./filesystem"
-import { Global } from "./global"
-import { EffectFlock } from "./util/effect-flock"
-
-export namespace Npm {
- export class InstallFailedError extends Schema.TaggedErrorClass<InstallFailedError>()("NpmInstallFailedError", {
- add: Schema.Array(Schema.String).pipe(Schema.optional),
- dir: Schema.String,
- cause: Schema.optional(Schema.Defect),
- }) {}
-
- export interface EntryPoint {
- readonly directory: string
- readonly entrypoint: Option.Option<string>
- }
-
- export interface Interface {
- readonly add: (pkg: string) => Effect.Effect<EntryPoint, InstallFailedError | EffectFlock.LockError>
- readonly install: (
- dir: string,
- input?: { add: string[] },
- ) => Effect.Effect<void, EffectFlock.LockError | InstallFailedError>
- readonly outdated: (pkg: string, cachedVersion: string) => Effect.Effect<boolean>
- readonly which: (pkg: string) => Effect.Effect<Option.Option<string>>
- }
-
- export class Service extends Context.Service<Service, Interface>()("@opencode/Npm") {}
-
- const illegal = process.platform === "win32" ? new Set(["<", ">", ":", '"', "|", "?", "*"]) : undefined
-
- export function sanitize(pkg: string) {
- if (!illegal) return pkg
- return Array.from(pkg, (char) => (illegal.has(char) || char.charCodeAt(0) < 32 ? "_" : char)).join("")
- }
-
- const resolveEntryPoint = (name: string, dir: string): EntryPoint => {
- let entrypoint: Option.Option<string>
- try {
- const resolved = typeof Bun !== "undefined" ? import.meta.resolve(name, dir) : import.meta.resolve(dir)
- entrypoint = Option.some(resolved)
- } catch {
- entrypoint = Option.none()
- }
- return {
- directory: dir,
- entrypoint,
- }
- }
-
- interface ArboristNode {
- name: string
- path: string
- }
-
- interface ArboristTree {
- edgesOut: Map<string, { to?: ArboristNode }>
- }
-
- const reify = (input: { dir: string; add?: string[] }) =>
- Effect.gen(function* () {
- const { Arborist } = yield* Effect.promise(() => import("@npmcli/arborist"))
- const arborist = new Arborist({
- path: input.dir,
- binLinks: true,
- progress: false,
- savePrefix: "",
- ignoreScripts: true,
- })
- return yield* Effect.tryPromise({
- try: () =>
- arborist.reify({
- add: input?.add || [],
- save: true,
- saveType: "prod",
- }),
- catch: (cause) =>
- new InstallFailedError({
- cause,
- add: input?.add,
- dir: input.dir,
- }),
- }) as Effect.Effect<ArboristTree, InstallFailedError>
- }).pipe(
- Effect.withSpan("Npm.reify", {
- attributes: input,
- }),
- )
-
- export const layer = Layer.effect(
- Service,
- Effect.gen(function* () {
- const afs = yield* AppFileSystem.Service
- const global = yield* Global.Service
- const fs = yield* FileSystem.FileSystem
- const flock = yield* EffectFlock.Service
- const directory = (pkg: string) => path.join(global.cache, "packages", sanitize(pkg))
-
- const outdated = Effect.fn("Npm.outdated")(function* (pkg: string, cachedVersion: string) {
- const response = yield* Effect.tryPromise({
- try: () => fetch(`https://registry.npmjs.org/${pkg}`),
- catch: () => undefined,
- }).pipe(Effect.orElseSucceed(() => undefined))
-
- if (!response || !response.ok) {
- return false
- }
-
- const data = yield* Effect.tryPromise({
- try: () => response.json() as Promise<{ "dist-tags"?: { latest?: string } }>,
- catch: () => undefined,
- }).pipe(Effect.orElseSucceed(() => undefined))
-
- const latestVersion = data?.["dist-tags"]?.latest
- if (!latestVersion) {
- return false
- }
-
- const range = /[\s^~*xX<>|=]/.test(cachedVersion)
- if (range) return !semver.satisfies(latestVersion, cachedVersion)
-
- return semver.lt(cachedVersion, latestVersion)
- })
-
- const add = Effect.fn("Npm.add")(function* (pkg: string) {
- const dir = directory(pkg)
- yield* flock.acquire(`npm-install:${dir}`)
-
- const tree = yield* reify({ dir, add: [pkg] })
- const first = tree.edgesOut.values().next().value?.to
- if (!first) return yield* new InstallFailedError({ add: [pkg], dir })
- return resolveEntryPoint(first.name, first.path)
- }, Effect.scoped)
-
- const install = Effect.fn("Npm.install")(function* (dir: string, input?: { add: string[] }) {
- const canWrite = yield* afs.access(dir, { writable: true }).pipe(
- Effect.as(true),
- Effect.orElseSucceed(() => false),
- )
- if (!canWrite) return
-
- yield* flock.acquire(`npm-install:${dir}`)
-
- yield* Effect.gen(function* () {
- const nodeModulesExists = yield* afs.existsSafe(path.join(dir, "node_modules"))
- if (!nodeModulesExists) {
- yield* reify({ add: input?.add, dir })
- return
- }
- }).pipe(Effect.withSpan("Npm.checkNodeModules"))
-
- yield* Effect.gen(function* () {
- const pkg = yield* afs.readJson(path.join(dir, "package.json")).pipe(Effect.orElseSucceed(() => ({})))
- const lock = yield* afs.readJson(path.join(dir, "package-lock.json")).pipe(Effect.orElseSucceed(() => ({})))
-
- const pkgAny = pkg as any
- const lockAny = lock as any
- const declared = new Set([
- ...Object.keys(pkgAny?.dependencies || {}),
- ...Object.keys(pkgAny?.devDependencies || {}),
- ...Object.keys(pkgAny?.peerDependencies || {}),
- ...Object.keys(pkgAny?.optionalDependencies || {}),
- ...(input?.add || []),
- ])
-
- const root = lockAny?.packages?.[""] || {}
- const locked = new Set([
- ...Object.keys(root?.dependencies || {}),
- ...Object.keys(root?.devDependencies || {}),
- ...Object.keys(root?.peerDependencies || {}),
- ...Object.keys(root?.optionalDependencies || {}),
- ])
-
- for (const name of declared) {
- if (!locked.has(name)) {
- yield* reify({ dir, add: input?.add })
- return
- }
- }
- }).pipe(Effect.withSpan("Npm.checkDirty"))
-
- return
- }, Effect.scoped)
-
- const which = Effect.fn("Npm.which")(function* (pkg: string) {
- const dir = directory(pkg)
- const binDir = path.join(dir, "node_modules", ".bin")
-
- const pick = Effect.fnUntraced(function* () {
- const files = yield* fs.readDirectory(binDir).pipe(Effect.catch(() => Effect.succeed([] as string[])))
-
- if (files.length === 0) return Option.none<string>()
- if (files.length === 1) return Option.some(files[0])
-
- const pkgJson = yield* afs.readJson(path.join(dir, "node_modules", pkg, "package.json")).pipe(Effect.option)
-
- if (Option.isSome(pkgJson)) {
- const parsed = pkgJson.value as { bin?: string | Record<string, string> }
- if (parsed?.bin) {
- const unscoped = pkg.startsWith("@") ? pkg.split("/")[1] : pkg
- const bin = parsed.bin
- if (typeof bin === "string") return Option.some(unscoped)
- const keys = Object.keys(bin)
- if (keys.length === 1) return Option.some(keys[0])
- return bin[unscoped] ? Option.some(unscoped) : Option.some(keys[0])
- }
- }
-
- return Option.some(files[0])
- })
-
- return yield* Effect.gen(function* () {
- const bin = yield* pick()
- if (Option.isSome(bin)) {
- return Option.some(path.join(binDir, bin.value))
- }
-
- yield* fs.remove(path.join(dir, "package-lock.json")).pipe(Effect.orElseSucceed(() => {}))
-
- yield* add(pkg)
-
- const resolved = yield* pick()
- if (Option.isNone(resolved)) return Option.none<string>()
- return Option.some(path.join(binDir, resolved.value))
- }).pipe(
- Effect.scoped,
- Effect.orElseSucceed(() => Option.none<string>()),
- )
- })
-
- return Service.of({
- add,
- install,
- outdated,
- which,
- })
- }),
- )
-
- export const defaultLayer = layer.pipe(
- Layer.provide(EffectFlock.layer),
- Layer.provide(AppFileSystem.layer),
- Layer.provide(Global.layer),
- Layer.provide(NodeFileSystem.layer),
- )
-}