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.
Files changed (66) hide show
  1. package/dist/bin/chunk-Bdh3yLIe.js +1 -0
  2. package/dist/bin/cli.js +282 -281
  3. package/dist/bin/dns-CUigd8AG.js +1 -0
  4. package/dist/bin/{jiti-D2Njwwqq.js → jiti-BTBDwj9g.js} +2 -2
  5. package/dist/bin/renovate-DecR7mby.js +1 -0
  6. package/dist/bin/shared-C_yS1qGF.js +5 -0
  7. package/dist/lib/file-matching.js +20 -13
  8. package/dist/lib/log.d.ts +6 -4
  9. package/dist/lib/log.js +5 -3
  10. package/dist/lib/metadata-types.d.ts +33 -21
  11. package/dist/lib/metadata-types.js +8 -9
  12. package/dist/lib/metadata.js +7 -7
  13. package/dist/lib/package.js +1 -1
  14. package/dist/lib/parsers/gemspec-parser.js +16 -14
  15. package/dist/lib/parsers/go-mod-parser.js +13 -10
  16. package/dist/lib/parsers/makefile-config-parser.js +20 -17
  17. package/dist/lib/parsers/properties-parser.js +3 -3
  18. package/dist/lib/parsers/rfc822-header-parser.js +4 -4
  19. package/dist/lib/parsers/setup-py-parser.js +3 -4
  20. package/dist/lib/source.d.ts +1 -0
  21. package/dist/lib/source.js +5 -4
  22. package/dist/lib/sources/arduino-library-properties.js +32 -15
  23. package/dist/lib/sources/cinder-cinderblock-xml.js +21 -9
  24. package/dist/lib/sources/codemeta-json.js +24 -20
  25. package/dist/lib/sources/dependency-updates.js +12 -23
  26. package/dist/lib/sources/git-stats.js +1 -1
  27. package/dist/lib/sources/github-actions.js +9 -2
  28. package/dist/lib/sources/github.js +1 -1
  29. package/dist/lib/sources/go-goreleaser-yaml.js +5 -4
  30. package/dist/lib/sources/java-pom-xml.js +38 -19
  31. package/dist/lib/sources/license-file.d.ts +1 -4
  32. package/dist/lib/sources/license-file.js +1 -4
  33. package/dist/lib/sources/metadata-file.js +15 -12
  34. package/dist/lib/sources/node-npm-registry.js +1 -1
  35. package/dist/lib/sources/openframeworks-addon-config-mk.js +7 -0
  36. package/dist/lib/sources/openframeworks-install-xml.js +24 -14
  37. package/dist/lib/sources/processing-library-properties.js +25 -10
  38. package/dist/lib/sources/processing-sketch-properties.js +51 -7
  39. package/dist/lib/sources/publiccode-yaml.js +37 -5
  40. package/dist/lib/sources/python-pkg-info.js +2 -2
  41. package/dist/lib/sources/python-setup-cfg.js +2 -2
  42. package/dist/lib/sources/readme-file.js +9 -2
  43. package/dist/lib/sources/ruby-gemspec.js +1 -0
  44. package/dist/lib/sources/rust-cargo-toml.js +29 -10
  45. package/dist/lib/sources/xcode-info-plist.js +48 -24
  46. package/dist/lib/sources/xcode-project-pbxproj.js +22 -12
  47. package/dist/lib/templates/codemeta-json.d.ts +4 -5
  48. package/dist/lib/templates/codemeta-json.js +4 -5
  49. package/dist/lib/templates/codemeta.js +30 -30
  50. package/dist/lib/templates/frontmatter.d.ts +1 -1
  51. package/dist/lib/templates/index.d.ts +1 -1
  52. package/dist/lib/templates/metadata.d.ts +2 -2
  53. package/dist/lib/templates/metadata.js +3 -3
  54. package/dist/lib/utilities/codemeta-helpers.d.ts +3 -2
  55. package/dist/lib/utilities/codemeta-helpers.js +14 -12
  56. package/dist/lib/utilities/fetch.js +2 -2
  57. package/dist/lib/utilities/formatting.js +4 -3
  58. package/dist/lib/utilities/github.js +3 -3
  59. package/dist/lib/utilities/license-identification.d.ts +25 -0
  60. package/dist/lib/utilities/license-identification.js +132 -29
  61. package/dist/lib/utilities/schema-primitives.js +11 -10
  62. package/dist/lib/utilities/template-helpers.js +3 -3
  63. package/package.json +19 -18
  64. package/readme.md +23 -19
  65. package/dist/.DS_Store +0 -0
  66. 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 object.
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 void 0;
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
- * Strips all non-letter characters, lowercases, and matches against the canonical map.
88
- * For comma-separated values (e.g. "Sensors, Timing"), takes the first valid match.
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 void 0;
108
+ if (value === void 0) return;
92
109
  const trimmed = value.trim();
