install-guard 1.1.1 → 1.1.2

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.
@@ -0,0 +1,64 @@
1
+ import { getCached, setCache } from "./cache.js";
2
+
3
+ /**
4
+ * Extracts owner/repo from a repository URL.
5
+ */
6
+ function parseRepoUrl(url) {
7
+ if (!url) return null;
8
+ // Handles: git+https://github.com/owner/repo.git, https://github.com/owner/repo, etc.
9
+ const match = url.match(
10
+ /github\.com[/:]([^/]+)\/([^/.#]+)/
11
+ );
12
+ if (!match) return null;
13
+ return { owner: match[1], repo: match[2] };
14
+ }
15
+
16
+ async function ghFetch(path) {
17
+ const headers = { "User-Agent": "install-guard-cli" };
18
+ // Use token if available to avoid rate limits
19
+ if (process.env.GITHUB_TOKEN) {
20
+ headers.Authorization = `token ${process.env.GITHUB_TOKEN}`;
21
+ }
22
+ const res = await fetch(`https://api.github.com${path}`, { headers });
23
+ if (!res.ok) return null;
24
+ return res.json();
25
+ }
26
+
27
+ /**
28
+ * Checks if a given version tag exists on GitHub.
29
+ * Tries both `v1.2.3` and `1.2.3` tag formats.
30
+ */
31
+ export async function checkGitHubTag(repoUrl, version) {
32
+ const repo = parseRepoUrl(repoUrl);
33
+ if (!repo) return { hasRepo: false, tagFound: false, recentCommits: false };
34
+
35
+ const key = `gh-tag:${repo.owner}/${repo.repo}:${version}`;
36
+ const cached = getCached(key);
37
+ if (cached) return cached;
38
+
39
+ // Try v-prefixed and plain tag
40
+ const tags = await ghFetch(`/repos/${repo.owner}/${repo.repo}/tags?per_page=100`);
41
+ if (!tags) {
42
+ const result = { hasRepo: true, tagFound: false, recentCommits: false, error: "rate-limited or private" };
43
+ setCache(key, result);
44
+ return result;
45
+ }
46
+
47
+ const tagNames = tags.map((t) => t.name);
48
+ const tagFound = tagNames.includes(`v${version}`) || tagNames.includes(version);
49
+
50
+ // Check recent commits
51
+ const commits = await ghFetch(
52
+ `/repos/${repo.owner}/${repo.repo}/commits?per_page=1`
53
+ );
54
+ let recentCommits = false;
55
+ if (commits && commits.length > 0) {
56
+ const lastCommitDate = new Date(commits[0].commit?.committer?.date || 0);
57
+ const daysSinceCommit = (Date.now() - lastCommitDate.getTime()) / (1000 * 60 * 60 * 24);
58
+ recentCommits = daysSinceCommit < 90;
59
+ }
60
+
61
+ const result = { hasRepo: true, tagFound, recentCommits };
62
+ setCache(key, result);
63
+ return result;
64
+ }
@@ -0,0 +1,99 @@
1
+ import { getCached, setCache } from "./cache.js";
2
+
3
+ function encodePkg(pkg) {
4
+ return encodeURIComponent(pkg).replace("%40", "@");
5
+ }
6
+
7
+ async function fetchJSON(url) {
8
+ const res = await fetch(url);
9
+ if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
10
+ return res.json();
11
+ }
12
+
13
+ /**
14
+ * Fetches full registry metadata for a package.
15
+ * Returns the raw document from registry.npmjs.org/<pkg>
16
+ */
17
+ export async function getRegistryData(pkg) {
18
+ const key = `registry:${pkg}`;
19
+ const cached = getCached(key);
20
+ if (cached) return cached;
21
+
22
+ const data = await fetchJSON(`https://registry.npmjs.org/${encodePkg(pkg)}`);
23
+ setCache(key, data);
24
+ return data;
25
+ }
26
+
27
+ /**
28
+ * Fetches weekly download count.
29
+ */
30
+ export async function getDownloads(pkg) {
31
+ const key = `downloads:${pkg}`;
32
+ const cached = getCached(key);
33
+ if (cached) return cached;
34
+
35
+ try {
36
+ const data = await fetchJSON(
37
+ `https://api.npmjs.org/downloads/point/last-week/${encodePkg(pkg)}`
38
+ );
39
+ setCache(key, data);
40
+ return data;
41
+ } catch {
42
+ return { downloads: 0 };
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Resolves version, fetches metadata + downloads, returns a normalized context
48
+ * that every check module can consume.
49
+ */
50
+ export async function buildContext(pkg, requestedVersion) {
51
+ const registry = await getRegistryData(pkg);
52
+ const latest = registry["dist-tags"]?.latest;
53
+ if (!latest) throw new Error(`No published version found for "${pkg}"`);
54
+
55
+ const version = requestedVersion || latest;
56
+ const versionData = registry.versions?.[version];
57
+ if (!versionData) throw new Error(`Version "${version}" not found for "${pkg}"`);
58
+
59
+ const timeData = registry.time || {};
60
+ const allVersions = Object.keys(registry.versions || {});
61
+ const versionIndex = allVersions.indexOf(version);
62
+ const previousVersion = versionIndex > 0 ? allVersions[versionIndex - 1] : null;
63
+ const previousVersionData = previousVersion
64
+ ? registry.versions[previousVersion]
65
+ : null;
66
+
67
+ const downloads = await getDownloads(pkg);
68
+
69
+ return {
70
+ name: registry.name,
71
+ version,
72
+ previousVersion,
73
+ description: registry.description || "",
74
+ downloads: downloads.downloads || 0,
75
+ maintainers: registry.maintainers || [],
76
+ license: versionData.license || registry.license || "Unknown",
77
+ publishedAt: timeData[version],
78
+ previousPublishedAt: previousVersion ? timeData[previousVersion] : null,
79
+ firstPublished: timeData.created,
80
+ repository: registry.repository?.url || versionData.repository?.url || null,
81
+ deprecated: versionData.deprecated || false,
82
+ totalVersions: allVersions.length,
83
+ allVersions,
84
+
85
+ // Script data
86
+ scripts: versionData.scripts || {},
87
+
88
+ // Dependency data
89
+ dependencies: versionData.dependencies || {},
90
+ previousDependencies: previousVersionData?.dependencies || {},
91
+
92
+ // Maintainer history — registry only exposes current maintainers
93
+ currentMaintainers: registry.maintainers || [],
94
+
95
+ // Raw registry for advanced checks
96
+ _registry: registry,
97
+ _versionData: versionData,
98
+ };
99
+ }