metascope 0.3.0 → 0.4.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.
@@ -7,6 +7,7 @@ import { FileStatsData } from "./sources/file-stats.js";
7
7
  import { GitConfigData } from "./sources/git-config.js";
8
8
  import { GitStatsData } from "./sources/git-stats.js";
9
9
  import { GitHubData } from "./sources/github.js";
10
+ import { GitHubActionsData } from "./sources/github-actions.js";
10
11
  import { GoGoModData } from "./sources/go-go-mod.js";
11
12
  import { GoGoreleaserYamlData } from "./sources/go-goreleaser-yaml.js";
12
13
  import { JavaPomXmlData } from "./sources/java-pom-xml.js";
@@ -47,6 +48,7 @@ type MetadataContext = {
47
48
  fileStats: FileStatsData;
48
49
  gitConfig: GitConfigData;
49
50
  github: GitHubData;
51
+ githubActions: GitHubActionsData;
50
52
  gitStats: GitStatsData;
51
53
  goGoMod: GoGoModData;
52
54
  goGoreleaserYaml: GoGoreleaserYamlData;
@@ -11,6 +11,7 @@ import { fileStatsSource } from "./sources/file-stats.js";
11
11
  import { gitConfigSource } from "./sources/git-config.js";
12
12
  import { gitStatsSource } from "./sources/git-stats.js";
13
13
  import { githubSource } from "./sources/github.js";
14
+ import { githubActionsSource } from "./sources/github-actions.js";
14
15
  import { goGoModSource } from "./sources/go-go-mod.js";
15
16
  import { goGoreleaserYamlSource } from "./sources/go-goreleaser-yaml.js";
16
17
  import { javaPomXmlSource } from "./sources/java-pom-xml.js";
@@ -56,6 +57,7 @@ const sources = [
56
57
  cinderCinderblockXmlSource,
57
58
  codemetaJsonSource,
58
59
  gitConfigSource,
60
+ githubActionsSource,
59
61
  goGoModSource,
60
62
  goGoreleaserYamlSource,
61
63
  javaPomXmlSource,
@@ -172,6 +174,7 @@ async function getMetadata(options) {
172
174
  fileStats: void 0,
173
175
  gitConfig: void 0,
174
176
  github: void 0,
177
+ githubActions: void 0,
175
178
  gitStats: void 0,
176
179
  goGoMod: void 0,
177
180
  goGoreleaserYaml: void 0,
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
2
  var name = "metascope";
3
- var version = "0.3.0";
3
+ var version = "0.4.0";
4
4
  //#endregion
5
5
  export { name, version };
@@ -1,7 +1,5 @@
1
1
  import { log } from "../log.js";
2
2
  import { defineSource } from "../source.js";
3
- import { dirname, join } from "node:path";
4
- import { readFileSync } from "node:fs";
5
3
  import { exec } from "tinyexec";
6
4
  import { z } from "zod";
7
5
  import { fileURLToPath } from "node:url";
@@ -15,19 +13,13 @@ const depSchema = z.object({
15
13
  });
16
14
  const updatesOutputSchema = z.object({ results: z.record(z.string(), z.record(z.string(), z.record(z.string(), depSchema))) });
17
15
  /**
18
- * Resolve the path to the `updates` CLI binary from its installed package.
16
+ * Resolve the path to the `updates` CLI binary.
19
17
  *
20
- * Uses `import.meta.resolve` instead of `createRequire` so that resolution
21
- * works even when metascope's code is re-bundled by a downstream consumer
22
- * (where `import.meta.url` would point to the consumer's output, not to
23
- * metascope's node_modules).
18
+ * Consumers that bundle metascope must externalize it (e.g. via `neverBundle`)
19
+ * so that `import.meta.resolve` can find the `updates` dependency.
24
20
  */
25
21
  function resolveUpdatesBinary() {
26
- const packageJsonPath = fileURLToPath(import.meta.resolve("updates/package.json"));
27
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
28
- const bin = typeof packageJson === "object" && packageJson !== null && "bin" in packageJson && typeof packageJson.bin === "string" ? packageJson.bin : void 0;
29
- if (!bin) throw new Error("Could not resolve updates binary path");
30
- return join(dirname(packageJsonPath), bin);
22
+ return fileURLToPath(import.meta.resolve("updates/dist/index.js"));
31
23
  }
32
24
  /**
33
25
  * Parse an age string from the `updates` CLI (via the `timerel` library) into fractional years.
@@ -0,0 +1,12 @@
1
+ import { OneOrMany, SourceRecord } from "../source.js";
2
+ import { z } from "zod";
3
+
4
+ //#region src/lib/sources/github-actions.d.ts
5
+ declare const githubActionSchema: z.ZodObject<{
6
+ file: z.ZodString;
7
+ name: z.ZodString;
8
+ }, z.core.$strip>;
9
+ type GitHubAction = z.infer<typeof githubActionSchema>;
10
+ type GitHubActionsData = OneOrMany<SourceRecord<GitHubAction>> | undefined;
11
+ //#endregion
12
+ export { GitHubActionsData };
@@ -0,0 +1,41 @@
1
+ import { getMatches } from "../file-matching.js";
2
+ import { formatPath } from "../utilities/formatting.js";
3
+ import { defineSource } from "../source.js";
4
+ import { readFile } from "node:fs/promises";
5
+ import { resolve } from "node:path";
6
+ import { z } from "zod";
7
+ import { parse } from "yaml";
8
+ //#region src/lib/sources/github-actions.ts
9
+ /**
10
+ * Source for GitHub Actions workflow files.
11
+ *
12
+ * Discovers `.github/workflows/*.yml` and `*.yaml` files and extracts the
13
+ * workflow name and file path from each.
14
+ */
15
+ const githubActionSchema = z.object({
16
+ file: z.string(),
17
+ name: z.string()
18
+ });
19
+ const githubActionsSource = defineSource({
20
+ async discover(context) {
21
+ return getMatches(context.options, [".github/workflows/*.{yml,yaml}"]);
22
+ },
23
+ key: "githubActions",
24
+ async parse(input, context) {
25
+ const parsed = parse(await readFile(resolve(context.options.path, input), "utf8"));
26
+ if (typeof parsed !== "object" || parsed === null || !("name" in parsed)) return;
27
+ const { name } = parsed;
28
+ if (typeof name !== "string") return;
29
+ const file = formatPath(resolve(context.options.path, input), context.options.path, context.options.absolute);
30
+ return {
31
+ data: githubActionSchema.parse({
32
+ file,
33
+ name
34
+ }),
35
+ source: input
36
+ };
37
+ },
38
+ phase: 1
39
+ });
40
+ //#endregion
41
+ export { githubActionsSource };
@@ -361,12 +361,12 @@ function mapRepoData(data, extras) {
361
361
  const githubSource = defineSource({
362
362
  async discover(context) {
363
363
  if (context.options.offline) {
364
- log.warn("Skipping GitHub data source since we're in offline mode");
364
+ log.debug("Skipping GitHub data source since we're in offline mode");
365
365
  return [];
366
366
  }
367
367
  let gitRemotes = ensureArray(context.metadata?.gitConfig).map((config) => config.data.remote).filter((remote) => remote !== void 0);
368
368
  if (gitRemotes.length === 0 && !context.completedSources?.has("gitConfig")) {
369
- log.warn(`Missing gitConfig in source context metadata for ${context.options.path}, extracting it now...`);
369
+ log.debug(`Missing gitConfig in source context metadata for ${context.options.path}, extracting it now...`);
370
370
  gitRemotes = ensureArray(await gitConfigSource.extract(context)).map((config) => config.data.remote).filter((remote) => remote !== void 0);
371
371
  }
372
372
  return [...new Set(gitRemotes.map((config) => getGitHubRemoteFromConfig(config)).filter((remote) => remote !== void 0).map((remote) => `${remote.owner}/${remote.repo}`))];
@@ -10,12 +10,12 @@ import packageJson from "package-json";
10
10
  const nodeNpmRegistrySource = defineSource({
11
11
  async discover(context) {
12
12
  if (context.options.offline) {
13
- log.warn("Skipping Node NPM registry data source since we're in offline mode");
13
+ log.debug("Skipping Node NPM registry data source since we're in offline mode");
14
14
  return [];
15
15
  }
16
16
  let packages = ensureArray(context.metadata?.nodePackageJson);
17
17
  if (packages.length === 0 && !context.completedSources?.has("nodePackageJson")) {
18
- log.warn(`Missing nodePackageJson in source context metadata for ${context.options.path}, extracting it now...`);
18
+ log.debug(`Missing nodePackageJson in source context metadata for ${context.options.path}, extracting it now...`);
19
19
  packages = ensureArray(await nodePackageJsonSource.extract(context));
20
20
  }
21
21
  return packages.filter((packageJson) => {
@@ -10,12 +10,12 @@ const pluginStatsSchema = z.record(z.string(), z.record(z.string(), z.number()))
10
10
  const obsidianPluginRegistrySource = defineSource({
11
11
  async discover(context) {
12
12
  if (context.options.offline) {
13
- log.warn("Skipping Obsidian plugin registry data source since we're in offline mode");
13
+ log.debug("Skipping Obsidian plugin registry data source since we're in offline mode");
14
14
  return [];
15
15
  }
16
16
  let pluginIds = ensureArray(context.metadata?.obsidianPluginManifestJson).map((value) => value.data.id);
17
17
  if (pluginIds.length === 0 && !context.completedSources?.has("obsidianPluginManifestJson")) {
18
- log.warn(`Missing obsidianPluginManifestJson in source context metadata for ${context.options.path}, extracting it now...`);
18
+ log.debug(`Missing obsidianPluginManifestJson in source context metadata for ${context.options.path}, extracting it now...`);
19
19
  pluginIds = ensureArray(await obsidianPluginManifestJsonSource.extract(context)).map((value) => value.data.id);
20
20
  }
21
21
  return pluginIds;
@@ -32,7 +32,7 @@ const pypistatsOverallSchema = z.object({ data: z.array(z.object({
32
32
  const pythonPypiRegistrySource = defineSource({
33
33
  async discover(context) {
34
34
  if (context.options.offline) {
35
- log.warn("Skipping Python PyPI registry data source since we're in offline mode");
35
+ log.debug("Skipping Python PyPI registry data source since we're in offline mode");
36
36
  return [];
37
37
  }
38
38
  let packageNames = [];
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
 
4
4
  //#region src/lib/sources/readme-file.d.ts
5
5
  declare const readmeSchema: z.ZodObject<{
6
+ frontmatter: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
6
7
  name: z.ZodString;
7
8
  }, z.core.$strip>;
8
9
  type Readme = z.infer<typeof readmeSchema>;
@@ -3,10 +3,14 @@ import { defineSource } from "../source.js";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { resolve } from "node:path";
5
5
  import { z } from "zod";
6
+ import { matter } from "gray-matter-es";
6
7
  import remarkParse from "remark-parse";
7
8
  import { unified } from "unified";
8
9
  //#region src/lib/sources/readme-file.ts
9
- const readmeSchema = z.object({ name: z.string() });
10
+ const readmeSchema = z.object({
11
+ frontmatter: z.record(z.string(), z.unknown()).optional(),
12
+ name: z.string()
13
+ });
10
14
  /**
11
15
  * Recursively extract plain text from mdast phrasing content.
12
16
  */
@@ -33,9 +37,13 @@ function extractFirstH1(markdown) {
33
37
  * @returns Parsed metadata, or `undefined` if no H1 heading is found.
34
38
  */
35
39
  function parse(content) {
36
- const name = extractFirstH1(content);
40
+ const { content: markdown, data } = matter(content);
41
+ const name = extractFirstH1(markdown);
37
42
  if (!name) return void 0;
38
- return readmeSchema.parse({ name });
43
+ return readmeSchema.parse({
44
+ frontmatter: Object.keys(data).length > 0 ? data : void 0,
45
+ name
46
+ });
39
47
  }
40
48
  const readmeFileSource = defineSource({
41
49
  async discover(context) {
@@ -18,7 +18,7 @@ declare const frontmatter: Template<{
18
18
  Public: boolean;
19
19
  Fork: boolean;
20
20
  Published: boolean;
21
- Status: "unknown" | "maintainer" | "author" | "observer";
21
+ Status: "author" | "maintainer" | "unknown" | "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: "unknown" | "maintainer" | "author" | "observer";
160
+ Status: "author" | "maintainer" | "unknown" | "observer";
161
161
  Tags: string[] | null;
162
162
  Aliases: (string | null | undefined)[] | null;
163
163
  License: string[] | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metascope",
3
- "version": "0.3.0",
3
+ "version": "0.4.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",
@@ -50,10 +50,11 @@
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.4",
53
+ "defu": "^6.1.6",
54
54
  "fast-xml-parser": "^5.5.9",
55
55
  "find-workspaces": "^0.3.1",
56
56
  "git-url-parse": "^16.1.0",
57
+ "gray-matter-es": "^0.2.1",
57
58
  "jiti": "^2.6.1",
58
59
  "lognow": "^0.5.2",
59
60
  "octokit": "^5.0.5",
@@ -74,7 +75,7 @@
74
75
  "tinyglobby": "^0.2.15",
75
76
  "unified": "^11.0.5",
76
77
  "updates": "^17.13.1",
77
- "web-tree-sitter": "^0.26.7",
78
+ "web-tree-sitter": "^0.26.8",
78
79
  "yaml": "^2.8.3",
79
80
  "yargs": "^18.0.0",
80
81
  "zod": "^4.3.6"
@@ -88,7 +89,7 @@
88
89
  "@types/semver": "^7.7.1",
89
90
  "bumpp": "^11.0.1",
90
91
  "jsonld": "^9.0.0",
91
- "mdat-plugin-cli-help": "^1.0.7",
92
+ "mdat-plugin-cli-help": "^1.0.8",
92
93
  "msw": "2.12.14",
93
94
  "publint": "^0.3.18",
94
95
  "tree-sitter-python": "^0.25.0",