93
- if (trimmed.length === 0) return void 0;
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 constraints.
132
- * e.g. "ArduinoHttpClient (>=1.0.0), ArduinoJson" →
133
- * [{ name: "ArduinoHttpClient", versionConstraint: ">=1.0.0" }, ...]
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
- * Processing's identically-named format.
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
- * Requires name=, version=, author= and either an Arduino-specific field
183
- * or no Processing-exclusive fields.
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
- * Returns undefined if the XML is malformed or missing the expected structure.
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 void 0;
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 void 0;
71
+ if (!is.plainObject(data.cinder)) return;
61
72
  const { cinder } = data;
62
- if (!is.plainObject(cinder.block)) return void 0;
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
- * fast-xml-parser stores attributes with the `@_` prefix.
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 void 0;
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 os="...">` elements.
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
- * expansion — just an honest representation of what's in the file.
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
- * - `@`-prefixed JSON-LD keys stripped, v1 property names remapped
20
- * - `{"@type": "xsd:anyURI", "@value": "..."}` unwrapped to plain strings
21
- * - Person/org objects normalized (`@type`→`type`, `@id`→`id`, affiliation-as-object)
22
- * - Comma-separated strings split to arrays, `{"name":"..."}` objects unwrapped
23
- * - Dependencies normalized (string → `{name: string}`)
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
- * - Single comma-separated string → split to array (common in v1)
41
- * - Array of stringspass through
42
- * - Array of `{"name":"..."}` objects extract name strings (e.g. programmingLanguage)
42
+ *
43
+ * - Single comma-separated stringsplit to array (common in v1)
44
+ * - Array of stringspass 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
- * Handles: plain string → {name: string}, `@type`→`type`, `@id`→`id`,
77
- * affiliation-as-object → affiliation string.
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
- * and each element through `preprocessPersonOrOrg`.
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
- * Handles: plain string → {name: string}.
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
- * and each element through `preprocessDependency`.
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 void 0;
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
- * Resolve the path to the `updates` CLI binary.
16
+ * Parse an age string from the `updates` CLI (via the `timerel` library) into
17
+ * fractional years.
18
18
  *
19
- * Consumers that bundle metascope must externalize it (e.g. via `neverBundle`)
20
- * so that `import.meta.resolve` can find the `updates` dependency.
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
- * Falls back to 'major' for non-semver versions (e.g. GitHub Actions tags).
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 exec("node", [
76
- resolveUpdatesBinary(),
77
- "--file",
78
- input,
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(JSON.parse(result.stdout));
72
+ parsed = updatesOutputSchema.parse(result);
84
73
  } catch {
85
74
  log.debug("No dependency files found for updates analysis.");
86
75
  return;
@@ -48,7 +48,7 @@ const gitStatsSource = defineSource({
48
48
  }
49
49
  }).then((sizes) => sizes.reduce((sum, size) => sum + size, 0)),
50
50
  (async () => {
51
- if (!tagNameLatest) return void 0;
51
+ if (!tagNameLatest) return;
52
52
  try {
53
53
  return (await git.raw([
54
54
  "log",
@@ -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 void 0;
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 void 0;
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 void 0;
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
- * and v2 plural forms.
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 void 0;
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
- * Returns undefined if the XML is malformed or missing the `<project>` root element.
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 void 0;
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 void 0;
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
- * Returns undefined for empty strings, non-strings, whitespace-only, or Maven variable references.
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 void 0;
127
+ if (typeof value !== "string") return;
110
128
  const trimmed = value.trim();
111
- if (trimmed.length === 0) return void 0;
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 void 0;
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 void 0;
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. `<ciManagement><url>...</url></ciManagement>`).
135
- * Filters out Maven variable references.
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 void 0;
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 void 0;
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 void 0;
236
+ if (!is.plainObject(project.organization)) return;
218
237
  const name = getString(project.organization.name);
219
- if (!name) return void 0;
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
- * Checks `java.version`, `maven.compiler.source`, and `java.compiler.source`.
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 void 0;
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` / `metadata.yml` files.
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
- * project metadata. It supports JSON and YAML formats.
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
- * description description
19
- * homepage | url | repository (normalized) | website homepage
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
- * The `format` parameter determines whether to parse as JSON or YAML.
32
- * Returns undefined if the content is malformed or not an object.
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 void 0;
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 void 0;
67
+ if (!response.ok) return;
68
68
  return npmDownloadsSchema.parse(await response.json()).downloads;
69
69
  } catch {
70
70
  return;