|
|
@@ -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,41 +144,20 @@ export namespace Npm {
|
|
|
|
|
|
yield* flock.acquire(`npm-install:${dir}`)
|
|
|
|
|
|
- const reify = Effect.fn("Npm.reify")(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
|
|
|
+ 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 || {}),
|
|
|
@@ -195,11 +176,12 @@ export namespace Npm {
|
|
|
|
|
|
for (const name of declared) {
|
|
|
if (!locked.has(name)) {
|
|
|
- yield* reify()
|
|
|
+ yield* reify({ dir, add: input?.add })
|
|
|
return
|
|
|
}
|
|
|
}
|
|
|
}).pipe(Effect.withSpan("Npm.checkDirty"))
|
|
|
+
|
|
|
return
|
|
|
}, Effect.scoped)
|
|
|
|