@opencode-ai/util 0.0.0-dev-18789 → 0.0.0-dev-18793

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/npm.d.ts +8 -1
  2. package/dist/npm.js +227 -102
  3. package/package.json +3 -1
package/dist/npm.d.ts CHANGED
@@ -12,15 +12,20 @@ export declare class InstallFailedError extends InstallFailedError_base {
12
12
  export interface EntryPoint {
13
13
  readonly directory: string;
14
14
  readonly entrypoint?: string;
15
+ readonly version?: string;
16
+ readonly revision?: string;
15
17
  }
16
18
  export interface Interface {
17
19
  readonly add: (pkg: string, options?: {
18
20
  readonly subpaths?: readonly string[];
19
- readonly refresh?: boolean;
20
21
  }) => Effect.Effect<EntryPoint, InstallFailedError | EffectFlock.LockError>;
21
22
  readonly resolve: (pkg: string, options?: {
22
23
  readonly subpaths?: readonly string[];
23
24
  }) => Effect.Effect<EntryPoint>;
25
+ readonly check: (pkg: string) => Effect.Effect<boolean, InstallFailedError>;
26
+ readonly update: (pkg: string, options?: {
27
+ readonly subpaths?: readonly string[];
28
+ }) => Effect.Effect<EntryPoint, InstallFailedError | EffectFlock.LockError>;
24
29
  readonly which: (pkg: string, bin?: string) => Effect.Effect<string | undefined>;
25
30
  }
26
31
  declare const Service_base: Context.ServiceClass<Service, "@opencode/Npm", Interface>;
@@ -33,4 +38,6 @@ export declare function cacheKey(pkg: string): Promise<string>;
33
38
  export declare const node: LayerNode.Provider<Service, never, LayerNode.Tag<"global">>;
34
39
  export declare function add(...args: Parameters<Interface["add"]>): Promise<EntryPoint>;
35
40
  export declare function resolve(...args: Parameters<Interface["resolve"]>): Promise<EntryPoint>;
41
+ export declare function check(...args: Parameters<Interface["check"]>): Promise<boolean>;
42
+ export declare function update(...args: Parameters<Interface["update"]>): Promise<EntryPoint>;
36
43
  export declare function which(...args: Parameters<Interface["which"]>): Promise<string | undefined>;
package/dist/npm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export * as Npm from "./npm.js";
2
2
  import path from "path";
3
- import { createHash } from "node:crypto";
4
- import { Effect, Schema, Context, Layer, Option, FileSystem } from "effect";
3
+ import { createHash, randomUUID } from "node:crypto";
4
+ import { Clock, Effect, Schema, Context, Layer, Option, FileSystem } from "effect";
5
5
  import { FSUtil } from "./fs-util.js";
6
6
  import { Global } from "./global.js";
7
7
  import { EffectFlock } from "./effect-flock.js";
@@ -26,36 +26,62 @@ export function sanitize(pkg) {
26
26
  return Array.from(pkg, (char) => (illegal.has(char) || char.charCodeAt(0) < 32 ? "_" : char)).join("");
27
27
  }
28
28
  export async function isRegistryPackage(pkg) {
29
- const { default: npa } = await import("npm-package-arg");
30
- try {
31
- const result = npa(pkg);
32
- return result.name !== undefined && ["version", "range", "tag"].includes(result.type);
33
- }
34
- catch {
35
- return false;
36
- }
29
+ return (await parse(pkg))?.type === "registry";
37
30
  }
38
31
  export async function isInstallablePackage(pkg) {
39
- const { default: npa } = await import("npm-package-arg");
40
- try {
41
- const result = npa(pkg);
42
- return result.type === "git" || (result.name !== undefined && ["version", "range", "tag"].includes(result.type));
43
- }
44
- catch {
45
- return false;
46
- }
32
+ return (await parse(pkg)) !== undefined;
47
33
  }
48
34
  export async function cacheKey(pkg) {
35
+ return key(pkg, await parse(pkg));
36
+ }
37
+ async function parse(pkg) {
49
38
  const { default: npa } = await import("npm-package-arg");
50
39
  try {
51
- if (npa(pkg).type === "git")
52
- return `git-${createHash("sha256").update(pkg).digest("hex")}`;
40
+ const result = npa(pkg);
41
+ if (result.type === "git") {
42
+ return {
43
+ type: "git",
44
+ ...(result.name ? { name: result.name } : {}),
45
+ slug: gitSlug(pkg),
46
+ mutable: !isCommit(result.gitCommittish),
47
+ };
48
+ }
49
+ if (!result.name || !["version", "range", "tag"].includes(result.type))
50
+ return;
51
+ return {
52
+ type: "registry",
53
+ name: result.name,
54
+ spec: result.raw === result.name ? "latest" : result.rawSpec,
55
+ mutable: result.type !== "version",
56
+ };
53
57
  }
54
58
  catch {
55
- // Preserve the existing fallback for invalid and non-registry package strings.
59
+ return;
56
60
  }
61
+ }
62
+ function key(pkg, target) {
63
+ if (target?.type === "git")
64
+ return `git-${target.slug}-${createHash("sha256").update(pkg).digest("hex").slice(0, 12)}`;
65
+ if (target?.type === "registry")
66
+ return sanitize(`${target.name}@${target.spec}`);
57
67
  return sanitize(pkg);
58
68
  }
69
+ function gitSlug(pkg) {
70
+ const target = (() => {
71
+ try {
72
+ return decodeURIComponent(pkg.split("#")[0]);
73
+ }
74
+ catch {
75
+ return pkg.split("#")[0];
76
+ }
77
+ })();
78
+ return (target
79
+ .replace(/\.git$/i, "")
80
+ .split(/[/:\\]/)
81
+ .at(-1)
82
+ ?.replace(/[^a-zA-Z0-9._-]+/g, "-")
83
+ .replace(/^-+|-+$/g, "") || "repository");
84
+ }
59
85
  const resolveEntryPoint = (name, dir, subpaths = [""]) => {
60
86
  const entrypoint = subpaths
61
87
  .map((subpath) => {
@@ -74,16 +100,34 @@ const resolveEntryPoint = (name, dir, subpaths = [""]) => {
74
100
  };
75
101
  const PackageJson = Schema.Struct({
76
102
  dependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
103
+ version: Schema.optional(Schema.String),
104
+ });
105
+ const PackageLock = Schema.Struct({
106
+ packages: Schema.optional(Schema.Record(Schema.String, Schema.Struct({ resolved: Schema.optional(Schema.String) }))),
77
107
  });
108
+ const retention = 7 * 24 * 60 * 60 * 1_000;
109
+ const stagingRetention = 60 * 60 * 1_000;
78
110
  const layer = Layer.effect(Service, Effect.gen(function* () {
79
111
  const afs = yield* FSUtil.Service;
80
112
  const global = yield* Global.Service;
81
113
  const fs = yield* FileSystem.FileSystem;
82
114
  const flock = yield* EffectFlock.Service;
83
- const directory = (pkg) => Effect.map(Effect.promise(() => cacheKey(pkg)), (key) => path.join(global.cache, "packages", key));
84
- const installedName = Effect.fnUntraced(function* (pkg, dir, parsedName) {
85
- if (parsedName)
86
- return parsedName;
115
+ const directory = (pkg, target) => path.join(global.cache, "npm", key(pkg, target));
116
+ const generations = Effect.fnUntraced(function* (dir) {
117
+ return yield* fs.readDirectory(dir).pipe(Effect.orElseSucceed(() => []), Effect.map((entries) => entries
118
+ .filter((entry) => /^\d+$/.test(entry))
119
+ .toSorted((a, b) => Number(a) - Number(b))));
120
+ });
121
+ const current = Effect.fnUntraced(function* (dir) {
122
+ const latest = (yield* generations(dir)).at(-1);
123
+ return latest ? path.join(dir, latest) : undefined;
124
+ });
125
+ const mkdir = (dir) => fs.makeDirectory(dir, { recursive: true }).pipe(Effect.mapError((cause) => new InstallFailedError({ dir, cause })));
126
+ const remove = (target, dir) => fs.remove(target, { recursive: true, force: true }).pipe(Effect.mapError((cause) => new InstallFailedError({ dir, cause })));
127
+ const rename = (from, to, dir) => fs.rename(from, to).pipe(Effect.mapError((cause) => new InstallFailedError({ dir, cause })));
128
+ const installedName = Effect.fnUntraced(function* (pkg, dir, target) {
129
+ if (target?.name)
130
+ return target.name;
87
131
  const manifest = yield* afs
88
132
  .readJson(path.join(dir, "package.json"))
89
133
  .pipe(Effect.flatMap(Schema.decodeUnknownEffect(PackageJson)), Effect.option);
@@ -94,12 +138,40 @@ const layer = Layer.effect(Service, Effect.gen(function* () {
94
138
  }
95
139
  return pkg;
96
140
  });
97
- const refreshed = new Set();
141
+ const installedRevision = Effect.fnUntraced(function* (root, name, target) {
142
+ const dir = path.join(root, "node_modules", name);
143
+ if (target.type === "registry") {
144
+ const manifest = yield* afs
145
+ .readJson(path.join(dir, "package.json"))
146
+ .pipe(Effect.flatMap(Schema.decodeUnknownEffect(PackageJson)), Effect.option);
147
+ return Option.isSome(manifest) ? manifest.value.version : undefined;
148
+ }
149
+ for (const file of [path.join(root, "package-lock.json"), path.join(root, "node_modules", ".package-lock.json")]) {
150
+ const lock = yield* afs
151
+ .readJson(file)
152
+ .pipe(Effect.flatMap(Schema.decodeUnknownEffect(PackageLock)), Effect.option);
153
+ const revision = gitRevision(Option.isSome(lock) ? lock.value.packages?.[`node_modules/${name}`]?.resolved : undefined);
154
+ if (revision)
155
+ return revision;
156
+ }
157
+ });
158
+ const entry = Effect.fnUntraced(function* (root, name, dir, target, subpaths) {
159
+ const manifest = yield* afs
160
+ .readJson(path.join(dir, "package.json"))
161
+ .pipe(Effect.flatMap(Schema.decodeUnknownEffect(PackageJson)), Effect.option);
162
+ const manifestVersion = Option.isSome(manifest) ? manifest.value.version : undefined;
163
+ const revision = target ? (yield* installedRevision(root, name, target)) ?? manifestVersion : undefined;
164
+ const version = target?.type === "git" ? revision : manifestVersion;
165
+ return {
166
+ ...resolveEntryPoint(name, dir, subpaths),
167
+ ...(version ? { version } : {}),
168
+ ...(revision ? { revision } : {}),
169
+ };
170
+ });
98
171
  const reify = (input) => Effect.gen(function* () {
99
- yield* flock.acquire(`npm-install:${input.dir}`);
100
172
  const { Arborist } = yield* Effect.promise(() => import("@npmcli/arborist"));
101
173
  const add = input.add ?? [];
102
- const npmOptions = yield* NpmConfig.load(input.dir);
174
+ const npmOptions = yield* NpmConfig.load(input.config ?? input.dir);
103
175
  const options = input.update ? { ...npmOptions, preferOnline: true, noGitRevCache: true } : npmOptions;
104
176
  const arborist = new Arborist({
105
177
  ...options,
@@ -126,104 +198,154 @@ const layer = Layer.effect(Service, Effect.gen(function* () {
126
198
  }).pipe(Effect.withSpan("Npm.reify", {
127
199
  attributes: input,
128
200
  }));
129
- const add = Effect.fn("Npm.add")(function* (pkg, options) {
130
- const { default: npa } = yield* Effect.promise(() => import("npm-package-arg"));
131
- const parsed = (() => {
132
- try {
133
- return npa(pkg);
134
- }
135
- catch {
136
- return undefined;
137
- }
138
- })();
139
- const parsedName = parsed?.name ?? undefined;
140
- const dir = yield* directory(pkg);
141
- const name = yield* installedName(pkg, dir, parsedName);
142
- const cached = yield* afs.existsSafe(path.join(dir, "node_modules", name));
143
- const refresh = options?.refresh && isMutable(parsed) && !refreshed.has(pkg);
144
- if (refresh) {
145
- refreshed.add(pkg);
146
- if (cached)
147
- yield* reify({ dir, add: [pkg], update: true }).pipe(Effect.catchCause(() => Effect.logWarning("failed to refresh cached package; using installed version")));
201
+ const install = Effect.fnUntraced(function* (pkg, target, dir, subpaths, update) {
202
+ yield* flock.acquire(`npm-install:${dir}`);
203
+ const active = yield* current(dir);
204
+ const name = yield* installedName(pkg, active ?? dir, target);
205
+ if (active && !update && (yield* afs.existsSafe(path.join(active, "node_modules", name)))) {
206
+ return yield* entry(active, name, path.join(active, "node_modules", name), target, subpaths);
148
207
  }
149
- if (cached) {
150
- return resolveEntryPoint(name, path.join(dir, "node_modules", name), options?.subpaths);
151
- }
152
- const tree = yield* reify({ dir, add: [pkg] });
153
- if (isMutable(parsed))
154
- refreshed.add(pkg);
155
- const first = tree.edgesOut.values().next().value?.to;
156
- if (!first) {
157
- const installed = yield* installedName(pkg, dir, parsedName);
158
- const result = resolveEntryPoint(installed, path.join(dir, "node_modules", installed), options?.subpaths);
159
- if (result.entrypoint)
160
- return result;
161
- return yield* new InstallFailedError({ add: [pkg], dir });
208
+ yield* mkdir(dir);
209
+ const startedAt = yield* Clock.currentTimeMillis;
210
+ const staging = path.join(dir, `.staging-${startedAt}-${randomUUID()}`);
211
+ const staged = yield* Effect.gen(function* () {
212
+ const tree = yield* reify({ dir: staging, config: dir, add: [pkg], update });
213
+ const installed = tree.edgesOut.values().next().value?.to;
214
+ const installedNameValue = installed?.name ?? (yield* installedName(pkg, staging, target));
215
+ const result = yield* entry(staging, installedNameValue, installed?.path ?? path.join(staging, "node_modules", installedNameValue), target, subpaths);
216
+ if (!installed && !result.entrypoint)
217
+ return yield* new InstallFailedError({ add: [pkg], dir: staging });
218
+ return { name: installedNameValue, result };
219
+ }).pipe(Effect.onError(() => remove(staging, dir).pipe(Effect.ignore)));
220
+ if (active) {
221
+ const activeEntry = yield* entry(active, name, path.join(active, "node_modules", name), target, subpaths);
222
+ if (activeEntry.revision && activeEntry.revision === staged.result.revision) {
223
+ yield* remove(staging, dir);
224
+ return activeEntry;
225
+ }
162
226
  }
163
- return resolveEntryPoint(first.name, first.path, options?.subpaths);
227
+ const completedAt = yield* Clock.currentTimeMillis;
228
+ const newest = Number((yield* generations(dir)).at(-1) ?? 0);
229
+ const generation = path.join(dir, String(Math.max(completedAt, newest + 1)));
230
+ yield* rename(staging, generation, dir);
231
+ return yield* entry(generation, staged.name, path.join(generation, "node_modules", staged.name), target, subpaths);
232
+ });
233
+ const collect = Effect.fnUntraced(function* (dir) {
234
+ const now = yield* Clock.currentTimeMillis;
235
+ const completed = yield* generations(dir);
236
+ const keep = new Set(completed.slice(-2));
237
+ const entries = yield* fs.readDirectory(dir).pipe(Effect.orElseSucceed(() => []));
238
+ yield* Effect.forEach(entries, (name) => {
239
+ const timestamp = /^\d+$/.test(name)
240
+ ? Number(name)
241
+ : Number(name.match(/^\.staging-(\d+)-/)?.[1] ?? Number.NaN);
242
+ const maximumAge = name.startsWith(".staging-") ? stagingRetention : retention;
243
+ if (!Number.isFinite(timestamp) || keep.has(name) || now - timestamp <= maximumAge)
244
+ return Effect.void;
245
+ return remove(path.join(dir, name), dir).pipe(Effect.catchCause((cause) => Effect.logWarning("failed to remove stale npm generation", { dir, name, cause })));
246
+ }, { concurrency: "unbounded", discard: true });
247
+ });
248
+ const add = Effect.fn("Npm.add")(function* (pkg, options) {
249
+ const target = yield* Effect.promise(() => parse(pkg));
250
+ const dir = directory(pkg, target);
251
+ return yield* install(pkg, target, dir, options?.subpaths, false);
164
252
  }, Effect.scoped);
165
253
  const resolve = Effect.fn("Npm.resolve")(function* (pkg, options) {
166
- const { default: npa } = yield* Effect.promise(() => import("npm-package-arg"));
167
- const parsedName = (() => {
168
- try {
169
- return npa(pkg).name ?? undefined;
170
- }
171
- catch {
172
- return undefined;
173
- }
174
- })();
175
- const root = yield* directory(pkg);
176
- const name = yield* installedName(pkg, root, parsedName);
177
- const dir = path.join(root, "node_modules", name);
254
+ const target = yield* Effect.promise(() => parse(pkg));
255
+ const root = directory(pkg, target);
256
+ const generation = yield* current(root);
257
+ const name = yield* installedName(pkg, generation ?? root, target);
258
+ const dir = path.join(generation ?? root, "node_modules", name);
178
259
  if (!(yield* afs.existsSafe(dir)))
179
260
  return { directory: dir };
180
- return resolveEntryPoint(name, dir, options?.subpaths);
261
+ return yield* entry(generation ?? root, name, dir, target, options?.subpaths);
262
+ });
263
+ const check = Effect.fn("Npm.check")(function* (pkg) {
264
+ const target = yield* Effect.promise(() => parse(pkg));
265
+ const root = directory(pkg, target);
266
+ if (!target)
267
+ return yield* new InstallFailedError({
268
+ dir: root,
269
+ cause: new Error("Package checks only support registry and Git package specs"),
270
+ });
271
+ if (!target.mutable)
272
+ return false;
273
+ const generation = yield* current(root);
274
+ const name = yield* installedName(pkg, generation ?? root, target);
275
+ const installed = generation ? yield* installedRevision(generation, name, target) : undefined;
276
+ if (!installed)
277
+ return yield* new InstallFailedError({ dir: root, cause: new Error(`Package is not installed: ${pkg}`) });
278
+ const { manifest, resolve } = yield* Effect.promise(() => import("pacote"));
279
+ const options = { ...(yield* NpmConfig.load(root)), preferOnline: true, noGitRevCache: true, ignoreScripts: true };
280
+ const available = yield* Effect.tryPromise({
281
+ try: async () => target.type === "git" ? gitRevision(await resolve(pkg, options)) : (await manifest(pkg, options)).version,
282
+ catch: (cause) => new InstallFailedError({ dir: root, cause }),
283
+ });
284
+ if (!available)
285
+ return yield* new InstallFailedError({ dir: root, cause: new Error(`Package revision not found: ${pkg}`) });
286
+ return installed !== available;
181
287
  });
288
+ const update = Effect.fn("Npm.update")(function* (pkg, options) {
289
+ const target = yield* Effect.promise(() => parse(pkg));
290
+ const dir = directory(pkg, target);
291
+ if (!target)
292
+ return yield* new InstallFailedError({
293
+ dir,
294
+ cause: new Error("Package updates only support registry and Git package specs"),
295
+ });
296
+ if (!target.mutable)
297
+ return yield* add(pkg, options);
298
+ const installed = yield* install(pkg, target, dir, options?.subpaths, true);
299
+ yield* collect(dir);
300
+ return installed;
301
+ }, Effect.scoped);
182
302
  const which = Effect.fn("Npm.which")(function* (pkg, bin) {
183
- const dir = yield* directory(pkg);
184
- const binDir = path.join(dir, "node_modules", ".bin");
185
- const pick = Effect.fnUntraced(function* () {
303
+ const target = yield* Effect.promise(() => parse(pkg));
304
+ const root = directory(pkg, target);
305
+ const pick = Effect.fnUntraced(function* (dir) {
306
+ const binDir = path.join(dir, "node_modules", ".bin");
186
307
  const files = yield* fs.readDirectory(binDir).pipe(Effect.orElseSucceed(() => []));
187
308
  if (files.length === 0)
188
309
  return Option.none();
189
310
  // Caller picked a specific bin (e.g. pyright exposes both `pyright` and
190
311
  // `pyright-langserver`); trust the hint if the package provides it.
191
312
  if (bin)
192
- return files.includes(bin) ? Option.some(bin) : Option.none();
313
+ return files.includes(bin) ? Option.some(path.join(binDir, bin)) : Option.none();
193
314
  if (files.length === 1)
194
- return Option.some(files[0]);
195
- const pkgJson = yield* afs.readJson(path.join(dir, "node_modules", pkg, "package.json")).pipe(Effect.option);
315
+ return Option.some(path.join(binDir, files[0]));
316
+ const packageName = target?.name ?? pkg;
317
+ const pkgJson = yield* afs.readJson(path.join(dir, "node_modules", packageName, "package.json")).pipe(Effect.option);
196
318
  if (Option.isSome(pkgJson)) {
197
319
  const parsed = pkgJson.value;
198
320
  if (parsed?.bin) {
199
- const unscoped = pkg.startsWith("@") ? pkg.split("/")[1] : pkg;
321
+ const unscoped = packageName.startsWith("@") ? packageName.split("/")[1] : packageName;
200
322
  const parsedBin = parsed.bin;
201
323
  if (typeof parsedBin === "string")
202
- return Option.some(unscoped);
324
+ return Option.some(path.join(binDir, unscoped));
203
325
  const keys = Object.keys(parsedBin);
204
- if (keys.length === 1)
205
- return Option.some(keys[0]);
206
- return parsedBin[unscoped] ? Option.some(unscoped) : Option.some(keys[0]);
326
+ const selected = parsedBin[unscoped] ? unscoped : keys[0];
327
+ return selected ? Option.some(path.join(binDir, selected)) : Option.none();
207
328
  }
208
329
  }
209
- return Option.some(files[0]);
330
+ return Option.some(path.join(binDir, files[0]));
210
331
  });
211
332
  return Option.getOrUndefined(yield* Effect.gen(function* () {
212
- const bin = yield* pick();
213
- if (Option.isSome(bin)) {
214
- return Option.some(path.join(binDir, bin.value));
215
- }
216
- yield* fs.remove(path.join(dir, "package-lock.json")).pipe(Effect.orElseSucceed(() => { }));
333
+ const generation = yield* current(root);
334
+ const selected = generation ? yield* pick(generation) : Option.none();
335
+ if (Option.isSome(selected))
336
+ return selected;
217
337
  yield* add(pkg);
218
- const resolved = yield* pick();
219
- if (Option.isNone(resolved))
338
+ const installed = yield* current(root);
339
+ if (!installed)
220
340
  return Option.none();
221
- return Option.some(path.join(binDir, resolved.value));
341
+ return yield* pick(installed);
222
342
  }).pipe(Effect.scoped, Effect.orElseSucceed(() => Option.none())));
223
343
  });
224
344
  return Service.of({
225
345
  add,
226
346
  resolve,
347
+ check,
348
+ update,
227
349
  which,
228
350
  });
229
351
  }));
@@ -239,15 +361,18 @@ export async function add(...args) {
239
361
  export async function resolve(...args) {
240
362
  return runPromise((svc) => svc.resolve(...args));
241
363
  }
364
+ export async function check(...args) {
365
+ return runPromise((svc) => svc.check(...args));
366
+ }
367
+ export async function update(...args) {
368
+ return runPromise((svc) => svc.update(...args));
369
+ }
242
370
  export async function which(...args) {
243
371
  return runPromise((svc) => svc.which(...args));
244
372
  }
245
- function isMutable(parsed) {
246
- if (!parsed)
247
- return false;
248
- if (["tag", "range"].includes(parsed.type))
249
- return true;
250
- if (parsed.type !== "git")
251
- return false;
252
- return !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i.test(parsed.gitCommittish ?? "");
373
+ function gitRevision(resolved) {
374
+ return resolved?.match(/#([a-f0-9]{40}|[a-f0-9]{64})(?=::|$)/i)?.[1];
375
+ }
376
+ function isCommit(value) {
377
+ return /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i.test(value ?? "");
253
378
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@opencode-ai/util",
4
- "version": "0.0.0-dev-18789",
4
+ "version": "0.0.0-dev-18793",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -60,6 +60,7 @@
60
60
  "mime-types": "3.0.2",
61
61
  "minimatch": "10.2.5",
62
62
  "npm-package-arg": "13.0.2",
63
+ "pacote": "21.5.1",
63
64
  "resolve.exports": "2.0.3"
64
65
  },
65
66
  "devDependencies": {
@@ -69,6 +70,7 @@
69
70
  "@types/node": "24.12.2",
70
71
  "@types/npm-package-arg": "6.1.4",
71
72
  "@types/npmcli__arborist": "6.3.3",
73
+ "@types/pacote": "11.1.8",
72
74
  "@typescript/native-preview": "7.0.0-dev.20251207.1"
73
75
  }
74
76
  }