metascope 0.1.0 → 0.2.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.
Files changed (111) hide show
  1. package/dist/.DS_Store +0 -0
  2. package/dist/bin/cli.js +14 -14
  3. package/dist/lib/{chunk-DrSxFLj_.js → _virtual/_rolldown/runtime.js} +1 -1
  4. package/dist/lib/file-matching.js +152 -0
  5. package/dist/lib/index.d.ts +11 -1496
  6. package/dist/lib/index.js +6 -6215
  7. package/dist/lib/log.d.ts +11 -0
  8. package/dist/lib/log.js +20 -0
  9. package/dist/lib/metadata-types.d.ts +151 -0
  10. package/dist/lib/metadata-types.js +30 -0
  11. package/dist/lib/metadata.d.ts +16 -0
  12. package/dist/lib/metadata.js +235 -0
  13. package/dist/lib/package.js +5 -0
  14. package/dist/lib/parsers/configparser-parser.js +43 -0
  15. package/dist/lib/parsers/gemspec-parser.js +256 -0
  16. package/dist/lib/parsers/go-mod-parser.js +153 -0
  17. package/dist/lib/parsers/makefile-config-parser.js +102 -0
  18. package/dist/lib/parsers/properties-parser.js +31 -0
  19. package/dist/lib/parsers/rfc822-header-parser.js +48 -0
  20. package/dist/lib/parsers/setup-py-parser.js +173 -0
  21. package/dist/lib/source.d.ts +17 -0
  22. package/dist/lib/source.js +34 -0
  23. package/dist/lib/sources/arduino-library-properties.d.ts +45 -0
  24. package/dist/lib/sources/arduino-library-properties.js +208 -0
  25. package/dist/lib/sources/cinder-cinderblock-xml.d.ts +21 -0
  26. package/dist/lib/sources/cinder-cinderblock-xml.js +134 -0
  27. package/dist/lib/sources/code-stats.d.ts +14 -0
  28. package/dist/lib/sources/code-stats.js +40 -0
  29. package/dist/lib/sources/codemeta-json.d.ts +117 -0
  30. package/dist/lib/sources/codemeta-json.js +226 -0
  31. package/dist/lib/sources/dependency-updates.d.ts +22 -0
  32. package/dist/lib/sources/dependency-updates.js +132 -0
  33. package/dist/lib/sources/file-stats.d.ts +12 -0
  34. package/dist/lib/sources/file-stats.js +48 -0
  35. package/dist/lib/sources/git-config.d.ts +8 -0
  36. package/dist/lib/sources/git-config.js +21 -0
  37. package/dist/lib/sources/git-stats.d.ts +35 -0
  38. package/dist/lib/sources/git-stats.js +130 -0
  39. package/dist/lib/sources/github.d.ts +94 -0
  40. package/dist/lib/sources/github.js +399 -0
  41. package/dist/lib/sources/go-go-mod.d.ts +19 -0
  42. package/dist/lib/sources/go-go-mod.js +38 -0
  43. package/dist/lib/sources/go-goreleaser-yaml.d.ts +19 -0
  44. package/dist/lib/sources/go-goreleaser-yaml.js +152 -0
  45. package/dist/lib/sources/java-pom-xml.d.ts +52 -0
  46. package/dist/lib/sources/java-pom-xml.js +248 -0
  47. package/dist/lib/sources/license-file.d.ts +10 -0
  48. package/dist/lib/sources/license-file.js +26 -0
  49. package/dist/lib/sources/metadata-file.d.ts +14 -0
  50. package/dist/lib/sources/metadata-file.js +109 -0
  51. package/dist/lib/sources/metascope.d.ts +14 -0
  52. package/dist/lib/sources/metascope.js +35 -0
  53. package/dist/lib/sources/node-npm-registry.d.ts +19 -0
  54. package/dist/lib/sources/node-npm-registry.js +74 -0
  55. package/dist/lib/sources/node-package-json.d.ts +7 -0
  56. package/dist/lib/sources/node-package-json.js +27 -0
  57. package/dist/lib/sources/obsidian-plugin-manifest-json.d.ts +17 -0
  58. package/dist/lib/sources/obsidian-plugin-manifest-json.js +34 -0
  59. package/dist/lib/sources/obsidian-plugin-registry.d.ts +10 -0
  60. package/dist/lib/sources/obsidian-plugin-registry.js +44 -0
  61. package/dist/lib/sources/openframeworks-addon-config-mk.d.ts +17 -0
  62. package/dist/lib/sources/openframeworks-addon-config-mk.js +39 -0
  63. package/dist/lib/sources/openframeworks-install-xml.d.ts +20 -0
  64. package/dist/lib/sources/openframeworks-install-xml.js +153 -0
  65. package/dist/lib/sources/processing-library-properties.d.ts +44 -0
  66. package/dist/lib/sources/processing-library-properties.js +219 -0
  67. package/dist/lib/sources/processing-sketch-properties.d.ts +38 -0
  68. package/dist/lib/sources/processing-sketch-properties.js +185 -0
  69. package/dist/lib/sources/publiccode-yaml.d.ts +73 -0
  70. package/dist/lib/sources/publiccode-yaml.js +256 -0
  71. package/dist/lib/sources/python-pkg-info.d.ts +31 -0
  72. package/dist/lib/sources/python-pkg-info.js +115 -0
  73. package/dist/lib/sources/python-pypi-registry.d.ts +19 -0
  74. package/dist/lib/sources/python-pypi-registry.js +101 -0
  75. package/dist/lib/sources/python-pyproject-toml.d.ts +7 -0
  76. package/dist/lib/sources/python-pyproject-toml.js +30 -0
  77. package/dist/lib/sources/python-setup-cfg.d.ts +28 -0
  78. package/dist/lib/sources/python-setup-cfg.js +106 -0
  79. package/dist/lib/sources/python-setup-py.d.ts +28 -0
  80. package/dist/lib/sources/python-setup-py.js +48 -0
  81. package/dist/lib/sources/readme-file.d.ts +11 -0
  82. package/dist/lib/sources/readme-file.js +55 -0
  83. package/dist/lib/sources/ruby-gemspec.d.ts +44 -0
  84. package/dist/lib/sources/ruby-gemspec.js +62 -0
  85. package/dist/lib/sources/rust-cargo-toml.d.ts +40 -0
  86. package/dist/lib/sources/rust-cargo-toml.js +159 -0
  87. package/dist/lib/sources/xcode-info-plist.d.ts +22 -0
  88. package/dist/lib/sources/xcode-info-plist.js +199 -0
  89. package/dist/lib/sources/xcode-project-pbxproj.d.ts +21 -0
  90. package/dist/lib/sources/xcode-project-pbxproj.js +222 -0
  91. package/dist/lib/templates/codemeta.d.ts +47 -0
  92. package/dist/lib/templates/codemeta.js +494 -0
  93. package/dist/lib/templates/frontmatter.d.ts +87 -0
  94. package/dist/lib/templates/frontmatter.js +111 -0
  95. package/dist/lib/templates/index.d.ts +181 -0
  96. package/dist/lib/templates/index.js +22 -0
  97. package/dist/lib/templates/metadata.d.ts +17 -0
  98. package/dist/lib/templates/metadata.js +35 -0
  99. package/dist/lib/templates/project.d.ts +39 -0
  100. package/dist/lib/templates/project.js +51 -0
  101. package/dist/lib/utilities/codemeta-helpers.d.ts +39 -0
  102. package/dist/lib/utilities/codemeta-helpers.js +83 -0
  103. package/dist/lib/utilities/fetch.js +43 -0
  104. package/dist/lib/utilities/formatting.js +28 -0
  105. package/dist/lib/utilities/license-identification.js +141 -0
  106. package/dist/lib/utilities/schema-primitives.js +47 -0
  107. package/dist/lib/utilities/template-helpers.d.ts +135 -0
  108. package/dist/lib/utilities/template-helpers.js +310 -0
  109. package/dist/lib/utilities/tree-sitter-wasm.js +30 -0
  110. package/package.json +6 -6
  111. package/readme.md +62 -15
