t56-read-task-index.mjs 1.8 KB

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