|
|
@@ -0,0 +1,26 @@
|
|
|
+import { readFileSync, readdirSync } from 'node:fs'
|
|
|
+const root = '.scratch/cockpit-shell-panel-layout/'
|
|
|
+const files = readdirSync(`${root}issues`).filter((file) => /^\d+-.*\.md$/.test(file))
|
|
|
+const rows = files.map((file) => {
|
|
|
+ const text = readFileSync(`${root}issues/${file}`, 'utf8').replace(/^\uFEFF/, '')
|
|
|
+ const title = /^#\s+(.+)$/m.exec(text)?.[1]
|
|
|
+ const status = /^Status:\s*(.+)$/m.exec(text)?.[1]?.trim().toLowerCase()
|
|
|
+ if (!title || !status) throw new Error(`Missing title/status: ${file}`)
|
|
|
+ return { file, title, closed: ['resolved', 'closed', 'done', 'wontfix'].includes(status) }
|
|
|
+}).sort((a, b) => parseInt(a.file) - parseInt(b.file))
|
|
|
+console.log(JSON.stringify({ total: rows.length, closed: rows.filter((row) => row.closed).length }))
|
|
|
+const map = readFileSync(`${root}map.md`, 'utf8')
|
|
|
+if (process.argv.includes('--check')) {
|
|
|
+ if (!/^## Destination$/m.test(map)) throw new Error('Destination missing')
|
|
|
+ const matches = [...map.matchAll(/^- \[([ x])\] \[.+\]\(issues\/([^\s)]+)\)$/gm)]
|
|
|
+ if (matches.length !== rows.length) throw new Error(`Checklist ${matches.length} != children ${rows.length}`)
|
|
|
+ const references = new Map(matches.map((match) => [match[2], match[1] === 'x']))
|
|
|
+ if (references.size !== rows.length) throw new Error('Duplicate checklist ticket')
|
|
|
+ for (const row of rows) {
|
|
|
+ if (!references.has(row.file) || references.get(row.file) !== row.closed) throw new Error(`Checklist mismatch: ${row.file}`)
|
|
|
+ }
|
|
|
+ console.log(`PASS checklist=${rows.length}, closed=${rows.filter((row) => row.closed).length}, Destination retained`)
|
|
|
+} else {
|
|
|
+ console.log('## 任务清单\n\n本节为全部子票的唯一完整任务清单;每票一项,历史说明保持在原节,状态以票面和地图快照为准。\n')
|
|
|
+ for (const row of rows) console.log(`- [${row.closed ? 'x' : ' '}] [${row.title}](issues/${row.file})`)
|
|
|
+}
|