metascope 0.6.3 → 0.7.1
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/bin/chunk-Bdh3yLIe.js +1 -0
- package/dist/bin/cli.js +282 -281
- package/dist/bin/dns-CUigd8AG.js +1 -0
- package/dist/bin/{jiti-D2Njwwqq.js → jiti-BTBDwj9g.js} +2 -2
- package/dist/bin/renovate-DecR7mby.js +1 -0
- package/dist/bin/shared-C_yS1qGF.js +5 -0
- package/dist/lib/file-matching.js +20 -13
- package/dist/lib/log.d.ts +6 -4
- package/dist/lib/log.js +5 -3
- package/dist/lib/metadata-types.d.ts +33 -21
- package/dist/lib/metadata-types.js +8 -9
- package/dist/lib/metadata.js +7 -7
- package/dist/lib/package.js +1 -1
- package/dist/lib/parsers/gemspec-parser.js +16 -14
- package/dist/lib/parsers/go-mod-parser.js +13 -10
- package/dist/lib/parsers/makefile-config-parser.js +20 -17
- package/dist/lib/parsers/properties-parser.js +3 -3
- package/dist/lib/parsers/rfc822-header-parser.js +4 -4
- package/dist/lib/parsers/setup-py-parser.js +3 -4
- package/dist/lib/source.d.ts +1 -0
- package/dist/lib/source.js +5 -4
- package/dist/lib/sources/arduino-library-properties.js +32 -15
- package/dist/lib/sources/cinder-cinderblock-xml.js +21 -9
- package/dist/lib/sources/codemeta-json.js +24 -20
- package/dist/lib/sources/dependency-updates.js +12 -23
- package/dist/lib/sources/git-stats.js +1 -1
- package/dist/lib/sources/github-actions.js +9 -2
- package/dist/lib/sources/github.js +1 -1
- package/dist/lib/sources/go-goreleaser-yaml.js +5 -4
- package/dist/lib/sources/java-pom-xml.js +38 -19
- package/dist/lib/sources/license-file.d.ts +1 -4
- package/dist/lib/sources/license-file.js +1 -4
- package/dist/lib/sources/metadata-file.js +15 -12
- package/dist/lib/sources/node-npm-registry.js +1 -1
- package/dist/lib/sources/openframeworks-addon-config-mk.js +7 -0
- package/dist/lib/sources/openframeworks-install-xml.js +24 -14
- package/dist/lib/sources/processing-library-properties.js +25 -10
- package/dist/lib/sources/processing-sketch-properties.js +51 -7
- package/dist/lib/sources/publiccode-yaml.js +37 -5
- package/dist/lib/sources/python-pkg-info.js +2 -2
- package/dist/lib/sources/python-setup-cfg.js +2 -2
- package/dist/lib/sources/readme-file.js +9 -2
- package/dist/lib/sources/ruby-gemspec.js +1 -0
- package/dist/lib/sources/rust-cargo-toml.js +29 -10
- package/dist/lib/sources/xcode-info-plist.js +48 -24
- package/dist/lib/sources/xcode-project-pbxproj.js +22 -12
- package/dist/lib/templates/codemeta-json.d.ts +4 -5
- package/dist/lib/templates/codemeta-json.js +4 -5
- package/dist/lib/templates/codemeta.js +30 -30
- package/dist/lib/templates/frontmatter.d.ts +1 -1
- package/dist/lib/templates/index.d.ts +1 -1
- package/dist/lib/templates/metadata.d.ts +2 -2
- package/dist/lib/templates/metadata.js +3 -3
- package/dist/lib/utilities/codemeta-helpers.d.ts +3 -2
- package/dist/lib/utilities/codemeta-helpers.js +14 -12
- package/dist/lib/utilities/fetch.js +2 -2
- package/dist/lib/utilities/formatting.js +4 -3
- package/dist/lib/utilities/github.js +3 -3
- package/dist/lib/utilities/license-identification.d.ts +25 -0
- package/dist/lib/utilities/license-identification.js +132 -29
- package/dist/lib/utilities/schema-primitives.js +11 -10
- package/dist/lib/utilities/template-helpers.js +3 -3
- package/package.json +19 -18
- package/readme.md +23 -19
- package/dist/.DS_Store +0 -0
- package/dist/bin/chunk-BjEoQXZ0.js +0 -1
|
@@ -32,24 +32,40 @@ const CANONICAL_CATEGORIES = [
|
|
|
32
32
|
const CATEGORY_MAP = new Map(CANONICAL_CATEGORIES.map((cat) => [cat.replaceAll(/[^a-z]/gi, "").toLowerCase(), cat]));
|
|
33
33
|
CATEGORY_MAP.set("sensor", "Sensors");
|
|
34
34
|
const arduinoLibraryPropertiesSchema = z.object({
|
|
35
|
+
/** Comma-separated architecture identifiers, or ["*"] for all. */
|
|
35
36
|
architectures: stringArray,
|
|
37
|
+
/** Parsed author entries. */
|
|
36
38
|
authors: z.array(arduinoLibraryPropertiesPersonEntrySchema),
|
|
39
|
+
/** Normalized category, or undefined if invalid/empty. */
|
|
37
40
|
category: z.enum(CANONICAL_CATEGORIES).optional(),
|
|
41
|
+
/** Parsed dependency entries. */
|
|
38
42
|
depends: z.array(arduinoLibraryPropertiesDependencyEntrySchema),
|
|
43
|
+
/** Top-level email field (non-standard, used in some fixtures). */
|
|
39
44
|
email: nonEmptyString,
|
|
45
|
+
/** Comma-separated header files. */
|
|
40
46
|
includes: stringArray,
|
|
47
|
+
/** License value (non-standard field). */
|
|
41
48
|
license: nonEmptyString,
|
|
49
|
+
/** Parsed maintainer entry. */
|
|
42
50
|
maintainer: arduinoLibraryPropertiesPersonEntrySchema.optional(),
|
|
51
|
+
/** Library name. */
|
|
43
52
|
name: nonEmptyString,
|
|
53
|
+
/** Extended description paragraph. */
|
|
44
54
|
paragraph: nonEmptyString,
|
|
55
|
+
/** Raw key-value pairs. */
|
|
45
56
|
raw: z.record(z.string(), z.string()),
|
|
57
|
+
/** Repository URL (non-standard field). */
|
|
46
58
|
repository: optionalUrl,
|
|
59
|
+
/** One-sentence description. */
|
|
47
60
|
sentence: nonEmptyString,
|
|
61
|
+
/** Project URL. */
|
|
48
62
|
url: optionalUrl,
|
|
63
|
+
/** Library version. */
|
|
49
64
|
version: nonEmptyString
|
|
50
65
|
});
|
|
51
66
|
/**
|
|
52
|
-
* Parse an Arduino `library.properties` content string into a structured
|
|
67
|
+
* Parse an Arduino `library.properties` content string into a structured
|
|
68
|
+
* object.
|
|
53
69
|
*/
|
|
54
70
|
function parse(content) {
|
|
55
71
|
const raw = parseProperties(content);
|
|
@@ -78,19 +94,20 @@ function get(raw, key) {
|
|
|
78
94
|
}
|
|
79
95
|
/** Return a trimmed string, or undefined if empty/whitespace-only. */
|
|
80
96
|
function nonEmpty(value) {
|
|
81
|
-
if (value === void 0) return
|
|
97
|
+
if (value === void 0) return;
|
|
82
98
|
const trimmed = value.trim();
|
|
83
99
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
84
100
|
}
|
|
85
101
|
/**
|
|
86
|
-
* Normalize a category string to the canonical Arduino category.
|
|
87
|
-
*
|
|
88
|
-
*
|
|
102
|
+
* Normalize a category string to the canonical Arduino category. Strips all
|
|
103
|
+
* non-letter characters, lowercases, and matches against the canonical map. For
|
|
104
|
+
* comma-separated values (e.g. "Sensors, Timing"), takes the first valid
|
|
105
|
+
* match.
|
|
89
106
|
*/
|
|
90
107
|
function normalizeCategory(value) {
|
|
91
|
-
if (value === void 0) return
|
|
108
|
+
if (value === void 0) return;
|
|
92
109
|
const trimmed = value.trim();
|
|
93
|
-
if (trimmed.length === 0) return
|
|
110
|
+
if (trimmed.length === 0) return;
|
|
94
111
|
for (const part of trimmed.split(",")) {
|
|
95
112
|
const key = part.replaceAll(/[^a-z]/gi, "").toLowerCase();
|
|
96
113
|
if (key.length === 0) continue;
|
|
@@ -128,9 +145,9 @@ function parsePersonList(value) {
|
|
|
128
145
|
return results;
|
|
129
146
|
}
|
|
130
147
|
/**
|
|
131
|
-
* Parse a comma-separated list of dependencies with optional version
|
|
132
|
-
* e.g. "ArduinoHttpClient (>=1.0.0), ArduinoJson" →
|
|
133
|
-
*
|
|
148
|
+
* Parse a comma-separated list of dependencies with optional version
|
|
149
|
+
* constraints. e.g. "ArduinoHttpClient (>=1.0.0), ArduinoJson" → [{ name:
|
|
150
|
+
* "ArduinoHttpClient", versionConstraint: ">=1.0.0" }, ...]
|
|
134
151
|
*/
|
|
135
152
|
function parseDependencies(value) {
|
|
136
153
|
const trimmed = value.trim();
|
|
@@ -162,8 +179,8 @@ function parseDependencies(value) {
|
|
|
162
179
|
return results;
|
|
163
180
|
}
|
|
164
181
|
/**
|
|
165
|
-
* Arduino-specific fields that distinguish library.properties from
|
|
166
|
-
*
|
|
182
|
+
* Arduino-specific fields that distinguish library.properties from Processing's
|
|
183
|
+
* identically-named format.
|
|
167
184
|
*/
|
|
168
185
|
const ARDUINO_SPECIFIC_FIELDS = new Set([
|
|
169
186
|
"architectures",
|
|
@@ -178,9 +195,9 @@ const PROCESSING_EXCLUSIVE_FIELDS = new Set([
|
|
|
178
195
|
"prettyversion"
|
|
179
196
|
]);
|
|
180
197
|
/**
|
|
181
|
-
* Validate that a library.properties file is Arduino (not Processing).
|
|
182
|
-
*
|
|
183
|
-
*
|
|
198
|
+
* Validate that a library.properties file is Arduino (not Processing). Requires
|
|
199
|
+
* name=, version=, author= and either an Arduino-specific field or no
|
|
200
|
+
* Processing-exclusive fields.
|
|
184
201
|
*/
|
|
185
202
|
function isArduinoLibraryProperties(content) {
|
|
186
203
|
const raw = parseProperties(content);
|
|
@@ -18,16 +18,27 @@ import { XMLParser } from "fast-xml-parser";
|
|
|
18
18
|
* Uses `fast-xml-parser` with attribute parsing enabled.
|
|
19
19
|
*/
|
|
20
20
|
const cinderCinderblockSchema = z.object({
|
|
21
|
+
/** Block author name(s), split from comma-separated values. */
|
|
21
22
|
author: stringArray,
|
|
23
|
+
/** Git repository URL. */
|
|
22
24
|
git: optionalUrl,
|
|
25
|
+
/** Block identifier (e.g. "info.v002.syphon"). */
|
|
23
26
|
id: nonEmptyString,
|
|
27
|
+
/** Library or libraryUrl reference link. */
|
|
24
28
|
library: optionalUrl,
|
|
29
|
+
/** License identifier. */
|
|
25
30
|
license: nonEmptyString,
|
|
31
|
+
/** Block display name. */
|
|
26
32
|
name: nonEmptyString,
|
|
33
|
+
/** Software dependencies from `<requires>` elements. */
|
|
27
34
|
requires: stringArray,
|
|
35
|
+
/** Block summary / description. */
|
|
28
36
|
summary: nonEmptyString,
|
|
37
|
+
/** Supported operating systems from `<supports os="...">` elements. */
|
|
29
38
|
supports: stringArray,
|
|
39
|
+
/** Project URL. */
|
|
30
40
|
url: optionalUrl,
|
|
41
|
+
/** Version string. */
|
|
31
42
|
version: nonEmptyString
|
|
32
43
|
});
|
|
33
44
|
/**
|
|
@@ -40,8 +51,8 @@ const OS_MAP = {
|
|
|
40
51
|
msw: "Windows"
|
|
41
52
|
};
|
|
42
53
|
/**
|
|
43
|
-
* Parse a `cinderblock.xml` content string into a structured object.
|
|
44
|
-
*
|
|
54
|
+
* Parse a `cinderblock.xml` content string into a structured object. Returns
|
|
55
|
+
* undefined if the XML is malformed or missing the expected structure.
|
|
45
56
|
*/
|
|
46
57
|
function parse(content) {
|
|
47
58
|
const parser = new XMLParser({
|
|
@@ -52,14 +63,14 @@ function parse(content) {
|
|
|
52
63
|
let data;
|
|
53
64
|
try {
|
|
54
65
|
const parsed = parser.parse(content);
|
|
55
|
-
if (!is.plainObject(parsed)) return
|
|
66
|
+
if (!is.plainObject(parsed)) return;
|
|
56
67
|
data = parsed;
|
|
57
68
|
} catch {
|
|
58
69
|
return;
|
|
59
70
|
}
|
|
60
|
-
if (!is.plainObject(data.cinder)) return
|
|
71
|
+
if (!is.plainObject(data.cinder)) return;
|
|
61
72
|
const { cinder } = data;
|
|
62
|
-
if (!is.plainObject(cinder.block)) return
|
|
73
|
+
if (!is.plainObject(cinder.block)) return;
|
|
63
74
|
const { block } = cinder;
|
|
64
75
|
return cinderCinderblockSchema.parse({
|
|
65
76
|
author: splitCommaSeparated(getAttribute(block, "author")),
|
|
@@ -76,17 +87,18 @@ function parse(content) {
|
|
|
76
87
|
});
|
|
77
88
|
}
|
|
78
89
|
/**
|
|
79
|
-
* Get a trimmed string attribute from a parsed XML element.
|
|
80
|
-
*
|
|
90
|
+
* Get a trimmed string attribute from a parsed XML element. fast-xml-parser
|
|
91
|
+
* stores attributes with the `@_` prefix.
|
|
81
92
|
*/
|
|
82
93
|
function getAttribute(element, name) {
|
|
83
94
|
const value = element[`@_${name}`];
|
|
84
|
-
if (typeof value !== "string") return
|
|
95
|
+
if (typeof value !== "string") return;
|
|
85
96
|
const trimmed = value.trim();
|
|
86
97
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
87
98
|
}
|
|
88
99
|
/**
|
|
89
|
-
* Extract deduplicated, mapped operating system names from `<supports
|
|
100
|
+
* Extract deduplicated, mapped operating system names from `<supports
|
|
101
|
+
* os="...">` elements.
|
|
90
102
|
*/
|
|
91
103
|
function parseOperatingSystems(block) {
|
|
92
104
|
const results = [];
|
|
@@ -12,15 +12,17 @@ import { z } from "zod";
|
|
|
12
12
|
* CodeMeta JSON metadata source.
|
|
13
13
|
*
|
|
14
14
|
* Reads a codemeta.json file (v1, v2, or v3) and normalizes it into a
|
|
15
|
-
* consistent shape with predictable types. No enrichment, no JSON-LD
|
|
16
|
-
*
|
|
15
|
+
* consistent shape with predictable types. No enrichment, no JSON-LD expansion
|
|
16
|
+
* — just an honest representation of what's in the file.
|
|
17
17
|
*
|
|
18
18
|
* Uses Zod preprocess schemas to handle v1/v2/v3 normalization:
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* -
|
|
19
|
+
*
|
|
20
|
+
* - `@`-prefixed JSON-LD keys stripped, v1 property names remapped
|
|
21
|
+
* - `{"@type": "xsd:anyURI", "@value": "..."}` unwrapped to plain strings
|
|
22
|
+
* - Person/org objects normalized (`@type`→`type`, `@id`→`id`,
|
|
23
|
+
* affiliation-as-object)
|
|
24
|
+
* - Comma-separated strings split to arrays, `{"name":"..."}` objects unwrapped
|
|
25
|
+
* - Dependencies normalized (string → `{name: string}`)
|
|
24
26
|
*/
|
|
25
27
|
/**
|
|
26
28
|
* A string that also unwraps `{"@value": "..."}` JSON-LD typed values.
|
|
@@ -37,9 +39,11 @@ const codeMetaUrl = z.preprocess((value) => {
|
|
|
37
39
|
}, optionalUrl);
|
|
38
40
|
/**
|
|
39
41
|
* A string array that handles CodeMeta's polymorphic inputs:
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
42
|
+
*
|
|
43
|
+
* - Single comma-separated string → split to array (common in v1)
|
|
44
|
+
* - Array of strings → pass through
|
|
45
|
+
* - Array of `{"name":"..."}` objects → extract name strings (e.g.
|
|
46
|
+
* programmingLanguage)
|
|
43
47
|
*/
|
|
44
48
|
const codeMetaStringArray = z.preprocess((value) => {
|
|
45
49
|
if (value === void 0 || value === null) return;
|
|
@@ -72,9 +76,9 @@ const codeMetaPersonOrOrgSchema = z.object({
|
|
|
72
76
|
url: optionalUrl
|
|
73
77
|
});
|
|
74
78
|
/**
|
|
75
|
-
* Preprocess a raw person/org value into a normalized shape.
|
|
76
|
-
*
|
|
77
|
-
* affiliation
|
|
79
|
+
* Preprocess a raw person/org value into a normalized shape. Handles: plain
|
|
80
|
+
* string → {name: string}, `@type`→`type`, `@id`→`id`, affiliation-as-object →
|
|
81
|
+
* affiliation string.
|
|
78
82
|
*/
|
|
79
83
|
function preprocessPersonOrOrg(value) {
|
|
80
84
|
if (typeof value === "string") return { name: value };
|
|
@@ -96,8 +100,8 @@ function preprocessPersonOrOrg(value) {
|
|
|
96
100
|
if (result.name ?? result.givenName ?? result.familyName ?? result.email) return result;
|
|
97
101
|
}
|
|
98
102
|
/**
|
|
99
|
-
* A person/org array that normalizes single values to arrays,
|
|
100
|
-
*
|
|
103
|
+
* A person/org array that normalizes single values to arrays, and each element
|
|
104
|
+
* through `preprocessPersonOrOrg`.
|
|
101
105
|
*/
|
|
102
106
|
const codeMetaPersonArray = z.preprocess((value) => {
|
|
103
107
|
if (value === void 0 || value === null) return;
|
|
@@ -111,8 +115,8 @@ const codeMetaDependencySchema = z.object({
|
|
|
111
115
|
version: nonEmptyString
|
|
112
116
|
});
|
|
113
117
|
/**
|
|
114
|
-
* Preprocess a raw dependency value into a normalized shape.
|
|
115
|
-
*
|
|
118
|
+
* Preprocess a raw dependency value into a normalized shape. Handles: plain
|
|
119
|
+
* string → {name: string}.
|
|
116
120
|
*/
|
|
117
121
|
function preprocessDependency(value) {
|
|
118
122
|
if (typeof value === "string") return { name: value };
|
|
@@ -125,8 +129,8 @@ function preprocessDependency(value) {
|
|
|
125
129
|
if (dep.name ?? dep.identifier) return dep;
|
|
126
130
|
}
|
|
127
131
|
/**
|
|
128
|
-
* A dependency array that normalizes single values to arrays,
|
|
129
|
-
*
|
|
132
|
+
* A dependency array that normalizes single values to arrays, and each element
|
|
133
|
+
* through `preprocessDependency`.
|
|
130
134
|
*/
|
|
131
135
|
const codeMetaDependencyArray = z.preprocess((value) => {
|
|
132
136
|
if (value === void 0 || value === null) return;
|
|
@@ -191,7 +195,7 @@ const v1PropertyMap = {
|
|
|
191
195
|
*/
|
|
192
196
|
function parse(content) {
|
|
193
197
|
const raw = parseJsonRecord(content);
|
|
194
|
-
if (!raw) return
|
|
198
|
+
if (!raw) return;
|
|
195
199
|
const migrated = migrateV1Properties(raw);
|
|
196
200
|
if (migrated.datePublished === void 0 && typeof migrated.dateReleased === "string") migrated.datePublished = migrated.dateReleased;
|
|
197
201
|
return codeMetaJsonDataSchema.parse(migrated);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { log } from "../log.js";
|
|
2
2
|
import { defineSource } from "../source.js";
|
|
3
|
-
import { exec } from "tinyexec";
|
|
4
3
|
import { z } from "zod";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
4
|
import { coerce, diff } from "semver";
|
|
5
|
+
import { updates } from "updates";
|
|
7
6
|
//#region src/lib/sources/dependency-updates.ts
|
|
8
7
|
const AGE_VALUE_UNIT_REGEX = /^(\d+)\s+(\w+)$/;
|
|
9
8
|
const depSchema = z.object({
|
|
@@ -14,19 +13,11 @@ const depSchema = z.object({
|
|
|
14
13
|
});
|
|
15
14
|
const updatesOutputSchema = z.object({ results: z.record(z.string(), z.record(z.string(), z.record(z.string(), depSchema))) });
|
|
16
15
|
/**
|
|
17
|
-
*
|
|
16
|
+
* Parse an age string from the `updates` CLI (via the `timerel` library) into
|
|
17
|
+
* fractional years.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*/
|
|
22
|
-
function resolveUpdatesBinary() {
|
|
23
|
-
return fileURLToPath(import.meta.resolve("updates/dist/index.js"));
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Parse an age string from the `updates` CLI (via the `timerel` library) into fractional years.
|
|
27
|
-
*
|
|
28
|
-
* Possible formats: "now", "<n> sec(s)", "<n> min(s)", "<n> hour(s)",
|
|
29
|
-
* "<n> day(s)", "<n> week(s)", "<n> month(s)", "<n> year(s)"
|
|
19
|
+
* Possible formats: "now", "<n> sec(s)", "<n> min(s)", "<n> hour(s)", "<n>
|
|
20
|
+
* day(s)", "<n> week(s)", "<n> month(s)", "<n> year(s)"
|
|
30
21
|
*/
|
|
31
22
|
function parseAgeToYears(age) {
|
|
32
23
|
if (age === "now") return 0;
|
|
@@ -52,8 +43,8 @@ function parseAgeToYears(age) {
|
|
|
52
43
|
}
|
|
53
44
|
}
|
|
54
45
|
/**
|
|
55
|
-
* Classify a version bump as major, minor, or patch using semver.
|
|
56
|
-
*
|
|
46
|
+
* Classify a version bump as major, minor, or patch using semver. Falls back to
|
|
47
|
+
* 'major' for non-semver versions (e.g. GitHub Actions tags).
|
|
57
48
|
*/
|
|
58
49
|
function classifyBump(oldVersion, newVersion) {
|
|
59
50
|
const oldSemver = coerce(oldVersion);
|
|
@@ -72,15 +63,13 @@ const dependencyUpdatesSource = defineSource({
|
|
|
72
63
|
key: "dependencyUpdates",
|
|
73
64
|
async parse(input) {
|
|
74
65
|
log.debug("Extracting dependency update information via updates...");
|
|
75
|
-
const result = await
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"--json"
|
|
80
|
-
]);
|
|
66
|
+
const result = await updates({
|
|
67
|
+
files: [input],
|
|
68
|
+
json: true
|
|
69
|
+
});
|
|
81
70
|
let parsed;
|
|
82
71
|
try {
|
|
83
|
-
parsed = updatesOutputSchema.parse(
|
|
72
|
+
parsed = updatesOutputSchema.parse(result);
|
|
84
73
|
} catch {
|
|
85
74
|
log.debug("No dependency files found for updates analysis.");
|
|
86
75
|
return;
|
|
@@ -39,12 +39,19 @@ const workflowRunConclusion = z.enum([
|
|
|
39
39
|
"timed_out"
|
|
40
40
|
]);
|
|
41
41
|
const githubActionSchema = z.object({
|
|
42
|
+
/** Relative path to the workflow file. */
|
|
42
43
|
file: z.string(),
|
|
44
|
+
/** ISO 8601 timestamp of the latest completed run on the default branch. */
|
|
43
45
|
lastRunAt: z.string().optional(),
|
|
46
|
+
/** Conclusion of the latest run on the default branch. */
|
|
44
47
|
lastRunConclusion: workflowRunConclusion.optional(),
|
|
48
|
+
/** Duration of the latest run in milliseconds. */
|
|
45
49
|
lastRunDurationMs: z.number().optional(),
|
|
50
|
+
/** Status of the latest run on the default branch. */
|
|
46
51
|
lastRunStatus: workflowRunStatus.optional(),
|
|
52
|
+
/** URL of the latest run on GitHub. */
|
|
47
53
|
lastRunUrl: z.string().optional(),
|
|
54
|
+
/** Workflow name from the `name` field. */
|
|
48
55
|
name: z.string()
|
|
49
56
|
});
|
|
50
57
|
/** Resolve owner/repo from git config remotes in context. */
|
|
@@ -93,7 +100,7 @@ async function fetchWorkflowRuns(octokit, owner, repo, defaultBranch) {
|
|
|
93
100
|
const githubActionsSource = {
|
|
94
101
|
async extract(context) {
|
|
95
102
|
const inputs = await getMatches(context.options, [".github/workflows/*.{yml,yaml}"]);
|
|
96
|
-
if (inputs.length === 0) return
|
|
103
|
+
if (inputs.length === 0) return;
|
|
97
104
|
const records = [];
|
|
98
105
|
for (const input of inputs) try {
|
|
99
106
|
const parsed = parse(await readFile(input, "utf8"));
|
|
@@ -112,7 +119,7 @@ const githubActionsSource = {
|
|
|
112
119
|
} catch (error) {
|
|
113
120
|
log.warn(`Failed to parse workflow "${input}": ${error instanceof Error ? error.message : String(error)}`);
|
|
114
121
|
}
|
|
115
|
-
if (records.length === 0) return
|
|
122
|
+
if (records.length === 0) return;
|
|
116
123
|
if (context.options.offline) log.debug("Skipping GitHub Actions run data (offline mode)");
|
|
117
124
|
else try {
|
|
118
125
|
const ownerRepo = await resolveOwnerRepo(context);
|
|
@@ -218,7 +218,7 @@ async function checkHasPages(octokit, owner, repo) {
|
|
|
218
218
|
}
|
|
219
219
|
async function getUpstreamComparison(octokit, owner, repo, defaultBranch, parent) {
|
|
220
220
|
const parentBranch = parent.defaultBranchRef?.name;
|
|
221
|
-
if (!parentBranch) return
|
|
221
|
+
if (!parentBranch) return;
|
|
222
222
|
try {
|
|
223
223
|
const response = await octokit.rest.repos.compareCommitsWithBasehead({
|
|
224
224
|
basehead: `${parent.owner.login}:${parentBranch}...${owner}:${defaultBranch}`,
|
|
@@ -43,7 +43,8 @@ function isNonEmptyString(value) {
|
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
45
|
* Get the first non-empty string value of a given field from an array of
|
|
46
|
-
* package-manager section entries. Skips Go template strings (containing
|
|
46
|
+
* package-manager section entries. Skips Go template strings (containing
|
|
47
|
+
* `{{`).
|
|
47
48
|
*/
|
|
48
49
|
function firstString(sections, field) {
|
|
49
50
|
for (const section of sections) if (isPlainObject(section)) {
|
|
@@ -52,8 +53,8 @@ function firstString(sections, field) {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
/**
|
|
55
|
-
* Collect all section entries for a given key, handling both v1 singular
|
|
56
|
-
*
|
|
56
|
+
* Collect all section entries for a given key, handling both v1 singular and v2
|
|
57
|
+
* plural forms.
|
|
57
58
|
*/
|
|
58
59
|
function collectSections(data, ...keys) {
|
|
59
60
|
const result = [];
|
|
@@ -78,7 +79,7 @@ function parse$1(source) {
|
|
|
78
79
|
} catch {
|
|
79
80
|
return;
|
|
80
81
|
}
|
|
81
|
-
if (!isPlainObject(data)) return
|
|
82
|
+
if (!isPlainObject(data)) return;
|
|
82
83
|
const result = {
|
|
83
84
|
description: void 0,
|
|
84
85
|
homepage: void 0,
|
|
@@ -38,28 +38,46 @@ const pomXmlOrganizationSchema = z.object({
|
|
|
38
38
|
url: z.string().optional()
|
|
39
39
|
});
|
|
40
40
|
const pomXmlSchema = z.object({
|
|
41
|
+
/** Maven artifactId. */
|
|
41
42
|
artifactId: nonEmptyString,
|
|
43
|
+
/** CI management URL. */
|
|
42
44
|
ciManagementUrl: optionalUrl,
|
|
45
|
+
/** Project contributors. */
|
|
43
46
|
contributors: z.array(pomXmlPersonEntrySchema),
|
|
47
|
+
/** Runtime dependencies (non-test scope). */
|
|
44
48
|
dependencies: z.array(pomXmlDependencyEntrySchema),
|
|
49
|
+
/** Project description. */
|
|
45
50
|
description: nonEmptyString,
|
|
51
|
+
/** Test-scope dependencies. */
|
|
46
52
|
devDependencies: z.array(pomXmlDependencyEntrySchema),
|
|
53
|
+
/** Project developers / authors. */
|
|
47
54
|
developers: z.array(pomXmlPersonEntrySchema),
|
|
55
|
+
/** Maven groupId. */
|
|
48
56
|
groupId: nonEmptyString,
|
|
57
|
+
/** Combined identifier (groupId.artifactId). */
|
|
49
58
|
identifier: nonEmptyString,
|
|
59
|
+
/** Year of project inception. */
|
|
50
60
|
inceptionYear: nonEmptyString,
|
|
61
|
+
/** Issue tracker URL. */
|
|
51
62
|
issueManagementUrl: optionalUrl,
|
|
63
|
+
/** Java version from properties. */
|
|
52
64
|
javaVersion: nonEmptyString,
|
|
65
|
+
/** Project licenses. */
|
|
53
66
|
licenses: z.array(pomXmlLicenseEntrySchema),
|
|
67
|
+
/** Project name (with Maven variables resolved). */
|
|
54
68
|
name: nonEmptyString,
|
|
69
|
+
/** Producer organization. */
|
|
55
70
|
organization: pomXmlOrganizationSchema.optional(),
|
|
71
|
+
/** SCM (source code management) URL. */
|
|
56
72
|
scmUrl: optionalUrl,
|
|
73
|
+
/** Project URL / homepage. */
|
|
57
74
|
url: optionalUrl,
|
|
75
|
+
/** Project version. */
|
|
58
76
|
version: nonEmptyString
|
|
59
77
|
});
|
|
60
78
|
/**
|
|
61
|
-
* Parse a Maven `pom.xml` content string into a structured object.
|
|
62
|
-
*
|
|
79
|
+
* Parse a Maven `pom.xml` content string into a structured object. Returns
|
|
80
|
+
* undefined if the XML is malformed or missing the `<project>` root element.
|
|
63
81
|
*/
|
|
64
82
|
function parse(content) {
|
|
65
83
|
const parser = new XMLParser({
|
|
@@ -70,12 +88,12 @@ function parse(content) {
|
|
|
70
88
|
let data;
|
|
71
89
|
try {
|
|
72
90
|
const parsed = parser.parse(content);
|
|
73
|
-
if (!is.plainObject(parsed)) return
|
|
91
|
+
if (!is.plainObject(parsed)) return;
|
|
74
92
|
data = parsed;
|
|
75
93
|
} catch {
|
|
76
94
|
return;
|
|
77
95
|
}
|
|
78
|
-
if (!is.plainObject(data.project)) return
|
|
96
|
+
if (!is.plainObject(data.project)) return;
|
|
79
97
|
const { project } = data;
|
|
80
98
|
const groupId = getString(project.groupId);
|
|
81
99
|
const artifactId = getString(project.artifactId);
|
|
@@ -102,13 +120,13 @@ function parse(content) {
|
|
|
102
120
|
});
|
|
103
121
|
}
|
|
104
122
|
/**
|
|
105
|
-
* Get a trimmed non-empty string from a parsed XML value.
|
|
106
|
-
*
|
|
123
|
+
* Get a trimmed non-empty string from a parsed XML value. Returns undefined for
|
|
124
|
+
* empty strings, non-strings, whitespace-only, or Maven variable references.
|
|
107
125
|
*/
|
|
108
126
|
function getString(value) {
|
|
109
|
-
if (typeof value !== "string") return
|
|
127
|
+
if (typeof value !== "string") return;
|
|
110
128
|
const trimmed = value.trim();
|
|
111
|
-
if (trimmed.length === 0) return
|
|
129
|
+
if (trimmed.length === 0) return;
|
|
112
130
|
return trimmed;
|
|
113
131
|
}
|
|
114
132
|
/**
|
|
@@ -116,7 +134,7 @@ function getString(value) {
|
|
|
116
134
|
*/
|
|
117
135
|
function getCleanString(value) {
|
|
118
136
|
const s = getString(value);
|
|
119
|
-
if (s?.includes("$")) return
|
|
137
|
+
if (s?.includes("$")) return;
|
|
120
138
|
return s;
|
|
121
139
|
}
|
|
122
140
|
/**
|
|
@@ -124,18 +142,19 @@ function getCleanString(value) {
|
|
|
124
142
|
*/
|
|
125
143
|
function resolveName(project, groupId, artifactId) {
|
|
126
144
|
const name = getString(project.name);
|
|
127
|
-
if (!name) return
|
|
145
|
+
if (!name) return;
|
|
128
146
|
let resolved = name;
|
|
129
147
|
if (groupId) resolved = resolved.replaceAll("${project.groupId}", groupId);
|
|
130
148
|
if (artifactId) resolved = resolved.replaceAll("${project.artifactId}", artifactId);
|
|
131
149
|
return resolved;
|
|
132
150
|
}
|
|
133
151
|
/**
|
|
134
|
-
* Extract a URL from a nested object (e.g.
|
|
135
|
-
* Filters out Maven variable
|
|
152
|
+
* Extract a URL from a nested object (e.g.
|
|
153
|
+
* `<ciManagement><url>...</url></ciManagement>`). Filters out Maven variable
|
|
154
|
+
* references.
|
|
136
155
|
*/
|
|
137
156
|
function getNestedUrl(container) {
|
|
138
|
-
if (!is.plainObject(container)) return
|
|
157
|
+
if (!is.plainObject(container)) return;
|
|
139
158
|
return getCleanString(container.url);
|
|
140
159
|
}
|
|
141
160
|
/**
|
|
@@ -207,27 +226,27 @@ function parseDependencies(project) {
|
|
|
207
226
|
* Parse SCM URL, filtering out Maven variable references.
|
|
208
227
|
*/
|
|
209
228
|
function parseScmUrl(project) {
|
|
210
|
-
if (!is.plainObject(project.scm)) return
|
|
229
|
+
if (!is.plainObject(project.scm)) return;
|
|
211
230
|
return getCleanString(project.scm.url);
|
|
212
231
|
}
|
|
213
232
|
/**
|
|
214
233
|
* Parse organization from `<organization>` element.
|
|
215
234
|
*/
|
|
216
235
|
function parseOrganization(project) {
|
|
217
|
-
if (!is.plainObject(project.organization)) return
|
|
236
|
+
if (!is.plainObject(project.organization)) return;
|
|
218
237
|
const name = getString(project.organization.name);
|
|
219
|
-
if (!name) return
|
|
238
|
+
if (!name) return;
|
|
220
239
|
return {
|
|
221
240
|
name,
|
|
222
241
|
url: getString(project.organization.url)
|
|
223
242
|
};
|
|
224
243
|
}
|
|
225
244
|
/**
|
|
226
|
-
* Extract Java version from project properties.
|
|
227
|
-
*
|
|
245
|
+
* Extract Java version from project properties. Checks `java.version`,
|
|
246
|
+
* `maven.compiler.source`, and `java.compiler.source`.
|
|
228
247
|
*/
|
|
229
248
|
function parseJavaVersion(project) {
|
|
230
|
-
if (!is.plainObject(project.properties)) return
|
|
249
|
+
if (!is.plainObject(project.properties)) return;
|
|
231
250
|
return getCleanString(project.properties["java.version"]) ?? getCleanString(project.properties["maven.compiler.source"]) ?? getCleanString(project.properties["java.compiler.source"]);
|
|
232
251
|
}
|
|
233
252
|
const javaPomXmlSource = defineSource({
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { OneOrMany, SourceRecord } from "../source.js";
|
|
2
|
+
import { LicenseMatch } from "../utilities/license-identification.js";
|
|
2
3
|
|
|
3
4
|
//#region src/lib/sources/license-file.d.ts
|
|
4
|
-
type LicenseMatch = {
|
|
5
|
-
/** Match confidence between 0 and 1. */confidence: number; /** SPDX license identifier (e.g. "MIT"). */
|
|
6
|
-
spdxId: string;
|
|
7
|
-
};
|
|
8
5
|
type LicenseFileData = OneOrMany<SourceRecord<LicenseMatch>> | undefined;
|
|
9
6
|
//#endregion
|
|
10
7
|
export { LicenseFileData };
|
|
@@ -13,10 +13,7 @@ const licenseFileSource = defineSource({
|
|
|
13
13
|
const match = identifyLicense(await readFile(resolve(context.options.path, input), "utf8"));
|
|
14
14
|
if (!match) return;
|
|
15
15
|
return {
|
|
16
|
-
data:
|
|
17
|
-
confidence: match.confidence,
|
|
18
|
-
spdxId: match.spdxId
|
|
19
|
-
},
|
|
16
|
+
data: match,
|
|
20
17
|
source: input
|
|
21
18
|
};
|
|
22
19
|
},
|
|
@@ -9,27 +9,30 @@ import { z } from "zod";
|
|
|
9
9
|
import { parse } from "yaml";
|
|
10
10
|
//#region src/lib/sources/metadata-file.ts
|
|
11
11
|
/**
|
|
12
|
-
* Source and parser for custom `metadata.json` / `metadata.yaml` /
|
|
12
|
+
* Source and parser for custom `metadata.json` / `metadata.yaml` /
|
|
13
|
+
* `metadata.yml` files.
|
|
13
14
|
*
|
|
14
|
-
* This is a simple custom format with synonymous field names for common
|
|
15
|
-
*
|
|
15
|
+
* This is a simple custom format with synonymous field names for common project
|
|
16
|
+
* metadata. It supports JSON and YAML formats.
|
|
16
17
|
*
|
|
17
|
-
* Field mapping (with fallback chains):
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* keywords | tags | topics → keywords
|
|
21
|
-
* repository (normalized) → repository
|
|
18
|
+
* Field mapping (with fallback chains): description → description homepage |
|
|
19
|
+
* url | repository (normalized) | website → homepage keywords | tags | topics →
|
|
20
|
+
* keywords repository (normalized) → repository
|
|
22
21
|
*/
|
|
23
22
|
const metadataSchema = z.object({
|
|
23
|
+
/** Project description. */
|
|
24
24
|
description: nonEmptyString,
|
|
25
|
+
/** Project homepage URL (resolved from homepage, url, repository, or website). */
|
|
25
26
|
homepage: optionalUrl,
|
|
27
|
+
/** Keyword list (resolved from keywords, tags, or topics). */
|
|
26
28
|
keywords: stringArray,
|
|
29
|
+
/** Repository URL (normalized, stripped of git+ prefix and .git suffix). */
|
|
27
30
|
repository: optionalUrl
|
|
28
31
|
});
|
|
29
32
|
/**
|
|
30
|
-
* Parse a metadata file content string into a structured object.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
+
* Parse a metadata file content string into a structured object. The `format`
|
|
34
|
+
* parameter determines whether to parse as JSON or YAML. Returns undefined if
|
|
35
|
+
* the content is malformed or not an object.
|
|
33
36
|
*/
|
|
34
37
|
function parse$1(content, format) {
|
|
35
38
|
let data;
|
|
@@ -53,7 +56,7 @@ function parse$1(content, format) {
|
|
|
53
56
|
}
|
|
54
57
|
/** Return a trimmed string if the value is a non-empty string, else undefined. */
|
|
55
58
|
function nonEmpty(value) {
|
|
56
|
-
if (typeof value !== "string") return
|
|
59
|
+
if (typeof value !== "string") return;
|
|
57
60
|
const trimmed = value.trim();
|
|
58
61
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
59
62
|
}
|
|
@@ -64,7 +64,7 @@ const npmDownloadsSchema = z.object({ downloads: z.number() });
|
|
|
64
64
|
async function fetchDownloads(packageName, period) {
|
|
65
65
|
try {
|
|
66
66
|
const response = await fetchWithRetry(`https://api.npmjs.org/downloads/point/${period}/${encodeURIComponent(packageName)}`);
|
|
67
|
-
if (!response.ok) return
|
|
67
|
+
if (!response.ok) return;
|
|
68
68
|
return npmDownloadsSchema.parse(await response.json()).downloads;
|
|
69
69
|
} catch {
|
|
70
70
|
return;
|