Skip to content

Commit 4310e7b

Browse files
authored
feat: add plugin data generation script (#3)
* feat: add plugin data generation script * chore: update valid tags * chore: update database support * chore: update icon
1 parent 5662fe6 commit 4310e7b

File tree

7 files changed

+982
-0
lines changed

7 files changed

+982
-0
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Generate Plugins Data
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
generate:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout repository
17+
uses: actions/checkout@v4
18+
with:
19+
submodules: recursive
20+
token: ${{ secrets.GITHUB_TOKEN }}
21+
22+
- name: Setup Node.js
23+
uses: actions/setup-node@v4
24+
with:
25+
node-version: '22'
26+
27+
- name: Setup pnpm
28+
uses: pnpm/action-setup@v4
29+
with:
30+
version: 10
31+
32+
- name: Install dependencies
33+
run: pnpm install
34+
35+
- name: Run generate script
36+
run: pnpm generate
37+
38+
- name: Check for changes
39+
id: changes
40+
run: |
41+
if [ -n "$(git status --porcelain)" ]; then
42+
echo "has_changes=true" >> $GITHUB_OUTPUT
43+
fi
44+
45+
- name: Commit and push changes
46+
if: steps.changes.outputs.has_changes == 'true'
47+
run: |
48+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
49+
git config --local user.name "github-actions[bot]"
50+
git add plugins-data.ts plugins-data.json
51+
git commit -m "chore: auto-generate plugins data"
52+
git push

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# 依赖
2+
node_modules/
3+
4+
# IDE
5+
.idea/
6+
.vscode/
7+
*.swp
8+
*.swo
9+
*~

generate.ts

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
import { fileURLToPath } from 'url'
4+
import toml from 'toml'
5+
6+
const __filename = fileURLToPath(import.meta.url)
7+
const __dirname = path.dirname(__filename)
8+
9+
const VALID_TAGS = [
10+
"ai",
11+
"mcp",
12+
"agent",
13+
"rag",
14+
"permission",
15+
"sso",
16+
"rbac",
17+
"auth",
18+
"ldap",
19+
"storage",
20+
"notification",
21+
"task",
22+
"other",
23+
] as const
24+
25+
const VALID_DATABASES = ['mysql', 'postgresql'] as const
26+
27+
const DEFAULT_ICON = 'https://wu-clan.github.io/picx-images-hosting/logo/fba.svg'
28+
29+
interface PluginTomlPlugin {
30+
icon?: string
31+
summary: string
32+
version: string
33+
description: string
34+
author: string
35+
tags?: string[]
36+
database?: string[]
37+
}
38+
39+
interface PluginToml {
40+
plugin: PluginTomlPlugin
41+
}
42+
43+
interface GitModule {
44+
path: string
45+
url: string
46+
branch: string
47+
}
48+
49+
interface PluginData {
50+
plugin: PluginTomlPlugin
51+
git: GitModule
52+
}
53+
54+
55+
function resolveIconUrl(iconPath: string | undefined, gitUrl: string, branch: string): string {
56+
if (!iconPath) return DEFAULT_ICON
57+
58+
if (iconPath.startsWith('http://') || iconPath.startsWith('https://')) {
59+
return iconPath
60+
}
61+
62+
const match = gitUrl.match(/github\.com[/:]([^/]+)\/([^/.]+)/)
63+
if (!match) return DEFAULT_ICON
64+
65+
const [, owner, repo] = match
66+
return `https://raw.githubusercontent.com/${ owner }/${ repo }/${ branch }/${ iconPath }`
67+
}
68+
69+
function loadPluginToml(pluginPath: string): PluginToml | null {
70+
const tomlPath = path.join(pluginPath, 'plugin.toml')
71+
if (!fs.existsSync(tomlPath)) return null
72+
73+
try {
74+
return toml.parse(fs.readFileSync(tomlPath, 'utf-8')) as PluginToml
75+
} catch {
76+
return null
77+
}
78+
}
79+
80+
function parseGitModules(gitmodulesPath: string): Map<string, GitModule> {
81+
const modules = new Map<string, GitModule>()
82+
if (!fs.existsSync(gitmodulesPath)) return modules
83+
84+
const content = fs.readFileSync(gitmodulesPath, 'utf-8')
85+
const lines = content.split('\n')
86+
87+
let currentPath = ''
88+
let currentUrl = ''
89+
let currentBranch = ''
90+
91+
for (const line of lines) {
92+
const pathMatch = line.match(/path\s*=\s*(.+)/)
93+
const urlMatch = line.match(/url\s*=\s*(.+)/)
94+
const branchMatch = line.match(/branch\s*=\s*(.+)/)
95+
96+
if (pathMatch) currentPath = pathMatch[1].trim()
97+
if (urlMatch) currentUrl = urlMatch[1].trim()
98+
if (branchMatch) currentBranch = branchMatch[1].trim()
99+
100+
if (currentPath && currentUrl) {
101+
modules.set(currentPath, {
102+
path: currentPath,
103+
url: currentUrl,
104+
branch: currentBranch || 'master',
105+
})
106+
currentPath = ''
107+
currentUrl = ''
108+
currentBranch = ''
109+
}
110+
}
111+
112+
return modules
113+
}
114+
115+
function generatePluginData(pluginsDir: string, gitmodulesPath: string): PluginData[] {
116+
const gitModules = parseGitModules(gitmodulesPath)
117+
const pluginDataList: PluginData[] = []
118+
119+
const pluginDirs = fs.readdirSync(pluginsDir, { withFileTypes: true })
120+
.filter(entry => entry.isDirectory() && !entry.name.startsWith('.'))
121+
.map(entry => entry.name)
122+
.sort()
123+
124+
for (const pluginName of pluginDirs) {
125+
const pluginPath = path.join(pluginsDir, pluginName)
126+
const pluginConfig = loadPluginToml(pluginPath)
127+
128+
if (!pluginConfig?.plugin) {
129+
console.warn(`警告: ${ pluginName } 没有有效的 plugin.toml`)
130+
continue
131+
}
132+
133+
const modulePath = `plugins/${ pluginName }`
134+
const gitModule = gitModules.get(modulePath)
135+
136+
if (!gitModule) {
137+
console.warn(`警告: ${ pluginName } 没有对应的 git submodule`)
138+
continue
139+
}
140+
141+
const rawPlugin = pluginConfig.plugin
142+
let database: string[] | undefined
143+
if (rawPlugin.database && Array.isArray(rawPlugin.database)) {
144+
const filtered = rawPlugin.database
145+
.map(db => {
146+
const lower = db.toLowerCase()
147+
if (lower === 'pgsql') return 'postgresql'
148+
return lower
149+
})
150+
.filter(db => VALID_DATABASES.includes(db as any))
151+
if (filtered.length > 0) {
152+
database = [...new Set(filtered)]
153+
}
154+
}
155+
156+
const plugin: PluginTomlPlugin = {
157+
icon: resolveIconUrl(rawPlugin.icon, gitModule.url, gitModule.branch),
158+
summary: rawPlugin.summary,
159+
version: rawPlugin.version,
160+
description: rawPlugin.description,
161+
author: rawPlugin.author,
162+
...(rawPlugin.tags && { tags: rawPlugin.tags }),
163+
...(database && { database }),
164+
}
165+
166+
pluginDataList.push({
167+
plugin,
168+
git: gitModule,
169+
})
170+
}
171+
172+
return pluginDataList
173+
}
174+
175+
function generateTypeScriptCode(pluginDataList: PluginData[]): string {
176+
return `export const validTags = ${ JSON.stringify(VALID_TAGS, null, 2) } as const
177+
178+
export interface PluginTomlPlugin {
179+
icon: string
180+
summary: string
181+
version: string
182+
description: string
183+
author: string
184+
tags?: string[]
185+
database?: string[]
186+
}
187+
188+
export interface GitModule {
189+
path: string
190+
url: string
191+
branch: string
192+
}
193+
194+
export interface PluginData {
195+
plugin: PluginTomlPlugin
196+
git: GitModule
197+
}
198+
199+
export const pluginDataList: PluginData[] = ${ JSON.stringify(pluginDataList, null, 2) }
200+
`
201+
}
202+
203+
function main() {
204+
const baseDir = __dirname
205+
const pluginsDir = path.join(baseDir, 'plugins')
206+
const gitmodulesPath = path.join(baseDir, '.gitmodules')
207+
208+
console.log('生成插件数据...')
209+
210+
const pluginDataList = generatePluginData(pluginsDir, gitmodulesPath)
211+
console.log(`找到 ${ pluginDataList.length } 个插件`)
212+
213+
fs.writeFileSync(
214+
path.join(baseDir, 'plugins-data.ts'),
215+
generateTypeScriptCode(pluginDataList),
216+
'utf-8'
217+
)
218+
219+
fs.writeFileSync(
220+
path.join(baseDir, 'plugins-data.json'),
221+
JSON.stringify({ validTags: VALID_TAGS, pluginDataList }, null, 2),
222+
'utf-8'
223+
)
224+
225+
console.log('完成')
226+
}
227+
228+
main()

package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"type": "module",
3+
"scripts": {
4+
"generate": "tsx generate.ts",
5+
"build": "tsx generate.ts"
6+
},
7+
"devDependencies": {
8+
"@types/node": "^20.0.0",
9+
"tsx": "^4.7.0"
10+
},
11+
"dependencies": {
12+
"toml": "^3.0.0"
13+
}
14+
}

0 commit comments

Comments
 (0)