@opencode-ai/util 0.0.0-dev-18909 → 0.0.0-dev-18952

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.
package/dist/npm.d.ts CHANGED
@@ -9,23 +9,17 @@ declare const InstallFailedError_base: Schema.Class<InstallFailedError, Schema.T
9
9
  }>, import("effect/Cause").YieldableError>;
10
10
  export declare class InstallFailedError extends InstallFailedError_base {
11
11
  }
12
- export interface EntryPoint {
12
+ export interface Package {
13
13
  readonly directory: string;
14
- readonly entrypoint?: string;
14
+ readonly name: string;
15
15
  readonly version?: string;
16
16
  readonly revision?: string;
17
17
  }
18
18
  export interface Interface {
19
- readonly add: (pkg: string, options?: {
20
- readonly subpaths?: readonly string[];
21
- }) => Effect.Effect<EntryPoint, InstallFailedError | EffectFlock.LockError>;
22
- readonly resolve: (pkg: string, options?: {
23
- readonly subpaths?: readonly string[];
24
- }) => Effect.Effect<EntryPoint>;
19
+ readonly add: (pkg: string) => Effect.Effect<Package, InstallFailedError | EffectFlock.LockError>;
20
+ readonly resolve: (pkg: string) => Effect.Effect<Package>;
25
21
  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>;
22
+ readonly update: (pkg: string) => Effect.Effect<Package, InstallFailedError | EffectFlock.LockError>;
29
23
  readonly which: (pkg: string, bin?: string) => Effect.Effect<string | undefined>;
30
24
  }
31
25
  declare const Service_base: Context.ServiceClass<Service, "@opencode/Npm", Interface>;
@@ -36,8 +30,8 @@ export declare function isRegistryPackage(pkg: string): Promise<boolean>;
36
30
  export declare function isInstallablePackage(pkg: string): Promise<boolean>;
37
31
  export declare function cacheKey(pkg: string): Promise<string>;
38
32
  export declare const node: LayerNode.Provider<Service, never, LayerNode.Tag<"global">>;
39
- export declare function add(...args: Parameters<Interface["add"]>): Promise<EntryPoint>;
40
- export declare function resolve(...args: Parameters<Interface["resolve"]>): Promise<EntryPoint>;
33
+ export declare function add(...args: Parameters<Interface["add"]>): Promise<Package>;
34
+ export declare function resolve(...args: Parameters<Interface["resolve"]>): Promise<Package>;
41
35
  export declare function check(...args: Parameters<Interface["check"]>): Promise<boolean>;
42
- export declare function update(...args: Parameters<Interface["update"]>): Promise<EntryPoint>;
36
+ export declare function update(...args: Parameters<Interface["update"]>): Promise<Package>;
43
37
  export declare function which(...args: Parameters<Interface["which"]>): Promise<string | undefined>;
package/dist/npm.js CHANGED
@@ -10,7 +10,6 @@ import { filesystem } from "./effect/app-node-platform.js";
10
10
  import { LayerNode } from "./effect/layer-node.js";
11
11
  import { makeRuntime } from "./effect/runtime.js";
12
12
  import { NpmConfig } from "./npm-config.js";
13
- import { resolveModule } from "#runtime-import";
14
13
  export class InstallFailedError extends Schema.TaggedError()("NpmInstallFailedError", {
15
14
  add: Schema.Array(Schema.String).pipe(Schema.optional),
16
15
  dir: Schema.String,
@@ -82,22 +81,6 @@ function gitSlug(pkg) {
82
81
  ?.replace(/[^a-zA-Z0-9._-]+/g, "-")
83
82
  .replace(/^-+|-+$/g, "") || "repository");
84
83
  }
85
- const resolveEntryPoint = (name, dir, subpaths = [""]) => {
86
- const entrypoint = subpaths
87
- .map((subpath) => {
88
- try {
89
- return resolveModule([name, subpath].filter(Boolean).join("/"), dir);
90
- }
91
- catch {
92
- return undefined;
93
- }
94
- })
95
- .find((entrypoint) => entrypoint !== undefined);
96
- return {
97
- directory: dir,
98
- entrypoint,
99
- };
100
- };
101
84
  const PackageJson = Schema.Struct({
102
85
  dependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
103
86
  version: Schema.optional(Schema.String),
@@ -155,7 +138,7 @@ const layer = Layer.effect(Service, Effect.gen(function* () {
155
138
  return revision;
156
139
  }
157
140
  });
158
- const entry = Effect.fnUntraced(function* (root, name, dir, target, subpaths) {
141
+ const metadata = Effect.fnUntraced(function* (root, name, dir, target) {
159
142
  const manifest = yield* afs
160
143
  .readJson(path.join(dir, "package.json"))
161
144
  .pipe(Effect.flatMap(Schema.decodeUnknownEffect(PackageJson)), Effect.option);
@@ -163,7 +146,8 @@ const layer = Layer.effect(Service, Effect.gen(function* () {
163
146
  const revision = target ? (yield* installedRevision(root, name, target)) ?? manifestVersion : undefined;
164
147
  const version = target?.type === "git" ? revision : manifestVersion;
165
148
  return {
166
- ...resolveEntryPoint(name, dir, subpaths),
149
+ directory: dir,
150
+ name,
167
151
  ...(version ? { version } : {}),
168
152
  ...(revision ? { revision } : {}),
169
153
  };
@@ -198,32 +182,36 @@ const layer = Layer.effect(Service, Effect.gen(function* () {
198
182
  }).pipe(Effect.withSpan("Npm.reify", {
199
183
  attributes: input,
200
184
  }));
201
- const install = Effect.fnUntraced(function* (pkg, target, dir, subpaths, update) {
185
+ const install = Effect.fnUntraced(function* (pkg, target, dir, update) {
202
186
  yield* flock.acquire(`npm-install:${dir}`);
203
187
  const active = yield* current(dir);
204
188
  const name = yield* installedName(pkg, active ?? dir, target);
205
189
  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);
190
+ return yield* metadata(active, name, path.join(active, "node_modules", name), target);
207
191
  }
208
192
  yield* mkdir(dir);
209
193
  const startedAt = yield* Clock.currentTimeMillis;
210
- const staging = path.join(dir, `.staging-${startedAt}-${randomUUID()}`);
194
+ // Arborist keys lockfile entries relative to the root's real path. When the cache
195
+ // directory is reached through a symlink (macOS `/var` → `/private/var`, a linked
196
+ // XDG cache), the keys become `../../…` paths that installedRevision never finds,
197
+ // so Git checks report "not installed" and updates go undetected. Stage under the
198
+ // resolved directory so the root path and real path agree.
199
+ const root = yield* fs.realPath(dir).pipe(Effect.mapError((cause) => new InstallFailedError({ dir, cause })));
200
+ const staging = path.join(root, `.staging-${startedAt}-${randomUUID()}`);
211
201
  const staged = yield* Effect.gen(function* () {
212
202
  const tree = yield* reify({ dir: staging, config: dir, add: [pkg], update });
213
203
  const installed = tree.edgesOut.values().next().value?.to;
214
204
  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,
216
- // Resolve installed entrypoints after rename so Bun cannot hold the staging directory open on Windows.
217
- installed ? [] : subpaths);
218
- if (!installed && !result.entrypoint)
205
+ const result = yield* metadata(staging, installedNameValue, installed?.path ?? path.join(staging, "node_modules", installedNameValue), target);
206
+ if (!installed && !(yield* afs.isDir(result.directory)))
219
207
  return yield* new InstallFailedError({ add: [pkg], dir: staging });
220
208
  const links = process.platform === "win32"
221
209
  ? Array.from(tree.inventory.values()).filter((node) => node.isLink && FSUtil.contains(staging, node.path) && FSUtil.contains(staging, node.realpath))
222
210
  : [];
223
- return { name: installedNameValue, result, links };
211
+ return { result, links };
224
212
  }).pipe(Effect.onError(() => remove(staging, dir).pipe(Effect.ignore)));
225
213
  if (active) {
226
- const activeEntry = yield* entry(active, name, path.join(active, "node_modules", name), target, subpaths);
214
+ const activeEntry = yield* metadata(active, name, path.join(active, "node_modules", name), target);
227
215
  if (activeEntry.revision && activeEntry.revision === staged.result.revision) {
228
216
  yield* remove(staging, dir);
229
217
  return activeEntry;
@@ -244,7 +232,7 @@ const layer = Layer.effect(Service, Effect.gen(function* () {
244
232
  }), { discard: true }).pipe(Effect.onError(() => remove(staging, dir).pipe(Effect.ignore)));
245
233
  }
246
234
  yield* rename(staging, generation, dir);
247
- return yield* entry(generation, staged.name, path.join(generation, "node_modules", staged.name), target, subpaths);
235
+ return { ...staged.result, directory: path.join(generation, "node_modules", staged.result.name) };
248
236
  });
249
237
  const collect = Effect.fnUntraced(function* (dir) {
250
238
  const now = yield* Clock.currentTimeMillis;
@@ -261,20 +249,20 @@ const layer = Layer.effect(Service, Effect.gen(function* () {
261
249
  return remove(path.join(dir, name), dir).pipe(Effect.catchCause((cause) => Effect.logWarning("failed to remove stale npm generation", { dir, name, cause })));
262
250
  }, { concurrency: "unbounded", discard: true });
263
251
  });
264
- const add = Effect.fn("Npm.add")(function* (pkg, options) {
252
+ const add = Effect.fn("Npm.add")(function* (pkg) {
265
253
  const target = yield* Effect.promise(() => parse(pkg));
266
254
  const dir = directory(pkg, target);
267
- return yield* install(pkg, target, dir, options?.subpaths, false);
255
+ return yield* install(pkg, target, dir, false);
268
256
  }, Effect.scoped);
269
- const resolve = Effect.fn("Npm.resolve")(function* (pkg, options) {
257
+ const resolve = Effect.fn("Npm.resolve")(function* (pkg) {
270
258
  const target = yield* Effect.promise(() => parse(pkg));
271
259
  const root = directory(pkg, target);
272
260
  const generation = yield* current(root);
273
261
  const name = yield* installedName(pkg, generation ?? root, target);
274
262
  const dir = path.join(generation ?? root, "node_modules", name);
275
263
  if (!(yield* afs.existsSafe(dir)))
276
- return { directory: dir };
277
- return yield* entry(generation ?? root, name, dir, target, options?.subpaths);
264
+ return { directory: dir, name };
265
+ return yield* metadata(generation ?? root, name, dir, target);
278
266
  });
279
267
  const check = Effect.fn("Npm.check")(function* (pkg) {
280
268
  const target = yield* Effect.promise(() => parse(pkg));
@@ -301,7 +289,7 @@ const layer = Layer.effect(Service, Effect.gen(function* () {
301
289
  return yield* new InstallFailedError({ dir: root, cause: new Error(`Package revision not found: ${pkg}`) });
302
290
  return installed !== available;
303
291
  });
304
- const update = Effect.fn("Npm.update")(function* (pkg, options) {
292
+ const update = Effect.fn("Npm.update")(function* (pkg) {
305
293
  const target = yield* Effect.promise(() => parse(pkg));
306
294
  const dir = directory(pkg, target);
307
295
  if (!target)
@@ -310,8 +298,8 @@ const layer = Layer.effect(Service, Effect.gen(function* () {
310
298
  cause: new Error("Package updates only support registry and Git package specs"),
311
299
  });
312
300
  if (!target.mutable)
313
- return yield* add(pkg, options);
314
- const installed = yield* install(pkg, target, dir, options?.subpaths, true);
301
+ return yield* add(pkg);
302
+ const installed = yield* install(pkg, target, dir, true);
315
303
  yield* collect(dir);
316
304
  return installed;
317
305
  }, Effect.scoped);
@@ -1,6 +1,8 @@
1
+ import { pathToFileURL } from "node:url";
1
2
  export function importModule(specifier) {
2
3
  return import(specifier);
3
4
  }
4
5
  export function resolveModule(specifier, directory) {
5
- return import.meta.resolve(specifier, directory);
6
+ const resolved = Bun.resolveSync(specifier, directory);
7
+ return resolved.startsWith("node:") ? resolved : pathToFileURL(resolved).href;
6
8
  }
@@ -1,19 +1,8 @@
1
1
  import { Script, constants } from "node:vm";
2
- import { createRequire, registerHooks } from "node:module";
2
+ import { registerHooks } from "node:module";
3
+ import { statSync } from "node:fs";
3
4
  import path from "node:path";
4
5
  import { pathToFileURL } from "node:url";
5
- import { resolve } from "resolve.exports";
6
- let conditions = [];
7
- const conditionHooks = registerHooks({
8
- resolve(specifier, context, nextResolve) {
9
- conditions = context.conditions;
10
- return nextResolve(specifier, context);
11
- },
12
- });
13
- await new Script('import("node:module")', {
14
- importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,
15
- }).runInThisContext();
16
- conditionHooks.deregister();
17
6
  export async function importModule(specifier) {
18
7
  const imported = (await new Script(`import(${JSON.stringify(specifier)})`, {
19
8
  importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,
@@ -27,10 +16,47 @@ export async function importModule(specifier) {
27
16
  return Object.assign({}, module, exports);
28
17
  }
29
18
  export function resolveModule(specifier, directory) {
30
- const pkg = createRequire(import.meta.url)(path.join(directory, "package.json"));
31
- const target = resolve(pkg, specifier, { conditions, unsafe: true })?.[0];
32
- if (target)
33
- return pathToFileURL(path.resolve(directory, target)).href;
34
- const legacyTarget = specifier === pkg.name ? directory : path.resolve(directory, specifier.slice(pkg.name.length + 1));
35
- return pathToFileURL(createRequire(path.join(directory, "package.json")).resolve(legacyTarget)).href;
19
+ // Node only accepts import.meta.resolve's parent URL behind an experimental
20
+ // flag. Scope this synchronous resolution to the caller's package directory
21
+ // through the supported resolver hook instead.
22
+ const hook = registerHooks({
23
+ resolve(specifier, context, nextResolve) {
24
+ return nextResolve(specifier, { ...context, parentURL: pathToFileURL(path.join(directory, "package.json")).href });
25
+ },
26
+ });
27
+ try {
28
+ const resolve = (target) => {
29
+ const resolved = import.meta.resolve(path.isAbsolute(target) ? pathToFileURL(target).href : target);
30
+ if (resolved.startsWith("file:"))
31
+ statSync(new URL(resolved));
32
+ return resolved;
33
+ };
34
+ try {
35
+ return resolve(specifier);
36
+ }
37
+ catch (error) {
38
+ if (path.extname(specifier) || !missing(error))
39
+ throw error;
40
+ // Node does not infer extensions for local files or legacy package
41
+ // subpaths. Resolve each candidate natively so package exports still apply.
42
+ for (const extension of [".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs", ".cts", ".cjs"]) {
43
+ try {
44
+ return resolve(specifier + extension);
45
+ }
46
+ catch (cause) {
47
+ if (!missing(cause))
48
+ throw cause;
49
+ }
50
+ }
51
+ throw error;
52
+ }
53
+ }
54
+ finally {
55
+ hook.deregister();
56
+ }
57
+ }
58
+ function missing(error) {
59
+ return (error instanceof Error &&
60
+ "code" in error &&
61
+ ["ENOENT", "ENOTDIR", "ERR_MODULE_NOT_FOUND"].includes(String(error.code)));
36
62
  }
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-18909",
4
+ "version": "0.0.0-dev-18952",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -60,8 +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",
64
- "resolve.exports": "2.0.3"
63
+ "pacote": "21.5.1"
65
64
  },
66
65
  "devDependencies": {
67
66
  "@tsconfig/bun": "1.0.9",