From b8f315d93f74ee75ac5a838dbdfa53fefdbdba37 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 5 Nov 2026 14:24:41 +0000 Subject: [PATCH 1/2] =?UTF-8?q?ci:=20=E3=83=90=E3=83=B3=E3=83=89=E3=83=AB?= =?UTF-8?q?=E5=AF=BE=E8=B1=A1=E3=82=92=20macOS=20=E3=81=AE=20app=20?= =?UTF-8?q?=E3=81=A8=20dmg=20=E3=81=AB=E9=99=90=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows 向けの NSIS/MSI を要求しない。"all" のままだと Windows ランナーでは icon.ico 不足で tauri-build が落ちる。 Co-authored-by: riiim --- src-tauri/tauri.conf.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index d566fda..8c380a7 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -28,7 +28,7 @@ }, "bundle": { "active": true, - "targets": "all", + "targets": ["app", "dmg"], "icon": [ "icons/icon_16x16.png", "icons/icon_32x32.png", From c7520a0465ab548197adc102ab0c630ec74ccd74 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 5 Nov 2026 14:26:38 +0000 Subject: [PATCH 2/2] =?UTF-8?q?ci:=20=E3=83=AA=E3=83=AA=E3=83=BC=E3=82=B9?= =?UTF-8?q?=E3=83=8E=E3=83=BC=E3=83=88=E3=81=AF=20PR=20=E3=81=AE=E6=A6=82?= =?UTF-8?q?=E8=A6=81=E7=AF=80=E3=81=A0=E3=81=91=E3=82=92=E8=BC=89=E3=81=9B?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本文をそのまま結合すると確認やレビュー依頼まで載る。## 概要 が無い PR はタイトルだけにする。 Co-authored-by: riiim --- .github/pull_request_template.md | 4 ++ .github/workflows/release.yml | 16 ++++-- scripts/release-notes.cjs | 82 +++++++++++++++++++++++++++ scripts/release-notes.test.cjs | 96 ++++++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 scripts/release-notes.cjs create mode 100644 scripts/release-notes.test.cjs diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..e9ff44a --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,4 @@ + + +## 概要 + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 69d045d..3f78962 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,12 +23,18 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Create draft release with PR body + - name: Create draft release from PR overviews id: create-release uses: actions/github-script@v7 with: script: | const fs = require('fs'); + const path = require('path'); + const { buildReleaseNotes } = require(path.join( + process.env.GITHUB_WORKSPACE, + 'scripts', + 'release-notes.cjs' + )); const tagName = context.ref.startsWith('refs/tags/') ? context.ref.replace('refs/tags/', '') : context.payload.inputs.tag_name; @@ -54,18 +60,18 @@ jobs: core.warning(`pr-release-relation.yml の読み込みに失敗: ${e.message}`); } - // 各PRのbodyを取得して結合 - const bodies = []; + // ## 概要 だけを載せる。節が無ければ PR タイトル。 + const pulls = []; for (const prNumber of prNumbers) { const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: prNumber, }); - if (pr.body) bodies.push(pr.body.trim()); + pulls.push({ title: pr.title, body: pr.body || '' }); } - const body = bodies.join('\n\n---\n\n') || `Release ${tagName}`; + const body = buildReleaseNotes(pulls) || `Release ${tagName}`; const { data: release } = await github.rest.repos.createRelease({ owner: context.repo.owner, diff --git a/scripts/release-notes.cjs b/scripts/release-notes.cjs new file mode 100644 index 0000000..c577ba1 --- /dev/null +++ b/scripts/release-notes.cjs @@ -0,0 +1,82 @@ +// リリースノートは各 PR の「## 概要」だけ。節が無ければタイトルの箇条書き。 + +function extractOverview(markdown) { + const lines = String(markdown ?? "") + .replace(/\r\n/g, "\n") + .replace(/\r/g, "\n") + .split("\n"); + let fence = null; + let capturing = false; + const out = []; + + const atFence = (line) => { + const m = /^( {0,3})(`{3,}|~{3,})(.*)$/.exec(line); + if (!m) return false; + const marker = m[2]; + const rest = m[3]; + if (!fence) { + fence = { char: marker[0], len: marker.length }; + return true; + } + if (marker[0] === fence.char && marker.length >= fence.len && rest.trim() === "") { + fence = null; + return true; + } + return false; + }; + + for (const line of lines) { + if (atFence(line) || fence) { + if (capturing) out.push(line); + continue; + } + if (/^##\s+概要\s*$/.test(line)) { + capturing = true; + continue; + } + if (capturing && /^#{1,2}\s+\S/.test(line)) break; + if (capturing) out.push(line); + } + + const text = out.join("\n").trim(); + return text || null; +} + +function buildReleaseNotes(pulls) { + const chunks = []; + const bullets = []; + const flush = () => { + if (bullets.length === 0) return; + chunks.push(bullets.join("\n")); + bullets.length = 0; + }; + + for (const pr of pulls || []) { + const overview = extractOverview(pr && pr.body); + if (overview) { + flush(); + chunks.push(`## 概要\n\n${overview}`); + continue; + } + const title = String((pr && pr.title) || "") + .replace(/\s+/g, " ") + .trim(); + if (title) bullets.push(`- ${title}`); + } + flush(); + return chunks.join("\n\n"); +} + +module.exports = { extractOverview, buildReleaseNotes }; + +if (require.main === module) { + const fs = require("fs"); + const file = process.argv[2]; + if (!file) { + process.stderr.write("usage: node scripts/release-notes.cjs \n"); + process.exit(2); + } + const notes = buildReleaseNotes(JSON.parse(fs.readFileSync(file, "utf8"))); + process.stdout.write(notes); + if (notes && !notes.endsWith("\n")) process.stdout.write("\n"); +} diff --git a/scripts/release-notes.test.cjs b/scripts/release-notes.test.cjs new file mode 100644 index 0000000..2576237 --- /dev/null +++ b/scripts/release-notes.test.cjs @@ -0,0 +1,96 @@ +const test = require("node:test"); +const assert = require("node:assert/strict"); +const { extractOverview, buildReleaseNotes } = require("./release-notes.cjs"); + +const pr16 = `## 概要 + +next版の7項目を実装。 + +| コミット | 内容 | +|---|---| +| \`feat:\` ローカルAPI + MCP | HTTP API | + +### 補足 + +フォーカス移動を含む。 + +## 確認 + +- \`tsc\` 成功 + +## レビューしてほしい点 + +1. Google連携は未検証です。 + +## 既知の割り切り(\`ponytail:\` コメントで明記) + +- トークンファイルの 0600 は unix のみ(Windows未対応) +`; + +test("概要だけを抜き、確認・レビュー・割り切りは捨てる", () => { + const notes = buildReleaseNotes([{ title: "feat: next版", body: pr16 }]); + assert.match(notes, /next版の7項目を実装/); + assert.match(notes, /ローカルAPI \+ MCP/); + assert.match(notes, /### 補足/); + assert.match(notes, /フォーカス移動を含む/); + assert.doesNotMatch(notes, /## 確認/); + assert.doesNotMatch(notes, /tsc/); + assert.doesNotMatch(notes, /レビューしてほしい点/); + assert.doesNotMatch(notes, /未検証/); + assert.doesNotMatch(notes, /既知の割り切り/); + assert.doesNotMatch(notes, /Windows未対応/); +}); + +test("見出しが無い本文はタイトルにフォールバックする", () => { + const notes = buildReleaseNotes([ + { title: "fix: バグ修正", body: "- ウィンドウが閉じない\n- 日付が更新されない\n" }, + ]); + assert.equal(notes, "- fix: バグ修正"); +}); + +test("概要が空、または確認しか無いときは本文を載せない", () => { + const empty = buildReleaseNotes([ + { title: "feat: 空", body: "## 概要\n\n\n## 確認\n\n- cargo test\n" }, + ]); + assert.equal(empty, "- feat: 空"); + + const checklistOnly = buildReleaseNotes([ + { title: "chore: 確認だけ", body: "## 確認\n\n- 目視した\n\n## レビューしてほしい点\n\n- なし\n" }, + ]); + assert.equal(checklistOnly, "- chore: 確認だけ"); +}); + +test("コードフェンス内の見出しでは節を切らない", () => { + const body = [ + "## 概要", + "", + "手順:", + "", + "```", + "## 確認", + "レビューしてほしい点", + "```", + "", + "以上。", + "", + "## 確認", + "", + "- 実機", + ].join("\n"); + const section = extractOverview(body); + assert.match(section, /## 確認/); + assert.match(section, /以上。/); + assert.doesNotMatch(section, /実機/); +}); + +test("複数 PR は概要とタイトル箇条書きを順に結合する", () => { + const notes = buildReleaseNotes([ + { title: "chore: 先", body: "" }, + { title: "feat: 本体", body: "## 概要\r\n\r\nカレンダーを削除。\r\n" }, + { title: "chore: 後\nの続き", body: "本文は使わない" }, + ]); + assert.equal( + notes, + ["- chore: 先", "## 概要\n\nカレンダーを削除。", "- chore: 後 の続き"].join("\n\n"), + ); +});