Repository navigation
Expand file tree
/
Copy pathgenerate-projects-data.script.ts
More file actions
143 lines (113 loc) · 3.74 KB
/
Copy pathgenerate-projects-data.script.ts
File metadata and controls
143 lines (113 loc) · 3.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import {
readdir,
readFile,
writeFile,
} from "node:fs/promises"
import { join } from "node:path"
import type {
GithubRepoResponse,
ProjectData,
ProjectPageConfig,
} from "./app/types.ts"
function extractProjectConfig(vueSource: string, path: string): ProjectPageConfig | null {
const componentMatch = vueSource.match(/<HomeProjectPage\b([\s\S]*?)\/>/m)
if (!componentMatch?.[1]) {
return null
}
const attrs = componentMatch[1]
const repoMatch = attrs.match(/\bname\s*=\s*["']([^"']+)["']/)
if (!repoMatch?.[1]) {
return null
}
const branchMatch = attrs.match(/\bbranch\s*=\s*["']([^"']+)["']/)
return {
repo: repoMatch[1],
branch: branchMatch?.[1] || "master",
path,
}
}
function toGithubHeaders(): Record<string, string> {
return {
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2026-03-10",
}
}
async function readProjectPages(projectsDir: string): Promise<ProjectPageConfig[]> {
const entries = await readdir(projectsDir, { withFileTypes: true })
const pages = entries
.filter((entry) => entry.isFile() && entry.name.endsWith(".vue"))
.map((entry) => entry.name)
const configs = await Promise.all(pages.map(async (fileName) => {
const source = await readFile(join(projectsDir, fileName), "utf-8")
const path = fileName.replace(/\.vue$/, "")
return extractProjectConfig(source, path)
}))
return configs.filter((item): item is ProjectPageConfig => Boolean(item))
}
async function fetchRepoMetadata(repo: string): Promise<{
name: string;
description: string;
}> {
const response = await fetch(`https://api.github.com/repos/${repo}`, {
headers: toGithubHeaders(),
})
if (!response.ok) {
throw new Error(`GitHub API failed for ${repo} (status ${response.status})`)
}
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
const json = await response.json() as GithubRepoResponse
if (!json.full_name) {
throw new Error(`GitHub API returned no full_name for ${repo}`)
}
return {
name: json.full_name,
description: json.description || "",
}
}
function dedupeProjects(projects: ProjectPageConfig[]): ProjectPageConfig[] {
const unique = new Map<string, ProjectPageConfig>()
for (const project of projects) {
unique.set(`${project.repo}::${project.branch}`, project)
}
return [...unique.values()]
}
async function generateProjectsData(): Promise<void> {
console.log("🔄️ Generating projects metadata...\n")
const cwd = process.cwd()
const projectsDir = join(cwd, "app", "pages", "projects")
const outputPath = join(cwd, "app", "assets", "data", "projects.json")
const projectConfigs = dedupeProjects(await readProjectPages(projectsDir))
const generated: ProjectData[] = []
for (const config of projectConfigs) {
try {
// oxlint-disable-next-line no-await-in-loop
const details = await fetchRepoMetadata(config.repo)
generated.push({
repo: config.repo,
branch: config.branch,
path: config.path,
name: details.name,
description: details.description,
})
} catch (error) {
console.warn(`⚠️ Could not fetch ${config.repo}, using fallback data.`)
console.warn(error)
generated.push({
repo: config.repo,
branch: config.branch,
path: config.path,
name: config.repo,
description: "",
})
}
}
generated.sort((a, b) => a.repo.localeCompare(b.repo))
await writeFile(outputPath, `${JSON.stringify(generated, null, 2)}\n`, "utf-8")
console.log(`✅ Generated app/assets/data/projects.json (${generated.length} projects)\n`)
}
try {
await generateProjectsData()
} catch (error) {
console.error("❌ Error generating project data :", error)
process.exitCode = 1
}