@tangle-network/agent-app 0.45.38 → 0.45.39
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/{agent-session-controls-CCeu5QLS.d.ts → agent-session-controls-BGNNTeJ5.d.ts} +49 -2
- package/dist/assistant/index.d.ts +1 -1
- package/dist/assistant/index.js +2 -2
- package/dist/chat-react/index.d.ts +1 -1
- package/dist/chat-react/index.js +1 -1
- package/dist/{chunk-HNVAASAO.js → chunk-4TZZXCLF.js} +2 -2
- package/dist/chunk-4TZZXCLF.js.map +1 -0
- package/dist/{chunk-3AXSERRK.js → chunk-JRPXENLF.js} +25 -7
- package/dist/chunk-JRPXENLF.js.map +1 -0
- package/dist/chunk-QL7HXXXL.js +609 -0
- package/dist/chunk-QL7HXXXL.js.map +1 -0
- package/dist/peer-floors/check.d.ts +188 -1
- package/dist/peer-floors/check.js +13 -1
- package/dist/peer-floors/cli.d.ts +7 -0
- package/dist/peer-floors/cli.js +40 -5
- package/dist/peer-floors/cli.js.map +1 -1
- package/dist/web-react/index.d.ts +1 -1
- package/dist/web-react/index.js +8 -2
- package/package.json +1 -1
- package/dist/chunk-3AXSERRK.js.map +0 -1
- package/dist/chunk-HNVAASAO.js.map +0 -1
- package/dist/chunk-S24VVLKV.js +0 -130
- package/dist/chunk-S24VVLKV.js.map +0 -1
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
// src/peer-floors/check.ts
|
|
2
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
3
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
4
|
+
|
|
5
|
+
// src/peer-floors/dependency-source.ts
|
|
6
|
+
import { createHash } from "crypto";
|
|
7
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, statSync } from "fs";
|
|
8
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
9
|
+
var TARBALL = /\.(?:tgz|tar\.gz)$/i;
|
|
10
|
+
function classifyDependencySpecifier(specifier) {
|
|
11
|
+
const spec = specifier.trim();
|
|
12
|
+
if (spec.startsWith("workspace:")) return "workspace";
|
|
13
|
+
if (spec.startsWith("catalog:")) return "catalog";
|
|
14
|
+
for (const protocol of ["file:", "link:", "portal:"]) {
|
|
15
|
+
if (spec.startsWith(protocol)) {
|
|
16
|
+
const path = spec.slice(protocol.length);
|
|
17
|
+
if (TARBALL.test(path)) return "tarball";
|
|
18
|
+
return protocol.slice(0, -1);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (/^(?:git|git\+ssh|git\+https?|git\+file|ssh):/.test(spec)) return "git";
|
|
22
|
+
if (/^(?:github|gitlab|bitbucket):/.test(spec)) return "git";
|
|
23
|
+
if (/^https?:\/\//.test(spec)) return TARBALL.test(spec.split(/[?#]/)[0] ?? "") ? "tarball" : "remote";
|
|
24
|
+
if (/^[\w.-]+\/[\w.-]+(?:#.+)?$/.test(spec) && !spec.startsWith("@")) return "git";
|
|
25
|
+
return "registry";
|
|
26
|
+
}
|
|
27
|
+
function isReproducible(protocol) {
|
|
28
|
+
return protocol === "registry" || protocol === "workspace" || protocol === "catalog";
|
|
29
|
+
}
|
|
30
|
+
function resolveLocalPathSource(args) {
|
|
31
|
+
const root = resolve(args.repoDir);
|
|
32
|
+
const target = isAbsolute(args.path) ? resolve(args.path) : resolve(args.fromDir, args.path);
|
|
33
|
+
if (target !== root && !target.startsWith(root + sep)) return "outside-repo";
|
|
34
|
+
const manifest = join(target, "package.json");
|
|
35
|
+
if (!existsSync(manifest) || !statSync(target).isDirectory()) return "not-a-directory";
|
|
36
|
+
let declared;
|
|
37
|
+
try {
|
|
38
|
+
declared = JSON.parse(readFileSync(manifest, "utf8")).name;
|
|
39
|
+
} catch {
|
|
40
|
+
return "not-a-directory";
|
|
41
|
+
}
|
|
42
|
+
return declared === args.name ? "in-repo-source" : "name-mismatch";
|
|
43
|
+
}
|
|
44
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
45
|
+
"node_modules",
|
|
46
|
+
".git",
|
|
47
|
+
"dist",
|
|
48
|
+
"build",
|
|
49
|
+
"out",
|
|
50
|
+
"coverage",
|
|
51
|
+
".wrangler",
|
|
52
|
+
".react-router",
|
|
53
|
+
".next",
|
|
54
|
+
".turbo",
|
|
55
|
+
".cache",
|
|
56
|
+
"storybook-static"
|
|
57
|
+
]);
|
|
58
|
+
function walkSourceTree(dir, repoDir, exclude, seen) {
|
|
59
|
+
let entries;
|
|
60
|
+
try {
|
|
61
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
62
|
+
} catch {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const full = join(dir, entry.name);
|
|
67
|
+
const rel = relative(repoDir, full).split(sep).join("/");
|
|
68
|
+
if (exclude.some((prefix) => rel === prefix || rel.startsWith(`${prefix}/`))) continue;
|
|
69
|
+
if (entry.isDirectory()) {
|
|
70
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
71
|
+
walkSourceTree(full, repoDir, exclude, seen);
|
|
72
|
+
} else if (entry.isFile()) {
|
|
73
|
+
if (entry.name === "package.json") seen.manifests.push(full);
|
|
74
|
+
else if (TARBALL.test(entry.name)) seen.tarballs.push(full);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function unquote(text) {
|
|
79
|
+
const t = text.trim();
|
|
80
|
+
if (t.startsWith("'") && t.endsWith("'") || t.startsWith('"') && t.endsWith('"')) {
|
|
81
|
+
return t.slice(1, -1);
|
|
82
|
+
}
|
|
83
|
+
return t;
|
|
84
|
+
}
|
|
85
|
+
function readYamlLines(text) {
|
|
86
|
+
const out = [];
|
|
87
|
+
const stack = [];
|
|
88
|
+
text.split("\n").forEach((raw, index) => {
|
|
89
|
+
if (!raw.trim() || raw.trimStart().startsWith("#")) return;
|
|
90
|
+
const indent = raw.length - raw.trimStart().length;
|
|
91
|
+
const match = /^\s*(?:'((?:[^']|'')*)'|"([^"]*)"|([^\s:#][^:]*?))\s*:(?:\s+(.*))?$/.exec(raw);
|
|
92
|
+
if (!match) return;
|
|
93
|
+
const key = (match[1] ?? match[2] ?? match[3] ?? "").replace(/''/g, "'");
|
|
94
|
+
const rawValue = (match[4] ?? "").split(" #")[0] ?? "";
|
|
95
|
+
const depth = Math.floor(indent / 2);
|
|
96
|
+
stack.length = depth;
|
|
97
|
+
const path = [...stack];
|
|
98
|
+
stack[depth] = key;
|
|
99
|
+
out.push({ indent, key, value: unquote(rawValue), path, lineNumber: index + 1 });
|
|
100
|
+
});
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
function sectionOf(line) {
|
|
104
|
+
return line.indent === 0 ? line.key : line.path[0];
|
|
105
|
+
}
|
|
106
|
+
var DEP_FIELDS = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
|
|
107
|
+
function judge(args) {
|
|
108
|
+
const protocol = classifyDependencySpecifier(args.specifier);
|
|
109
|
+
if (isReproducible(protocol)) return null;
|
|
110
|
+
const base = { check: args.check, name: args.name, specifier: args.specifier, protocol, where: args.where };
|
|
111
|
+
if (protocol === "tarball") {
|
|
112
|
+
return {
|
|
113
|
+
...base,
|
|
114
|
+
detail: "resolves a PACKED TARBALL, not a published release. A .tgz is opaque bytes that no diff shows and no registry can reproduce: the version inside it can collide with a real release and ship a different API. Publish the change and pin the published range."
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (protocol === "git" || protocol === "remote") {
|
|
118
|
+
return {
|
|
119
|
+
...base,
|
|
120
|
+
detail: `resolves from ${protocol === "git" ? "a git ref" : "a remote URL"} rather than the registry, so what installs depends on what that ref points at today. Publish the change and pin the published range.`
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const path = args.specifier.slice(args.specifier.indexOf(":") + 1);
|
|
124
|
+
const source = resolveLocalPathSource({ fromDir: args.fromDir, repoDir: args.repoDir, path, name: args.name });
|
|
125
|
+
if (source === "in-repo-source") return null;
|
|
126
|
+
const why = {
|
|
127
|
+
"outside-repo": "points OUTSIDE this repository, so it resolves on one machine and nowhere else \u2014 a clean checkout, a CI runner and a sign-off gate that installs into an exported tree all get a different answer or fail at install.",
|
|
128
|
+
"not-a-directory": "does not resolve to a package directory in this repository. A local specifier is only legitimate when it points at in-repo SOURCE a reviewer sees in the diff.",
|
|
129
|
+
"name-mismatch": `resolves to an in-repo directory that declares a DIFFERENT package name, so the dependency ${args.name} is being satisfied by something else entirely.`
|
|
130
|
+
};
|
|
131
|
+
return { ...base, detail: `${why[source]} (${source})` };
|
|
132
|
+
}
|
|
133
|
+
function scanManifest(file, repoDir) {
|
|
134
|
+
let manifest;
|
|
135
|
+
try {
|
|
136
|
+
manifest = JSON.parse(readFileSync(file, "utf8"));
|
|
137
|
+
} catch {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
const fromDir = dirname(file);
|
|
141
|
+
const where = relative(repoDir, file).split(sep).join("/") || "package.json";
|
|
142
|
+
const findings = [];
|
|
143
|
+
for (const field of DEP_FIELDS) {
|
|
144
|
+
const block = manifest[field];
|
|
145
|
+
if (!block || typeof block !== "object") continue;
|
|
146
|
+
for (const [name, specifier] of Object.entries(block)) {
|
|
147
|
+
if (typeof specifier !== "string") continue;
|
|
148
|
+
const finding = judge({ check: "declared", name, specifier, where: `${where} \u2192 ${field}`, fromDir, repoDir });
|
|
149
|
+
if (finding) findings.push(finding);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const overrideBlocks = [
|
|
153
|
+
["pnpm.overrides", manifest.pnpm?.overrides],
|
|
154
|
+
["resolutions", manifest.resolutions]
|
|
155
|
+
];
|
|
156
|
+
for (const [label, block] of overrideBlocks) {
|
|
157
|
+
if (!block || typeof block !== "object") continue;
|
|
158
|
+
for (const [name, specifier] of Object.entries(block)) {
|
|
159
|
+
if (typeof specifier !== "string") continue;
|
|
160
|
+
const finding = judge({
|
|
161
|
+
check: "override",
|
|
162
|
+
// An override key can carry a range suffix (`foo@1 > bar`); the package
|
|
163
|
+
// name is the leading segment.
|
|
164
|
+
name: overrideKeyName(name),
|
|
165
|
+
specifier,
|
|
166
|
+
where: `${where} \u2192 ${label}['${name}']`,
|
|
167
|
+
fromDir,
|
|
168
|
+
repoDir
|
|
169
|
+
});
|
|
170
|
+
if (finding) findings.push(finding);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return findings;
|
|
174
|
+
}
|
|
175
|
+
function overrideKeyName(key) {
|
|
176
|
+
const head = (key.split(">").pop() ?? key).trim();
|
|
177
|
+
const at = head.lastIndexOf("@");
|
|
178
|
+
return at > 0 ? head.slice(0, at) : head;
|
|
179
|
+
}
|
|
180
|
+
function scanWorkspaceYaml(file, repoDir) {
|
|
181
|
+
const findings = [];
|
|
182
|
+
const where = relative(repoDir, file).split(sep).join("/");
|
|
183
|
+
for (const line of readYamlLines(readFileSync(file, "utf8"))) {
|
|
184
|
+
const section = sectionOf(line);
|
|
185
|
+
if (section !== "overrides" && section !== "catalog" && section !== "catalogs") continue;
|
|
186
|
+
if (line.indent === 0 || !line.value) continue;
|
|
187
|
+
const finding = judge({
|
|
188
|
+
check: "override",
|
|
189
|
+
name: overrideKeyName(line.key),
|
|
190
|
+
specifier: line.value,
|
|
191
|
+
where: `${where}:${line.lineNumber} \u2192 ${[...line.path, line.key].join(".")}`,
|
|
192
|
+
fromDir: dirname(file),
|
|
193
|
+
repoDir
|
|
194
|
+
});
|
|
195
|
+
if (finding) findings.push(finding);
|
|
196
|
+
}
|
|
197
|
+
return findings;
|
|
198
|
+
}
|
|
199
|
+
function scanLockfile(file, repoDir) {
|
|
200
|
+
const findings = [];
|
|
201
|
+
const where = relative(repoDir, file).split(sep).join("/");
|
|
202
|
+
const fromDir = dirname(file);
|
|
203
|
+
const seen = /* @__PURE__ */ new Set();
|
|
204
|
+
const push = (finding) => {
|
|
205
|
+
if (!finding) return;
|
|
206
|
+
const key = `${finding.name}|${finding.specifier}`;
|
|
207
|
+
if (seen.has(key)) return;
|
|
208
|
+
seen.add(key);
|
|
209
|
+
findings.push(finding);
|
|
210
|
+
};
|
|
211
|
+
for (const line of readYamlLines(readFileSync(file, "utf8"))) {
|
|
212
|
+
const section = sectionOf(line);
|
|
213
|
+
if (section === "overrides" && line.indent > 0 && line.value) {
|
|
214
|
+
push(judge({
|
|
215
|
+
check: "lockfile",
|
|
216
|
+
name: overrideKeyName(line.key),
|
|
217
|
+
specifier: line.value,
|
|
218
|
+
where: `${where}:${line.lineNumber} \u2192 overrides`,
|
|
219
|
+
fromDir,
|
|
220
|
+
repoDir
|
|
221
|
+
}));
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (section === "importers" && line.key === "specifier" && line.value) {
|
|
225
|
+
push(judge({
|
|
226
|
+
check: "lockfile",
|
|
227
|
+
name: line.path[line.path.length - 1] ?? "(unknown)",
|
|
228
|
+
specifier: line.value,
|
|
229
|
+
where: `${where}:${line.lineNumber} \u2192 importers`,
|
|
230
|
+
fromDir,
|
|
231
|
+
repoDir
|
|
232
|
+
}));
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if ((section === "packages" || section === "snapshots") && line.indent === 2 && !line.value) {
|
|
236
|
+
const parsed = parsePackageKey(line.key);
|
|
237
|
+
if (!parsed) continue;
|
|
238
|
+
push(judge({
|
|
239
|
+
check: "lockfile",
|
|
240
|
+
name: parsed.name,
|
|
241
|
+
specifier: parsed.reference,
|
|
242
|
+
where: `${where}:${line.lineNumber} \u2192 ${section}`,
|
|
243
|
+
fromDir,
|
|
244
|
+
repoDir
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return findings;
|
|
249
|
+
}
|
|
250
|
+
function parsePackageKey(key) {
|
|
251
|
+
const withoutPeers = key.replace(/\(.*\)$/, "");
|
|
252
|
+
const at = withoutPeers.lastIndexOf("@");
|
|
253
|
+
if (at <= 0) return null;
|
|
254
|
+
return { name: withoutPeers.slice(0, at), reference: withoutPeers.slice(at + 1) };
|
|
255
|
+
}
|
|
256
|
+
function scanVirtualStore(repoDir, modulesDir) {
|
|
257
|
+
const store = join(repoDir, modulesDir, ".pnpm");
|
|
258
|
+
if (!existsSync(store)) return [];
|
|
259
|
+
const findings = [];
|
|
260
|
+
for (const entry of readdirSync(store, { withFileTypes: true })) {
|
|
261
|
+
if (!entry.isDirectory() || entry.name === modulesDir) continue;
|
|
262
|
+
const protocolMatch = /^(file|link|portal|git|https?)\+/.exec(entry.name);
|
|
263
|
+
if (!protocolMatch) continue;
|
|
264
|
+
const protocol = protocolMatch[1];
|
|
265
|
+
const encoded = entry.name.slice(protocol.length + 1);
|
|
266
|
+
const name = installedPackageName(join(store, entry.name), modulesDir);
|
|
267
|
+
const where = `${modulesDir}/.pnpm/${entry.name}`;
|
|
268
|
+
if (protocol === "file" || protocol === "link" || protocol === "portal") {
|
|
269
|
+
const path = encoded.split("+").join("/");
|
|
270
|
+
const specifier = `${protocol}:${path}`;
|
|
271
|
+
const finding = judge({
|
|
272
|
+
check: "installed",
|
|
273
|
+
name: name ?? path,
|
|
274
|
+
specifier,
|
|
275
|
+
where,
|
|
276
|
+
// A virtual-store path is written relative to the install root.
|
|
277
|
+
fromDir: repoDir,
|
|
278
|
+
repoDir
|
|
279
|
+
});
|
|
280
|
+
if (finding) findings.push(finding);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
findings.push({
|
|
284
|
+
check: "installed",
|
|
285
|
+
name,
|
|
286
|
+
specifier: encoded.split("+++").join("://").split("+").join("/"),
|
|
287
|
+
protocol: protocol === "git" ? "git" : "remote",
|
|
288
|
+
where,
|
|
289
|
+
detail: "is INSTALLED from a git ref or remote URL rather than the registry. The manifests may read clean \u2014 this is what the tree on disk actually holds. Reinstall from a published range."
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return findings;
|
|
293
|
+
}
|
|
294
|
+
function installedPackageName(entryDir, modulesDir) {
|
|
295
|
+
const nested = join(entryDir, modulesDir);
|
|
296
|
+
if (!existsSync(nested)) return null;
|
|
297
|
+
for (const child of readdirSync(nested, { withFileTypes: true })) {
|
|
298
|
+
if (child.name === ".bin" || child.isSymbolicLink()) continue;
|
|
299
|
+
if (!child.isDirectory()) continue;
|
|
300
|
+
if (child.name.startsWith("@")) {
|
|
301
|
+
const scopeDir = join(nested, child.name);
|
|
302
|
+
for (const scoped of readdirSync(scopeDir, { withFileTypes: true })) {
|
|
303
|
+
if (scoped.isSymbolicLink() || !scoped.isDirectory()) continue;
|
|
304
|
+
if (existsSync(join(scopeDir, scoped.name, "package.json"))) return `${child.name}/${scoped.name}`;
|
|
305
|
+
}
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (existsSync(join(nested, child.name, "package.json"))) return child.name;
|
|
309
|
+
}
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
function locateStoreCas(repoDir, modulesDir) {
|
|
313
|
+
const modulesState = join(repoDir, modulesDir, ".modules.yaml");
|
|
314
|
+
if (!existsSync(modulesState)) return [];
|
|
315
|
+
let configured;
|
|
316
|
+
try {
|
|
317
|
+
const text = readFileSync(modulesState, "utf8");
|
|
318
|
+
configured = (/"storeDir"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(text)?.[1] ?? /^storeDir:\s*(.+)$/m.exec(text)?.[1])?.trim();
|
|
319
|
+
} catch {
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
if (!configured) return [];
|
|
323
|
+
const root = unquote(configured.replace(/\\\\/g, "\\"));
|
|
324
|
+
const candidates = /* @__PURE__ */ new Set([root, dirname(root)]);
|
|
325
|
+
for (const base of [root, dirname(root)]) {
|
|
326
|
+
try {
|
|
327
|
+
for (const entry of readdirSync(base, { withFileTypes: true })) {
|
|
328
|
+
if (entry.isDirectory() && /^v\d+$/.test(entry.name)) candidates.add(join(base, entry.name));
|
|
329
|
+
}
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return [...candidates].map((dir) => join(dir, "files")).filter((dir) => existsSync(dir));
|
|
334
|
+
}
|
|
335
|
+
function collectFiles(dir, modulesDir, out = []) {
|
|
336
|
+
let entries;
|
|
337
|
+
try {
|
|
338
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
339
|
+
} catch {
|
|
340
|
+
return out;
|
|
341
|
+
}
|
|
342
|
+
for (const entry of entries) {
|
|
343
|
+
if (entry.name === modulesDir) continue;
|
|
344
|
+
const full = join(dir, entry.name);
|
|
345
|
+
if (entry.isSymbolicLink()) continue;
|
|
346
|
+
if (entry.isDirectory()) collectFiles(full, modulesDir, out);
|
|
347
|
+
else if (entry.isFile()) out.push(full);
|
|
348
|
+
}
|
|
349
|
+
return out;
|
|
350
|
+
}
|
|
351
|
+
function storeHolds(casDirs, bytes) {
|
|
352
|
+
const hex = createHash("sha512").update(bytes).digest("hex");
|
|
353
|
+
const tail = join(hex.slice(0, 2), hex.slice(2));
|
|
354
|
+
return casDirs.some((dir) => existsSync(join(dir, tail)) || existsSync(join(dir, `${tail}-exec`)));
|
|
355
|
+
}
|
|
356
|
+
function checkInstalledIntegrity(args) {
|
|
357
|
+
const virtualStore = join(args.repoDir, args.modulesDir, ".pnpm");
|
|
358
|
+
const casDirs = locateStoreCas(args.repoDir, args.modulesDir);
|
|
359
|
+
const findings = [];
|
|
360
|
+
let packagesExamined = 0;
|
|
361
|
+
let filesExamined = 0;
|
|
362
|
+
let filesHashed = 0;
|
|
363
|
+
if (casDirs.length > 0 && existsSync(virtualStore)) {
|
|
364
|
+
for (const entry of readdirSync(virtualStore, { withFileTypes: true })) {
|
|
365
|
+
if (!entry.isDirectory() || entry.name === args.modulesDir) continue;
|
|
366
|
+
const entryDir = join(virtualStore, entry.name);
|
|
367
|
+
const name = installedPackageName(entryDir, args.modulesDir);
|
|
368
|
+
if (!name || !name.startsWith(args.scope)) continue;
|
|
369
|
+
const packageDir = join(entryDir, args.modulesDir, name);
|
|
370
|
+
if (!existsSync(packageDir)) continue;
|
|
371
|
+
const files = collectFiles(packageDir, args.modulesDir);
|
|
372
|
+
if (files.length === 0) continue;
|
|
373
|
+
packagesExamined += 1;
|
|
374
|
+
filesExamined += files.length;
|
|
375
|
+
const foreign = [];
|
|
376
|
+
for (const file of files) {
|
|
377
|
+
try {
|
|
378
|
+
if (lstatSync(file).nlink > 1) continue;
|
|
379
|
+
filesHashed += 1;
|
|
380
|
+
if (storeHolds(casDirs, readFileSync(file))) continue;
|
|
381
|
+
foreign.push(relative(packageDir, file).split(sep).join("/"));
|
|
382
|
+
} catch {
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (foreign.length === 0) continue;
|
|
386
|
+
const shown = foreign.slice(0, 5);
|
|
387
|
+
findings.push({
|
|
388
|
+
check: "installed",
|
|
389
|
+
name,
|
|
390
|
+
specifier: null,
|
|
391
|
+
protocol: null,
|
|
392
|
+
where: `${args.modulesDir}/.pnpm/${entry.name} \u2192 ${shown.join(", ")}${foreign.length > shown.length ? ` (+${foreign.length - shown.length} more)` : ""}`,
|
|
393
|
+
detail: `holds ${foreign.length} file(s) whose bytes this pnpm store has never contained \u2014 the shape of a package HAND-PATCHED after install. The version on disk, the manifest and the lockfile all still agree; only the bytes do not, which is how a product typechecks green against an API its declared dependency does not ship. Delete the tree and reinstall (\`rm -rf ${args.modulesDir} && pnpm install --frozen-lockfile\`), then publish whatever change made the patch look necessary.`
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
coverage: {
|
|
399
|
+
basis: "store-cas",
|
|
400
|
+
storeLocated: casDirs.length > 0,
|
|
401
|
+
packagesExamined,
|
|
402
|
+
filesExamined,
|
|
403
|
+
filesHashed
|
|
404
|
+
},
|
|
405
|
+
findings
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function checkDependencySources(options) {
|
|
409
|
+
const repoDir = resolve(options.repoDir);
|
|
410
|
+
const { scope = "@tangle-network/", modulesDir = "node_modules", exclude = [] } = options;
|
|
411
|
+
const seen = { manifests: [], tarballs: [] };
|
|
412
|
+
walkSourceTree(repoDir, repoDir, exclude, seen);
|
|
413
|
+
const findings = [];
|
|
414
|
+
for (const manifest of seen.manifests) findings.push(...scanManifest(manifest, repoDir));
|
|
415
|
+
const workspaceYaml = join(repoDir, "pnpm-workspace.yaml");
|
|
416
|
+
if (existsSync(workspaceYaml)) findings.push(...scanWorkspaceYaml(workspaceYaml, repoDir));
|
|
417
|
+
const lockfile = join(repoDir, "pnpm-lock.yaml");
|
|
418
|
+
const lockfileScanned = existsSync(lockfile);
|
|
419
|
+
if (lockfileScanned) findings.push(...scanLockfile(lockfile, repoDir));
|
|
420
|
+
for (const tarball of seen.tarballs) {
|
|
421
|
+
findings.push({
|
|
422
|
+
check: "vendored-tarball",
|
|
423
|
+
name: null,
|
|
424
|
+
specifier: null,
|
|
425
|
+
protocol: "tarball",
|
|
426
|
+
where: relative(repoDir, tarball).split(sep).join("/"),
|
|
427
|
+
detail: `is a PACKED TARBALL committed into the source tree. Even when nothing points at it today, it is a build nobody can reproduce from the registry sitting one \`file:\` line away from shipping. Delete it; if ${basename(tarball)} is genuinely test data, move it under a path passed to \`--exclude\`.`
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
findings.push(...scanVirtualStore(repoDir, modulesDir));
|
|
431
|
+
const integrity = checkInstalledIntegrity({ repoDir, modulesDir, scope });
|
|
432
|
+
findings.push(...integrity.findings);
|
|
433
|
+
return {
|
|
434
|
+
repoDir,
|
|
435
|
+
manifestsScanned: seen.manifests.length,
|
|
436
|
+
lockfileScanned,
|
|
437
|
+
integrity: integrity.coverage,
|
|
438
|
+
findings,
|
|
439
|
+
ok: findings.length === 0
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
var CHECK_LABEL = {
|
|
443
|
+
declared: "DECLARED",
|
|
444
|
+
override: "OVERRIDE",
|
|
445
|
+
lockfile: "LOCKFILE",
|
|
446
|
+
"vendored-tarball": "TARBALL",
|
|
447
|
+
installed: "INSTALLED"
|
|
448
|
+
};
|
|
449
|
+
function describeDependencySourceFinding(finding) {
|
|
450
|
+
const subject = finding.name ? `${finding.name}${finding.specifier ? ` (${finding.specifier})` : ""}` : finding.where;
|
|
451
|
+
return `DEPENDENCY SOURCE: ${subject} ${finding.detail}
|
|
452
|
+
at ${finding.where}`;
|
|
453
|
+
}
|
|
454
|
+
function formatDependencySourceReport(report) {
|
|
455
|
+
const { integrity } = report;
|
|
456
|
+
const integrityLine = integrity.storeLocated ? ` integrity (${integrity.basis}): ${integrity.packagesExamined} package(s), ${integrity.filesExamined} file(s), ${integrity.filesHashed} hashed against the store` : ` integrity (${integrity.basis}): NOT VERIFIED \u2014 no pnpm content-addressed store is reachable from this tree, so no installed bytes were checked against anything`;
|
|
457
|
+
const lines = [
|
|
458
|
+
"dependency sources",
|
|
459
|
+
"",
|
|
460
|
+
` scanned ${report.manifestsScanned} manifest(s), ${report.lockfileScanned ? "pnpm-lock.yaml" : "no lockfile"}`,
|
|
461
|
+
integrityLine,
|
|
462
|
+
""
|
|
463
|
+
];
|
|
464
|
+
if (report.ok) {
|
|
465
|
+
lines.push(
|
|
466
|
+
integrity.storeLocated && integrity.packagesExamined > 0 ? " ok every declared source is the registry, and every installed byte came from the store" : " ok every declared source is the registry \u2014 installed bytes UNVERIFIED (see above)"
|
|
467
|
+
);
|
|
468
|
+
} else {
|
|
469
|
+
for (const finding of report.findings) {
|
|
470
|
+
lines.push(` FAIL [${CHECK_LABEL[finding.check]}] ${describeDependencySourceFinding(finding)}`, "");
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return lines.join("\n");
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// src/peer-floors/check.ts
|
|
477
|
+
function readInstalledManifest(name, fromDir, modulesDir) {
|
|
478
|
+
let dir = fromDir;
|
|
479
|
+
for (; ; ) {
|
|
480
|
+
const manifest = join2(dir, modulesDir, name, "package.json");
|
|
481
|
+
if (existsSync2(manifest)) {
|
|
482
|
+
return JSON.parse(readFileSync2(manifest, "utf8"));
|
|
483
|
+
}
|
|
484
|
+
if (existsSync2(join2(dir, ".git"))) return null;
|
|
485
|
+
const parent = dirname2(dir);
|
|
486
|
+
if (parent === dir) return null;
|
|
487
|
+
dir = parent;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function parseVersion(version) {
|
|
491
|
+
const [core] = version.split(/[-+]/);
|
|
492
|
+
const parts = (core ?? "").split(".").map((p) => Number.parseInt(p, 10));
|
|
493
|
+
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
494
|
+
}
|
|
495
|
+
function compare(a, b) {
|
|
496
|
+
const va = parseVersion(a);
|
|
497
|
+
const vb = parseVersion(b);
|
|
498
|
+
for (let i = 0; i < 3; i += 1) {
|
|
499
|
+
if (va[i] !== vb[i]) return va[i] < vb[i] ? -1 : 1;
|
|
500
|
+
}
|
|
501
|
+
return 0;
|
|
502
|
+
}
|
|
503
|
+
function satisfiesComparator(version, comparator) {
|
|
504
|
+
const trimmed = comparator.trim();
|
|
505
|
+
if (!trimmed || trimmed === "*" || trimmed === "x") return true;
|
|
506
|
+
const match = /^(>=|<=|>|<|=|\^|~)?\s*v?(.+)$/.exec(trimmed);
|
|
507
|
+
if (!match) return false;
|
|
508
|
+
const [, op = "=", target = ""] = match;
|
|
509
|
+
const cmp = compare(version, target);
|
|
510
|
+
switch (op) {
|
|
511
|
+
case ">=":
|
|
512
|
+
return cmp >= 0;
|
|
513
|
+
case "<=":
|
|
514
|
+
return cmp <= 0;
|
|
515
|
+
case ">":
|
|
516
|
+
return cmp > 0;
|
|
517
|
+
case "<":
|
|
518
|
+
return cmp < 0;
|
|
519
|
+
case "=":
|
|
520
|
+
return cmp === 0;
|
|
521
|
+
case "~": {
|
|
522
|
+
const [major, minor] = parseVersion(target);
|
|
523
|
+
const [vMajor, vMinor] = parseVersion(version);
|
|
524
|
+
return cmp >= 0 && vMajor === major && vMinor === minor;
|
|
525
|
+
}
|
|
526
|
+
case "^": {
|
|
527
|
+
const [major, minor] = parseVersion(target);
|
|
528
|
+
const [vMajor, vMinor] = parseVersion(version);
|
|
529
|
+
if (cmp < 0) return false;
|
|
530
|
+
if (major > 0) return vMajor === major;
|
|
531
|
+
if (minor > 0) return vMajor === 0 && vMinor === minor;
|
|
532
|
+
return vMajor === 0 && vMinor === 0;
|
|
533
|
+
}
|
|
534
|
+
default:
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function satisfiesRange(version, range) {
|
|
539
|
+
return range.split("||").some(
|
|
540
|
+
(alternative) => alternative.trim().split(/\s+/).filter(Boolean).every((c) => satisfiesComparator(version, c))
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
function checkPeerFloors(options) {
|
|
544
|
+
const {
|
|
545
|
+
appDir,
|
|
546
|
+
shell = "@tangle-network/agent-app",
|
|
547
|
+
scope = "@tangle-network/",
|
|
548
|
+
modulesDir = "node_modules"
|
|
549
|
+
} = options;
|
|
550
|
+
const shellManifest = options.shellManifest ?? readInstalledManifest(shell, appDir, modulesDir);
|
|
551
|
+
if (!shellManifest) throw new Error(`${shell} is not installed under ${appDir}`);
|
|
552
|
+
const appManifest = JSON.parse(readFileSync2(join2(appDir, "package.json"), "utf8"));
|
|
553
|
+
const declared = {
|
|
554
|
+
...appManifest.dependencies,
|
|
555
|
+
...appManifest.devDependencies,
|
|
556
|
+
...appManifest.optionalDependencies
|
|
557
|
+
};
|
|
558
|
+
const floors = Object.entries(shellManifest.peerDependencies ?? {}).filter(([name]) => name.startsWith(scope));
|
|
559
|
+
const rows = floors.map(([name, range]) => {
|
|
560
|
+
const installed = readInstalledManifest(name, appDir, modulesDir)?.version ?? null;
|
|
561
|
+
if (installed === null) {
|
|
562
|
+
return { name, range, installed, verdict: declared[name] ? "absent-but-declared" : "absent-unused" };
|
|
563
|
+
}
|
|
564
|
+
return {
|
|
565
|
+
name,
|
|
566
|
+
range,
|
|
567
|
+
installed,
|
|
568
|
+
verdict: satisfiesRange(installed, range) ? "satisfied" : "below-floor"
|
|
569
|
+
};
|
|
570
|
+
});
|
|
571
|
+
const violations = rows.filter((row) => row.verdict === "below-floor" || row.verdict === "absent-but-declared");
|
|
572
|
+
return {
|
|
573
|
+
shellVersion: shellManifest.version ?? "unknown",
|
|
574
|
+
rows,
|
|
575
|
+
violations,
|
|
576
|
+
ok: violations.length === 0
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
function describePeerFloorViolation(row, shellVersion, shell = "@tangle-network/agent-app") {
|
|
580
|
+
if (row.verdict === "below-floor") {
|
|
581
|
+
return `PEER FLOOR VIOLATED: ${shell}@${shellVersion} requires ${row.name}@${row.range}, but ${row.installed} is installed. A peer floor encodes a wire contract \u2014 bump the dependency, do not widen the floor. A caret on a 0.x version is minor-locked (^0.36.0 can never resolve to 0.38.0), so reinstalling alone will not fix this: change the pin, in EVERY place it appears including pnpm.overrides.`;
|
|
582
|
+
}
|
|
583
|
+
return `${row.name} is a declared dependency of this app, but no installed version could be read, so its peer floor ${row.range} went UNCHECKED. Failing loudly rather than reporting a pass this guard did not earn.`;
|
|
584
|
+
}
|
|
585
|
+
function formatPeerFloorReport(report, shell = "@tangle-network/agent-app") {
|
|
586
|
+
const width = Math.max(...report.rows.map((r) => r.name.length), 4);
|
|
587
|
+
const lines = [
|
|
588
|
+
`${shell}@${report.shellVersion} \u2014 peer floors`,
|
|
589
|
+
"",
|
|
590
|
+
...report.rows.map((row) => ` ${row.verdict === "satisfied" ? "ok " : row.verdict.startsWith("absent") ? "-- " : "FAIL"} ${row.name.padEnd(width)} installed ${(row.installed ?? "(none)").padEnd(10)} floor ${row.range}`),
|
|
591
|
+
"",
|
|
592
|
+
report.ok ? `all ${report.rows.length} floors satisfied` : report.violations.map((row) => describePeerFloorViolation(row, report.shellVersion, shell)).join("\n\n")
|
|
593
|
+
];
|
|
594
|
+
return lines.join("\n");
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export {
|
|
598
|
+
classifyDependencySpecifier,
|
|
599
|
+
resolveLocalPathSource,
|
|
600
|
+
checkInstalledIntegrity,
|
|
601
|
+
checkDependencySources,
|
|
602
|
+
describeDependencySourceFinding,
|
|
603
|
+
formatDependencySourceReport,
|
|
604
|
+
satisfiesRange,
|
|
605
|
+
checkPeerFloors,
|
|
606
|
+
describePeerFloorViolation,
|
|
607
|
+
formatPeerFloorReport
|
|
608
|
+
};
|
|
609
|
+
//# sourceMappingURL=chunk-QL7HXXXL.js.map
|