@@ -0,0 +1,130 @@
1
+ import { log } from "../log.js";
2
+ import { getMatches } from "../file-matching.js";
3
+ import { batchMap } from "../utilities/formatting.js";
4
+ import { defineSource } from "../source.js";
5
+ import { stat } from "node:fs/promises";
6
+ import { join, resolve } from "node:path";
7
+ import { simpleGit } from "simple-git";
8
+ //#region src/lib/sources/git-stats.ts
9
+ const gitStatsSource = defineSource({
10
+ async discover(context) {
11
+ return (await getMatches(context.options, [".git/config"])).map((value) => resolve(value, "../../"));
12
+ },
13
+ key: "gitStats",
14
+ async parse(input, context) {
15
+ log.debug("Extracting git statistics metadata...");
16
+ const git = simpleGit(input);
17
+ const [statusResult, logResult, branchResult, tagResult, commitDateFirst, trackedFiles, remotes, submoduleCount, hasLfs] = await Promise.all([
18
+ git.status(),
19
+ git.log(),
20
+ git.branch(),
21
+ git.tags(),
22
+ git.raw([
23
+ "rev-list",
24
+ "--max-parents=0",
25
+ "HEAD",
26
+ "--format=%aI"
27
+ ]).then((output) => {
28
+ return output.trim().split("\n").filter((line) => !line.startsWith("commit ")).at(-1) ?? void 0;
29
+ }),
30
+ git.raw(["ls-files"]).then((output) => output.trim().split("\n").filter(Boolean)),
31
+ git.getRemotes(),
32
+ git.raw(["submodule", "status"]).then((output) => {
33
+ const count = output.trim().split("\n").filter(Boolean).length;
34
+ return count > 0 ? count : void 0;
35
+ }).catch(() => void 0),
36
+ git.raw(["lfs", "ls-files"]).then((output) => output.trim().length > 0 ? true : void 0).catch(() => void 0)
37
+ ]);
38
+ const trackedSizeBytes = (await batchMap(trackedFiles, async (file) => {
39
+ try {
40
+ return (await stat(join(context.options.path, file))).size;
41
+ } catch {
42
+ return 0;
43
+ }
44
+ })).reduce((sum, size) => sum + size, 0);
45
+ const contributors = new Set(logResult.all.map((commit) => commit.author_email));
46
+ let tagDateLatest;
47
+ const tagNameLatest = tagResult.latest ?? void 0;
48
+ if (tagNameLatest) try {
49
+ tagDateLatest = (await git.raw([
50
+ "log",
51
+ "-1",
52
+ "--format=%aI",
53
+ tagNameLatest
54
+ ])).trim() || void 0;
55
+ } catch {}
56
+ const versionTagPattern = /^v?\d+(?:\.\d+){1,2}$/;
57
+ let tagReleaseCount;
58
+ let tagVersionLatest;
59
+ let tagVersionDateLatest;
60
+ try {
61
+ const versionTags = (await git.raw(["tag", "--sort=-creatordate"])).trim().split("\n").filter(Boolean).filter((tag) => versionTagPattern.test(tag));
62
+ tagReleaseCount = versionTags.length > 0 ? versionTags.length : void 0;
63
+ const match = versionTags[0];
64
+ if (match) {
65
+ const tagDate = await git.raw([
66
+ "log",
67
+ "-1",
68
+ "--format=%aI",
69
+ match
70
+ ]);
71
+ tagVersionLatest = match.replace(/^v/, "");
72
+ tagVersionDateLatest = tagDate.trim() || void 0;
73
+ }
74
+ } catch {}
75
+ const remoteStatusEntries = await Promise.all(remotes.map(async (remote) => {
76
+ for (const branch of ["main", "master"]) {
77
+ const reference = `${remote.name}/${branch}`;
78
+ try {
79
+ const [ahead, behind] = (await git.raw([
80
+ "rev-list",
81
+ "--left-right",
82
+ "--count",
83
+ `HEAD...${reference}`
84
+ ])).trim().split(" ").map(Number);
85
+ return [remote.name, {
86
+ ahead,
87
+ behind
88
+ }];
89
+ } catch {}
90
+ }
91
+ }));
92
+ const remoteStatus = {};
93
+ for (const entry of remoteStatusEntries) if (entry) remoteStatus[entry[0]] = entry[1];
94
+ const remoteStatusValues = Object.values(remoteStatus);
95
+ const totalAhead = remoteStatusValues.length > 0 ? remoteStatusValues.reduce((sum, s) => sum + s.ahead, 0) : void 0;
96
+ const totalBehind = remoteStatusValues.length > 0 ? remoteStatusValues.reduce((sum, s) => sum + s.behind, 0) : void 0;
97
+ return {
98
+ data: {
99
+ branchCount: branchResult.all.length,
100
+ branchCurrent: branchResult.current,
101
+ commitCount: logResult.total,
102
+ commitDateFirst,
103
+ commitDateLast: logResult.latest?.date ?? void 0,
104
+ contributorCount: contributors.size,
105
+ hasLfs,
106
+ isClean: statusResult.isClean(),
107
+ isDirty: !statusResult.isClean(),
108
+ isRemoteAhead: Object.values(remoteStatus).some((s) => s.behind > 0) || void 0,
109
+ remoteCount: remotes.length,
110
+ remoteStatus: Object.keys(remoteStatus).length > 0 ? remoteStatus : void 0,
111
+ submoduleCount,
112
+ tagCount: tagResult.all.length,
113
+ tagDateLatest,
114
+ tagNameLatest,
115
+ tagReleaseCount,
116
+ tagVersionDateLatest,
117
+ tagVersionLatest,
118
+ totalAhead,
119
+ totalBehind,
120
+ trackedFileCount: trackedFiles.length,
121
+ trackedSizeBytes,
122
+ uncommittedFileCount: statusResult.files.length
123
+ },
124
+ source: input
125
+ };
126
+ },
127
+ phase: 1
128
+ });
129
+ //#endregion
130
+ export { gitStatsSource };
@@ -0,0 +1,94 @@
1
+ import { OneOrMany, SourceRecord } from "../source.js";
2
+
3
+ //#region src/lib/sources/github.d.ts
4
+ type GitHubInfo = {
5
+ /** ISO 8601 date when the repo was archived, if applicable. */archivedAt?: string; /** Name of the repository's code of conduct. */
6
+ codeOfConduct?: string; /** Commits the default branch is ahead of the upstream fork parent. */
7
+ commitsAheadUpstream?: number; /** Commits the default branch is behind the upstream fork parent. */
8
+ commitsBehindUpstream?: number; /** Number of contributors to the repository. */
9
+ contributorCount?: number; /** ISO 8601 date when the repo was created. */
10
+ createdAt?: string; /** GitHub's internal numeric repository ID. */
11
+ databaseId?: number; /** Name of the default branch (e.g. "main"). */
12
+ defaultBranch?: string; /** Repository description. */
13
+ description?: string; /** Total number of discussions. */
14
+ discussionCount?: number; /** Repository disk usage in bytes. */
15
+ diskUsageBytes?: number; /** Number of forks. */
16
+ forkCount?: number; /** URL of the upstream repository this was forked from. */
17
+ forkedFrom?: string; /** Funding links configured on the repository. */
18
+ fundingLinks?: Array<{
19
+ platform: string;
20
+ url: string;
21
+ }>; /** Whether a CONTRIBUTING file exists. */
22
+ hasContributing?: boolean; /** Whether discussions are enabled. */
23
+ hasDiscussionsEnabled?: boolean; /** Whether issues are enabled. */
24
+ hasIssuesEnabled?: boolean; /** Whether the repo uses Git LFS (detected via .gitattributes). */
25
+ hasLfs?: boolean; /** Whether GitHub Pages is enabled. */
26
+ hasPages?: boolean; /** Whether projects are enabled. */
27
+ hasProjectsEnabled?: boolean; /** Whether sponsorships are enabled. */
28
+ hasSponsorshipsEnabled?: boolean; /** Whether vulnerability alerts are enabled. */
29
+ hasVulnerabilityAlertsEnabled?: boolean; /** Whether the wiki is enabled. */
30
+ hasWikiEnabled?: boolean; /** Homepage URL set on the repository. */
31
+ homepageUrl?: string; /** Whether the repository is archived. */
32
+ isArchived?: boolean; /** Whether the repository is disabled. */
33
+ isDisabled?: boolean; /** Whether the repository is a fork. */
34
+ isFork?: boolean; /** Whether the repository belongs to an organization. */
35
+ isInOrganization?: boolean; /** Whether the repository is a mirror. */
36
+ isMirror?: boolean; /** Whether the repository is private. */
37
+ isPrivate?: boolean; /** Whether a security policy is enabled. */
38
+ isSecurityPolicyEnabled?: boolean; /** Number of closed issues. */
39
+ issueCountClosed?: number; /** Number of open issues. */
40
+ issueCountOpen?: number; /** Whether the repository is a template. */
41
+ isTemplate?: boolean; /** Languages used in the repo, keyed by name with size in bytes. */
42
+ languages?: Record<string, number>;
43
+ /** License identifier (unused; codemeta provides this). */
44
+ /** SPDX license key (e.g. "mit"). */
45
+ licenseKey?: string; /** Human-readable license name. */
46
+ licenseName?: string; /** SPDX license identifier (e.g. "MIT"). */
47
+ licenseSpdxId?: string; /** URL to the license text. */
48
+ licenseUrl?: string; /** URL of the upstream mirror, if applicable. */
49
+ mirrorUrl?: string; /** Repository name. */
50
+ name?: string; /** Full "owner/repo" identifier. */
51
+ nameWithOwner?: string; /** URL to the repository's Open Graph image. */
52
+ openGraphImageUrl?: string; /** GitHub username of the repository owner. */
53
+ ownerLogin?: string; /** Owner type, e.g. "User" or "Organization". */
54
+ ownerType?: string; /** Full "owner/repo" of the parent fork source. */
55
+ parentNameWithOwner?: string; /** Primary programming language of the repository. */
56
+ primaryLanguage?: string; /** Number of closed pull requests. */
57
+ pullRequestCountClosed?: number; /** Number of merged pull requests. */
58
+ pullRequestCountMerged?: number; /** Number of open pull requests. */
59
+ pullRequestCountOpen?: number; /** ISO 8601 date of the most recent push. */
60
+ pushedAt?: string; /** Total number of releases. */
61
+ releaseCount?: number; /** ISO 8601 date of the latest release. */
62
+ releaseDateLatest?: string; /** Total download count across latest release assets. */
63
+ releaseDownloadCount?: number; /** Tag name of the latest release. */
64
+ releaseVersionLatest?: string; /** URL to the security policy. */
65
+ securityPolicyUrl?: string; /** Repository merge and branch settings. */
66
+ settings?: {
67
+ /** Whether "Update branch" button is enabled. */allowUpdateBranch?: boolean; /** Whether auto-merge is allowed. */
68
+ autoMergeAllowed?: boolean; /** Whether branches are deleted after merge. */
69
+ deleteBranchOnMerge?: boolean; /** Whether forking is allowed. */
70
+ forkingAllowed?: boolean; /** Whether merge commits are allowed. */
71
+ mergeCommitAllowed?: boolean; /** Template for merge commit messages. */
72
+ mergeCommitMessage?: string; /** Template for merge commit titles. */
73
+ mergeCommitTitle?: string; /** Whether rebase merging is allowed. */
74
+ rebaseMergeAllowed?: boolean; /** Whether squash merging is allowed. */
75
+ squashMergeAllowed?: boolean; /** Template for squash merge commit messages. */
76
+ squashMergeCommitMessage?: string; /** Template for squash merge commit titles. */
77
+ squashMergeCommitTitle?: string; /** Whether web-based commits require sign-off. */
78
+ webCommitSignoffRequired?: boolean;
79
+ }; /** SSH clone URL. */
80
+ sshUrl?: string; /** Number of stars. */
81
+ stargazerCount?: number; /** Number of git submodules (detected via .gitmodules). */
82
+ submoduleCount?: number; /** URL of the template repository this was created from. */
83
+ templateFrom?: string; /** Repository topics. */
84
+ topics?: string[]; /** ISO 8601 date the repo was last updated. */
85
+ updatedAt?: string; /** GitHub URL of the repository. */
86
+ url?: string; /** Whether a custom Open Graph image is set. */
87
+ usesCustomOpenGraphImage?: boolean; /** Repository visibility (e.g. "PUBLIC", "PRIVATE"). */
88
+ visibility?: string; /** Number of open vulnerability alerts. */
89
+ vulnerabilityAlertCount?: number; /** Number of watchers. */
90
+ watcherCount?: number;
91
+ };
92
+ type GitHubData = OneOrMany<SourceRecord<GitHubInfo>> | undefined;
93
+ //#endregion
94
+ export { GitHubData };
@@ -0,0 +1,399 @@
1
+ import { log } from "../log.js";
2
+ import { defineSource } from "../source.js";
3
+ import { ensureArray } from "../utilities/template-helpers.js";
4
+ import { gitConfigSource } from "./git-config.js";
5
+ import { z } from "zod";
6
+ import gitUrlParse from "git-url-parse";
7
+ import { Octokit } from "octokit";
8
+ //#region src/lib/sources/github.ts
9
+ const gitHubRepoSchema = z.object({ repository: z.object({
10
+ allowUpdateBranch: z.boolean(),
11
+ archivedAt: z.string().nullable(),
12
+ autoMergeAllowed: z.boolean(),
13
+ closedIssues: z.object({ totalCount: z.number() }),
14
+ closedPullRequests: z.object({ totalCount: z.number() }),
15
+ codeOfConduct: z.object({ name: z.string() }).nullable(),
16
+ contributingGuidelines: z.object({ body: z.string() }).nullable(),
17
+ contributorCount: z.number().optional(),
18
+ createdAt: z.string(),
19
+ databaseId: z.number(),
20
+ defaultBranchRef: z.object({ name: z.string() }).nullable(),
21
+ deleteBranchOnMerge: z.boolean(),
22
+ description: z.string().nullable(),
23
+ discussions: z.object({ totalCount: z.number() }),
24
+ diskUsage: z.number().nullable(),
25
+ forkCount: z.number(),
26
+ forkingAllowed: z.boolean(),
27
+ fundingLinks: z.array(z.object({
28
+ platform: z.string(),
29
+ url: z.string()
30
+ })),
31
+ gitattributes: z.object({ text: z.string().nullable() }).nullable(),
32
+ gitmodules: z.object({ text: z.string().nullable() }).nullable(),
33
+ hasDiscussionsEnabled: z.boolean(),
34
+ hasIssuesEnabled: z.boolean(),
35
+ hasProjectsEnabled: z.boolean(),
36
+ hasSponsorshipsEnabled: z.boolean(),
37
+ hasVulnerabilityAlertsEnabled: z.boolean(),
38
+ hasWikiEnabled: z.boolean(),
39
+ homepageUrl: z.string().nullable(),
40
+ isArchived: z.boolean(),
41
+ isDisabled: z.boolean(),
42
+ isFork: z.boolean(),
43
+ isInOrganization: z.boolean(),
44
+ isMirror: z.boolean(),
45
+ isPrivate: z.boolean(),
46
+ isSecurityPolicyEnabled: z.boolean(),
47
+ isTemplate: z.boolean(),
48
+ languages: z.object({ edges: z.array(z.object({
49
+ node: z.object({ name: z.string() }),
50
+ size: z.number()
51
+ })) }).nullable(),
52
+ latestRelease: z.object({
53
+ createdAt: z.string(),
54
+ releaseAssets: z.object({ nodes: z.array(z.object({ downloadCount: z.number() })) }),
55
+ tagName: z.string()
56
+ }).nullable(),
57
+ licenseInfo: z.object({
58
+ key: z.string(),
59
+ name: z.string(),
60
+ spdxId: z.string().nullable(),
61
+ url: z.string().nullable()
62
+ }).nullable(),
63
+ mergeCommitAllowed: z.boolean(),
64
+ mergeCommitMessage: z.string(),
65
+ mergeCommitTitle: z.string(),
66
+ mergedPullRequests: z.object({ totalCount: z.number() }),
67
+ mirrorUrl: z.string().nullable(),
68
+ name: z.string(),
69
+ nameWithOwner: z.string(),
70
+ openGraphImageUrl: z.string(),
71
+ openIssues: z.object({ totalCount: z.number() }),
72
+ openPullRequests: z.object({ totalCount: z.number() }),
73
+ owner: z.object({
74
+ __typename: z.string(),
75
+ login: z.string()
76
+ }),
77
+ parent: z.object({
78
+ defaultBranchRef: z.object({ name: z.string() }).nullable(),
79
+ name: z.string(),
80
+ nameWithOwner: z.string(),
81
+ owner: z.object({ login: z.string() }),
82
+ url: z.string()
83
+ }).nullable(),
84
+ primaryLanguage: z.object({ name: z.string() }).nullable(),
85
+ pushedAt: z.string().nullable(),
86
+ rebaseMergeAllowed: z.boolean(),
87
+ releases: z.object({ totalCount: z.number() }),
88
+ repositoryTopics: z.object({ nodes: z.array(z.object({ topic: z.object({ name: z.string() }) })) }),
89
+ securityPolicyUrl: z.string().nullable(),
90
+ squashMergeAllowed: z.boolean(),
91
+ squashMergeCommitMessage: z.string(),
92
+ squashMergeCommitTitle: z.string(),
93
+ sshUrl: z.string(),
94
+ stargazerCount: z.number(),
95
+ templateRepository: z.object({
96
+ name: z.string(),
97
+ owner: z.object({ login: z.string() }),
98
+ url: z.string()
99
+ }).nullable(),
100
+ updatedAt: z.string(),
101
+ url: z.string(),
102
+ usesCustomOpenGraphImage: z.boolean(),
103
+ visibility: z.string(),
104
+ vulnerabilityAlerts: z.object({ totalCount: z.number() }).nullable(),
105
+ watchers: z.object({ totalCount: z.number() }),
106
+ webCommitSignoffRequired: z.boolean()
107
+ }) });
108
+ /**
109
+ * Extract a GitHub owner/repo from git config remote URLs.
110
+ * Prefers the "origin" remote, falls back to the first GitHub remote found.
111
+ */
112
+ function getGitHubRemoteFromConfig(remotes) {
113
+ if (!remotes) return void 0;
114
+ const sorted = Object.entries(remotes).toSorted(([a], [b]) => {
115
+ if (a === "origin") return -1;
116
+ if (b === "origin") return 1;
117
+ return 0;
118
+ });
119
+ for (const [, remote] of sorted) {
120
+ const { url } = remote;
121
+ if (!url) continue;
122
+ try {
123
+ const parsed = gitUrlParse(url);
124
+ if (parsed.source === "github.com" && parsed.owner && parsed.name) return {
125
+ owner: parsed.owner,
126
+ repo: parsed.name
127
+ };
128
+ } catch {}
129
+ }
130
+ }
131
+ const graphqlQuery = `
132
+ query($owner: String!, $repo: String!) {
133
+ repository(owner: $owner, name: $repo) {
134
+ name
135
+ nameWithOwner
136
+ owner { __typename login }
137
+ url
138
+ description
139
+ homepageUrl
140
+ createdAt
141
+ updatedAt
142
+ pushedAt
143
+ archivedAt
144
+ databaseId
145
+ isArchived
146
+ isDisabled
147
+ isFork
148
+ isInOrganization
149
+ isMirror
150
+ isPrivate
151
+ isTemplate
152
+ visibility
153
+ diskUsage
154
+ stargazerCount
155
+ forkCount
156
+ sshUrl
157
+ hasWikiEnabled
158
+ hasDiscussionsEnabled
159
+ hasIssuesEnabled
160
+ hasProjectsEnabled
161
+ hasSponsorshipsEnabled
162
+ hasVulnerabilityAlertsEnabled
163
+ isSecurityPolicyEnabled
164
+ securityPolicyUrl
165
+ openGraphImageUrl
166
+ usesCustomOpenGraphImage
167
+ autoMergeAllowed
168
+ allowUpdateBranch
169
+ deleteBranchOnMerge
170
+ forkingAllowed
171
+ mergeCommitAllowed
172
+ mergeCommitMessage
173
+ mergeCommitTitle
174
+ mirrorUrl
175
+ rebaseMergeAllowed
176
+ squashMergeAllowed
177
+ squashMergeCommitMessage
178
+ squashMergeCommitTitle
179
+ webCommitSignoffRequired
180
+ codeOfConduct { name }
181
+ contributingGuidelines { body }
182
+ fundingLinks { platform url }
183
+ licenseInfo { key name spdxId url }
184
+ defaultBranchRef { name }
185
+ primaryLanguage { name }
186
+ parent {
187
+ owner { login }
188
+ name
189
+ nameWithOwner
190
+ url
191
+ defaultBranchRef { name }
192
+ }
193
+ templateRepository {
194
+ owner { login }
195
+ name
196
+ url
197
+ }
198
+ gitattributes: object(expression: "HEAD:.gitattributes") {
199
+ ... on Blob { text }
200
+ }
201
+ gitmodules: object(expression: "HEAD:.gitmodules") {
202
+ ... on Blob { text }
203
+ }
204
+ openIssues: issues(states: OPEN) { totalCount }
205
+ closedIssues: issues(states: CLOSED) { totalCount }
206
+ openPullRequests: pullRequests(states: OPEN) { totalCount }
207
+ closedPullRequests: pullRequests(states: CLOSED) { totalCount }
208
+ mergedPullRequests: pullRequests(states: MERGED) { totalCount }
209
+ discussions { totalCount }
210
+ vulnerabilityAlerts(states: OPEN) { totalCount }
211
+ watchers { totalCount }
212
+ releases { totalCount }
213
+ latestRelease {
214
+ tagName
215
+ createdAt
216
+ releaseAssets(first: 100) {
217
+ nodes { downloadCount }
218
+ }
219
+ }
220
+ repositoryTopics(first: 50) {
221
+ nodes { topic { name } }
222
+ }
223
+ languages(first: 20, orderBy: { field: SIZE, direction: DESC }) {
224
+ edges {
225
+ node { name }
226
+ size
227
+ }
228
+ }
229
+ }
230
+ }
231
+ `;
232
+ async function checkHasPages(octokit, owner, repo) {
233
+ try {
234
+ return (await octokit.rest.repos.get({
235
+ owner,
236
+ repo
237
+ })).data.has_pages;
238
+ } catch {
239
+ return false;
240
+ }
241
+ }
242
+ async function getUpstreamComparison(octokit, owner, repo, defaultBranch, parent) {
243
+ const parentBranch = parent.defaultBranchRef?.name;
244
+ if (!parentBranch) return void 0;
245
+ try {
246
+ const response = await octokit.rest.repos.compareCommitsWithBasehead({
247
+ basehead: `${parent.owner.login}:${parentBranch}...${owner}:${defaultBranch}`,
248
+ owner,
249
+ repo
250
+ });
251
+ return {
252
+ ahead: response.data.ahead_by,
253
+ behind: response.data.behind_by
254
+ };
255
+ } catch {
256
+ return;
257
+ }
258
+ }
259
+ function countSubmodules(gitmodulesText) {
260
+ if (!gitmodulesText) return 0;
261
+ return gitmodulesText.match(/\[submodule\s/g)?.length ?? 0;
262
+ }
263
+ function detectLfs(gitattributesText) {
264
+ if (!gitattributesText) return false;
265
+ return gitattributesText.includes("filter=lfs");
266
+ }
267
+ function extractLanguages(data) {
268
+ const languages = {};
269
+ if (data.languages?.edges) for (const edge of data.languages.edges) languages[edge.node.name] = edge.size;
270
+ return languages;
271
+ }
272
+ function mapRepoData(data, extras) {
273
+ const releaseDownloadCount = (data.latestRelease?.releaseAssets.nodes.reduce((sum, asset) => sum + asset.downloadCount, 0) ?? 0) || void 0;
274
+ return {
275
+ archivedAt: data.archivedAt ?? void 0,
276
+ codeOfConduct: data.codeOfConduct?.name ?? void 0,
277
+ commitsAheadUpstream: extras.commitsAheadUpstream,
278
+ commitsBehindUpstream: extras.commitsBehindUpstream,
279
+ contributorCount: data.contributorCount,
280
+ createdAt: data.createdAt,
281
+ databaseId: data.databaseId,
282
+ defaultBranch: data.defaultBranchRef?.name ?? void 0,
283
+ description: data.description ?? void 0,
284
+ discussionCount: data.discussions.totalCount,
285
+ diskUsageBytes: data.diskUsage === null ? void 0 : data.diskUsage * 1e3,
286
+ forkCount: data.forkCount,
287
+ forkedFrom: data.parent?.url ?? void 0,
288
+ fundingLinks: data.fundingLinks.length > 0 ? data.fundingLinks.map((link) => ({
289
+ platform: link.platform,
290
+ url: link.url
291
+ })) : void 0,
292
+ hasContributing: data.contributingGuidelines !== null,
293
+ hasDiscussionsEnabled: data.hasDiscussionsEnabled,
294
+ hasIssuesEnabled: data.hasIssuesEnabled,
295
+ hasLfs: detectLfs(data.gitattributes?.text ?? void 0),
296
+ hasPages: extras.hasPages,
297
+ hasProjectsEnabled: data.hasProjectsEnabled,
298
+ hasSponsorshipsEnabled: data.hasSponsorshipsEnabled,
299
+ hasVulnerabilityAlertsEnabled: data.hasVulnerabilityAlertsEnabled,
300
+ hasWikiEnabled: data.hasWikiEnabled,
301
+ homepageUrl: data.homepageUrl === "" ? void 0 : data.homepageUrl ?? void 0,
302
+ isArchived: data.isArchived,
303
+ isDisabled: data.isDisabled,
304
+ isFork: data.isFork,
305
+ isInOrganization: data.isInOrganization,
306
+ isMirror: data.isMirror,
307
+ isPrivate: data.isPrivate,
308
+ isSecurityPolicyEnabled: data.isSecurityPolicyEnabled,
309
+ issueCountClosed: data.closedIssues.totalCount,
310
+ issueCountOpen: data.openIssues.totalCount,
311
+ isTemplate: data.isTemplate,
312
+ languages: extractLanguages(data),
313
+ licenseKey: data.licenseInfo?.key ?? void 0,
314
+ licenseName: data.licenseInfo?.name ?? void 0,
315
+ licenseSpdxId: data.licenseInfo?.spdxId === "NOASSERTION" ? void 0 : data.licenseInfo?.spdxId ?? void 0,
316
+ licenseUrl: data.licenseInfo?.url ?? void 0,
317
+ mirrorUrl: data.mirrorUrl ?? void 0,
318
+ name: data.name,
319
+ nameWithOwner: data.nameWithOwner,
320
+ openGraphImageUrl: data.openGraphImageUrl,
321
+ ownerLogin: data.owner.login,
322
+ ownerType: data.owner.__typename,
323
+ parentNameWithOwner: data.parent?.nameWithOwner ?? void 0,
324
+ primaryLanguage: data.primaryLanguage?.name ?? void 0,
325
+ pullRequestCountClosed: data.closedPullRequests.totalCount,
326
+ pullRequestCountMerged: data.mergedPullRequests.totalCount,
327
+ pullRequestCountOpen: data.openPullRequests.totalCount,
328
+ pushedAt: data.pushedAt ?? void 0,
329
+ releaseCount: data.releases.totalCount || void 0,
330
+ releaseDateLatest: data.latestRelease?.createdAt ?? void 0,
331
+ releaseDownloadCount,
332
+ releaseVersionLatest: data.latestRelease?.tagName ?? void 0,
333
+ securityPolicyUrl: data.securityPolicyUrl ?? void 0,
334
+ settings: {
335
+ allowUpdateBranch: data.allowUpdateBranch,
336
+ autoMergeAllowed: data.autoMergeAllowed,
337
+ deleteBranchOnMerge: data.deleteBranchOnMerge,
338
+ forkingAllowed: data.forkingAllowed,
339
+ mergeCommitAllowed: data.mergeCommitAllowed,
340
+ mergeCommitMessage: data.mergeCommitMessage,
341
+ mergeCommitTitle: data.mergeCommitTitle,
342
+ rebaseMergeAllowed: data.rebaseMergeAllowed,
343
+ squashMergeAllowed: data.squashMergeAllowed,
344
+ squashMergeCommitMessage: data.squashMergeCommitMessage,
345
+ squashMergeCommitTitle: data.squashMergeCommitTitle,
346
+ webCommitSignoffRequired: data.webCommitSignoffRequired
347
+ },
348
+ sshUrl: data.sshUrl,
349
+ stargazerCount: data.stargazerCount,
350
+ submoduleCount: countSubmodules(data.gitmodules?.text ?? void 0),
351
+ templateFrom: data.templateRepository?.url ?? void 0,
352
+ topics: data.repositoryTopics.nodes.map((n) => n.topic.name),
353
+ updatedAt: data.updatedAt,
354
+ url: data.url,
355
+ usesCustomOpenGraphImage: data.usesCustomOpenGraphImage,
356
+ visibility: data.visibility,
357
+ vulnerabilityAlertCount: data.vulnerabilityAlerts?.totalCount ?? void 0,
358
+ watcherCount: data.watchers.totalCount
359
+ };
360
+ }
361
+ const githubSource = defineSource({
362
+ async discover(context) {
363
+ let gitRemotes = ensureArray(context.metadata?.gitConfig).map((config) => config.data.remote).filter((remote) => remote !== void 0);
364
+ if (gitRemotes.length === 0 && !context.completedSources?.has("gitConfig")) {
365
+ log.warn(`Missing gitConfig in source context metadata for ${context.options.path}, extracting it now...`);
366
+ gitRemotes = ensureArray(await gitConfigSource.extract(context)).map((config) => config.data.remote).filter((remote) => remote !== void 0);
367
+ }
368
+ return [...new Set(gitRemotes.map((config) => getGitHubRemoteFromConfig(config)).filter((remote) => remote !== void 0).map((remote) => `${remote.owner}/${remote.repo}`))];
369
+ },
370
+ key: "github",
371
+ async parse(input, context) {
372
+ log.debug("Extracting GitHub metadata...");
373
+ const [owner, repo] = input.split("/");
374
+ const octokit = new Octokit(context.options.credentials?.githubToken ? { auth: context.options.credentials.githubToken } : void 0);
375
+ const [graphqlResult, hasPages] = await Promise.all([octokit.graphql(graphqlQuery, {
376
+ owner,
377
+ repo
378
+ }), checkHasPages(octokit, owner, repo)]);
379
+ const data = gitHubRepoSchema.parse(graphqlResult).repository;
380
+ let commitsAheadUpstream;
381
+ let commitsBehindUpstream;
382
+ if (data.isFork && data.parent && data.defaultBranchRef) {
383
+ const comparison = await getUpstreamComparison(octokit, owner, repo, data.defaultBranchRef.name, data.parent);
384
+ commitsAheadUpstream = comparison?.ahead;
385
+ commitsBehindUpstream = comparison?.behind;
386
+ }
387
+ return {
388
+ data: mapRepoData(data, {
389
+ commitsAheadUpstream,
390
+ commitsBehindUpstream,
391
+ hasPages
392
+ }),
393
+ source: `https://github.com/${owner}/${repo}`
394
+ };
395
+ },
396
+ phase: 2
397
+ });
398
+ //#endregion
399
+ export { githubSource };
@@ -0,0 +1,19 @@
1
+ import { OneOrMany, SourceRecord } from "../source.js";
2
+ import { z } from "zod";
3
+
4
+ //#region src/lib/sources/go-go-mod.d.ts
5
+ declare const goModDataSchema: z.ZodObject<{
6
+ dependencies: z.ZodArray<z.ZodObject<{
7
+ module: z.ZodString;
8
+ version: z.ZodString;
9
+ }, z.core.$strip>>;
10
+ go_version: z.ZodPipe<z.ZodTransform<string | undefined, unknown>, z.ZodOptional<z.ZodString>>;
11
+ module: z.ZodPipe<z.ZodTransform<string | undefined, unknown>, z.ZodOptional<z.ZodString>>;
12
+ repository_url: z.ZodPipe<z.ZodTransform<string | undefined, unknown>, z.ZodOptional<z.ZodString>>;
13
+ tool_dependencies: z.ZodPipe<z.ZodTransform<string[], unknown>, z.ZodArray<z.ZodString>>;
14
+ }, z.core.$strip>;
15
+ /** Parsed go.mod metadata */
16
+ type GoMod = z.infer<typeof goModDataSchema>;
17
+ type GoGoModData = OneOrMany<SourceRecord<GoMod>> | undefined;
18
+ //#endregion
19
+ export { GoGoModData };
@@ -0,0 +1,38 @@
1
+ import { getMatches } from "../file-matching.js";
2
+ import { defineSource } from "../source.js";
3
+ import { nonEmptyString, optionalUrl, stringArray } from "../utilities/schema-primitives.js";
4
+ import { parseGoMod } from "../parsers/go-mod-parser.js";
5
+ import { readFile } from "node:fs/promises";
6
+ import { resolve } from "node:path";
7
+ import { z } from "zod";
8
+ //#region src/lib/sources/go-go-mod.ts
9
+ const goModDependencySchema = z.object({
10
+ module: z.string(),
11
+ version: z.string()
12
+ });
13
+ const goModDataSchema = z.object({
14
+ dependencies: z.array(goModDependencySchema),
15
+ go_version: nonEmptyString,
16
+ module: nonEmptyString,
17
+ repository_url: optionalUrl,
18
+ tool_dependencies: stringArray
19
+ });
20
+ /** Parse a go.mod file string and validate through the schema. */
21
+ function parse(content) {
22
+ return goModDataSchema.parse(parseGoMod(content));
23
+ }
24
+ const goGoModSource = defineSource({
25
+ async discover(context) {
26
+ return getMatches(context.options, ["go.mod"]);
27
+ },
28
+ key: "goGoMod",
29
+ async parse(input, context) {
30
+ return {
31
+ data: parse(await readFile(resolve(context.options.path, input), "utf8")),
32
+ source: input
33
+ };
34
+ },
35
+ phase: 1
36
+ });
37
+ //#endregion
38
+ export { goGoModSource };