@openclaw/plugin-inspector 0.3.5 → 0.3.6
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 +12 -1
- package/package.json +1 -1
- package/src/advanced.js +5 -0
- package/src/api.js +4 -0
- package/src/artifacts.js +1 -1
- package/src/ci-policy.js +3 -1
- package/src/ci-summary.js +16 -3
- package/src/compatibility-report.js +39 -3
- package/src/config.js +35 -5
- package/src/contract-probes.js +25 -0
- package/src/fixture-summary.js +406 -0
- package/src/import-loop-profile.js +227 -5
- package/src/index.js +7 -0
- package/src/inspector.js +35 -4
- package/src/issues.js +66 -1
- package/src/openclaw-target.js +79 -2
- package/src/platform-probes.js +29 -6
- package/src/prune-workspace-dev-deps-cli.js +23 -0
- package/src/report.js +19 -1
- package/src/runtime-reconciliation.js +124 -0
- package/src/sdk-mock.js +64 -6
- package/src/synthetic-probes.js +45 -0
- package/src/workspace-plan.js +19 -4
package/src/fixture-summary.js
CHANGED
|
@@ -19,9 +19,12 @@ const channelRegistrations = new Set([
|
|
|
19
19
|
"registerChannel",
|
|
20
20
|
]);
|
|
21
21
|
const hostLinkedRuntimeDependencies = new Set(["openclaw"]);
|
|
22
|
+
const unsupportedSecurityManifestName = "openclaw.security.json";
|
|
23
|
+
const unavailableSecurityManifestSchema = "https://openclaw.ai/schemas/plugin-security.json";
|
|
22
24
|
|
|
23
25
|
export async function buildCompatibilityFixtureReport({ fixture, inspection, checkoutPath, sourceRoot, rootDir = process.cwd() }) {
|
|
24
26
|
const pluginManifests = await readPluginManifests({ checkoutPath, sourceRoot, rootDir });
|
|
27
|
+
const securityManifests = await readSecurityManifests({ checkoutPath, sourceRoot, rootDir });
|
|
25
28
|
const packageSummaries = await readPackageSummaries({ checkoutPath, sourceRoot, rootDir });
|
|
26
29
|
const packageJson = selectPrimaryPackage(packageSummaries);
|
|
27
30
|
const sdkImports = unique((inspection.sdkImports ?? []).map((sdkImport) => sdkImport.specifier));
|
|
@@ -41,6 +44,7 @@ export async function buildCompatibilityFixtureReport({ fixture, inspection, che
|
|
|
41
44
|
manifestFiles: inspection.manifestFiles ?? [],
|
|
42
45
|
sourceFiles: inspection.sourceFiles ?? [],
|
|
43
46
|
pluginManifests,
|
|
47
|
+
securityManifests,
|
|
44
48
|
package: packageJson,
|
|
45
49
|
packages: packageSummaries,
|
|
46
50
|
sdkImports,
|
|
@@ -74,6 +78,46 @@ export async function readPluginManifests({ checkoutPath, sourceRoot, rootDir =
|
|
|
74
78
|
return manifests;
|
|
75
79
|
}
|
|
76
80
|
|
|
81
|
+
export async function readSecurityManifests({ checkoutPath, sourceRoot, rootDir = process.cwd() }) {
|
|
82
|
+
const candidates = unique(
|
|
83
|
+
[
|
|
84
|
+
path.join(sourceRoot, unsupportedSecurityManifestName),
|
|
85
|
+
path.join(checkoutPath, unsupportedSecurityManifestName),
|
|
86
|
+
].filter(existsSync),
|
|
87
|
+
);
|
|
88
|
+
const manifests = [];
|
|
89
|
+
|
|
90
|
+
for (const manifestPath of candidates) {
|
|
91
|
+
const relativePath = path.relative(rootDir, manifestPath);
|
|
92
|
+
try {
|
|
93
|
+
const manifest = await readJsonFile(manifestPath);
|
|
94
|
+
manifests.push({
|
|
95
|
+
path: relativePath,
|
|
96
|
+
schema: typeof manifest.$schema === "string" ? manifest.$schema : null,
|
|
97
|
+
version: typeof manifest.version === "string" ? manifest.version : null,
|
|
98
|
+
plugin: typeof manifest.plugin === "string" ? manifest.plugin : null,
|
|
99
|
+
expectedBehaviorCount: Array.isArray(manifest.expectedBehaviors)
|
|
100
|
+
? manifest.expectedBehaviors.length
|
|
101
|
+
: 0,
|
|
102
|
+
securityNoteCount: Array.isArray(manifest.securityNotes) ? manifest.securityNotes.length : 0,
|
|
103
|
+
validJson: true,
|
|
104
|
+
});
|
|
105
|
+
} catch {
|
|
106
|
+
manifests.push({
|
|
107
|
+
path: relativePath,
|
|
108
|
+
schema: null,
|
|
109
|
+
version: null,
|
|
110
|
+
plugin: null,
|
|
111
|
+
expectedBehaviorCount: 0,
|
|
112
|
+
securityNoteCount: 0,
|
|
113
|
+
validJson: false,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return manifests;
|
|
119
|
+
}
|
|
120
|
+
|
|
77
121
|
export async function readPackageSummaries({ checkoutPath, sourceRoot, rootDir = process.cwd(), maxDepth = 3 }) {
|
|
78
122
|
const candidates = unique([
|
|
79
123
|
path.join(sourceRoot, "package.json"),
|
|
@@ -108,6 +152,9 @@ export function summarizePackage(packagePath, packageJson, options = {}) {
|
|
|
108
152
|
typeof packageJson.openclaw.build?.pluginSdkVersion === "string"
|
|
109
153
|
? packageJson.openclaw.build.pluginSdkVersion
|
|
110
154
|
: null,
|
|
155
|
+
install: summarizeOpenClawInstall(packageJson.openclaw.install),
|
|
156
|
+
release: summarizeOpenClawRelease(packageJson.openclaw.release),
|
|
157
|
+
unsupportedMetadata: unsupportedOpenClawPackageMetadata(packageJson.openclaw),
|
|
111
158
|
}
|
|
112
159
|
: null;
|
|
113
160
|
|
|
@@ -121,6 +168,7 @@ export function summarizePackage(packagePath, packageJson, options = {}) {
|
|
|
121
168
|
version: packageJson.version ?? null,
|
|
122
169
|
type: packageJson.type ?? null,
|
|
123
170
|
main: typeof packageJson.main === "string" ? packageJson.main : null,
|
|
171
|
+
npmPack: summarizeNpmPack(packageJson, openclaw),
|
|
124
172
|
dependencies: Object.keys(packageJson.dependencies ?? {}).sort(),
|
|
125
173
|
peerDependencies: Object.keys(packageJson.peerDependencies ?? {}).sort(),
|
|
126
174
|
optionalDependencies: Object.keys(packageJson.optionalDependencies ?? {}).sort(),
|
|
@@ -202,6 +250,79 @@ export function classifyPackageContracts({ fixture, inspection, fixtureReport })
|
|
|
202
250
|
});
|
|
203
251
|
}
|
|
204
252
|
|
|
253
|
+
if ((packageSummary.openclaw?.unsupportedMetadata ?? []).length > 0) {
|
|
254
|
+
warnings.push({
|
|
255
|
+
fixture: fixture.id,
|
|
256
|
+
code: "package-openclaw-unsupported-metadata",
|
|
257
|
+
level: "warning",
|
|
258
|
+
message: "package declares unsupported OpenClaw metadata",
|
|
259
|
+
evidence: packageSummary.openclaw.unsupportedMetadata,
|
|
260
|
+
});
|
|
261
|
+
decisions.push({
|
|
262
|
+
fixture: fixture.id,
|
|
263
|
+
decision: "plugin-upstream-fix",
|
|
264
|
+
seam: "package-metadata",
|
|
265
|
+
action: "Remove unsupported OpenClaw metadata; native plugins use openclaw.plugin.json plus supported package openclaw fields.",
|
|
266
|
+
evidence: packageSummary.openclaw.unsupportedMetadata.join(", "),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const installMetadataIssues = packageInstallMetadataIssues(packageSummary);
|
|
271
|
+
if (installMetadataIssues.length > 0) {
|
|
272
|
+
warnings.push({
|
|
273
|
+
fixture: fixture.id,
|
|
274
|
+
code: "package-install-metadata-incomplete",
|
|
275
|
+
level: "warning",
|
|
276
|
+
message: "package OpenClaw install metadata does not match advertised release targets",
|
|
277
|
+
evidence: installMetadataIssues,
|
|
278
|
+
});
|
|
279
|
+
decisions.push({
|
|
280
|
+
fixture: fixture.id,
|
|
281
|
+
decision: "plugin-upstream-fix",
|
|
282
|
+
seam: "package-metadata",
|
|
283
|
+
action: "Ask the plugin to align openclaw.install metadata with openclaw.release publishing targets.",
|
|
284
|
+
evidence: installMetadataIssues.join(", "),
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (packageMinHostVersionDrift(packageSummary)) {
|
|
289
|
+
warnings.push({
|
|
290
|
+
fixture: fixture.id,
|
|
291
|
+
code: "package-min-host-version-drift",
|
|
292
|
+
level: "warning",
|
|
293
|
+
message: "package openclaw.install.minHostVersion is not a semver floor for the target OpenClaw build version",
|
|
294
|
+
evidence: [
|
|
295
|
+
`minHostVersion:${packageSummary.openclaw.install.minHostVersion}`,
|
|
296
|
+
`buildOpenClawVersion:${packageSummary.openclaw.buildOpenClawVersion}`,
|
|
297
|
+
],
|
|
298
|
+
});
|
|
299
|
+
decisions.push({
|
|
300
|
+
fixture: fixture.id,
|
|
301
|
+
decision: "plugin-upstream-fix",
|
|
302
|
+
seam: "package-metadata",
|
|
303
|
+
action: "Ask the plugin to publish install.minHostVersion as a semver floor for the OpenClaw package surface it targets.",
|
|
304
|
+
evidence: packageSummary.path,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const npmPackIssues = packageNpmPackIssues(packageSummary, fixtureReport);
|
|
309
|
+
for (const finding of npmPackIssues) {
|
|
310
|
+
warnings.push({
|
|
311
|
+
fixture: fixture.id,
|
|
312
|
+
code: finding.code,
|
|
313
|
+
level: "warning",
|
|
314
|
+
message: finding.message,
|
|
315
|
+
evidence: finding.evidence,
|
|
316
|
+
});
|
|
317
|
+
decisions.push({
|
|
318
|
+
fixture: fixture.id,
|
|
319
|
+
decision: "plugin-upstream-fix",
|
|
320
|
+
seam: "package-artifact",
|
|
321
|
+
action: "Ask the plugin to make its advertised npm install artifact match the published OpenClaw metadata.",
|
|
322
|
+
evidence: finding.evidence.join(", "),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
205
326
|
if (packageSummary.openclaw && packageSummary.openclaw.entrypoints.length === 0) {
|
|
206
327
|
warnings.push({
|
|
207
328
|
fixture: fixture.id,
|
|
@@ -351,6 +472,7 @@ export function classifyCompatibilityFixture({ fixture, inspection, fixtureRepor
|
|
|
351
472
|
suggestions.push(...packageContracts.suggestions);
|
|
352
473
|
logs.push(...packageContracts.logs);
|
|
353
474
|
decisions.push(...packageContracts.decisions);
|
|
475
|
+
classifySecurityManifestCoverage({ fixture, fixtureReport, warnings, decisions });
|
|
354
476
|
|
|
355
477
|
for (const pluginManifest of fixtureReport.pluginManifests) {
|
|
356
478
|
const providerAuthKeys = Object.keys(pluginManifest.providerAuthEnvVars ?? {});
|
|
@@ -557,6 +679,45 @@ export function classifyCompatibilityFixture({ fixture, inspection, fixtureRepor
|
|
|
557
679
|
return { warnings, suggestions, logs, decisions };
|
|
558
680
|
}
|
|
559
681
|
|
|
682
|
+
function classifySecurityManifestCoverage({ fixture, fixtureReport, warnings, decisions }) {
|
|
683
|
+
for (const securityManifest of fixtureReport.securityManifests ?? []) {
|
|
684
|
+
warnings.push({
|
|
685
|
+
fixture: fixture.id,
|
|
686
|
+
code: "unrecognized-security-manifest",
|
|
687
|
+
level: "warning",
|
|
688
|
+
message:
|
|
689
|
+
"openclaw.security.json is not a supported OpenClaw or ClawHub security contract and is ignored by install safety checks",
|
|
690
|
+
evidence: [securityManifest.path],
|
|
691
|
+
});
|
|
692
|
+
decisions.push({
|
|
693
|
+
fixture: fixture.id,
|
|
694
|
+
decision: "plugin-upstream-fix",
|
|
695
|
+
seam: "security-metadata",
|
|
696
|
+
action:
|
|
697
|
+
"Remove the advisory security manifest or replace it with a supported, versioned OpenClaw/ClawHub security contract once one exists.",
|
|
698
|
+
evidence: securityManifest.path,
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
if (securityManifest.schema === unavailableSecurityManifestSchema) {
|
|
702
|
+
warnings.push({
|
|
703
|
+
fixture: fixture.id,
|
|
704
|
+
code: "security-manifest-schema-unavailable",
|
|
705
|
+
level: "warning",
|
|
706
|
+
message: "openclaw.security.json references an OpenClaw schema URL that is not currently published",
|
|
707
|
+
evidence: [`${securityManifest.path}:$schema=${securityManifest.schema}`],
|
|
708
|
+
});
|
|
709
|
+
decisions.push({
|
|
710
|
+
fixture: fixture.id,
|
|
711
|
+
decision: "plugin-upstream-fix",
|
|
712
|
+
seam: "security-metadata",
|
|
713
|
+
action:
|
|
714
|
+
"Do not rely on the schema URL until OpenClaw publishes and documents a real plugin security metadata schema.",
|
|
715
|
+
evidence: securityManifest.schema,
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
560
721
|
function registrationCaptureGapDetails(inspection, targetOpenClaw) {
|
|
561
722
|
const apiRegistrationDetails = inspection.registrationDetails.filter((registration) =>
|
|
562
723
|
registration.name.startsWith("register"),
|
|
@@ -787,6 +948,235 @@ function selectPrimaryPackage(packages) {
|
|
|
787
948
|
return packages[0] ?? null;
|
|
788
949
|
}
|
|
789
950
|
|
|
951
|
+
function summarizeOpenClawInstall(install) {
|
|
952
|
+
if (!install || typeof install !== "object") {
|
|
953
|
+
return null;
|
|
954
|
+
}
|
|
955
|
+
return {
|
|
956
|
+
clawhubSpec: stringOrNull(install.clawhubSpec),
|
|
957
|
+
npmSpec: stringOrNull(install.npmSpec),
|
|
958
|
+
defaultChoice: stringOrNull(install.defaultChoice),
|
|
959
|
+
minHostVersion: stringOrNull(install.minHostVersion),
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function summarizeOpenClawRelease(release) {
|
|
964
|
+
if (!release || typeof release !== "object") {
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
return {
|
|
968
|
+
publishToClawHub: booleanOrNull(release.publishToClawHub),
|
|
969
|
+
publishToNpm: booleanOrNull(release.publishToNpm),
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function unsupportedOpenClawPackageMetadata(openclaw) {
|
|
974
|
+
if (!openclaw || typeof openclaw !== "object") {
|
|
975
|
+
return [];
|
|
976
|
+
}
|
|
977
|
+
return Object.keys(openclaw)
|
|
978
|
+
.filter((key) => key === "bundle")
|
|
979
|
+
.map((key) => `openclaw.${key}`);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function summarizeNpmPack(packageJson, openclaw) {
|
|
983
|
+
const files = arrayValues(packageJson.files).map(normalizePackagePath).filter((item) => item.length > 0);
|
|
984
|
+
return {
|
|
985
|
+
advertised: openclaw?.release?.publishToNpm === true || nonEmptyString(openclaw?.install?.npmSpec),
|
|
986
|
+
private: packageJson.private === true,
|
|
987
|
+
filesMode: files.length > 0 ? "allowlist" : "implicit",
|
|
988
|
+
files,
|
|
989
|
+
invalidFileSpecs: files.filter((item) => invalidPackageFileSpec(item)),
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function packageInstallMetadataIssues(packageSummary) {
|
|
994
|
+
const openclaw = packageSummary.openclaw;
|
|
995
|
+
if (!openclaw) {
|
|
996
|
+
return [];
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
const issues = [];
|
|
1000
|
+
const install = openclaw.install;
|
|
1001
|
+
const release = openclaw.release;
|
|
1002
|
+
const publishToClawHub = release?.publishToClawHub === true;
|
|
1003
|
+
const publishToNpm = release?.publishToNpm === true;
|
|
1004
|
+
|
|
1005
|
+
if (publishToClawHub && !nonEmptyString(install?.clawhubSpec)) {
|
|
1006
|
+
issues.push("openclaw.release.publishToClawHub requires openclaw.install.clawhubSpec");
|
|
1007
|
+
}
|
|
1008
|
+
if (publishToNpm && !nonEmptyString(install?.npmSpec)) {
|
|
1009
|
+
issues.push("openclaw.release.publishToNpm requires openclaw.install.npmSpec");
|
|
1010
|
+
}
|
|
1011
|
+
if (publishToNpm && nonEmptyString(install?.npmSpec) && nonEmptyString(packageSummary.name) && install.npmSpec !== packageSummary.name) {
|
|
1012
|
+
issues.push(`openclaw.install.npmSpec:${install.npmSpec} does not match package name:${packageSummary.name}`);
|
|
1013
|
+
}
|
|
1014
|
+
if (nonEmptyString(install?.defaultChoice) && !["clawhub", "npm"].includes(install.defaultChoice)) {
|
|
1015
|
+
issues.push(`openclaw.install.defaultChoice:${install.defaultChoice} must be clawhub or npm`);
|
|
1016
|
+
}
|
|
1017
|
+
if (install?.defaultChoice === "clawhub" && !nonEmptyString(install.clawhubSpec)) {
|
|
1018
|
+
issues.push("openclaw.install.defaultChoice clawhub requires openclaw.install.clawhubSpec");
|
|
1019
|
+
}
|
|
1020
|
+
if (install?.defaultChoice === "npm" && !nonEmptyString(install.npmSpec)) {
|
|
1021
|
+
issues.push("openclaw.install.defaultChoice npm requires openclaw.install.npmSpec");
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
return issues;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
function packageNpmPackIssues(packageSummary, fixtureReport) {
|
|
1028
|
+
if (!packageSummary.npmPack?.advertised) {
|
|
1029
|
+
return [];
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
const findings = [];
|
|
1033
|
+
const unavailable = [];
|
|
1034
|
+
if (packageSummary.npmPack.private) {
|
|
1035
|
+
unavailable.push("package.json private:true");
|
|
1036
|
+
}
|
|
1037
|
+
if (!nonEmptyString(packageSummary.name)) {
|
|
1038
|
+
unavailable.push("package.json name missing");
|
|
1039
|
+
}
|
|
1040
|
+
if (!nonEmptyString(packageSummary.version)) {
|
|
1041
|
+
unavailable.push("package.json version missing");
|
|
1042
|
+
}
|
|
1043
|
+
unavailable.push(...packageSummary.npmPack.invalidFileSpecs.map((item) => `invalid files entry:${item}`));
|
|
1044
|
+
|
|
1045
|
+
if (unavailable.length > 0) {
|
|
1046
|
+
findings.push({
|
|
1047
|
+
code: "package-npm-pack-unavailable",
|
|
1048
|
+
message: "package advertises npm install or publish metadata but cannot produce a usable npm pack artifact",
|
|
1049
|
+
evidence: unavailable,
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
const missingMetadata = packageNpmPackMissingMetadata(packageSummary, fixtureReport);
|
|
1054
|
+
if (missingMetadata.length > 0) {
|
|
1055
|
+
findings.push({
|
|
1056
|
+
code: "package-npm-pack-metadata-missing",
|
|
1057
|
+
message: "advertised npm artifact would not include required OpenClaw package metadata",
|
|
1058
|
+
evidence: missingMetadata,
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const missingEntrypoints = packageSummary.openclaw?.entrypoints
|
|
1063
|
+
.filter((entrypoint) => !repoPathIncludedInNpmPack(packageSummary, entrypoint.relativePath))
|
|
1064
|
+
.map((entrypoint) => `${entrypoint.kind}:${entrypoint.specifier} -> ${entrypoint.relativePath}`) ?? [];
|
|
1065
|
+
if (missingEntrypoints.length > 0) {
|
|
1066
|
+
findings.push({
|
|
1067
|
+
code: "package-npm-pack-entrypoint-missing",
|
|
1068
|
+
message: "advertised npm artifact would not include declared OpenClaw entrypoints",
|
|
1069
|
+
evidence: missingEntrypoints,
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
return findings;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function packageNpmPackMissingMetadata(packageSummary, fixtureReport) {
|
|
1077
|
+
const missing = [];
|
|
1078
|
+
if (!repoPathIncludedInNpmPack(packageSummary, packageSummary.path)) {
|
|
1079
|
+
missing.push(packageSummary.path);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
for (const manifest of fixtureReport.pluginManifests ?? []) {
|
|
1083
|
+
if (repoPathWithinPackage(packageSummary, manifest.path) && !repoPathIncludedInNpmPack(packageSummary, manifest.path)) {
|
|
1084
|
+
missing.push(manifest.path);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
return missing;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
function packageMinHostVersionDrift(packageSummary) {
|
|
1092
|
+
const openclaw = packageSummary.openclaw;
|
|
1093
|
+
if (!nonEmptyString(openclaw?.install?.minHostVersion) || !nonEmptyString(openclaw?.buildOpenClawVersion)) {
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
return parseMinHostVersionFloor(openclaw.install.minHostVersion) !== openclaw.buildOpenClawVersion;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
function parseMinHostVersionFloor(value) {
|
|
1100
|
+
const match = /^>=([0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/.exec(value);
|
|
1101
|
+
return match?.[1] ?? null;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
function repoPathIncludedInNpmPack(packageSummary, repoPath) {
|
|
1105
|
+
const packageRelativePath = packageRelativeRepoPath(packageSummary, repoPath);
|
|
1106
|
+
if (!packageRelativePath) {
|
|
1107
|
+
return false;
|
|
1108
|
+
}
|
|
1109
|
+
if (npmAlwaysPacksPath(packageRelativePath)) {
|
|
1110
|
+
return true;
|
|
1111
|
+
}
|
|
1112
|
+
if (packageSummary.npmPack?.filesMode !== "allowlist") {
|
|
1113
|
+
return true;
|
|
1114
|
+
}
|
|
1115
|
+
return packageSummary.npmPack.files.some((spec) => packageFileSpecIncludesPath(spec, packageRelativePath));
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
function repoPathWithinPackage(packageSummary, repoPath) {
|
|
1119
|
+
return packageRelativeRepoPath(packageSummary, repoPath) !== null;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
function packageRelativeRepoPath(packageSummary, repoPath) {
|
|
1123
|
+
const packageDir = path.posix.dirname(normalizeRepoPath(packageSummary.path));
|
|
1124
|
+
const normalized = normalizeRepoPath(repoPath);
|
|
1125
|
+
if (packageDir === ".") {
|
|
1126
|
+
return normalized;
|
|
1127
|
+
}
|
|
1128
|
+
if (normalized === packageDir) {
|
|
1129
|
+
return "";
|
|
1130
|
+
}
|
|
1131
|
+
return normalized.startsWith(`${packageDir}/`) ? normalized.slice(packageDir.length + 1) : null;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function npmAlwaysPacksPath(packageRelativePath) {
|
|
1135
|
+
const base = path.posix.basename(packageRelativePath).toLowerCase();
|
|
1136
|
+
return packageRelativePath === "package.json" || /^readme(?:\..*)?$/u.test(base) || /^licen[cs]e(?:\..*)?$/u.test(base);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
function packageFileSpecIncludesPath(spec, packageRelativePath) {
|
|
1140
|
+
if (spec === "." || spec === packageRelativePath) {
|
|
1141
|
+
return true;
|
|
1142
|
+
}
|
|
1143
|
+
if (spec.includes("*")) {
|
|
1144
|
+
return globLikeSpecIncludesPath(spec, packageRelativePath);
|
|
1145
|
+
}
|
|
1146
|
+
return packageRelativePath.startsWith(`${spec.replace(/\/$/u, "")}/`);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function globLikeSpecIncludesPath(spec, packageRelativePath) {
|
|
1150
|
+
return globSegmentsIncludePath(spec.split("/"), packageRelativePath.split("/"));
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function globSegmentsIncludePath(specSegments, packageSegments) {
|
|
1154
|
+
if (specSegments.length === 0) {
|
|
1155
|
+
return packageSegments.length === 0;
|
|
1156
|
+
}
|
|
1157
|
+
const [head, ...tail] = specSegments;
|
|
1158
|
+
if (head === "**") {
|
|
1159
|
+
return globSegmentsIncludePath(tail, packageSegments) || (packageSegments.length > 0 && globSegmentsIncludePath(specSegments, packageSegments.slice(1)));
|
|
1160
|
+
}
|
|
1161
|
+
if (packageSegments.length === 0) {
|
|
1162
|
+
return false;
|
|
1163
|
+
}
|
|
1164
|
+
return globSegmentIncludesPath(head, packageSegments[0]) && globSegmentsIncludePath(tail, packageSegments.slice(1));
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function globSegmentIncludesPath(specSegment, packageSegment) {
|
|
1168
|
+
const escaped = specSegment.replace(/[.+?^${}()|[\]\\]/gu, "\\$&").replaceAll("*", ".*");
|
|
1169
|
+
return new RegExp(`^${escaped}$`, "u").test(packageSegment);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
function invalidPackageFileSpec(spec) {
|
|
1173
|
+
return spec.startsWith("/") || spec === ".." || spec.startsWith("../") || spec.includes("/../");
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
function normalizePackagePath(value) {
|
|
1177
|
+
return normalizeRepoPath(value).replace(/^\.\/+/u, "").replace(/\/$/u, "");
|
|
1178
|
+
}
|
|
1179
|
+
|
|
790
1180
|
function packageRank(packageSummary) {
|
|
791
1181
|
if (packageSummary.openclaw?.entrypoints.length > 0) {
|
|
792
1182
|
return 0;
|
|
@@ -801,6 +1191,22 @@ function arrayValues(value) {
|
|
|
801
1191
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
802
1192
|
}
|
|
803
1193
|
|
|
1194
|
+
function stringOrNull(value) {
|
|
1195
|
+
return typeof value === "string" ? value : null;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function booleanOrNull(value) {
|
|
1199
|
+
return typeof value === "boolean" ? value : null;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function nonEmptyString(value) {
|
|
1203
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function normalizeRepoPath(value) {
|
|
1207
|
+
return String(value ?? "").replaceAll("\\", "/").replace(/^\.\/+/u, "");
|
|
1208
|
+
}
|
|
1209
|
+
|
|
804
1210
|
function detailEvidence(details, key = "name") {
|
|
805
1211
|
return unique(details.map((detail) => `${detail[key]} @ ${detail.ref}`));
|
|
806
1212
|
}
|