@opencode-ai/util 0.0.0-dev-18253 → 0.0.0-dev-18257

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
@@ -27,6 +27,8 @@ export declare class Service extends Service_base {
27
27
  }
28
28
  export declare function sanitize(pkg: string): string;
29
29
  export declare function isRegistryPackage(pkg: string): Promise<boolean>;
30
+ export declare function isInstallablePackage(pkg: string): Promise<boolean>;
31
+ export declare function cacheKey(pkg: string): Promise<string>;
30
32
  export declare const node: LayerNode.Node<Service, never, LayerNode.Tag<"global">>;
31
33
  export declare function add(...args: Parameters<Interface["add"]>): Promise<EntryPoint>;
32
34
  export declare function resolve(...args: Parameters<Interface["resolve"]>): Promise<EntryPoint>;
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";
3
4
  import { Effect, Schema, Context, Layer, Option, FileSystem } from "effect";
4
- import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
5
5
  import { FSUtil } from "./fs-util.js";
6
6
  import { Global } from "./global.js";
7
7
  import { EffectFlock } from "./effect-flock.js";
@@ -35,6 +35,27 @@ export async function isRegistryPackage(pkg) {
35
35
  return false;
36
36
  }
37
37
  }
38
+ 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
+ }
47
+ }
48
+ export async function cacheKey(pkg) {
49
+ const { default: npa } = await import("npm-package-arg");
50
+ try {
51
+ if (npa(pkg).type === "git")
52
+ return `git-${createHash("sha256").update(pkg).digest("hex")}`;
53
+ }
54
+ catch {
55
+ // Preserve the existing fallback for invalid and non-registry package strings.
56
+ }
57
+ return sanitize(pkg);
58
+ }
38
59
  const resolveEntryPoint = (name, dir, subpaths = [""]) => {
39
60
  const entrypoint = subpaths
40
61
  .map((subpath) => {
@@ -51,12 +72,28 @@ const resolveEntryPoint = (name, dir, subpaths = [""]) => {
51
72
  entrypoint,
52
73
  };
53
74
  };
75
+ const PackageJson = Schema.Struct({
76
+ dependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
77
+ });
54
78
  const layer = Layer.effect(Service, Effect.gen(function* () {
55
79
  const afs = yield* FSUtil.Service;
56
80
  const global = yield* Global.Service;
57
81
  const fs = yield* FileSystem.FileSystem;
58
82
  const flock = yield* EffectFlock.Service;
59
- const directory = (pkg) => path.join(global.cache, "packages", sanitize(pkg));
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;
87
+ const manifest = yield* afs
88
+ .readJson(path.join(dir, "package.json"))
89
+ .pipe(Effect.flatMap(Schema.decodeUnknownEffect(PackageJson)), Effect.option);
90
+ if (Option.isSome(manifest)) {
91
+ const name = Object.keys(manifest.value.dependencies ?? {})[0];
92
+ if (name)
93
+ return name;
94
+ }
95
+ return pkg;
96
+ });
60
97
  const reify = (input) => Effect.gen(function* () {
61
98
  yield* flock.acquire(`npm-install:${input.dir}`);
62
99
  const { Arborist } = yield* Effect.promise(() => import("@npmcli/arborist"));
@@ -88,22 +125,24 @@ const layer = Layer.effect(Service, Effect.gen(function* () {
88
125
  }));
89
126
  const add = Effect.fn("Npm.add")(function* (pkg, options) {
90
127
  const { default: npa } = yield* Effect.promise(() => import("npm-package-arg"));
91
- const dir = directory(pkg);
92
- const name = (() => {
128
+ const parsedName = (() => {
93
129
  try {
94
- return npa(pkg).name ?? pkg;
130
+ return npa(pkg).name ?? undefined;
95
131
  }
96
132
  catch {
97
- return pkg;
133
+ return undefined;
98
134
  }
99
135
  })();
136
+ const dir = yield* directory(pkg);
137
+ const name = yield* installedName(pkg, dir, parsedName);
100
138
  if (yield* afs.existsSafe(path.join(dir, "node_modules", name))) {
101
139
  return resolveEntryPoint(name, path.join(dir, "node_modules", name), options?.subpaths);
102
140
  }
103
141
  const tree = yield* reify({ dir, add: [pkg] });
104
142
  const first = tree.edgesOut.values().next().value?.to;
105
143
  if (!first) {
106
- const result = resolveEntryPoint(name, path.join(dir, "node_modules", name), options?.subpaths);
144
+ const installed = yield* installedName(pkg, dir, parsedName);
145
+ const result = resolveEntryPoint(installed, path.join(dir, "node_modules", installed), options?.subpaths);
107
146
  if (result.entrypoint)
108
147
  return result;
109
148
  return yield* new InstallFailedError({ add: [pkg], dir });
@@ -112,21 +151,23 @@ const layer = Layer.effect(Service, Effect.gen(function* () {
112
151
  }, Effect.scoped);
113
152
  const resolve = Effect.fn("Npm.resolve")(function* (pkg, options) {
114
153
  const { default: npa } = yield* Effect.promise(() => import("npm-package-arg"));
115
- const name = (() => {
154
+ const parsedName = (() => {
116
155
  try {
117
- return npa(pkg).name ?? pkg;
156
+ return npa(pkg).name ?? undefined;
118
157
  }
119
158
  catch {
120
- return pkg;
159
+ return undefined;
121
160
  }
122
161
  })();
123
- const dir = path.join(directory(pkg), "node_modules", name);
162
+ const root = yield* directory(pkg);
163
+ const name = yield* installedName(pkg, root, parsedName);
164
+ const dir = path.join(root, "node_modules", name);
124
165
  if (!(yield* afs.existsSafe(dir)))
125
166
  return { directory: dir };
126
167
  return resolveEntryPoint(name, dir, options?.subpaths);
127
168
  });
128
169
  const which = Effect.fn("Npm.which")(function* (pkg, bin) {
129
- const dir = directory(pkg);
170
+ const dir = yield* directory(pkg);
130
171
  const binDir = path.join(dir, "node_modules", ".bin");
131
172
  const pick = Effect.fnUntraced(function* () {
132
173
  const files = yield* fs.readDirectory(binDir).pipe(Effect.orElseSucceed(() => []));
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-18253",
4
+ "version": "0.0.0-dev-18257",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -45,6 +45,7 @@
45
45
  "dependencies": {
46
46
  "@effect/opentelemetry": "4.0.0-rc.111",
47
47
  "@effect/platform-node": "4.0.0-rc.111",
48
+ "@effect/platform-node-shared": "4.0.0-rc.111",
48
49
  "@npmcli/arborist": "9.4.0",
49
50
  "@npmcli/config": "10.8.1",
50
51
  "@opentelemetry/api": "1.9.0",