@mjasnikovs/pi-task 0.13.36 → 0.13.37
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.
|
@@ -4,7 +4,7 @@ import * as os from 'node:os';
|
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { openCache as defaultOpenCache } from './docs-cache.js';
|
|
6
6
|
import { ensureIndexed as defaultEnsureIndexed } from './docs-index.js';
|
|
7
|
-
import { resolvePackage as defaultResolvePackage, ResolveError } from './docs-resolve.js';
|
|
7
|
+
import { resolvePackage as defaultResolvePackage, ResolveError, detectTypesRedirect, typesPackageName, hasTypeFiles, isDtsFile } from './docs-resolve.js';
|
|
8
8
|
import { retrieveChunks as defaultRetrieveChunks } from './docs-retrieve.js';
|
|
9
9
|
import { npmVersionLookup as defaultNpmVersionLookup } from './npm-version.js';
|
|
10
10
|
import { getPiInvocation } from '../shared/pi-invocation.js';
|
|
@@ -44,6 +44,52 @@ export async function runAutoInstall(spawn, packageName, signal) {
|
|
|
44
44
|
}, installDir, signal, { mode: 'text', discardStdout: true });
|
|
45
45
|
return { success: result.exitCode === 0 && !result.aborted, installDir, stderr: result.stderr };
|
|
46
46
|
}
|
|
47
|
+
/** Resolve `name` from cwd; on `not_installed`, auto-install it and resolve from
|
|
48
|
+
* the install dir. Returns null on any failure (caller keeps its fallback). */
|
|
49
|
+
async function tryResolveOrInstall(name, cwd, spawn, resolvePackage, signal) {
|
|
50
|
+
try {
|
|
51
|
+
return resolvePackage(name, cwd);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (!(err instanceof ResolveError) || err.kind !== 'not_installed')
|
|
55
|
+
return null;
|
|
56
|
+
const install = await runAutoInstall(spawn, extractParentPackage(name), signal);
|
|
57
|
+
if (!install.success)
|
|
58
|
+
return null;
|
|
59
|
+
try {
|
|
60
|
+
return resolvePackage(name, install.installDir);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Follow the @types/<name> + triple-slash `<reference types>` redirect chain
|
|
68
|
+
* from a package that ships no usable types of its own to the one that actually
|
|
69
|
+
* holds the declarations (e.g. bun -> @types/bun -> bun-types). Bounded to a few
|
|
70
|
+
* hops; returns the original package if no better source is found. */
|
|
71
|
+
async function resolveTypeSource(pkg, requested, cwd, spawn, resolvePackage, signal) {
|
|
72
|
+
const visited = new Set([pkg.name, extractParentPackage(requested)]);
|
|
73
|
+
let cur = pkg;
|
|
74
|
+
for (let depth = 0; depth < 3; depth++) {
|
|
75
|
+
let next = detectTypesRedirect(cur);
|
|
76
|
+
if (next && visited.has(next))
|
|
77
|
+
next = null;
|
|
78
|
+
if (!next && !hasTypeFiles(cur.root)) {
|
|
79
|
+
const types = typesPackageName(cur.name);
|
|
80
|
+
if (types && !visited.has(types))
|
|
81
|
+
next = types;
|
|
82
|
+
}
|
|
83
|
+
if (!next)
|
|
84
|
+
break;
|
|
85
|
+
visited.add(next);
|
|
86
|
+
const resolved = await tryResolveOrInstall(next, cwd, spawn, resolvePackage, signal);
|
|
87
|
+
if (!resolved)
|
|
88
|
+
break;
|
|
89
|
+
cur = resolved;
|
|
90
|
+
}
|
|
91
|
+
return cur;
|
|
92
|
+
}
|
|
47
93
|
export async function docsRaw(input) {
|
|
48
94
|
const resolvePackage = input.resolvePackage ?? defaultResolvePackage;
|
|
49
95
|
const ensureIndexed = input.ensureIndexed ?? defaultEnsureIndexed;
|
|
@@ -114,6 +160,13 @@ export async function docsRaw(input) {
|
|
|
114
160
|
};
|
|
115
161
|
}
|
|
116
162
|
}
|
|
163
|
+
// Step 1b: if the resolved package ships no usable type declarations (e.g.
|
|
164
|
+
// the `bun` runtime launcher, which is just a binary + install README), or is
|
|
165
|
+
// a pure `@types/<name>` redirect stub, follow the conventional
|
|
166
|
+
// @types/<name> + triple-slash `<reference types>` chain to the package that
|
|
167
|
+
// actually holds the declarations (e.g. bun -> @types/bun -> bun-types).
|
|
168
|
+
// Best-effort: any failure leaves the original resolution untouched.
|
|
169
|
+
pkg = await resolveTypeSource(pkg, input.pkg, input.cwd, spawn, resolvePackage, input.signal);
|
|
117
170
|
// Step 2: open cache
|
|
118
171
|
let cache = null;
|
|
119
172
|
let cacheError;
|
|
@@ -257,7 +310,7 @@ function walkDtsAlpha(root) {
|
|
|
257
310
|
const full = path.join(dir, entry.name);
|
|
258
311
|
if (entry.isDirectory())
|
|
259
312
|
stack.push(full);
|
|
260
|
-
else if (entry.isFile() && entry.name
|
|
313
|
+
else if (entry.isFile() && isDtsFile(entry.name))
|
|
261
314
|
out.push(full);
|
|
262
315
|
}
|
|
263
316
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
+
import { isDtsFile } from './docs-resolve.js';
|
|
4
5
|
const MAX_CHUNK_BYTES = 8 * 1024;
|
|
5
6
|
const ZERO_SEP = Buffer.from([0]);
|
|
6
7
|
const DECL_SPLIT_RE = /^(?:export\s+|declare\s+)?(?:default\s+)?(?:async\s+)?(?:function|class|interface|type|namespace|module|const|let|var|enum)\s+/m;
|
|
@@ -48,13 +49,13 @@ function walkDts(root) {
|
|
|
48
49
|
const stat = fs.statSync(realPath);
|
|
49
50
|
if (stat.isDirectory())
|
|
50
51
|
stack.push(realPath);
|
|
51
|
-
else if (stat.isFile() && realPath
|
|
52
|
+
else if (stat.isFile() && isDtsFile(realPath))
|
|
52
53
|
out.push(realPath);
|
|
53
54
|
continue;
|
|
54
55
|
}
|
|
55
56
|
if (entry.isDirectory())
|
|
56
57
|
stack.push(full);
|
|
57
|
-
else if (entry.isFile() && entry.name
|
|
58
|
+
else if (entry.isFile() && isDtsFile(entry.name))
|
|
58
59
|
out.push(full);
|
|
59
60
|
}
|
|
60
61
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** True for TypeScript declaration files: .d.ts, .d.mts, .d.cts. */
|
|
2
|
+
export declare function isDtsFile(name: string): boolean;
|
|
1
3
|
export interface ResolvedPackage {
|
|
2
4
|
name: string;
|
|
3
5
|
version: string;
|
|
@@ -10,3 +12,15 @@ export declare class ResolveError extends Error {
|
|
|
10
12
|
constructor(kind: 'not_installed' | 'invalid_name', message: string);
|
|
11
13
|
}
|
|
12
14
|
export declare function resolvePackage(moduleName: string, cwd: string): ResolvedPackage;
|
|
15
|
+
/** Conventional DefinitelyTyped package for a runtime package that ships no
|
|
16
|
+
* types of its own. `bun` -> `@types/bun`, `@scope/x` -> `@types/scope__x`.
|
|
17
|
+
* Returns null for packages that are already under the `@types` scope. */
|
|
18
|
+
export declare function typesPackageName(moduleName: string): string | null;
|
|
19
|
+
/** True if the package ships at least one declaration file. */
|
|
20
|
+
export declare function hasTypeFiles(root: string): boolean;
|
|
21
|
+
/** When a package is a pure pointer to another types package — a single-file
|
|
22
|
+
* `/// <reference types="X" />` (the `@types/bun -> bun-types` shape) or a lone
|
|
23
|
+
* `export * from "X"` re-export — return the target package name. Returns null
|
|
24
|
+
* for packages that ship their own declarations (more than one .d.ts file, or a
|
|
25
|
+
* local `/// <reference path=... />` aggregator entry). */
|
|
26
|
+
export declare function detectTypesRedirect(pkg: ResolvedPackage): string | null;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
+
const DTS_RE = /\.d\.(?:ts|mts|cts)$/;
|
|
5
|
+
/** True for TypeScript declaration files: .d.ts, .d.mts, .d.cts. */
|
|
6
|
+
export function isDtsFile(name) {
|
|
7
|
+
return DTS_RE.test(name);
|
|
8
|
+
}
|
|
4
9
|
export class ResolveError extends Error {
|
|
5
10
|
kind;
|
|
6
11
|
constructor(kind, message) {
|
|
@@ -52,10 +57,18 @@ function findReadme(root) {
|
|
|
52
57
|
function resolveEntryDts(moduleName, parent, root, pkg) {
|
|
53
58
|
if (moduleName !== parent) {
|
|
54
59
|
const subpath = moduleName.slice(parent.length + 1);
|
|
55
|
-
const candidates = [
|
|
60
|
+
const candidates = [
|
|
61
|
+
`${subpath}.d.ts`,
|
|
62
|
+
`${subpath}.d.mts`,
|
|
63
|
+
`${subpath}.d.cts`,
|
|
64
|
+
`${subpath}/index.d.ts`,
|
|
65
|
+
`${subpath}/index.d.mts`,
|
|
66
|
+
`${subpath}/index.d.cts`,
|
|
67
|
+
subpath
|
|
68
|
+
];
|
|
56
69
|
for (const c of candidates) {
|
|
57
70
|
const abs = path.join(root, c);
|
|
58
|
-
if (fs.existsSync(abs) && abs
|
|
71
|
+
if (fs.existsSync(abs) && isDtsFile(abs))
|
|
59
72
|
return abs;
|
|
60
73
|
}
|
|
61
74
|
}
|
|
@@ -71,9 +84,11 @@ function resolveEntryDts(moduleName, parent, root, pkg) {
|
|
|
71
84
|
if (fs.existsSync(abs))
|
|
72
85
|
return abs;
|
|
73
86
|
}
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
for (const name of ['index.d.ts', 'index.d.mts', 'index.d.cts']) {
|
|
88
|
+
const fallback = path.join(root, name);
|
|
89
|
+
if (fs.existsSync(fallback))
|
|
90
|
+
return fallback;
|
|
91
|
+
}
|
|
77
92
|
return null;
|
|
78
93
|
}
|
|
79
94
|
export function resolvePackage(moduleName, cwd) {
|
|
@@ -124,3 +139,95 @@ function findPackageJsonInNodeModules(parent, startDir) {
|
|
|
124
139
|
dir = up;
|
|
125
140
|
}
|
|
126
141
|
}
|
|
142
|
+
/** Conventional DefinitelyTyped package for a runtime package that ships no
|
|
143
|
+
* types of its own. `bun` -> `@types/bun`, `@scope/x` -> `@types/scope__x`.
|
|
144
|
+
* Returns null for packages that are already under the `@types` scope. */
|
|
145
|
+
export function typesPackageName(moduleName) {
|
|
146
|
+
const parent = parentPackageName(moduleName);
|
|
147
|
+
if (parent.startsWith('@types/'))
|
|
148
|
+
return null;
|
|
149
|
+
if (parent.startsWith('@')) {
|
|
150
|
+
const [scope, name] = parent.slice(1).split('/');
|
|
151
|
+
if (!scope || !name)
|
|
152
|
+
return null;
|
|
153
|
+
return `@types/${scope}__${name}`;
|
|
154
|
+
}
|
|
155
|
+
return `@types/${parent}`;
|
|
156
|
+
}
|
|
157
|
+
/** Count declaration files under root (excluding nested node_modules), stopping
|
|
158
|
+
* once `cap` is reached. */
|
|
159
|
+
function countTypeFiles(root, cap) {
|
|
160
|
+
let n = 0;
|
|
161
|
+
const stack = [root];
|
|
162
|
+
while (stack.length) {
|
|
163
|
+
const dir = stack.pop();
|
|
164
|
+
let entries;
|
|
165
|
+
try {
|
|
166
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
for (const entry of entries) {
|
|
172
|
+
if (entry.name === 'node_modules')
|
|
173
|
+
continue;
|
|
174
|
+
const full = path.join(dir, entry.name);
|
|
175
|
+
if (entry.isDirectory())
|
|
176
|
+
stack.push(full);
|
|
177
|
+
else if (entry.isFile() && isDtsFile(entry.name)) {
|
|
178
|
+
if (++n >= cap)
|
|
179
|
+
return n;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return n;
|
|
184
|
+
}
|
|
185
|
+
/** True if the package ships at least one declaration file. */
|
|
186
|
+
export function hasTypeFiles(root) {
|
|
187
|
+
return countTypeFiles(root, 1) > 0;
|
|
188
|
+
}
|
|
189
|
+
function findIndexDts(root) {
|
|
190
|
+
for (const name of ['index.d.ts', 'index.d.mts', 'index.d.cts']) {
|
|
191
|
+
const abs = path.join(root, name);
|
|
192
|
+
if (fs.existsSync(abs))
|
|
193
|
+
return abs;
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
function isBareSpecifier(spec) {
|
|
198
|
+
return spec.length > 0 && !spec.startsWith('.') && !spec.startsWith('/');
|
|
199
|
+
}
|
|
200
|
+
const REFERENCE_TYPES_RE = /\/\/\/\s*<reference\s+types=["']([^"']+)["']\s*\/>/;
|
|
201
|
+
const REEXPORT_ALL_RE = /^\s*export\s+(?:type\s+)?\*\s+from\s+["']([^"']+)["'];?\s*$/m;
|
|
202
|
+
/** When a package is a pure pointer to another types package — a single-file
|
|
203
|
+
* `/// <reference types="X" />` (the `@types/bun -> bun-types` shape) or a lone
|
|
204
|
+
* `export * from "X"` re-export — return the target package name. Returns null
|
|
205
|
+
* for packages that ship their own declarations (more than one .d.ts file, or a
|
|
206
|
+
* local `/// <reference path=... />` aggregator entry). */
|
|
207
|
+
export function detectTypesRedirect(pkg) {
|
|
208
|
+
// A package that ships multiple declaration files is an aggregator, not a
|
|
209
|
+
// redirect stub — use its own types.
|
|
210
|
+
if (countTypeFiles(pkg.root, 2) > 1)
|
|
211
|
+
return null;
|
|
212
|
+
const entry = pkg.entryDts ?? findIndexDts(pkg.root);
|
|
213
|
+
if (!entry)
|
|
214
|
+
return null;
|
|
215
|
+
let content;
|
|
216
|
+
try {
|
|
217
|
+
content = fs.readFileSync(entry, 'utf8');
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
// A local `/// <reference path="..." />` means the entry aggregates sibling
|
|
223
|
+
// declarations (e.g. bun-types) — not a redirect to another package.
|
|
224
|
+
if (/\/\/\/\s*<reference\s+path=/.test(content))
|
|
225
|
+
return null;
|
|
226
|
+
const ref = REFERENCE_TYPES_RE.exec(content);
|
|
227
|
+
if (ref && isBareSpecifier(ref[1]))
|
|
228
|
+
return parentPackageName(ref[1]);
|
|
229
|
+
const rex = REEXPORT_ALL_RE.exec(content);
|
|
230
|
+
if (rex && isBareSpecifier(rex[1]))
|
|
231
|
+
return parentPackageName(rex[1]);
|
|
232
|
+
return null;
|
|
233
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.37",
|
|
4
4
|
"description": "Deterministic spec-orchestration for local models, with a bundled real-time remote web view and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|