@opencode-ai/util 0.0.0-beta-18219 → 0.0.0-beta-18269
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 +3 -0
- package/dist/npm.js +76 -15
- package/package.json +2 -1
package/dist/npm.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface EntryPoint {
|
|
|
16
16
|
export interface Interface {
|
|
17
17
|
readonly add: (pkg: string, options?: {
|
|
18
18
|
readonly subpaths?: readonly string[];
|
|
19
|
+
readonly refresh?: boolean;
|
|
19
20
|
}) => Effect.Effect<EntryPoint, InstallFailedError | EffectFlock.LockError>;
|
|
20
21
|
readonly resolve: (pkg: string, options?: {
|
|
21
22
|
readonly subpaths?: readonly string[];
|
|
@@ -27,6 +28,8 @@ export declare class Service extends Service_base {
|
|
|
27
28
|
}
|
|
28
29
|
export declare function sanitize(pkg: string): string;
|
|
29
30
|
export declare function isRegistryPackage(pkg: string): Promise<boolean>;
|
|
31
|
+
export declare function isInstallablePackage(pkg: string): Promise<boolean>;
|
|
32
|
+
export declare function cacheKey(pkg: string): Promise<string>;
|
|
30
33
|
export declare const node: LayerNode.Node<Service, never, LayerNode.Tag<"global">>;
|
|
31
34
|
export declare function add(...args: Parameters<Interface["add"]>): Promise<EntryPoint>;
|
|
32
35
|
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,19 +72,37 @@ 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",
|
|
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
|
+
});
|
|
97
|
+
const refreshed = new Set();
|
|
60
98
|
const reify = (input) => Effect.gen(function* () {
|
|
61
99
|
yield* flock.acquire(`npm-install:${input.dir}`);
|
|
62
100
|
const { Arborist } = yield* Effect.promise(() => import("@npmcli/arborist"));
|
|
63
101
|
const add = input.add ?? [];
|
|
64
102
|
const npmOptions = yield* NpmConfig.load(input.dir);
|
|
103
|
+
const options = input.update ? { ...npmOptions, preferOnline: true, noGitRevCache: true } : npmOptions;
|
|
65
104
|
const arborist = new Arborist({
|
|
66
|
-
...
|
|
105
|
+
...options,
|
|
67
106
|
path: input.dir,
|
|
68
107
|
binLinks: true,
|
|
69
108
|
progress: false,
|
|
@@ -72,8 +111,9 @@ const layer = Layer.effect(Service, Effect.gen(function* () {
|
|
|
72
111
|
});
|
|
73
112
|
return yield* Effect.tryPromise({
|
|
74
113
|
try: () => arborist.reify({
|
|
75
|
-
...
|
|
114
|
+
...options,
|
|
76
115
|
add,
|
|
116
|
+
update: input.update,
|
|
77
117
|
save: true,
|
|
78
118
|
saveType: "prod",
|
|
79
119
|
}),
|
|
@@ -88,22 +128,32 @@ const layer = Layer.effect(Service, Effect.gen(function* () {
|
|
|
88
128
|
}));
|
|
89
129
|
const add = Effect.fn("Npm.add")(function* (pkg, options) {
|
|
90
130
|
const { default: npa } = yield* Effect.promise(() => import("npm-package-arg"));
|
|
91
|
-
const
|
|
92
|
-
const name = (() => {
|
|
131
|
+
const parsed = (() => {
|
|
93
132
|
try {
|
|
94
|
-
return npa(pkg)
|
|
133
|
+
return npa(pkg);
|
|
95
134
|
}
|
|
96
135
|
catch {
|
|
97
|
-
return
|
|
136
|
+
return undefined;
|
|
98
137
|
}
|
|
99
138
|
})();
|
|
100
|
-
|
|
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")));
|
|
148
|
+
}
|
|
149
|
+
if (cached) {
|
|
101
150
|
return resolveEntryPoint(name, path.join(dir, "node_modules", name), options?.subpaths);
|
|
102
151
|
}
|
|
103
152
|
const tree = yield* reify({ dir, add: [pkg] });
|
|
104
153
|
const first = tree.edgesOut.values().next().value?.to;
|
|
105
154
|
if (!first) {
|
|
106
|
-
const
|
|
155
|
+
const installed = yield* installedName(pkg, dir, parsedName);
|
|
156
|
+
const result = resolveEntryPoint(installed, path.join(dir, "node_modules", installed), options?.subpaths);
|
|
107
157
|
if (result.entrypoint)
|
|
108
158
|
return result;
|
|
109
159
|
return yield* new InstallFailedError({ add: [pkg], dir });
|
|
@@ -112,21 +162,23 @@ const layer = Layer.effect(Service, Effect.gen(function* () {
|
|
|
112
162
|
}, Effect.scoped);
|
|
113
163
|
const resolve = Effect.fn("Npm.resolve")(function* (pkg, options) {
|
|
114
164
|
const { default: npa } = yield* Effect.promise(() => import("npm-package-arg"));
|
|
115
|
-
const
|
|
165
|
+
const parsedName = (() => {
|
|
116
166
|
try {
|
|
117
|
-
return npa(pkg).name ??
|
|
167
|
+
return npa(pkg).name ?? undefined;
|
|
118
168
|
}
|
|
119
169
|
catch {
|
|
120
|
-
return
|
|
170
|
+
return undefined;
|
|
121
171
|
}
|
|
122
172
|
})();
|
|
123
|
-
const
|
|
173
|
+
const root = yield* directory(pkg);
|
|
174
|
+
const name = yield* installedName(pkg, root, parsedName);
|
|
175
|
+
const dir = path.join(root, "node_modules", name);
|
|
124
176
|
if (!(yield* afs.existsSafe(dir)))
|
|
125
177
|
return { directory: dir };
|
|
126
178
|
return resolveEntryPoint(name, dir, options?.subpaths);
|
|
127
179
|
});
|
|
128
180
|
const which = Effect.fn("Npm.which")(function* (pkg, bin) {
|
|
129
|
-
const dir = directory(pkg);
|
|
181
|
+
const dir = yield* directory(pkg);
|
|
130
182
|
const binDir = path.join(dir, "node_modules", ".bin");
|
|
131
183
|
const pick = Effect.fnUntraced(function* () {
|
|
132
184
|
const files = yield* fs.readDirectory(binDir).pipe(Effect.orElseSucceed(() => []));
|
|
@@ -188,3 +240,12 @@ export async function resolve(...args) {
|
|
|
188
240
|
export async function which(...args) {
|
|
189
241
|
return runPromise((svc) => svc.which(...args));
|
|
190
242
|
}
|
|
243
|
+
function isMutable(parsed) {
|
|
244
|
+
if (!parsed)
|
|
245
|
+
return false;
|
|
246
|
+
if (["tag", "range"].includes(parsed.type))
|
|
247
|
+
return true;
|
|
248
|
+
if (parsed.type !== "git")
|
|
249
|
+
return false;
|
|
250
|
+
return !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i.test(parsed.gitCommittish ?? "");
|
|
251
|
+
}
|
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-beta-
|
|
4
|
+
"version": "0.0.0-beta-18269",
|
|
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",
|