summaryrefslogtreecommitdiffhomepage
path: root/packages/shared
diff options
context:
space:
mode:
authorBrendan Allan <[email protected]>2026-04-17 11:50:17 +0800
committerBrendan Allan <[email protected]>2026-04-17 11:53:17 +0800
commita4882290aa0a0f12c6ba849ce23cda4991017337 (patch)
treeab39db4c19bb4809396563fb89c898d84e25ddf8 /packages/shared
parent42771c1db377d190b670ec623a951e2ad7d51c3d (diff)
parentfbbab9d6c8a03c4cd5bed0d13a85f52e3aca47ce (diff)
downloadopencode-a4882290aa0a0f12c6ba849ce23cda4991017337.tar.gz
opencode-a4882290aa0a0f12c6ba849ce23cda4991017337.zip
Merge branch 'dev' into nxl/improve-compaction-strategy
Diffstat (limited to 'packages/shared')
-rw-r--r--packages/shared/package.json7
-rw-r--r--packages/shared/src/npm.ts169
2 files changed, 81 insertions, 95 deletions
diff --git a/packages/shared/package.json b/packages/shared/package.json
index ac2d8f209..9dec6bdb6 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
- "version": "1.4.6",
+ "version": "1.4.7",
"name": "@opencode-ai/shared",
"type": "module",
"license": "MIT",
@@ -17,15 +17,16 @@
},
"imports": {},
"devDependencies": {
+ "@tsconfig/bun": "catalog:",
"@types/semver": "catalog:",
"@types/bun": "catalog:",
- "@types/npmcli__arborist": "6.3.3",
- "@tsconfig/bun": "catalog:"
+ "@types/npmcli__arborist": "6.3.3"
},
"dependencies": {
"@effect/platform-node": "catalog:",
"@npmcli/arborist": "catalog:",
"effect": "catalog:",
+ "glob": "13.0.5",
"mime-types": "3.0.2",
"minimatch": "10.2.5",
"semver": "catalog:",
diff --git a/packages/shared/src/npm.ts b/packages/shared/src/npm.ts
index 955cafa19..865e827b3 100644
--- a/packages/shared/src/npm.ts
+++ b/packages/shared/src/npm.ts
@@ -8,7 +8,8 @@ import { EffectFlock } from "./util/effect-flock"
export namespace Npm {
export class InstallFailedError extends Schema.TaggedErrorClass<InstallFailedError>()("NpmInstallFailedError", {
- pkg: Schema.String,
+ add: Schema.Array(Schema.String).pipe(Schema.optional),
+ dir: Schema.String,
cause: Schema.optional(Schema.Defect),
}) {}
@@ -19,7 +20,10 @@ export namespace Npm {
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>
+ 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>>
}
@@ -55,6 +59,37 @@ export namespace Npm {
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* () {
@@ -91,45 +126,12 @@ export namespace Npm {
})
const add = Effect.fn("Npm.add")(function* (pkg: string) {
- const { Arborist } = yield* Effect.promise(() => import("@npmcli/arborist"))
const dir = directory(pkg)
yield* flock.acquire(`npm-install:${dir}`)
- const arborist = new Arborist({
- path: dir,
- binLinks: true,
- progress: false,
- savePrefix: "",
- ignoreScripts: true,
- })
-
- const tree = yield* Effect.tryPromise({
- try: () => arborist.loadVirtual().catch(() => undefined),
- catch: () => undefined,
- }).pipe(Effect.orElseSucceed(() => undefined)) as Effect.Effect<ArboristTree | undefined>
-
- if (tree) {
- const first = tree.edgesOut.values().next().value?.to
- if (first) {
- return resolveEntryPoint(first.name, first.path)
- }
- }
-
- const result = yield* Effect.tryPromise({
- try: () =>
- arborist.reify({
- add: [pkg],
- save: true,
- saveType: "prod",
- }),
- catch: (cause) => new InstallFailedError({ pkg, cause }),
- }) as Effect.Effect<ArboristTree, InstallFailedError>
-
- const first = result.edgesOut.values().next().value?.to
- if (!first) {
- return yield* new InstallFailedError({ pkg })
- }
-
+ 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)
@@ -142,62 +144,45 @@ export namespace Npm {
yield* flock.acquire(`npm-install:${dir}`)
- const reify = Effect.fnUntraced(function* () {
- const { Arborist } = yield* Effect.promise(() => import("@npmcli/arborist"))
- const arb = new Arborist({
- path: dir,
- binLinks: true,
- progress: false,
- savePrefix: "",
- ignoreScripts: true,
- })
- yield* Effect.tryPromise({
- try: () =>
- arb
- .reify({
- add: input?.add || [],
- save: true,
- saveType: "prod",
- })
- .catch(() => {}),
- catch: () => {},
- }).pipe(Effect.orElseSucceed(() => {}))
- })
-
- const nodeModulesExists = yield* afs.existsSafe(path.join(dir, "node_modules"))
- if (!nodeModulesExists) {
- yield* reify()
- return
- }
-
- 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()
+ 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) {