@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 +8 -14
- package/dist/npm.js +25 -37
- package/dist/runtime/import.bun.js +3 -1
- package/dist/runtime/import.node.js +45 -19
- package/package.json +2 -3
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
|
|
12
|
+
export interface Package {
|
|
13
13
|
readonly directory: string;
|
|
14
|
-
readonly
|
|
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,
|
|
20
|
-
|
|
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,
|
|
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<
|
|
40
|
-
export declare function resolve(...args: Parameters<Interface["resolve"]>): Promise<
|
|
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<
|
|
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
|
|
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
|
-
|
|
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,
|
|
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*
|
|
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
|
-
|
|
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*
|
|
216
|
-
|
|
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 {
|
|
211
|
+
return { result, links };
|
|
224
212
|
}).pipe(Effect.onError(() => remove(staging, dir).pipe(Effect.ignore)));
|
|
225
213
|
if (active) {
|
|
226
|
-
const activeEntry = yield*
|
|
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
|
|
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
|
|
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,
|
|
255
|
+
return yield* install(pkg, target, dir, false);
|
|
268
256
|
}, Effect.scoped);
|
|
269
|
-
const resolve = Effect.fn("Npm.resolve")(function* (pkg
|
|
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*
|
|
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
|
|
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
|
|
314
|
-
const installed = yield* install(pkg, target, dir,
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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-
|
|
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",
|