metascope 0.2.2 → 0.2.3

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.
Binary file
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
2
  var name = "metascope";
3
- var version = "0.2.2";
3
+ var version = "0.2.3";
4
4
  //#endregion
5
5
  export { name, version };
@@ -360,6 +360,10 @@ function mapRepoData(data, extras) {
360
360
  }
361
361
  const githubSource = defineSource({
362
362
  async discover(context) {
363
+ if (context.options.offline) {
364
+ log.warn("Skipping GitHub data source since we're in offline mode");
365
+ return [];
366
+ }
363
367
  let gitRemotes = ensureArray(context.metadata?.gitConfig).map((config) => config.data.remote).filter((remote) => remote !== void 0);
364
368
  if (gitRemotes.length === 0 && !context.completedSources?.has("gitConfig")) {
365
369
  log.warn(`Missing gitConfig in source context metadata for ${context.options.path}, extracting it now...`);
@@ -8,13 +8,13 @@ declare const processingLibraryPropertiesSchema: z.ZodObject<{
8
8
  url: z.ZodOptional<z.ZodString>;
9
9
  }, z.core.$strip>>;
10
10
  categories: z.ZodArray<z.ZodEnum<{
11
+ GUI: "GUI";
11
12
  "3D": "3D";
12
13
  Animation: "Animation";
13
14
  Compilations: "Compilations";
14
15
  Data: "Data";
15
16
  Fabrication: "Fabrication";
16
17
  Geometry: "Geometry";
17
- GUI: "GUI";
18
18
  Hardware: "Hardware";
19
19
  "I/O": "I/O";
20
20
  Language: "Language";
@@ -31,11 +31,15 @@ const pypistatsOverallSchema = z.object({ data: z.array(z.object({
31
31
  })) });
32
32
  const pythonPypiRegistrySource = defineSource({
33
33
  async discover(context) {
34
+ if (context.options.offline) {
35
+ log.warn("Skipping Python PyPI registry data source since we're in offline mode");
36
+ return [];
37
+ }
34
38
  let packageNames = [];
35
- packageNames = ensureArray(context.metadata?.pythonPyprojectToml).map((value) => value.data.project?.name).filter((value) => value !== void 0);
36
- if (packageNames.length === 0) packageNames = ensureArray(context.metadata?.pythonSetupCfg).map((value) => value.data.name).filter((value) => value !== void 0);
37
- if (packageNames.length === 0) packageNames = ensureArray(context.metadata?.pythonSetupPy).map((value) => value.data.name).filter((value) => value !== void 0);
38
- if (packageNames.length === 0) packageNames = ensureArray(context.metadata?.pythonPkgInfo).map((value) => value.data.name).filter((value) => value !== void 0);
39
+ packageNames = ensureArray(context.metadata?.pythonPyprojectToml).filter((value) => !hasPrivateClassifier(value.data.project?.classifiers, value.data.project?.name, context.options.path)).map((value) => value.data.project?.name).filter((value) => value !== void 0);
40
+ if (packageNames.length === 0) packageNames = ensureArray(context.metadata?.pythonSetupCfg).filter((value) => !hasPrivateClassifier(value.data.classifiers, value.data.name, context.options.path)).map((value) => value.data.name).filter((value) => value !== void 0);
41
+ if (packageNames.length === 0) packageNames = ensureArray(context.metadata?.pythonSetupPy).filter((value) => !hasPrivateClassifier(value.data.classifiers, value.data.name, context.options.path)).map((value) => value.data.name).filter((value) => value !== void 0);
42
+ if (packageNames.length === 0) packageNames = ensureArray(context.metadata?.pythonPkgInfo).filter((value) => !hasPrivateClassifier(value.data.classifiers, value.data.name, context.options.path)).map((value) => value.data.name).filter((value) => value !== void 0);
39
43
  if (packageNames.length === 0) {
40
44
  if (![
41
45
  "pythonPyprojectToml",
@@ -44,10 +48,10 @@ const pythonPypiRegistrySource = defineSource({
44
48
  "pythonPkgInfo"
45
49
  ].some((key) => context.completedSources?.has(key))) {
46
50
  log.warn(`Missing python package names in source context metadata for ${context.options.path}, extracting them now...`);
47
- packageNames = ensureArray(await pythonPyprojectTomlSource.extract(context)).map((value) => value.data.project?.name).filter((value) => value !== void 0);
48
- if (packageNames.length === 0) packageNames = ensureArray(await pythonSetupCfgSource.extract(context)).map((value) => value.data.name).filter((value) => value !== void 0);
49
- if (packageNames.length === 0) packageNames = ensureArray(await pythonSetupPySource.extract(context)).map((value) => value.data.name).filter((value) => value !== void 0);
50
- if (packageNames.length === 0) packageNames = ensureArray(await pythonPkgInfoSource.extract(context)).map((value) => value.data.name).filter((value) => value !== void 0);
51
+ packageNames = ensureArray(await pythonPyprojectTomlSource.extract(context)).filter((value) => !hasPrivateClassifier(value.data.project?.classifiers, value.data.project?.name, context.options.path)).map((value) => value.data.project?.name).filter((value) => value !== void 0);
52
+ if (packageNames.length === 0) packageNames = ensureArray(await pythonSetupCfgSource.extract(context)).filter((value) => !hasPrivateClassifier(value.data.classifiers, value.data.name, context.options.path)).map((value) => value.data.name).filter((value) => value !== void 0);
53
+ if (packageNames.length === 0) packageNames = ensureArray(await pythonSetupPySource.extract(context)).filter((value) => !hasPrivateClassifier(value.data.classifiers, value.data.name, context.options.path)).map((value) => value.data.name).filter((value) => value !== void 0);
54
+ if (packageNames.length === 0) packageNames = ensureArray(await pythonPkgInfoSource.extract(context)).filter((value) => !hasPrivateClassifier(value.data.classifiers, value.data.name, context.options.path)).map((value) => value.data.name).filter((value) => value !== void 0);
51
55
  }
52
56
  }
53
57
  return packageNames;
@@ -97,5 +101,15 @@ const pythonPypiRegistrySource = defineSource({
97
101
  },
98
102
  phase: 2
99
103
  });
104
+ /**
105
+ * Check if classifiers contain a "Private ::" prefix, which signals that the
106
+ * package is not intended for publication on PyPI. PyPI itself rejects uploads
107
+ * with this classifier.
108
+ */
109
+ function hasPrivateClassifier(classifiers, packageName, path) {
110
+ const isPrivate = classifiers?.some((c) => c.startsWith("Private :: ")) ?? false;
111
+ if (isPrivate) log.debug(`Skipping PyPI registry lookup for "${packageName}" in "${path}" because it has a "Private ::" classifier`);
112
+ return isPrivate;
113
+ }
100
114
  //#endregion
101
115
  export { pythonPypiRegistrySource };
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "metascope",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
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",
7
7
  "codemeta",
8
8
  "cli",
9
+ "git",
10
+ "repository",
11
+ "software-analytics",
12
+ "metrics",
9
13
  "npm-package"
10
14
  ],
11
15
  "homepage": "https://github.com/kitschpatrol/metascope",
@@ -42,19 +46,19 @@
42
46
  "@kitschpatrol/tokei": "^2.0.0",
43
47
  "@sindresorhus/is": "^7.2.0",
44
48
  "@types/mdast": "^4.0.4",
45
- "@types/node": "~24.1.0",
49
+ "@types/node": "~22.17.2",
46
50
  "@types/plist": "^3.0.5",
47
51
  "@types/yargs": "^17.0.35",
48
52
  "case-police": "^2.2.0",
49
53
  "defu": "^6.1.4",
50
- "fast-xml-parser": "^5.5.3",
54
+ "fast-xml-parser": "^5.5.9",
51
55
  "find-workspaces": "^0.3.1",
52
56
  "git-url-parse": "^16.1.0",
53
57
  "jiti": "^2.6.1",
54
58
  "lognow": "^0.5.2",
55
59
  "octokit": "^5.0.5",
56
60
  "package-json": "^10.0.1",
57
- "picomatch": "^4.0.3",
61
+ "picomatch": "^4.0.4",
58
62
  "pkg-types": "^2.3.0",
59
63
  "plist": "^3.1.0",
60
64
  "pretty-ms": "^9.3.0",
@@ -64,34 +68,35 @@
64
68
  "scule": "^1.3.0",
65
69
  "semver": "^7.7.4",
66
70
  "simple-git": "^3.33.0",
67
- "smol-toml": "^1.6.0",
71
+ "smol-toml": "^1.6.1",
68
72
  "spdx-license-list": "^6.11.0",
69
- "tinyexec": "^1.0.2",
73
+ "tinyexec": "^1.0.4",
70
74
  "tinyglobby": "^0.2.15",
71
75
  "unified": "^11.0.5",
72
- "updates": "^17.9.1",
73
- "web-tree-sitter": "^0.26.6",
74
- "yaml": "^2.8.2",
76
+ "updates": "^17.13.1",
77
+ "web-tree-sitter": "^0.26.7",
78
+ "yaml": "^2.8.3",
75
79
  "yargs": "^18.0.0",
76
80
  "zod": "^4.3.6"
77
81
  },
78
82
  "devDependencies": {
79
83
  "@arethetypeswrong/core": "^0.18.2",
80
84
  "@fast-csv/parse": "^5.0.5",
81
- "@kitschpatrol/shared-config": "^6.0.3",
85
+ "@kitschpatrol/shared-config": "^6.1.0",
82
86
  "@types/jsonld": "^1.5.15",
83
87
  "@types/picomatch": "^4.0.2",
84
88
  "@types/semver": "^7.7.1",
85
- "bumpp": "^10.4.1",
89
+ "bumpp": "^11.0.1",
86
90
  "jsonld": "^9.0.0",
87
91
  "mdat-plugin-cli-help": "^1.0.7",
92
+ "msw": "2.12.14",
88
93
  "publint": "^0.3.18",
89
94
  "tree-sitter-python": "^0.25.0",
90
95
  "tree-sitter-ruby": "^0.23.1",
91
- "tsdown": "^0.21.2",
96
+ "tsdown": "^0.21.7",
92
97
  "tsx": "^4.21.0",
93
98
  "typescript": "~5.9.3",
94
- "vitest": "^4.0.18"
99
+ "vitest": "^4.1.2"
95
100
  },
96
101
  "engines": {
97
102
  "node": ">=22.17.0"
@@ -104,6 +109,7 @@
104
109
  "lint": "ksc lint",
105
110
  "release": "bumpp --commit 'Release: %s' && pnpm run build && NPM_AUTH_TOKEN=$(op read 'op://Personal/npm/token') && pnpm publish",
106
111
  "pretest": "mkdir -p src/grammars && cp node_modules/tree-sitter-ruby/tree-sitter-ruby.wasm src/grammars/tree-sitter-ruby.wasm && cp node_modules/tree-sitter-python/tree-sitter-python.wasm src/grammars/tree-sitter-python.wasm",
107
- "test": "vitest run"
112
+ "test": "vitest run",
113
+ "test:live": "METASCOPE_TEST_MOCK=false vitest run"
108
114
  }
109
115
  }
package/readme.md CHANGED
@@ -6,10 +6,18 @@
6
6
 
7
7
  <!-- /title -->
8
8
 
9
- <!-- badges -->
9
+ <!-- badges { custom: {
10
+ "CI": {
11
+ image: "https://github.com/kitschpatrol/metascope/actions/workflows/ci.yml/badge.svg",
12
+ link: "https://github.com/kitschpatrol/metascope/actions/workflows/ci.yml",
13
+ },
14
+ }
15
+ }
16
+ -->
10
17
 
11
18
  [![NPM Package metascope](https://img.shields.io/npm/v/metascope.svg)](https://npmjs.com/package/metascope)
12
19
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
20
+ [![CI](https://github.com/kitschpatrol/metascope/actions/workflows/ci.yml/badge.svg)](https://github.com/kitschpatrol/metascope/actions/workflows/ci.yml)
13
21
 
14
22
  <!-- /badges -->
15
23
 
@@ -35,7 +43,7 @@ Highlights:
35
43
  Metascope pulls project metadata from many available sources: `package.json`, `pyproject.toml`, NPM, PyPI, GitHub, git, filesystem stats, and [more](#sources).
36
44
 
37
45
  - **Graceful degradation**\
38
- Each source checks its own availability before extraction. Missing tools, unavailable APIs, or absent credentials are silently skipped — you always get back whatever data _is_ available.
46
+ Each source checks its own availability before extraction. Missing tools, unavailable APIs, or absent credentials are silently skipped — you always get back whatever data _is_ available within the constraints of the calling context.
39
47
 
40
48
  - **Parallel extraction**\
41
49
  After an initial codemeta pass for discovery hints (package name, repository URL, keywords), all remaining sources are checked and extracted concurrently.
@@ -561,6 +569,10 @@ Metascope was built to support automated generation of project dashboards, badge
561
569
  A suite of CLI tools to automate software compliance checks (Kotlin)
562
570
  - [Git Truck](https://github.com/git-truck/git-truck)\
563
571
  Repository visualization. (TypeScript)
572
+ - [Onefetch](https://github.com/o2sh/onefetch)
573
+ Offline command-line Git information tool (Rust)
574
+ - [Sokrates](https://www.sokrates.dev/)
575
+ Polyglot source code examination tool (Java)
564
576
 
565
577
  ## Slop factor
566
578