@openclaw/plugin-inspector 0.3.19 → 0.3.21
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/CHANGELOG.md +22 -0
- package/README.md +11 -3
- package/package.json +7 -2
- package/src/advanced.js +7 -0
- package/src/api.js +4 -0
- package/src/batch.js +8 -0
- package/src/cli.js +33 -10
- package/src/compatibility-report.js +6 -0
- package/src/config.js +2 -1
- package/src/fixture-summary.js +162 -20
- package/src/index.js +12 -3
- package/src/inspector.js +170 -11
- package/src/issues.js +4 -1
- package/src/openclaw-target.js +185 -2
- package/src/openclaw-version.js +246 -0
- package/src/report.js +1 -0
- package/src/sdk-mock.js +15 -0
package/src/index.js
CHANGED
|
@@ -8,7 +8,8 @@ import * as executionResultsApi from "./execution-results.js";
|
|
|
8
8
|
import * as importLoopProfileApi from "./import-loop-profile.js";
|
|
9
9
|
import * as inspectorApi from "./inspector.js";
|
|
10
10
|
import * as issuesApi from "./issues.js";
|
|
11
|
-
import * as openClawTargetApi from "./openclaw-
|
|
11
|
+
import * as openClawTargetApi from "./openclaw-version.js";
|
|
12
|
+
import * as openClawTargetSurfaceApi from "./openclaw-target.js";
|
|
12
13
|
import * as profileDiffApi from "./profile-diff.js";
|
|
13
14
|
import * as refDiffApi from "./ref-diff.js";
|
|
14
15
|
import * as reportApi from "./report.js";
|
|
@@ -67,8 +68,16 @@ export const reports = Object.freeze({
|
|
|
67
68
|
issueId: issuesApi.issueId,
|
|
68
69
|
classifyIssueFinding: issuesApi.classifyIssueFinding,
|
|
69
70
|
knownIssueCodes: issuesApi.knownIssueCodes,
|
|
70
|
-
openClawTargetPathCandidates:
|
|
71
|
-
readOpenClawTargetSurface:
|
|
71
|
+
openClawTargetPathCandidates: openClawTargetSurfaceApi.openClawTargetPathCandidates,
|
|
72
|
+
readOpenClawTargetSurface: openClawTargetSurfaceApi.readOpenClawTargetSurface,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export const openClawTargets = Object.freeze({
|
|
76
|
+
resolveVersion: openClawTargetApi.resolveOpenClawTargetVersion,
|
|
77
|
+
prepare: openClawTargetApi.prepareOpenClawTarget,
|
|
78
|
+
eligibilityVersion: openClawTargetApi.openClawEligibilityVersion,
|
|
79
|
+
satisfiesCompatibilityRange: openClawTargetApi.satisfiesOpenClawCompatibilityRange,
|
|
80
|
+
satisfiesRange: openClawTargetApi.satisfiesOpenClawVersionRange,
|
|
72
81
|
});
|
|
73
82
|
|
|
74
83
|
export const contracts = Object.freeze({
|
package/src/inspector.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { readdir, readFile } from "node:fs/promises";
|
|
4
|
+
import * as nodeModule from "node:module";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
7
|
import { promisify } from "node:util";
|
|
@@ -9,6 +10,7 @@ import { captureApiOptionsForPlugin } from "./capture-config.js";
|
|
|
9
10
|
import { fixtureCheckoutPath, fixtureSourceRoot } from "./config.js";
|
|
10
11
|
import { buildCompatibilityFixtureReport } from "./fixture-summary.js";
|
|
11
12
|
import { readOpenClawTargetSurface } from "./openclaw-target.js";
|
|
13
|
+
import { prepareOpenClawTarget, resolveOpenClawTargetVersion } from "./openclaw-version.js";
|
|
12
14
|
import { buildCompatibilityReport, buildReport } from "./report.js";
|
|
13
15
|
import { inspectSdkDeprecations } from "./sdk-deprecation-rules.js";
|
|
14
16
|
|
|
@@ -26,11 +28,13 @@ export async function inspectCompatibilityFixtureSet(config, options = {}) {
|
|
|
26
28
|
const { inspections, failures } = await inspectConfiguredFixtures(config, options);
|
|
27
29
|
const targetOpenClaw =
|
|
28
30
|
options.targetOpenClaw ??
|
|
29
|
-
(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
(options.openclawVersion
|
|
32
|
+
? await prepareOpenClawTarget(await resolveOpenClawTargetVersion(options.openclawVersion, options), options)
|
|
33
|
+
: await readOpenClawTargetSurface({
|
|
34
|
+
configuredPath: options.openclawPath,
|
|
35
|
+
manifest: config,
|
|
36
|
+
rootDir: config.rootDir,
|
|
37
|
+
}));
|
|
34
38
|
|
|
35
39
|
return buildCompatibilityReport({
|
|
36
40
|
config,
|
|
@@ -165,12 +169,7 @@ export function inspectSourceText(text, filePath = "source.js") {
|
|
|
165
169
|
...collectDetailedMatches(searchableText, /\b(createChatChannelPlugin)\s*\(/g, filePath, "name"),
|
|
166
170
|
...collectDetailedMatches(searchableText, /\b(definePluginEntry)\s*\(/g, filePath, "name"),
|
|
167
171
|
];
|
|
168
|
-
const sdkImports =
|
|
169
|
-
searchableText,
|
|
170
|
-
/(?:from\s*["'`]|import\(\s*["'`])([^"'`]*openclaw\/plugin-sdk[^"'`]*)/g,
|
|
171
|
-
filePath,
|
|
172
|
-
"specifier",
|
|
173
|
-
);
|
|
172
|
+
const sdkImports = collectSdkImports(searchableText, filePath);
|
|
174
173
|
const sdkDeprecations = inspectSdkDeprecations(searchableText, filePath);
|
|
175
174
|
|
|
176
175
|
return {
|
|
@@ -385,6 +384,165 @@ function collectDetailedMatches(text, regex, filePath, key) {
|
|
|
385
384
|
return details;
|
|
386
385
|
}
|
|
387
386
|
|
|
387
|
+
function collectSdkImports(text, filePath) {
|
|
388
|
+
const details = [];
|
|
389
|
+
for (const candidate of text.matchAll(/(?:^|;)[\t ]*(import|export)\b/gm)) {
|
|
390
|
+
const keyword = candidate[1];
|
|
391
|
+
const keywordIndex = (candidate.index ?? 0) + candidate[0].lastIndexOf(keyword);
|
|
392
|
+
const declaration = parseStaticModuleDeclaration(text, keywordIndex, keyword);
|
|
393
|
+
if (!declaration?.specifier.includes("openclaw/plugin-sdk")) continue;
|
|
394
|
+
if (isTypeOnlyStaticImportClause(declaration.clause)) continue;
|
|
395
|
+
const line = lineForOffset(text, keywordIndex);
|
|
396
|
+
details.push({
|
|
397
|
+
specifier: declaration.specifier,
|
|
398
|
+
file: filePath,
|
|
399
|
+
line,
|
|
400
|
+
ref: `${filePath}:${line}`,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const dynamicMatches = [...text.matchAll(/\bimport\(\s*["'`]([^"'`]*openclaw\/plugin-sdk[^"'`]*)/g)];
|
|
405
|
+
const runtimeDynamicImports = runtimeDynamicImportIndexes(text, dynamicMatches);
|
|
406
|
+
for (const [index, match] of dynamicMatches.entries()) {
|
|
407
|
+
if (!runtimeDynamicImports.has(index)) continue;
|
|
408
|
+
const line = lineForOffset(text, match.index ?? 0);
|
|
409
|
+
details.push({
|
|
410
|
+
specifier: match[1],
|
|
411
|
+
file: filePath,
|
|
412
|
+
line,
|
|
413
|
+
ref: `${filePath}:${line}`,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return details.sort((left, right) => left.line - right.line || left.specifier.localeCompare(right.specifier));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function parseStaticModuleDeclaration(text, keywordIndex, keyword) {
|
|
420
|
+
const clauseStart = keywordIndex + keyword.length;
|
|
421
|
+
let cursor = skipWhitespace(text, clauseStart);
|
|
422
|
+
if (text[cursor] === "(") return null;
|
|
423
|
+
if (keyword === "export" && text[cursor] !== "{" && text[cursor] !== "*" && !hasWordAt(text, cursor, "type")) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
let braceDepth = 0;
|
|
428
|
+
while (cursor < text.length) {
|
|
429
|
+
const char = text[cursor];
|
|
430
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
431
|
+
cursor = skipQuotedText(text, cursor, char);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (char === "{") braceDepth += 1;
|
|
435
|
+
if (char === "}") braceDepth = Math.max(0, braceDepth - 1);
|
|
436
|
+
if (char === ";" && braceDepth === 0) return null;
|
|
437
|
+
if (braceDepth === 0 && hasWordAt(text, cursor, "from")) {
|
|
438
|
+
const clause = text.slice(clauseStart, cursor).trim();
|
|
439
|
+
cursor = skipWhitespace(text, cursor + "from".length);
|
|
440
|
+
const quote = text[cursor];
|
|
441
|
+
if (quote !== '"' && quote !== "'" && quote !== "`") return null;
|
|
442
|
+
const specifierStart = cursor + 1;
|
|
443
|
+
const specifierEnd = skipQuotedText(text, cursor, quote) - 1;
|
|
444
|
+
return { clause, specifier: text.slice(specifierStart, specifierEnd) };
|
|
445
|
+
}
|
|
446
|
+
cursor += 1;
|
|
447
|
+
}
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function skipWhitespace(text, index) {
|
|
452
|
+
while (index < text.length && /\s/.test(text[index])) index += 1;
|
|
453
|
+
return index;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function hasWordAt(text, index, word) {
|
|
457
|
+
return (
|
|
458
|
+
text.startsWith(word, index) &&
|
|
459
|
+
!/[A-Za-z0-9_$]/.test(text[index - 1] ?? "") &&
|
|
460
|
+
!/[A-Za-z0-9_$]/.test(text[index + word.length] ?? "")
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function skipQuotedText(text, quoteIndex, quote) {
|
|
465
|
+
let cursor = quoteIndex + 1;
|
|
466
|
+
while (cursor < text.length) {
|
|
467
|
+
if (text[cursor] === "\\") {
|
|
468
|
+
cursor += 2;
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
cursor += 1;
|
|
472
|
+
if (text[cursor - 1] === quote) break;
|
|
473
|
+
}
|
|
474
|
+
return cursor;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function runtimeDynamicImportIndexes(text, matches) {
|
|
478
|
+
if (matches.length === 0) return new Set();
|
|
479
|
+
const markedImports = matches.map((match, index) => {
|
|
480
|
+
const specifier = match[1];
|
|
481
|
+
const specifierStart = (match.index ?? 0) + match[0].indexOf(specifier);
|
|
482
|
+
return {
|
|
483
|
+
index,
|
|
484
|
+
specifierStart,
|
|
485
|
+
specifierEnd: specifierStart + specifier.length,
|
|
486
|
+
marker: `${specifier}/__plugin_inspector_runtime_import_${index}__`,
|
|
487
|
+
};
|
|
488
|
+
});
|
|
489
|
+
let markedText = text;
|
|
490
|
+
for (const markedImport of markedImports.toReversed()) {
|
|
491
|
+
markedText =
|
|
492
|
+
markedText.slice(0, markedImport.specifierStart) +
|
|
493
|
+
markedImport.marker +
|
|
494
|
+
markedText.slice(markedImport.specifierEnd);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
try {
|
|
498
|
+
const runtimeText = eraseTypeScript(markedText);
|
|
499
|
+
if (runtimeText === null) {
|
|
500
|
+
return new Set(markedImports.map((markedImport) => markedImport.index));
|
|
501
|
+
}
|
|
502
|
+
return new Set(
|
|
503
|
+
markedImports.filter((markedImport) => runtimeText.includes(markedImport.marker)).map((markedImport) => markedImport.index),
|
|
504
|
+
);
|
|
505
|
+
} catch {
|
|
506
|
+
return new Set(markedImports.map((markedImport) => markedImport.index));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function eraseTypeScript(text) {
|
|
511
|
+
if (typeof nodeModule.stripTypeScriptTypes === "function") {
|
|
512
|
+
return nodeModule.stripTypeScriptTypes(text, { mode: "transform" });
|
|
513
|
+
}
|
|
514
|
+
if (typeof globalThis.Bun?.Transpiler === "function") {
|
|
515
|
+
const transpiler = new globalThis.Bun.Transpiler({ loader: "ts", target: "bun" });
|
|
516
|
+
return transpiler.transformSync(text);
|
|
517
|
+
}
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function isTypeOnlyStaticImportClause(clause) {
|
|
522
|
+
clause = clause.trim();
|
|
523
|
+
if (/^type\b/.test(clause)) {
|
|
524
|
+
const typeOnlyClause = clause.slice("type".length).trimStart();
|
|
525
|
+
return typeOnlyClause.length > 0 && !typeOnlyClause.startsWith(",");
|
|
526
|
+
}
|
|
527
|
+
if (!clause.startsWith("{") || !clause.endsWith("}")) {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const namedImports = clause
|
|
532
|
+
.slice(1, -1)
|
|
533
|
+
.split(",")
|
|
534
|
+
.map((specifier) => specifier.trim())
|
|
535
|
+
.filter(Boolean);
|
|
536
|
+
return namedImports.length > 0 && namedImports.every(isTypeOnlyNamedImport);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function isTypeOnlyNamedImport(specifier) {
|
|
540
|
+
const typeModifier = /^type\b/.exec(specifier);
|
|
541
|
+
if (!typeModifier) return false;
|
|
542
|
+
const importedName = specifier.slice(typeModifier[0].length).trimStart();
|
|
543
|
+
return importedName.length > 0 && !/^as\b/.test(importedName);
|
|
544
|
+
}
|
|
545
|
+
|
|
388
546
|
async function readManifestContracts(config, checkoutPath, sourceRoot) {
|
|
389
547
|
const manifests = new Set(
|
|
390
548
|
[path.join(sourceRoot, "openclaw.plugin.json"), path.join(checkoutPath, "openclaw.plugin.json")].filter(
|
|
@@ -436,6 +594,7 @@ async function readPackageMetadata(config, checkoutPath, sourceRoot) {
|
|
|
436
594
|
collectEntrypoint(entrypoints, entrypointFiles, packageDir, packageJson.openclaw?.entry);
|
|
437
595
|
collectEntrypoint(entrypoints, entrypointFiles, packageDir, packageJson.openclaw?.entrypoint);
|
|
438
596
|
collectEntrypoint(entrypoints, entrypointFiles, packageDir, packageJson.openclaw?.setupEntry);
|
|
597
|
+
collectEntrypoint(entrypoints, entrypointFiles, packageDir, packageJson.openclaw?.runtimeSetupEntry);
|
|
439
598
|
collectEntrypoint(entrypoints, entrypointFiles, packageDir, packageJson.exports?.["."]?.import);
|
|
440
599
|
collectEntrypoint(entrypoints, entrypointFiles, packageDir, packageJson.exports?.["."]?.default);
|
|
441
600
|
collectEntrypoints(entrypoints, entrypointFiles, packageDir, packageJson.openclaw?.extensions);
|
package/src/issues.js
CHANGED
|
@@ -518,7 +518,10 @@ export function buildIssues({ breakages = [], warnings = [], suggestions = [], t
|
|
|
518
518
|
runtimeCoverage: finding.runtimeCoverage ?? null,
|
|
519
519
|
...(finding.authorRemediation
|
|
520
520
|
? {
|
|
521
|
-
authorRemediation:
|
|
521
|
+
authorRemediation:
|
|
522
|
+
finding.authorRemediationDocs === false
|
|
523
|
+
? finding.authorRemediation
|
|
524
|
+
: withAuthorRemediationDocs(finding.code, finding.authorRemediation),
|
|
522
525
|
}
|
|
523
526
|
: {}),
|
|
524
527
|
}));
|
package/src/openclaw-target.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
5
|
export const defaultOpenClawCheckoutPaths = ["./openclaw", "../openclaw"];
|
|
@@ -26,6 +26,10 @@ export async function readOpenClawTargetSurface(options = {}) {
|
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
if (match.kind === "package") {
|
|
30
|
+
return readPackedOpenClawTargetSurface({ rootDir, requestedPaths, ...match });
|
|
31
|
+
}
|
|
32
|
+
|
|
29
33
|
const { requestedPath, resolvedPath, registryPath } = match;
|
|
30
34
|
const hookTypesPath = path.join(resolvedPath, "src/plugins/hook-types.ts");
|
|
31
35
|
const apiBuilderPath = path.join(resolvedPath, "src/plugins/api-builder.ts");
|
|
@@ -251,12 +255,191 @@ function findTargetCheckout(rootDir, requestedPaths) {
|
|
|
251
255
|
const resolvedPath = path.resolve(rootDir, requestedPath);
|
|
252
256
|
const registryPath = path.join(resolvedPath, "src/plugins/compat/registry.ts");
|
|
253
257
|
if (existsSync(registryPath)) {
|
|
254
|
-
return { requestedPath, resolvedPath, registryPath };
|
|
258
|
+
return { kind: "checkout", requestedPath, resolvedPath, registryPath };
|
|
259
|
+
}
|
|
260
|
+
if (existsSync(path.join(resolvedPath, "package.json")) && existsSync(path.join(resolvedPath, "dist"))) {
|
|
261
|
+
return { kind: "package", requestedPath, resolvedPath };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function readPackedOpenClawTargetSurface({ rootDir, requestedPaths, requestedPath, resolvedPath }) {
|
|
268
|
+
const packagePath = path.join(resolvedPath, "package.json");
|
|
269
|
+
const packageJson = JSON.parse(await readFile(packagePath, "utf8"));
|
|
270
|
+
if (packageJson.name !== "openclaw") {
|
|
271
|
+
return emptyTargetSurface({ configuredPath: requestedPath, searchedPaths: requestedPaths, status: "missing" });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const distPath = path.join(resolvedPath, "dist");
|
|
275
|
+
const declarationFiles = await listDeclarationFiles(distPath);
|
|
276
|
+
const declarations = [];
|
|
277
|
+
for (const filePath of declarationFiles) {
|
|
278
|
+
declarations.push({ filePath, source: await readFile(filePath, "utf8") });
|
|
279
|
+
}
|
|
280
|
+
const apiDeclaration = declarations
|
|
281
|
+
.map((declaration) => ({
|
|
282
|
+
...declaration,
|
|
283
|
+
values: parseObjectTypeFields(declaration.source, "OpenClawPluginApi", (value) => value.startsWith("register")),
|
|
284
|
+
}))
|
|
285
|
+
.sort((left, right) => right.values.length - left.values.length)[0];
|
|
286
|
+
const hookDeclaration = declarations.find((declaration) => declaration.source.includes("type PluginHookName ="));
|
|
287
|
+
const manifestDeclaration = declarations.find((declaration) => declaration.source.includes("type PluginManifest ="));
|
|
288
|
+
const manifestContractDeclaration = declarations.find((declaration) => declaration.source.includes("type PluginManifestContracts ="));
|
|
289
|
+
const apiRegistrars = apiDeclaration?.values ?? [];
|
|
290
|
+
const hookNames = hookDeclaration ? parseStringUnion(hookDeclaration.source, "PluginHookName") : [];
|
|
291
|
+
const manifestFields = manifestDeclaration
|
|
292
|
+
? parseObjectTypeFields(manifestDeclaration.source, "PluginManifest")
|
|
293
|
+
: [];
|
|
294
|
+
const manifestContractFields = manifestContractDeclaration
|
|
295
|
+
? parseObjectTypeFields(manifestContractDeclaration.source, "PluginManifestContracts")
|
|
296
|
+
: [];
|
|
297
|
+
const sdkExports = parsePluginSdkExports(packageJson);
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
configuredPath: requestedPath,
|
|
301
|
+
searchedPaths: requestedPaths,
|
|
302
|
+
status: "ok",
|
|
303
|
+
version: packageJson.version ?? null,
|
|
304
|
+
compatRegistryPath: null,
|
|
305
|
+
compatRecordCount: 0,
|
|
306
|
+
compatRecords: [],
|
|
307
|
+
compatRecordStatuses: {},
|
|
308
|
+
hookTypesPath: hookDeclaration ? relativePath(rootDir, hookDeclaration.filePath) : null,
|
|
309
|
+
hookNameCount: hookNames.length,
|
|
310
|
+
hookNames,
|
|
311
|
+
apiBuilderPath: apiDeclaration ? relativePath(rootDir, apiDeclaration.filePath) : null,
|
|
312
|
+
apiRegistrarCount: apiRegistrars.length,
|
|
313
|
+
apiRegistrars,
|
|
314
|
+
capturedRegistrationPath: apiDeclaration ? relativePath(rootDir, apiDeclaration.filePath) : null,
|
|
315
|
+
capturedRegistrarCount: apiRegistrars.length,
|
|
316
|
+
capturedRegistrars: apiRegistrars,
|
|
317
|
+
packagePath: relativePath(rootDir, packagePath),
|
|
318
|
+
sdkExportCount: sdkExports.length,
|
|
319
|
+
sdkExports,
|
|
320
|
+
pluginSdkEntrypointsPath: null,
|
|
321
|
+
reservedSdkExportCount: 0,
|
|
322
|
+
reservedSdkExports: [],
|
|
323
|
+
supportedFacadeSdkExports: [],
|
|
324
|
+
publicPluginOwnedSdkExports: [],
|
|
325
|
+
manifestTypesPath: manifestDeclaration ? relativePath(rootDir, manifestDeclaration.filePath) : null,
|
|
326
|
+
manifestFieldCount: manifestFields.length,
|
|
327
|
+
manifestFields,
|
|
328
|
+
manifestContractFieldCount: manifestContractFields.length,
|
|
329
|
+
manifestContractFields,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function listDeclarationFiles(rootDir) {
|
|
334
|
+
const files = [];
|
|
335
|
+
const entries = await readdir(rootDir, { withFileTypes: true });
|
|
336
|
+
for (const entry of entries) {
|
|
337
|
+
const entryPath = path.join(rootDir, entry.name);
|
|
338
|
+
if (entry.isDirectory()) {
|
|
339
|
+
files.push(...(await listDeclarationFiles(entryPath)));
|
|
340
|
+
} else if (entry.isFile() && entry.name.endsWith(".d.ts")) {
|
|
341
|
+
files.push(entryPath);
|
|
255
342
|
}
|
|
256
343
|
}
|
|
344
|
+
return files.sort();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function parseObjectTypeFields(source, typeName, filter = () => true) {
|
|
348
|
+
const body = readObjectTypeBody(source, typeName);
|
|
349
|
+
if (!body) return [];
|
|
350
|
+
return unique(parseTopLevelTypeProperties(body).filter(filter)).sort();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function parseTopLevelTypeProperties(body) {
|
|
354
|
+
const properties = [];
|
|
355
|
+
let braceDepth = 0;
|
|
356
|
+
let bracketDepth = 0;
|
|
357
|
+
let parenDepth = 0;
|
|
358
|
+
let propertyStart = true;
|
|
359
|
+
|
|
360
|
+
for (let index = 0; index < body.length; index += 1) {
|
|
361
|
+
const char = body[index];
|
|
362
|
+
const next = body[index + 1];
|
|
363
|
+
if (char === "/" && next === "*") {
|
|
364
|
+
index = body.indexOf("*/", index + 2);
|
|
365
|
+
if (index === -1) break;
|
|
366
|
+
index += 1;
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (char === "/" && next === "/") {
|
|
370
|
+
const newline = body.indexOf("\n", index + 2);
|
|
371
|
+
if (newline === -1) break;
|
|
372
|
+
index = newline;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
376
|
+
index = skipQuotedTypeText(body, index);
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (char === "{") braceDepth += 1;
|
|
380
|
+
else if (char === "}") braceDepth -= 1;
|
|
381
|
+
else if (char === "[") bracketDepth += 1;
|
|
382
|
+
else if (char === "]") bracketDepth -= 1;
|
|
383
|
+
else if (char === "(") parenDepth += 1;
|
|
384
|
+
else if (char === ")") parenDepth -= 1;
|
|
385
|
+
|
|
386
|
+
if (braceDepth !== 0 || bracketDepth !== 0 || parenDepth !== 0) continue;
|
|
387
|
+
if (char === ";" || char === ",") {
|
|
388
|
+
propertyStart = true;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (/\s/.test(char)) continue;
|
|
392
|
+
if (!propertyStart || !/[A-Za-z_$]/.test(char)) {
|
|
393
|
+
propertyStart = false;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const match = body.slice(index).match(/^([A-Za-z_$][A-Za-z0-9_$]*)/);
|
|
398
|
+
if (!match) {
|
|
399
|
+
propertyStart = false;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const name = match[1];
|
|
403
|
+
index += name.length - 1;
|
|
404
|
+
if (name === "readonly") continue;
|
|
405
|
+
let cursor = index + 1;
|
|
406
|
+
while (/\s/.test(body[cursor] ?? "")) cursor += 1;
|
|
407
|
+
if (body[cursor] === "?") cursor += 1;
|
|
408
|
+
while (/\s/.test(body[cursor] ?? "")) cursor += 1;
|
|
409
|
+
if (body[cursor] === ":") properties.push(name);
|
|
410
|
+
propertyStart = false;
|
|
411
|
+
}
|
|
412
|
+
return properties;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function skipQuotedTypeText(source, quoteIndex) {
|
|
416
|
+
const quote = source[quoteIndex];
|
|
417
|
+
for (let index = quoteIndex + 1; index < source.length; index += 1) {
|
|
418
|
+
if (source[index] === "\\") index += 1;
|
|
419
|
+
else if (source[index] === quote) return index;
|
|
420
|
+
}
|
|
421
|
+
return source.length - 1;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function readObjectTypeBody(source, typeName) {
|
|
425
|
+
const marker = new RegExp(`(?:export\\s+)?type\\s+${typeName}(?:\\$\\d+)?\\s*=\\s*\\{`, "g");
|
|
426
|
+
const match = marker.exec(source);
|
|
427
|
+
if (!match) return null;
|
|
428
|
+
const start = match.index + match[0].length;
|
|
429
|
+
let depth = 1;
|
|
430
|
+
for (let index = start; index < source.length; index += 1) {
|
|
431
|
+
if (source[index] === "{") depth += 1;
|
|
432
|
+
if (source[index] === "}") depth -= 1;
|
|
433
|
+
if (depth === 0) return source.slice(start, index);
|
|
434
|
+
}
|
|
257
435
|
return null;
|
|
258
436
|
}
|
|
259
437
|
|
|
438
|
+
function parseStringUnion(source, typeName) {
|
|
439
|
+
const match = source.match(new RegExp(`type\\s+${typeName}\\s*=\\s*([^;]+);`));
|
|
440
|
+
return match ? unique([...match[1].matchAll(/["']([^"']+)["']/g)].map((item) => item[1])).sort() : [];
|
|
441
|
+
}
|
|
442
|
+
|
|
260
443
|
function emptyTargetSurface({ configuredPath, searchedPaths = undefined, status }) {
|
|
261
444
|
return {
|
|
262
445
|
configuredPath,
|