metascope 0.6.3 → 0.7.0

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.
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
2
  var name = "metascope";
3
- var version = "0.6.3";
3
+ var version = "0.7.0";
4
4
  //#endregion
5
5
  export { name, version };
@@ -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;
@@ -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
  },
@@ -18,7 +18,7 @@ declare const frontmatter: Template<{
18
18
  Public: boolean;
19
19
  Fork: boolean;
20
20
  Published: boolean;
21
- Status: "author" | "maintainer" | "unknown" | "observer";
21
+ Status: "unknown" | "maintainer" | "author" | "observer";
22
22
  Tags: string[] | null;
23
23
  Aliases: (string | null | undefined)[] | null;
24
24
  License: string[] | null;
@@ -157,7 +157,7 @@ declare const templates: {
157
157
  Public: boolean;
158
158
  Fork: boolean;
159
159
  Published: boolean;
160
- Status: "author" | "maintainer" | "unknown" | "observer";
160
+ Status: "unknown" | "maintainer" | "author" | "observer";
161
161
  Tags: string[] | null;
162
162
  Aliases: (string | null | undefined)[] | null;
163
163
  License: string[] | null;
@@ -0,0 +1,23 @@
1
+ //#region src/lib/utilities/license-identification.d.ts
2
+ /**
3
+ * License identification using Dice coefficient on bigrams.
4
+ *
5
+ * Compares plain-text license file content against the full SPDX license list
6
+ * to identify the best-matching SPDX license identifier. Returns a standard
7
+ * SPDX license URL (e.g. "https://spdx.org/licenses/MIT").
8
+ *
9
+ * Handles:
10
+ *
11
+ * - Canonical SPDX / vendor URLs embedded in pointer-style license files
12
+ * - Standard license texts (MIT, BSD, Apache, etc.)
13
+ * - GNU family licenses via header pattern matching (LGPL, AGPL)
14
+ * - Markdown-formatted license files (strips headings, tables, links)
15
+ * - YAML front matter stripping
16
+ */
17
+ type LicenseMatch = {
18
+ /** Dice coefficient confidence score (0–1). */confidence: number; /** SPDX license identifier (e.g. "MIT", "Apache-2.0"). */
19
+ spdxId: string; /** SPDX license URL. */
20
+ spdxUrl: string;
21
+ };
22
+ //#endregion
23
+ export { LicenseMatch };
@@ -8,19 +8,24 @@ import spdxLicenseList from "spdx-license-list/full.js";
8
8
  * SPDX license URL (e.g. "https://spdx.org/licenses/MIT").
9
9
  *
10
10
  * Handles:
11
- * - Standard license texts (MIT, BSD, Apache, etc.)
12
- * - GNU family licenses via header pattern matching (LGPL, AGPL)
13
- * - Markdown-formatted license files (strips headings, tables, links)
14
- * - YAML front matter stripping
11
+ *
12
+ * - Canonical SPDX / vendor URLs embedded in pointer-style license files
13
+ * - Standard license texts (MIT, BSD, Apache, etc.)
14
+ * - GNU family licenses via header pattern matching (LGPL, AGPL)
15
+ * - Markdown-formatted license files (strips headings, tables, links)
16
+ * - YAML front matter stripping
15
17
  */
18
+ const SPDX_BASE_URL = "https://spdx.org/licenses/";
16
19
  /** Minimum similarity score to consider a match. */
17
20
  const CONFIDENCE_THRESHOLD = .75;
18
21
  /**
19
- * Identify the SPDX license that best matches the given text.
20
- * Returns the best match with confidence score, or undefined if no match
21
- * exceeds the confidence threshold.
22
+ * Identify the SPDX license that best matches the given text. Returns the best
23
+ * match with confidence score, or undefined if no match exceeds the confidence
24
+ * threshold.
22
25
  */
23
26
  function identifyLicense(text) {
27
+ const urlMatch = identifyByUrl(text);
28
+ if (urlMatch) return urlMatch;
24
29
  const headerMatch = identifyByHeader(text);
25
30
  if (headerMatch) return headerMatch;
26
31
  const normalizedInput = normalizeInput(text);
@@ -29,17 +34,19 @@ function identifyLicense(text) {
29
34
  const inputTotal = normalizedInput.length - 1;
30
35
  let bestMatch;
31
36
  let bestScore = 0;
32
- for (const { bigramsMap, normalized, spdxId, totalBigrams } of getNormalizedLicenses()) {
37
+ for (const { bigramsMap, normalized, spdxId, spdxUrl, totalBigrams } of getNormalizedLicenses()) {
33
38
  if (normalizedInput === normalized) return {
34
39
  confidence: 1,
35
- spdxId
40
+ spdxId,
41
+ spdxUrl
36
42
  };
37
43
  const score = diceCoefficientCached(inputBigramsMap, inputTotal, bigramsMap, totalBigrams);
38
44
  if (score > bestScore) {
39
45
  bestScore = score;
40
46
  bestMatch = {
41
47
  confidence: score,
42
- spdxId
48
+ spdxId,
49
+ spdxUrl
43
50
  };
44
51
  if (bestScore > .98) break;
45
52
  }
@@ -47,6 +54,21 @@ function identifyLicense(text) {
47
54
  if (bestMatch && bestMatch.confidence >= CONFIDENCE_THRESHOLD) return bestMatch;
48
55
  }
49
56
  /**
57
+ * Convert an SPDX license identifier to its canonical SPDX URL.
58
+ */
59
+ function spdxIdToUrl(spdxId) {
60
+ return `${SPDX_BASE_URL}${spdxId}`;
61
+ }
62
+ /**
63
+ * Resolve the canonical URL for an SPDX license ID. Prefers the upstream URL
64
+ * recorded in the SPDX list (e.g. `https://opensource.org/license/mit/`) and
65
+ * falls back to the SPDX registry URL for the handful of entries whose upstream
66
+ * URL is missing at runtime.
67
+ */
68
+ function getLicenseUrl(spdxId) {
69
+ return spdxLicenseList[spdxId]?.url ?? spdxIdToUrl(spdxId);
70
+ }
71
+ /**
50
72
  * Strip YAML front matter (--- delimited blocks at the start of a file).
51
73
  */
52
74
  function stripFrontMatter(text) {
@@ -57,16 +79,15 @@ function stripFrontMatter(text) {
57
79
  return text;
58
80
  }
59
81
  /**
60
- * Normalize license text for comparison.
61
- * Follows SPDX matching guidelines: collapse whitespace, strip copyright lines,
62
- * remove URLs, lowercase.
82
+ * Normalize license text for comparison. Follows SPDX matching guidelines:
83
+ * collapse whitespace, strip copyright lines, remove URLs, lowercase.
63
84
  */
64
85
  function normalizeText(text) {
65
86
  return text.replaceAll(/^#+\s+/gm, "").replaceAll(/^copyright.*$/gim, "").replaceAll(/^\|.*\|$/gm, "").replaceAll(/^[-|:\s]+$/gm, "").replaceAll(/https?:\/\/\S+/g, "").replaceAll(/\S+@\S+/g, "").replaceAll(/[[\]()]/g, " ").replaceAll(/\s+/g, " ").trim().toLowerCase();
66
87
  }
67
88
  /**
68
- * Normalize input text (user-provided license file).
69
- * Applies additional cleanup beyond what reference texts need.
89
+ * Normalize input text (user-provided license file). Applies additional cleanup
90
+ * beyond what reference texts need.
70
91
  */
71
92
  function normalizeInput(text) {
72
93
  return normalizeText(stripFrontMatter(text));
@@ -90,6 +111,7 @@ function getNormalizedLicenses() {
90
111
  bigramsMap: computeBigrams(normalized),
91
112
  normalized,
92
113
  spdxId,
114
+ spdxUrl: getLicenseUrl(spdxId),
93
115
  totalBigrams: normalized.length - 1
94
116
  };
95
117
  });
@@ -108,10 +130,10 @@ function diceCoefficientCached(inputBigrams, inputTotal, referenceBigrams, refer
108
130
  }
109
131
  /**
110
132
  * Title-based identification for GNU licenses whose SPDX templates embed
111
- * combined texts (e.g. LGPL-3.0-only = LGPL supplement + full GPL), making
112
- * Dice coefficient unreliable against real-world standalone files.
113
- * Only checks the first 500 characters to avoid matching references in
114
- * unrelated license texts (e.g. CeCILL-2.1 mentions AGPL in its body).
133
+ * combined texts (e.g. LGPL-3.0-only = LGPL supplement + full GPL), making Dice
134
+ * coefficient unreliable against real-world standalone files. Only checks the
135
+ * first 500 characters to avoid matching references in unrelated license texts
136
+ * (e.g. CeCILL-2.1 mentions AGPL in its body).
115
137
  */
116
138
  const HEADER_PATTERNS = [
117
139
  {
@@ -135,8 +157,93 @@ function identifyByHeader(text) {
135
157
  const header = text.slice(0, 500);
136
158
  for (const { pattern, spdxId } of HEADER_PATTERNS) if (pattern.test(header)) return {
137
159
  confidence: 1,
138
- spdxId
160
+ spdxId,
161
+ spdxUrl: getLicenseUrl(spdxId)
139
162
  };
140
163
  }
164
+ /**
165
+ * Extracts `http(s)://...` URLs from the raw text, stripping trailing
166
+ * punctuation that commonly follows a URL in prose (`.`, `,`, `)`, `]`, etc.)
167
+ * but is not part of the URL itself.
168
+ */
169
+ const URL_REGEX = /https?:\/\/[^\s<>"')\]}]+/gi;
170
+ /** Leading `www.` subdomain. */
171
+ const WWW_PREFIX_REGEX = /^www\./;
172
+ /** Trailing `/legalcode` or `/legalcode.<ext>` on Creative Commons URLs. */
173
+ const LEGALCODE_SUFFIX_REGEX = /\/legalcode(?:\.[a-z]+)?$/;
174
+ /** Trailing slashes. */
175
+ const TRAILING_SLASH_REGEX = /\/+$/;
176
+ /** Trailing prose punctuation after a URL extracted from text. */
177
+ const TRAILING_PUNCTUATION_REGEX = /[.,;:!?]+$/;
178
+ /**
179
+ * Normalize a URL for comparison: lowercase host+path, drop scheme, strip
180
+ * `www.`, strip trailing slashes, and strip trailing `/legalcode(.ext)?`
181
+ * suffixes used by Creative Commons canonical URLs.
182
+ */
183
+ function normalizeUrl(url) {
184
+ let parsed;
185
+ try {
186
+ parsed = new URL(url.trim());
187
+ } catch {
188
+ return;
189
+ }
190
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return void 0;
191
+ const host = parsed.hostname.toLowerCase().replace(WWW_PREFIX_REGEX, "");
192
+ let path = parsed.pathname.toLowerCase().replace(LEGALCODE_SUFFIX_REGEX, "");
193
+ path = path.replace(TRAILING_SLASH_REGEX, "");
194
+ return `${host}${path}`;
195
+ }
196
+ /**
197
+ * Rank an SPDX ID by how "current" its form is, higher is preferred. Used to
198
+ * break ties when multiple IDs share a canonical URL.
199
+ */
200
+ function scoreSpdxId(id) {
201
+ if (id.endsWith("+")) return 0;
202
+ if (id.endsWith("-only")) return 3;
203
+ if (id.endsWith("-or-later")) return 2;
204
+ return 1;
205
+ }
206
+ /**
207
+ * When multiple SPDX IDs share a canonical URL (typically deprecated legacy
208
+ * forms alongside current `-only` / `-or-later` variants), pick the current
209
+ * non-deprecated form.
210
+ */
211
+ function preferSpdxId(a, b) {
212
+ const sa = scoreSpdxId(a);
213
+ const sb = scoreSpdxId(b);
214
+ if (sa !== sb) return sa > sb ? a : b;
215
+ return a < b ? a : b;
216
+ }
217
+ /** Lazy index of normalized URL → preferred SPDX ID. */
218
+ let urlIndex;
219
+ function getUrlIndex() {
220
+ if (urlIndex) return urlIndex;
221
+ const index = /* @__PURE__ */ new Map();
222
+ for (const [spdxId, entry] of Object.entries(spdxLicenseList)) {
223
+ index.set(`spdx.org/licenses/${spdxId.toLowerCase()}`, spdxId);
224
+ if (!entry.url) continue;
225
+ const normalized = normalizeUrl(entry.url);
226
+ if (!normalized) continue;
227
+ const existing = index.get(normalized);
228
+ index.set(normalized, existing ? preferSpdxId(existing, spdxId) : spdxId);
229
+ }
230
+ urlIndex = index;
231
+ return index;
232
+ }
233
+ function identifyByUrl(text) {
234
+ const matches = text.match(URL_REGEX);
235
+ if (!matches) return void 0;
236
+ const index = getUrlIndex();
237
+ for (const raw of matches) {
238
+ const normalized = normalizeUrl(raw.replace(TRAILING_PUNCTUATION_REGEX, ""));
239
+ if (!normalized) continue;
240
+ const spdxId = index.get(normalized);
241
+ if (spdxId) return {
242
+ confidence: 1,
243
+ spdxId,
244
+ spdxUrl: getLicenseUrl(spdxId)
245
+ };
246
+ }
247
+ }
141
248
  //#endregion
142
249
  export { identifyLicense };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metascope",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "A CLI tool and TypeScript library to easily extract metadata from all kinds of software repositories.",
5
5
  "keywords": [
6
6
  "metadata",
@@ -44,14 +44,14 @@
44
44
  "dependencies": {
45
45
  "@bacons/xcode": "1.0.0-alpha.33",
46
46
  "@kitschpatrol/tokei": "^2.0.1",
47
- "@sindresorhus/is": "^7.2.0",
47
+ "@sindresorhus/is": "^8.0.0",
48
48
  "@types/mdast": "^4.0.4",
49
49
  "@types/node": "~22.17.2",
50
50
  "@types/plist": "^3.0.5",
51
51
  "@types/yargs": "^17.0.35",
52
52
  "case-police": "^2.2.0",
53
- "defu": "^6.1.6",
54
- "fast-xml-parser": "^5.5.10",
53
+ "defu": "^6.1.7",
54
+ "fast-xml-parser": "^5.5.11",
55
55
  "find-workspaces": "^0.3.1",
56
56
  "git-url-parse": "^16.1.0",
57
57
  "gray-matter-es": "^0.2.1",
@@ -71,10 +71,10 @@
71
71
  "smol-toml": "^1.6.1",
72
72
  "spdx-license-list": "^6.11.0",
73
73
  "string-ts": "^2.3.1",
74
- "tinyexec": "^1.0.4",
75
- "tinyglobby": "^0.2.15",
74
+ "tinyexec": "^1.1.1",
75
+ "tinyglobby": "^0.2.16",
76
76
  "unified": "^11.0.5",
77
- "updates": "^17.13.5",
77
+ "updates": "^17.14.0",
78
78
  "web-tree-sitter": "^0.26.8",
79
79
  "yaml": "^2.8.3",
80
80
  "yargs": "^18.0.0",
@@ -83,21 +83,22 @@
83
83
  "devDependencies": {
84
84
  "@arethetypeswrong/core": "^0.18.2",
85
85
  "@fast-csv/parse": "^5.0.5",
86
- "@kitschpatrol/shared-config": "^7.1.0",
86
+ "@kitschpatrol/shared-config": "^7.3.0",
87
87
  "@types/jsonld": "^1.5.15",
88
88
  "@types/picomatch": "^4.0.3",
89
89
  "@types/semver": "^7.7.1",
90
90
  "bumpp": "^11.0.1",
91
91
  "jsonld": "^9.0.0",
92
- "mdat-plugin-cli-help": "^2.1.1",
92
+ "mdat-plugin-cli-help": "^2.1.2",
93
93
  "msw": "2.12.14",
94
94
  "publint": "^0.3.18",
95
+ "shx": "^0.4.0",
95
96
  "tree-sitter-python": "^0.25.0",
96
97
  "tree-sitter-ruby": "^0.23.1",
97
98
  "tsdown": "^0.21.7",
98
99
  "tsx": "^4.21.0",
99
- "typescript": "~5.9.3",
100
- "vitest": "^4.1.2"
100
+ "typescript": "~6.0.2",
101
+ "vitest": "^4.1.4"
101
102
  },
102
103
  "engines": {
103
104
  "node": ">=22.17.0"
@@ -112,11 +113,11 @@
112
113
  "bench": "vitest bench --run --no-file-parallelism --compare test/benchmarks/baseline.json",
113
114
  "bench:baseline": "vitest bench --run --no-file-parallelism --outputJson test/benchmarks/baseline.json",
114
115
  "build": "tsdown",
115
- "clean": "git rm -f pnpm-lock.yaml ; git clean -fdX",
116
+ "clean": "shx rm -f pnpm-lock.yaml && git clean -fdX -e !.claude/",
116
117
  "docs": "tsx ./scripts/generate-examples.ts && ksc-prettier fix ./docs",
117
118
  "fix": "ksc fix",
118
119
  "lint": "ksc lint",
119
- "release": "bumpp --commit 'Release: %s' && pnpm run build && NPM_AUTH_TOKEN=$(op read 'op://Personal/npm/token') && pnpm publish",
120
+ "release": "bumpp --commit 'Release: %s' && pnpm build && NPM_AUTH_TOKEN=$(op read 'op://Personal/npm/token') && pnpm publish",
120
121
  "pretest": "tsx scripts/copy-grammars.ts",
121
122
  "test": "vitest run",
122
123
  "test:live": "METASCOPE_TEST_MOCK=false vitest run"
package/readme.md CHANGED
@@ -108,21 +108,21 @@ metascope [path]
108
108
  | ------------------- | ---------------------- | -------- | ------- |
109
109
  | `path` | Project directory path | `string` | `"."` |
110
110
 
111
- | Option | Description | Type | Default |
112
- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------- |
113
- | `--template`<br>`-t` | Built-in template name (`codemeta`, `codemetaJson`, `frontmatter`, `metadata`, `project`) or path to a custom template file | `string` | |
114
- | `--github-token` | GitHub API token (or set `$GITHUB_TOKEN`) | `string` | |
115
- | `--author-name` | Optional author name(s) for ownership checks in templates | `array` | |
116
- | `--github-account` | Optional GitHub account name(s) for ownership checks in templates | `array` | |
117
- | `--absolute` | Output absolute paths. Use `--no-absolute` for relative paths. | `boolean` | `true` |
118
- | `--offline` | Skip sources requiring network requests | `boolean` | `false` |
119
- | `--sources`<br>`-s` | Only run specific metadata sources (defaults to all) | `array` | |
120
- | `--no-ignore` | Include files ignored by .gitignore in the file tree | `boolean` | `false` |
121
- | `--recursive`<br>`-r` | Search for metadata files recursively in subdirectories | `boolean` | `false` |
122
- | `--workspaces`<br>`-w` | Include workspace-specific metadata in monorepos; pass a `boolean` to enable or disable auto-detection, or pass one or more `string`s to explicitly define workspace paths | | `true` |
123
- | `--verbose` | Run with verbose logging | `boolean` | `false` |
124
- | `--help`<br>`-h` | Show help | `boolean` | |
125
- | `--version`<br>`-v` | Show version number | `boolean` | |
111
+ | Option | Description | Type | Default |
112
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | ------- |
113
+ | `--template`<br>`-t` | Built-in template name (`codemeta`, `codemetaJson`, `frontmatter`, `metadata`, `project`) or path to a custom template file | `string` | |
114
+ | `--github-token` | GitHub API token (or set `$GITHUB_TOKEN`) | `string` | |
115
+ | `--author-name` | Optional author name(s) for ownership checks in templates | `array` | |
116
+ | `--github-account` | Optional GitHub account name(s) for ownership checks in templates | `array` | |
117
+ | `--absolute` | Output absolute paths. Use `--no-absolute` for relative paths. | `boolean` | `true` |
118
+ | `--offline` | Skip sources requiring network requests | `boolean` | `false` |
119
+ | `--sources`<br>`-s` | Only run specific metadata sources (`arduino-library-properties`, `cinder-cinderblock-xml`, `codemeta-json`, `git-config`, `go-go-mod`, `go-goreleaser-yaml`, `java-pom-xml`, `license-file`, `metadata-file`, `metascope`, `node-package-json`, `obsidian-plugin-manifest-json`, `openframeworks-addon-config-mk`, `openframeworks-install-xml`, `processing-library-properties`, `processing-sketch-properties`, `publiccode-yaml`, `python-pkg-info`, `python-pyproject-toml`, `python-setup-cfg`, `python-setup-py`, `readme-file`, `ruby-gemspec`, `rust-cargo-toml`, `xcode-info-plist`, `xcode-project-pbxproj`, `github-actions`, `code-stats`, `dependency-updates`, `file-stats`, `git-stats`, `github`, `node-npm-registry`, `obsidian-plugin-registry`, `python-pypi-registry`); defaults to all | `array` | |
120
+ | `--no-ignore` | Include files ignored by .gitignore in the file tree | `boolean` | `false` |
121
+ | `--recursive`<br>`-r` | Search for metadata files recursively in subdirectories | `boolean` | `false` |
122
+ | `--workspaces`<br>`-w` | Include workspace-specific metadata in monorepos; pass a `boolean` to enable or disable auto-detection, or pass one or more `string`s to explicitly define workspace paths | | `true` |
123
+ | `--verbose` | Run with verbose logging | `boolean` | `false` |
124
+ | `--help`<br>`-h` | Show help | `boolean` | |
125
+ | `--version`<br>`-v` | Show version number | `boolean` | |
126
126
 
127
127
  <!-- /cli-help -->
128
128
 
@@ -193,7 +193,7 @@ export default defineTemplate(({ codemetaJson, github, gitStats }) => {
193
193
  Extract metadata from only the sources you need, skipping everything else for faster results:
194
194
 
195
195
  ```sh
196
- metascope --sources nodePackageJson gitStats
196
+ metascope --sources node-package-json git-stats
197
197
  ```
198
198
 
199
199
  ##### Pipe compact JSON to another tool