|
1 | | -name: Sync Issue to CSV |
2 | | - |
3 | | -on: |
4 | | - issues: |
5 | | - types: [opened, edited] |
6 | | - issue_comment: |
7 | | - types: [created] |
8 | | - schedule: |
9 | | - - cron: "*/10 * * * *" |
10 | | - workflow_dispatch: |
11 | | - |
12 | | -permissions: |
13 | | - issues: read |
14 | | - contents: read |
15 | | - |
16 | | -jobs: |
17 | | - sync: |
18 | | - runs-on: ubuntu-latest |
19 | | - |
20 | | - steps: |
21 | | - - name: Checkout repo |
22 | | - uses: actions/checkout@v4 |
23 | | - |
24 | | - - name: Sync Issue to CSV |
25 | | - uses: actions/github-script@v7 |
26 | | - with: |
27 | | - script: | |
28 | | - const fs = require('fs'); |
29 | | - const owner = context.repo.owner; |
30 | | - const repo = context.repo.repo; |
31 | | -
|
32 | | - let csv = fs.existsSync('tasks.csv') ? fs.readFileSync('tasks.csv', 'utf8') : "Project,Repo,Issue,Owner,PR,Status,Source\n"; |
33 | | - let lines = csv.split('\n').filter(line => line.trim() !== ''); |
34 | | - const header = lines[0]; |
35 | | - let rows = lines.slice(1); |
36 | | -
|
37 | | - let issues = []; |
38 | | - |
39 | | - if (context.payload.issue) { |
40 | | - issues = [context.payload.issue]; |
41 | | - } else { |
42 | | - // 手动触发 / schedule → 拉全部 issue |
43 | | - const { data } = await github.rest.issues.listForRepo({ |
44 | | - owner, |
45 | | - repo, |
46 | | - state: 'open', |
47 | | - per_page: 100 |
48 | | - }); |
49 | | - issues = data; |
50 | | - } |
51 | | -
|
52 | | - for (const issue of issues) { |
53 | | - const title = issue.title; |
54 | | - const body = issue.body || ""; |
55 | | - const issueNumber = issue.number; |
56 | | - const issueUrl = `https://github.com/${owner}/${repo}/issues/${issueNumber}`; |
57 | | -
|
58 | | - const repoMatch = body.match(/https:\/\/github\.com\/[^\s]+/); |
59 | | - const repoUrl = repoMatch ? repoMatch[0] : ""; |
60 | | - if (!repoUrl) continue; |
61 | | -
|
62 | | - const ownerMatch = body.match(/Owner:\s*@([^\s]+)/); |
63 | | - const taskOwner = ownerMatch ? ownerMatch[1] : ""; |
64 | | -
|
65 | | - const prMatch = body.match(/PR:\s*(https:\/\/[^\s]+)/); |
66 | | - const prUrl = prMatch ? prMatch[1] : ""; |
67 | | -
|
68 | | - let status = ""; |
69 | | - if (prUrl) { |
70 | | - const mergedMatch = body.match(/Status:\s*(merged|closed|open)/i); |
71 | | - status = mergedMatch ? mergedMatch[1].toLowerCase() : "open"; |
72 | | - } |
73 | | -
|
74 | | - let found = false; |
75 | | - rows = rows.map(row => { |
76 | | - let parts = row.split(','); |
77 | | - while (parts.length < 7) parts.push(''); |
78 | | - if (parts[2] === issueUrl || (parts[2] === "" && parts[1] === repoUrl)) { |
79 | | - parts[2] = issueUrl; |
80 | | - parts[3] = taskOwner; |
81 | | - parts[4] = prUrl; |
82 | | - parts[5] = status; |
83 | | - found = true; |
84 | | - } |
85 | | - return parts.join(','); |
86 | | - }); |
87 | | -
|
88 | | - if (!found) { |
89 | | - const project = title.replace("[Task]", "").trim(); |
90 | | - const newRow = [ |
91 | | - project, |
92 | | - repoUrl, |
93 | | - issueUrl, |
94 | | - taskOwner, |
95 | | - prUrl, |
96 | | - status, |
97 | | - "community" |
98 | | - ].join(','); |
99 | | - rows.push(newRow); |
100 | | - } |
101 | | - } |
102 | | -
|
103 | | - const newCsv = [header, ...rows].join('\n'); |
104 | | - fs.writeFileSync('tasks.csv', newCsv); |
105 | | -
|
106 | | - - name: Show CSV |
107 | | - run: cat tasks.csv |
108 | | - |
109 | | - - name: Upload CSV artifact |
110 | | - uses: actions/upload-artifact@v4 |
111 | | - with: |
112 | | - name: tasks-csv |
113 | | - path: tasks.csv |
114 | | - |
115 | | - - name: Skip push |
116 | | - run: echo "No write permission, CSV changes are local to runner" |
| 1 | +- name: Sync Issue to CSV |
| 2 | + uses: actions/github-script@v7 |
| 3 | + with: |
| 4 | + script: | |
| 5 | + const fs = require('fs'); |
| 6 | + const owner = context.repo.owner; |
| 7 | + const repo = context.repo.repo; |
| 8 | +
|
| 9 | + let csv = fs.existsSync('tasks.csv') |
| 10 | + ? fs.readFileSync('tasks.csv', 'utf8') |
| 11 | + : "Project,Repo,Issue,Owner,PR,Status,Source\n"; |
| 12 | +
|
| 13 | + let lines = csv.split('\n').filter(line => line.trim() !== ''); |
| 14 | + const header = lines[0]; |
| 15 | + let rows = lines.slice(1); |
| 16 | +
|
| 17 | + let issues = []; |
| 18 | + if (context.payload.issue) { |
| 19 | + issues = [context.payload.issue]; |
| 20 | + } else { |
| 21 | + console.log("Skip full sync"); |
| 22 | + return; |
| 23 | + } |
| 24 | +
|
| 25 | + for (const issue of issues) { |
| 26 | + const title = issue.title; |
| 27 | + const body = issue.body || ""; |
| 28 | + const issueNumber = issue.number; |
| 29 | + const issueUrl = `https://github.com/${owner}/${repo}/issues/${issueNumber}`; |
| 30 | +
|
| 31 | + // ===== Repo ===== |
| 32 | + const repoMatch = body.match(/https:\/\/github\.com\/[^\s]+/); |
| 33 | + const repoUrl = repoMatch ? repoMatch[0] : ""; |
| 34 | +
|
| 35 | + // ===== Owner ===== |
| 36 | + const ownerMatch = body.match(/Owner:\s*@([^\s]+)/); |
| 37 | + const taskOwner = ownerMatch ? ownerMatch[1] : ""; |
| 38 | +
|
| 39 | + // ===== PR ===== |
| 40 | + const prMatch = body.match(/PR:\s*(https:\/\/[^\s]+)/); |
| 41 | + const prUrl = prMatch ? prMatch[1] : ""; |
| 42 | +
|
| 43 | + // ===== Status ===== |
| 44 | + let status = prUrl ? "open" : ""; |
| 45 | +
|
| 46 | + // ===== Source(核心逻辑)===== |
| 47 | + const sourceMatch = body.match(/Source:\s*(\w+)/i); |
| 48 | + let source = sourceMatch ? sourceMatch[1].toLowerCase() : "official"; |
| 49 | +
|
| 50 | + // 防御非法值 |
| 51 | + if (!["official", "community"].includes(source)) { |
| 52 | + source = "official"; |
| 53 | + } |
| 54 | +
|
| 55 | + let found = false; |
| 56 | +
|
| 57 | + rows = rows.map(row => { |
| 58 | + let parts = row.split(','); |
| 59 | + while (parts.length < 7) parts.push(''); |
| 60 | +
|
| 61 | + if (parts[2] === issueUrl) { |
| 62 | + parts[3] = taskOwner; |
| 63 | + parts[4] = prUrl; |
| 64 | + parts[5] = status; |
| 65 | + parts[6] = source; |
| 66 | + found = true; |
| 67 | + } |
| 68 | +
|
| 69 | + return parts.join(','); |
| 70 | + }); |
| 71 | +
|
| 72 | + if (!found) { |
| 73 | + const project = title.replace("[Task]", "").trim(); |
| 74 | + const newRow = [ |
| 75 | + project, |
| 76 | + repoUrl, |
| 77 | + issueUrl, |
| 78 | + taskOwner, |
| 79 | + prUrl, |
| 80 | + status, |
| 81 | + source |
| 82 | + ].join(','); |
| 83 | +
|
| 84 | + rows.push(newRow); |
| 85 | + } |
| 86 | + } |
| 87 | +
|
| 88 | + const newCsv = [header, ...rows].join('\n'); |
| 89 | + fs.writeFileSync('tasks.csv', newCsv); |
0 commit comments