diff --git a/.github/ISSUE_TEMPLATE/upstream-sync.md b/.github/ISSUE_TEMPLATE/upstream-sync.md new file mode 100644 index 0000000..797e475 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/upstream-sync.md @@ -0,0 +1,33 @@ +--- +name: Upstream Sync Report +about: Auto-generated report for upstream sync +title: '๐Ÿ”„ Upstream Sync: {{ date | date("YYYY-MM-DD") }}' +labels: upstream-sync, automated +assignees: "" +--- + +## ๐Ÿ”„ Upstream ่‡ชๅ‹•ๅŒๆญฅๅ ฑๅ‘Š + +**ๅŒๆญฅๆ—ฅๆœŸ:** {{ date }} +**ๆ–ฐ Commits:** {{ commit_count }} + +### ๐Ÿ“ ่ฎŠๆ›ดๆ‘˜่ฆ + +``` +{{ commits_list }} +``` + +### ๐Ÿณ Docker Image + +- **Tag:** `upstream-{{ date_tag }}` +- **Smoke Test:** {{ smoke_test_status }} + +### โณ ๅพŒ็บŒๅ‹•ไฝœ + +- [ ] ๆชข่ฆ–่ฎŠๆ›ดๅ…งๅฎน +- [ ] ็ขบ่ช smoke test ็ตๆžœ +- [ ] ๆฑบๅฎšๆ˜ฏๅฆ merge ๅˆฐ main + +--- + +_ๆญค Issue ็”ฑ GitHub Actions ่‡ชๅ‹•ๅปบ็ซ‹_ diff --git a/.github/UPSTREAM_SYNC.md b/.github/UPSTREAM_SYNC.md new file mode 100644 index 0000000..d5e4bd7 --- /dev/null +++ b/.github/UPSTREAM_SYNC.md @@ -0,0 +1,280 @@ +# ๐Ÿ”„ Upstream ่‡ชๅ‹•ๅŒๆญฅๆฉŸๅˆถ + +ๆœฌๅฐˆๆกˆๅฏฆไฝœไบ†ๅฎŒๆ•ด็š„ upstream ่‡ชๅ‹•ๅŒๆญฅใ€่ฎŠๆ›ด่ฟฝ่นค่ˆ‡ smoke test ๆฉŸๅˆถใ€‚ + +## ๐Ÿ“‹ ็›ฎ้Œ„ + +- [ๅŠŸ่ƒฝๆฆ‚่ฟฐ](#ๅŠŸ่ƒฝๆฆ‚่ฟฐ) +- [ๅทฅไฝœๆต็จ‹ๅœ–](#ๅทฅไฝœๆต็จ‹ๅœ–) +- [Workflow ่ชชๆ˜Ž](#workflow-่ชชๆ˜Ž) +- [Smoke Test ๅ…งๅฎน](#smoke-test-ๅ…งๅฎน) +- [่จญๅฎš่ชชๆ˜Ž](#่จญๅฎš่ชชๆ˜Ž) +- [ๆ‰‹ๅ‹•ๆ“ไฝœ](#ๆ‰‹ๅ‹•ๆ“ไฝœ) + +--- + +## ๅŠŸ่ƒฝๆฆ‚่ฟฐ + +### 1๏ธโƒฃ ่‡ชๅ‹•ๅŒๆญฅ Upstream + +| ๅŠŸ่ƒฝ | ่ชชๆ˜Ž | +| ---------- | ---------------------------- | +| ่งธ็™ผๆ–นๅผ | ๆฏ 6 ๅฐๆ™‚่‡ชๅ‹•ๆชขๆŸฅ / ๆ‰‹ๅ‹•่งธ็™ผ | +| ๅŒๆญฅ็›ฎๆจ™ | `dev` ๅˆ†ๆ”ฏ | +| Merge ็ญ–็•ฅ | ่‡ชๅ‹• merge๏ผˆ็„ก่ก็ชๆ™‚๏ผ‰ | +| ่ก็ช่™•็† | ่‡ชๅ‹•ๅปบ็ซ‹ GitHub Issue ้€š็Ÿฅ | + +### 2๏ธโƒฃ ่ฎŠๆ›ดๆ‘˜่ฆ + +ๅŒๆญฅๅฎŒๆˆๅพŒ่‡ชๅ‹•็”ข็”Ÿ๏ผš + +- โœ… GitHub Issue๏ผˆ่ฉณ็ดฐ่ฎŠๆ›ด่จ˜้Œ„๏ผ‰ +- โœ… `upstream-changelog.md`๏ผˆ็ดฏ็ฉ่ฎŠๆ›ดๆญทๅฒ๏ผ‰ +- โœ… GitHub Actions Summary๏ผˆๅฟซ้€ŸๆŸฅ็œ‹๏ผ‰ + +### 3๏ธโƒฃ Smoke Test + +| ๆธฌ่ฉฆ้ …็›ฎ | ๅ…งๅฎน | ๆ™‚้–“้™ๅˆถ | +| -------------- | -------------------------------------- | ------------- | +| Container ๅ•Ÿๅ‹• | ้ฉ—่ญ‰ image ๅฏๆญฃๅธธๅ•Ÿๅ‹• | 60s | +| ๅทฅๅ…ทๆชขๆŸฅ | libreoffice, pandoc, ffmpeg, tesseract | 30s | +| ๆœ€ๅฐ่ฝ‰ๆ› | txt โ†’ pdf | 60s | +| API ็ซฏ้ปž | healthcheck, root | 10s | +| **็ธฝ่จˆ** | | **< 10 ๅˆ†้˜** | + +--- + +## ๅทฅไฝœๆต็จ‹ๅœ– + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Upstream ่‡ชๅ‹•ๅŒๆญฅๆต็จ‹ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Upstream โ”‚ + โ”‚ (C4illin/ โ”‚ + โ”‚ ConvertX) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ ๆฏ 6 ๅฐๆ™‚ๆชขๆŸฅ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” ็„กๆ›ดๆ–ฐ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ ๆชขๆŸฅๆ˜ฏๅฆๆœ‰ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ ็ตๆŸ โ”‚ + โ”‚ ๆ–ฐ commits โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ ๆœ‰ๆ›ดๆ–ฐ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” ่ก็ช โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ ่‡ชๅ‹• Merge โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ ๅปบ็ซ‹ Issue โ”‚ + โ”‚ ๅˆฐ dev ๅˆ†ๆ”ฏ โ”‚ โ”‚ ้œ€ๆ‰‹ๅ‹•่™•็† โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ ๆˆๅŠŸ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ ็”ข็”Ÿ่ฎŠๆ›ดๆ‘˜่ฆ โ”‚ + โ”‚ โ€ข Issue โ”‚ + โ”‚ โ€ข Changelog โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Push ๅˆฐ dev โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ ่งธ็™ผ upstream-sync.yml + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Build ๆธฌ่ฉฆ โ”‚ + โ”‚ Image (amd64)โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Smoke Test โ”‚ + โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ + โ”‚ โ”‚ Container โ”‚ โ”‚ ๅทฅๅ…ท โ”‚ โ”‚ ่ฝ‰ๆ› โ”‚ โ”‚ + โ”‚ โ”‚ ๅ•Ÿๅ‹• โ”‚ โ”‚ ๆชขๆŸฅ โ”‚ โ”‚ ๆธฌ่ฉฆ โ”‚ โ”‚ + โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ + โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ + โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ + โ”‚ โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ + โ–ผ โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ โœ… ้€š้Ž โ”‚ โ”‚ โŒ ๅคฑๆ•— โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ + โ–ผ โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Build ไธฆ โ”‚ โ”‚ Workflow โ”‚ + โ”‚ Push ๅˆฐ โ”‚ โ”‚ ๅคฑๆ•— โ”‚ + โ”‚ Docker Hub โ”‚ โ”‚ (ไธ็™ผๅธƒ) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ upstream-YYYYMMDD tag โ”‚ + โ”‚ โŒ ไธๆ›ดๆ–ฐ latest โ”‚ + โ”‚ โŒ ไธๆ‰“ semver tag โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## Workflow ่ชชๆ˜Ž + +### `auto-upstream-sync.yml` + +**็”จ้€”๏ผš** ่‡ชๅ‹•ๆชขๆธฌไธฆๅŒๆญฅ upstream ๆ›ดๆ–ฐ + +**่งธ็™ผๆขไปถ๏ผš** + +- โฐ ๆฏ 6 ๅฐๆ™‚่‡ชๅ‹•ๅŸท่กŒ +- ๐Ÿ–ฑ๏ธ ๆ‰‹ๅ‹•่งธ็™ผ + +**ไธป่ฆๆญฅ้ฉŸ๏ผš** + +1. Fetch upstream ๆœ€ๆ–ฐ็จ‹ๅผ็ขผ +2. ๆชขๆŸฅๆ˜ฏๅฆๆœ‰ๆ–ฐ commits +3. ่‡ชๅ‹• merge ๅˆฐ `dev` ๅˆ†ๆ”ฏ +4. ็”ข็”Ÿ่ฎŠๆ›ดๆ‘˜่ฆ๏ผˆIssue + Changelog๏ผ‰ +5. Push ๅˆฐ `dev` + +### `upstream-sync.yml` + +**็”จ้€”๏ผš** ็•ถ `dev` ๅˆ†ๆ”ฏๆœ‰ push ๆ™‚๏ผŒๅŸท่กŒ build + smoke test + +**่งธ็™ผๆขไปถ๏ผš** + +- ๐Ÿ“ฆ `dev` ๅˆ†ๆ”ฏๆœ‰ push + +**ไธป่ฆๆญฅ้ฉŸ๏ผš** + +1. Build ๆธฌ่ฉฆ็”จ image๏ผˆๅƒ… amd64๏ผŒๅŠ ้€Ÿๆธฌ่ฉฆ๏ผ‰ +2. ๅŸท่กŒ Smoke Test๏ผˆ10 ๅˆ†้˜ๅ…ง๏ผ‰ +3. ๆธฌ่ฉฆ้€š้ŽๅพŒ build multi-arch image +4. Push ๅˆฐ Docker Hub๏ผˆtag: `upstream-YYYYMMDD`๏ผ‰ + +--- + +## Smoke Test ๅ…งๅฎน + +### Test 1: Container ๅ•Ÿๅ‹•ๆธฌ่ฉฆ + +```bash +# ๅ•Ÿๅ‹• container ไธฆ็ญ‰ๅพ… healthcheck ๅ›žๆ‡‰ +docker run -d --name convertx-test $IMAGE +curl http://localhost:3000/healthcheck +``` + +### Test 2: ้—œ้ตๅทฅๅ…ทๆชขๆŸฅ + +```bash +# ๅฟ…้ ˆ้€š้Ž๏ผˆไปปไธ€ๅคฑๆ•—ๅ‰‡ๆ•ด้ซ”ๅคฑๆ•—๏ผ‰ +libreoffice --version +pandoc --version +ffmpeg -version +tesseract --version +``` + +### Test 3: ๆœ€ๅฐ่ฝ‰ๆ›ๆธฌ่ฉฆ + +```bash +# txt โ†’ pdf ่ฝ‰ๆ›ๆธฌ่ฉฆ +echo "test" > test.txt +libreoffice --headless --convert-to pdf test.txt +``` + +### Test 4: API ็ซฏ้ปžๆชขๆŸฅ + +```bash +# ็ขบ่ชๆœๅ‹™ๆญฃๅธธ้‹ไฝœ +curl http://localhost:3000/healthcheck +curl http://localhost:3000/ +``` + +--- + +## ่จญๅฎš่ชชๆ˜Ž + +### ๅฟ…่ฆ็š„ GitHub Secrets + +ๅœจ Repository Settings โ†’ Secrets and variables โ†’ Actions ไธญ่จญๅฎš๏ผš + +| Secret ๅ็จฑ | ่ชชๆ˜Ž | +| -------------------- | ----------------------- | +| `DOCKERHUB_USERNAME` | Docker Hub ๅธณ่™Ÿ | +| `DOCKERHUB_TOKEN` | Docker Hub Access Token | + +### ๅˆ†ๆ”ฏไฟ่ญท่ฆๅ‰‡ + +ๅปบ่ญฐ่จญๅฎš๏ผš + +**`main` ๅˆ†ๆ”ฏ๏ผš** + +- โœ… Require pull request before merging +- โœ… Require status checks to pass +- โŒ Allow force pushes + +**`dev` ๅˆ†ๆ”ฏ๏ผš** + +- โœ… Allow direct pushes (for automation) + +--- + +## ๆ‰‹ๅ‹•ๆ“ไฝœ + +### ๆ‰‹ๅ‹•่งธ็™ผๅŒๆญฅ + +1. ๅ‰ๅพ€ Actions โ†’ Auto Upstream Sync +2. ้ปž้ธ "Run workflow" +3. ๅฏ้ธๆ“‡ใ€ŒๅผทๅˆถๅŒๆญฅใ€ + +### ๆ‰‹ๅ‹•่™•็† Merge ่ก็ช + +็•ถ่‡ชๅ‹•ๅŒๆญฅ็™ผ็”Ÿ่ก็ชๆ™‚๏ผš + +```bash +# 1. ๅˆ‡ๆ›ๅˆฐ dev ๅˆ†ๆ”ฏ +git checkout dev + +# 2. ๆทปๅŠ  upstream remote๏ผˆๅฆ‚ๆžœ้‚„ๆฒ’ๆœ‰๏ผ‰ +git remote add upstream https://github.com/C4illin/ConvertX.git + +# 3. Fetch upstream +git fetch upstream main + +# 4. Merge upstream +git merge upstream/main + +# 5. ่งฃๆฑบ่ก็ช +# ... ็ทจ่ผฏ่ก็ช็š„ๆช”ๆกˆ ... + +# 6. ๆไบคไธฆ push +git add . +git commit -m "Resolve merge conflicts with upstream" +git push origin dev +``` + +--- + +## โš ๏ธ ๆณจๆ„ไบ‹้ … + +1. **ไธๆœƒ่‡ชๅ‹•ๆ›ดๆ–ฐ `latest` tag** - ้œ€่ฆๆ‰‹ๅ‹•็™ผๅธƒๆญฃๅผ็‰ˆๆœฌ +2. **ไธๆœƒ่‡ชๅ‹•ๆ‰“ semver tag** - ้œ€่ฆๆ‰‹ๅ‹•ๅปบ็ซ‹ release +3. **`main` ๅˆ†ๆ”ฏไธๆœƒ่ขซ่‡ชๅ‹•ไฟฎๆ”น** - ๅชๆœ‰ `dev` ๆœƒ่‡ชๅ‹•ๅŒๆญฅ +4. **Smoke test ๅคฑๆ•—ๆ™‚ไธๆœƒ็™ผๅธƒ image** - ็ขบไฟๅ“่ณช + +--- + +## ๐Ÿ“Š Docker Image Tag ่ฆๅ‰‡ + +| Tag ๆ ผๅผ | ่ชชๆ˜Ž | ่‡ชๅ‹•/ๆ‰‹ๅ‹• | +| ------------------- | ---------------------------------- | --------- | +| `upstream-YYYYMMDD` | Upstream ๅŒๆญฅ็‰ˆ๏ผˆ็ถ“้Ž smoke test๏ผ‰ | ่‡ชๅ‹• | +| `vX.Y.Z` | ๆญฃๅผ็‰ˆๆœฌ | ๆ‰‹ๅ‹• | +| `latest` | ๆœ€ๆ–ฐ็ฉฉๅฎš็‰ˆ | ๆ‰‹ๅ‹• | diff --git a/.github/scripts/smoke-test.sh b/.github/scripts/smoke-test.sh new file mode 100644 index 0000000..1763d2b --- /dev/null +++ b/.github/scripts/smoke-test.sh @@ -0,0 +1,238 @@ +#!/bin/bash +# ============================================================================== +# ConvertX Smoke Test Script +# ็”จๆ–ผ้ฉ—่ญ‰ Docker image ็š„ๅŸบๆœฌๅŠŸ่ƒฝ +# ============================================================================== + +set -e + +CONTAINER_NAME="${CONTAINER_NAME:-convertx-test}" +TEST_TIMEOUT="${TEST_TIMEOUT:-60}" + +echo "==================================================" +echo "๐Ÿงช ConvertX Smoke Test" +echo "==================================================" +echo "" + +# ้ก่‰ฒๅฎš็พฉ +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +pass() { + echo -e "${GREEN}โœ… PASS${NC}: $1" +} + +fail() { + echo -e "${RED}โŒ FAIL${NC}: $1" + exit 1 +} + +warn() { + echo -e "${YELLOW}โš ๏ธ WARN${NC}: $1" +} + +# ============================================================================== +# Test 1: Container ๅ•Ÿๅ‹•ๆธฌ่ฉฆ +# ============================================================================== +test_container_startup() { + echo "" + echo "๐Ÿ“ฆ Test 1: Container Startup" + echo "----------------------------" + + echo "Waiting for container to be healthy..." + + for i in $(seq 1 $TEST_TIMEOUT); do + if docker exec $CONTAINER_NAME curl -sf http://localhost:3000/healthcheck > /dev/null 2>&1; then + pass "Container started in ${i}s" + return 0 + fi + sleep 1 + done + + fail "Container failed to start within ${TEST_TIMEOUT}s" +} + +# ============================================================================== +# Test 2: ้—œ้ตๅทฅๅ…ทๅญ˜ๅœจๆ€งๆชขๆŸฅ +# ============================================================================== +test_critical_tools() { + echo "" + echo "๐Ÿ”ง Test 2: Critical Tools Availability" + echo "---------------------------------------" + + local failed=0 + + # LibreOffice (ๅฟ…่ฆ) + echo -n " libreoffice: " + if docker exec $CONTAINER_NAME libreoffice --version > /dev/null 2>&1; then + VERSION=$(docker exec $CONTAINER_NAME libreoffice --version 2>/dev/null | head -1) + echo -e "${GREEN}OK${NC} ($VERSION)" + else + echo -e "${RED}MISSING${NC}" + failed=1 + fi + + # Pandoc (ๅฟ…่ฆ) + echo -n " pandoc: " + if docker exec $CONTAINER_NAME pandoc --version > /dev/null 2>&1; then + VERSION=$(docker exec $CONTAINER_NAME pandoc --version 2>/dev/null | head -1) + echo -e "${GREEN}OK${NC} ($VERSION)" + else + echo -e "${RED}MISSING${NC}" + failed=1 + fi + + # FFmpeg (ๅฟ…่ฆ) + echo -n " ffmpeg: " + if docker exec $CONTAINER_NAME ffmpeg -version > /dev/null 2>&1; then + VERSION=$(docker exec $CONTAINER_NAME ffmpeg -version 2>/dev/null | head -1 | awk '{print $3}') + echo -e "${GREEN}OK${NC} (v$VERSION)" + else + echo -e "${RED}MISSING${NC}" + failed=1 + fi + + # Tesseract (ๅฟ…่ฆ) + echo -n " tesseract: " + if docker exec $CONTAINER_NAME tesseract --version > /dev/null 2>&1; then + VERSION=$(docker exec $CONTAINER_NAME tesseract --version 2>/dev/null | head -1) + echo -e "${GREEN}OK${NC} ($VERSION)" + else + echo -e "${RED}MISSING${NC}" + failed=1 + fi + + echo "" + echo " Additional tools (optional):" + + # ImageMagick + echo -n " imagemagick: " + if docker exec $CONTAINER_NAME magick -version > /dev/null 2>&1; then + echo -e "${GREEN}OK${NC}" + else + echo -e "${YELLOW}Not found${NC}" + fi + + # Inkscape + echo -n " inkscape: " + if docker exec $CONTAINER_NAME inkscape --version > /dev/null 2>&1; then + echo -e "${GREEN}OK${NC}" + else + echo -e "${YELLOW}Not found${NC}" + fi + + # GraphicsMagick + echo -n " graphicsmagick: " + if docker exec $CONTAINER_NAME gm version > /dev/null 2>&1; then + echo -e "${GREEN}OK${NC}" + else + echo -e "${YELLOW}Not found${NC}" + fi + + # Calibre + echo -n " calibre: " + if docker exec $CONTAINER_NAME ebook-convert --version > /dev/null 2>&1; then + echo -e "${GREEN}OK${NC}" + else + echo -e "${YELLOW}Not found${NC}" + fi + + echo "" + + if [ $failed -eq 1 ]; then + fail "Critical tools check failed" + else + pass "All critical tools available" + fi +} + +# ============================================================================== +# Test 3: ๆœ€ๅฐ่ฝ‰ๆ›ๆธฌ่ฉฆ +# ============================================================================== +test_minimal_conversion() { + echo "" + echo "๐Ÿ“„ Test 3: Minimal Conversion" + echo "-----------------------------" + + # ๅœจ container ๅ…งๅปบ็ซ‹ๆธฌ่ฉฆๆช”ๆกˆไธฆ่ฝ‰ๆ› + echo " Creating test file..." + docker exec $CONTAINER_NAME sh -c "echo 'ConvertX Smoke Test - $(date)' > /tmp/test.txt" + + echo " Converting txt โ†’ pdf using LibreOffice..." + if docker exec $CONTAINER_NAME sh -c "cd /tmp && libreoffice --headless --convert-to pdf test.txt" > /dev/null 2>&1; then + # ๆชขๆŸฅ่ผธๅ‡บๆช”ๆกˆ + if docker exec $CONTAINER_NAME test -f /tmp/test.pdf; then + SIZE=$(docker exec $CONTAINER_NAME stat -c%s /tmp/test.pdf 2>/dev/null || echo "unknown") + pass "Conversion successful (output: ${SIZE} bytes)" + else + fail "Conversion failed - output file not found" + fi + else + fail "LibreOffice conversion command failed" + fi + + # ๆธ…็† + docker exec $CONTAINER_NAME rm -f /tmp/test.txt /tmp/test.pdf 2>/dev/null || true +} + +# ============================================================================== +# Test 4: API ็ซฏ้ปžๆชขๆŸฅ +# ============================================================================== +test_api_endpoints() { + echo "" + echo "๐ŸŒ Test 4: API Endpoints" + echo "------------------------" + + local failed=0 + + # Healthcheck + echo -n " GET /healthcheck: " + if docker exec $CONTAINER_NAME curl -sf http://localhost:3000/healthcheck > /dev/null 2>&1; then + echo -e "${GREEN}200 OK${NC}" + else + echo -e "${RED}FAILED${NC}" + failed=1 + fi + + # Root + echo -n " GET /: " + if docker exec $CONTAINER_NAME curl -sf http://localhost:3000/ > /dev/null 2>&1; then + echo -e "${GREEN}200 OK${NC}" + else + echo -e "${RED}FAILED${NC}" + failed=1 + fi + + echo "" + + if [ $failed -eq 1 ]; then + fail "API endpoints check failed" + else + pass "All API endpoints responding" + fi +} + +# ============================================================================== +# Main +# ============================================================================== +main() { + local start_time=$(date +%s) + + test_container_startup + test_critical_tools + test_minimal_conversion + test_api_endpoints + + local end_time=$(date +%s) + local duration=$((end_time - start_time)) + + echo "" + echo "==================================================" + echo -e "๐ŸŽ‰ ${GREEN}All smoke tests passed!${NC}" + echo " Total time: ${duration}s" + echo "==================================================" +} + +main "$@" diff --git a/.github/test-files/test.txt b/.github/test-files/test.txt new file mode 100644 index 0000000..8f9696f --- /dev/null +++ b/.github/test-files/test.txt @@ -0,0 +1,6 @@ +Hello, this is a smoke test file for ConvertX. +้€™ๆ˜ฏ ConvertX ็š„ๅ†’็…™ๆธฌ่ฉฆๆช”ๆกˆใ€‚ +ๆ—ฅๆœฌ่ชžใƒ†ใ‚นใƒˆ๏ผšใ“ใ‚“ใซใกใฏ +ํ•œ๊ตญ์–ด ํ…Œ์ŠคํŠธ: ์•ˆ๋…•ํ•˜์„ธ์š” + +This file should be less than 1KB for quick testing. diff --git a/.github/workflows/auto-upstream-sync.yml b/.github/workflows/auto-upstream-sync.yml new file mode 100644 index 0000000..1716491 --- /dev/null +++ b/.github/workflows/auto-upstream-sync.yml @@ -0,0 +1,281 @@ +# ============================================================================== +# ่‡ชๅ‹•ๅŒๆญฅ Upstream Repository +# ๅŠŸ่ƒฝ๏ผš +# 1. ๅฎšๆ™‚ๆชขๆŸฅ upstream ๆ›ดๆ–ฐ +# 2. ่‡ชๅ‹• merge ๅˆฐ dev ๅˆ†ๆ”ฏ +# 3. ็”ข็”Ÿ่ฎŠๆ›ดๆ‘˜่ฆ๏ผˆChange Summary๏ผ‰ +# 4. ่งธ็™ผๅพŒ็บŒ็š„ build + smoke test +# ============================================================================== + +name: Auto Upstream Sync + +on: + # ๆฏ 6 ๅฐๆ™‚ๆชขๆŸฅไธ€ๆฌก upstream + schedule: + - cron: "0 */6 * * *" + # ๆ‰‹ๅ‹•่งธ็™ผ + workflow_dispatch: + inputs: + force_sync: + description: "ๅผทๅˆถๅŒๆญฅ๏ผˆๅณไฝฟ็„กๆ–ฐ commit๏ผ‰" + type: boolean + default: false + +env: + UPSTREAM_REPO: https://github.com/C4illin/ConvertX.git + TARGET_BRANCH: dev + UPSTREAM_BRANCH: main + +permissions: + contents: write + issues: write + +jobs: + check-and-sync: + name: Check & Sync Upstream + runs-on: ubuntu-latest + outputs: + has_updates: ${{ steps.check.outputs.has_updates }} + new_commits: ${{ steps.check.outputs.new_commits }} + commit_count: ${{ steps.check.outputs.commit_count }} + sync_date: ${{ steps.date.outputs.date }} + changelog: ${{ steps.changelog.outputs.changelog }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ env.TARGET_BRANCH }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get current date + id: date + run: echo "date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Add upstream remote + run: | + git remote add upstream ${{ env.UPSTREAM_REPO }} || true + git fetch upstream ${{ env.UPSTREAM_BRANCH }} + + - name: Check for upstream updates + id: check + run: | + # ๅ–ๅพ—ๆœฌๅœฐ dev ๅˆ†ๆ”ฏ็š„ๆœ€ๅพŒไธ€ๆฌก upstream merge commit + # ๆˆ‘ๅ€‘็”จ upstream/main ่ˆ‡็›ฎๅ‰ HEAD ๆฏ”่ผƒ + + LOCAL_HEAD=$(git rev-parse HEAD) + UPSTREAM_HEAD=$(git rev-parse upstream/${{ env.UPSTREAM_BRANCH }}) + + echo "Local HEAD: $LOCAL_HEAD" + echo "Upstream HEAD: $UPSTREAM_HEAD" + + # ๆชขๆŸฅ upstream ๆ˜ฏๅฆๆœ‰ๆ–ฐ็š„ commits + MERGE_BASE=$(git merge-base HEAD upstream/${{ env.UPSTREAM_BRANCH }}) + echo "Merge base: $MERGE_BASE" + + if [ "$MERGE_BASE" = "$UPSTREAM_HEAD" ]; then + echo "โœ… Already up to date with upstream" + echo "has_updates=false" >> $GITHUB_OUTPUT + echo "commit_count=0" >> $GITHUB_OUTPUT + else + # ่จˆ็ฎ—ๆ–ฐ็š„ commits ๆ•ธ้‡ + NEW_COMMITS=$(git rev-list --count $MERGE_BASE..$UPSTREAM_HEAD) + echo "๐Ÿ”„ Found $NEW_COMMITS new commits from upstream" + echo "has_updates=true" >> $GITHUB_OUTPUT + echo "commit_count=$NEW_COMMITS" >> $GITHUB_OUTPUT + + # ๅ–ๅพ—ๆ–ฐ commits ็š„ๅˆ—่กจ + COMMITS_LIST=$(git log --oneline $MERGE_BASE..$UPSTREAM_HEAD) + echo "new_commits<> $GITHUB_OUTPUT + echo "$COMMITS_LIST" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + - name: Generate changelog before merge + id: changelog + if: steps.check.outputs.has_updates == 'true' || github.event.inputs.force_sync == 'true' + run: | + MERGE_BASE=$(git merge-base HEAD upstream/${{ env.UPSTREAM_BRANCH }}) + + echo "## ๐Ÿ“‹ Upstream ่ฎŠๆ›ดๆ‘˜่ฆ" > changelog.md + echo "" >> changelog.md + echo "**ๅŒๆญฅๆ™‚้–“:** $(date +'%Y-%m-%d %H:%M:%S UTC')" >> changelog.md + echo "**Upstream ๅˆ†ๆ”ฏ:** ${{ env.UPSTREAM_BRANCH }}" >> changelog.md + echo "**ๆ–ฐ Commits ๆ•ธ้‡:** ${{ steps.check.outputs.commit_count }}" >> changelog.md + echo "" >> changelog.md + + echo "### ๐Ÿ“ Commit ๅˆ—่กจ" >> changelog.md + echo "" >> changelog.md + echo "\`\`\`" >> changelog.md + git log --oneline --no-merges $MERGE_BASE..upstream/${{ env.UPSTREAM_BRANCH }} >> changelog.md + echo "\`\`\`" >> changelog.md + echo "" >> changelog.md + + echo "### ๐Ÿ“Š ่ฎŠๆ›ด็ตฑ่จˆ" >> changelog.md + echo "" >> changelog.md + echo "\`\`\`" >> changelog.md + git diff --stat $MERGE_BASE..upstream/${{ env.UPSTREAM_BRANCH }} >> changelog.md + echo "\`\`\`" >> changelog.md + echo "" >> changelog.md + + echo "### ๐Ÿ” ่ฎŠๆ›ด็š„ๆช”ๆกˆ" >> changelog.md + echo "" >> changelog.md + git diff --name-status $MERGE_BASE..upstream/${{ env.UPSTREAM_BRANCH }} | while read line; do + STATUS=$(echo "$line" | awk '{print $1}') + FILE=$(echo "$line" | awk '{print $2}') + case $STATUS in + A) echo "- โœ… **ๆ–ฐๅขž** \`$FILE\`" >> changelog.md ;; + M) echo "- โœ๏ธ **ไฟฎๆ”น** \`$FILE\`" >> changelog.md ;; + D) echo "- โŒ **ๅˆช้™ค** \`$FILE\`" >> changelog.md ;; + R*) echo "- ๐Ÿ”„ **้‡ๆ–ฐๅ‘ฝๅ** \`$FILE\`" >> changelog.md ;; + *) echo "- ๐Ÿ“„ \`$FILE\` ($STATUS)" >> changelog.md ;; + esac + done + + cat changelog.md + + # ่ผธๅ‡บ็ตฆๅพŒ็บŒๆญฅ้ฉŸไฝฟ็”จ + CHANGELOG_CONTENT=$(cat changelog.md) + echo "changelog<> $GITHUB_OUTPUT + echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Merge upstream changes + id: merge + if: steps.check.outputs.has_updates == 'true' || github.event.inputs.force_sync == 'true' + run: | + echo "๐Ÿ”„ Merging upstream/${{ env.UPSTREAM_BRANCH }} into ${{ env.TARGET_BRANCH }}..." + + # ๅ˜—่ฉฆ่‡ชๅ‹• merge + if git merge upstream/${{ env.UPSTREAM_BRANCH }} --no-edit -m "๐Ÿ”„ Auto-merge upstream changes ($(date +'%Y-%m-%d'))"; then + echo "โœ… Merge successful!" + echo "merge_success=true" >> $GITHUB_OUTPUT + else + echo "โŒ Merge conflict detected!" + echo "merge_success=false" >> $GITHUB_OUTPUT + + # ๅ–ๆถˆ merge + git merge --abort + + # ๅปบ็ซ‹ conflict issue + exit 1 + fi + + - name: Push changes + if: steps.merge.outputs.merge_success == 'true' + run: | + git push origin ${{ env.TARGET_BRANCH }} + echo "โœ… Changes pushed to ${{ env.TARGET_BRANCH }}" + + - name: Commit changelog + if: steps.merge.outputs.merge_success == 'true' + run: | + # ๆ›ดๆ–ฐ upstream-changelog.md + if [ -f upstream-changelog.md ]; then + # ๅœจๆช”ๆกˆ้–‹้ ญๆ’ๅ…ฅๆ–ฐ็š„ changelog + echo "" >> changelog.md + echo "---" >> changelog.md + echo "" >> changelog.md + cat upstream-changelog.md >> changelog.md + fi + + mv changelog.md upstream-changelog.md + git add upstream-changelog.md + git commit -m "๐Ÿ“‹ Update upstream changelog (${{ steps.date.outputs.date }})" || true + git push origin ${{ env.TARGET_BRANCH }} || true + + - name: Create GitHub Issue for sync summary + if: steps.merge.outputs.merge_success == 'true' && steps.check.outputs.commit_count > 0 + uses: actions/github-script@v7 + with: + script: | + const date = '${{ steps.date.outputs.date }}'; + const commitCount = '${{ steps.check.outputs.commit_count }}'; + + const body = `## ๐Ÿ”„ Upstream ่‡ชๅ‹•ๅŒๆญฅๅฎŒๆˆ + + **ๅŒๆญฅๆ—ฅๆœŸ:** ${date} + **ๆ–ฐ Commits:** ${commitCount} ๅ€‹ + **็›ฎๆจ™ๅˆ†ๆ”ฏ:** \`dev\` + + ### ๐Ÿ“ ่ฎŠๆ›ดๆ‘˜่ฆ + + \`\`\` + ${{ steps.check.outputs.new_commits }} + \`\`\` + + ### โณ ๅพŒ็บŒๅ‹•ไฝœ + + - [ ] Smoke test ๅŸท่กŒไธญ... + - [ ] Docker image ๅปบๆง‹ไธญ (\`upstream-${date}\`) + + --- + *ๆญค Issue ็”ฑ GitHub Actions ่‡ชๅ‹•ๅปบ็ซ‹* + `; + + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `๐Ÿ”„ Upstream Sync: ${date} (${commitCount} commits)`, + body: body, + labels: ['upstream-sync', 'automated'] + }); + + - name: Create Issue for merge conflict + if: failure() && steps.merge.outputs.merge_success == 'false' + uses: actions/github-script@v7 + with: + script: | + const date = '${{ steps.date.outputs.date }}'; + + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `โš ๏ธ Upstream Sync ่ก็ช: ${date}`, + body: `## โš ๏ธ Upstream ๅŒๆญฅ็™ผ็”Ÿ่ก็ช + + ้œ€่ฆๆ‰‹ๅ‹•่งฃๆฑบ merge conflictใ€‚ + + ### ่™•็†ๆญฅ้ฉŸ + + \`\`\`bash + git fetch upstream main + git checkout dev + git merge upstream/main + # ่งฃๆฑบ่ก็ชๅพŒ + git add . + git commit + git push origin dev + \`\`\` + + --- + *ๆญค Issue ็”ฑ GitHub Actions ่‡ชๅ‹•ๅปบ็ซ‹* + `, + labels: ['upstream-sync', 'merge-conflict', 'needs-attention'] + }); + + - name: Summary + if: always() + run: | + echo "## ๐Ÿ”„ Upstream Sync Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| ้ …็›ฎ | ็‹€ๆ…‹ |" >> $GITHUB_STEP_SUMMARY + echo "|------|------|" >> $GITHUB_STEP_SUMMARY + echo "| ๆชขๆŸฅๆ™‚้–“ | $(date +'%Y-%m-%d %H:%M:%S UTC') |" >> $GITHUB_STEP_SUMMARY + echo "| ๆœ‰ๆ–ฐๆ›ดๆ–ฐ | ${{ steps.check.outputs.has_updates }} |" >> $GITHUB_STEP_SUMMARY + echo "| ๆ–ฐ Commits | ${{ steps.check.outputs.commit_count }} |" >> $GITHUB_STEP_SUMMARY + echo "| Merge ๆˆๅŠŸ | ${{ steps.merge.outputs.merge_success || 'N/A' }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.check.outputs.has_updates }}" == "true" ]; then + echo "### ๐Ÿ“ New Commits" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.check.outputs.new_commits }}" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/upstream-sync.yml b/.github/workflows/upstream-sync.yml index e1db9c9..350e4b2 100644 --- a/.github/workflows/upstream-sync.yml +++ b/.github/workflows/upstream-sync.yml @@ -1,3 +1,12 @@ +# ============================================================================== +# Upstream Sync Build + Smoke Test +# ๅŠŸ่ƒฝ๏ผš +# 1. dev ๅˆ†ๆ”ฏๆœ‰ push ๆ™‚่‡ชๅ‹•่งธ็™ผ +# 2. ๅปบๆง‹ Docker image๏ผˆๅƒ… amd64 ็”จๆ–ผๆธฌ่ฉฆ๏ผ‰ +# 3. ๅŸท่กŒ smoke test๏ผˆ10 ๅˆ†้˜ๅ…งๅฎŒๆˆ๏ผ‰ +# 4. ๆธฌ่ฉฆ้€š้ŽๅพŒๆŽจ้€ multi-arch image +# ============================================================================== + name: Upstream Sync Build on: @@ -7,15 +16,56 @@ on: env: DOCKER_IMAGE: convertx/convertx-cn + TEST_IMAGE: convertx-cn-test + # Smoke test ่ถ…ๆ™‚ๆ™‚้–“๏ผˆ็ง’๏ผ‰ + SMOKE_TEST_TIMEOUT: 600 + +concurrency: + group: upstream-sync-${{ github.ref }} + cancel-in-progress: true jobs: - build-and-push: + # ========================================================================== + # Job 1: ๅฟซ้€Ÿๅปบๆง‹ๆธฌ่ฉฆ็”จ image๏ผˆๅƒ… amd64๏ผ‰ + # ========================================================================== + build-test-image: + name: Build Test Image runs-on: ubuntu-latest - + outputs: + image_tag: ${{ steps.date.outputs.date }} + steps: - name: Checkout repository uses: actions/checkout@v4 + # ======================================== + # ๆธ…็†็ฃ็ขŸ็ฉบ้–“๏ผˆ่งฃๆฑบ no space left on device๏ผ‰ + # ======================================== + - name: Free disk space + run: | + echo "๐Ÿงน Cleaning up disk space..." + echo "Before cleanup:" + df -h + + # ็งป้™คไธ้œ€่ฆ็š„้ ่ฃ่ปŸ้ซ” + sudo rm -rf /usr/share/dotnet + sudo rm -rf /usr/local/lib/android + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo rm -rf /usr/local/share/boost + sudo rm -rf /usr/share/swift + + # ๆธ…็† apt cache + sudo apt-get clean + sudo apt-get autoremove -y + + # ๆธ…็† Docker ่ˆŠ่ณ‡ๆ–™ + docker system prune -af --volumes || true + + echo "" + echo "After cleanup:" + df -h + - name: Get current date id: date run: echo "date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT @@ -23,6 +73,308 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Build test image (amd64 only) + uses: docker/build-push-action@v6 + with: + context: . + push: false + load: true + tags: ${{ env.TEST_IMAGE }}:test + platforms: linux/amd64 + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Save Docker image + run: | + docker save ${{ env.TEST_IMAGE }}:test -o /tmp/test-image.tar + + - name: Upload test image + uses: actions/upload-artifact@v4 + with: + name: test-image + path: /tmp/test-image.tar + retention-days: 1 + + # ========================================================================== + # Job 2: Smoke Test๏ผˆๅฟ…้ ˆๅœจ 10 ๅˆ†้˜ๅ…งๅฎŒๆˆ๏ผ‰ + # ========================================================================== + smoke-test: + name: Smoke Test + runs-on: ubuntu-latest + needs: build-test-image + timeout-minutes: 10 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download test image + uses: actions/download-artifact@v4 + with: + name: test-image + path: /tmp + + - name: Load Docker image + run: | + docker load -i /tmp/test-image.tar + + - name: Create test files + run: | + mkdir -p /tmp/smoke-test + + # ๅปบ็ซ‹ๆฅตๅฐ็š„ๆธฌ่ฉฆๆช”ๆกˆ (<1KB) + echo "Hello, ConvertX Smoke Test!" > /tmp/smoke-test/test.txt + echo "้€™ๆ˜ฏไธญๆ–‡ๆธฌ่ฉฆๆช”ๆกˆ" >> /tmp/smoke-test/test.txt + + # ======================================== + # Test 1: Container ๅ•Ÿๅ‹•ๆธฌ่ฉฆ + # ======================================== + - name: "Test 1: Container startup" + id: test_startup + run: | + echo "๐Ÿš€ Testing container startup..." + + # ๅ•Ÿๅ‹• container๏ผˆ่ƒŒๆ™ฏๅŸท่กŒ๏ผ‰ + docker run -d \ + --name convertx-test \ + -p 3000:3000 \ + -v /tmp/smoke-test:/data \ + -e DATA_DIR=/data \ + ${{ env.TEST_IMAGE }}:test + + # ็ญ‰ๅพ…ๅ•Ÿๅ‹•๏ผˆๆœ€ๅคš 60 ็ง’๏ผ‰ + echo "โณ Waiting for container to start..." + for i in {1..60}; do + if docker exec convertx-test curl -sf http://localhost:3000/healthcheck > /dev/null 2>&1; then + echo "โœ… Container started successfully in ${i}s" + echo "startup_time=${i}" >> $GITHUB_OUTPUT + exit 0 + fi + sleep 1 + done + + echo "โŒ Container failed to start within 60s" + docker logs convertx-test + exit 1 + + # ======================================== + # Test 2: ้—œ้ตๅทฅๅ…ทๅญ˜ๅœจๆ€งๆชขๆŸฅ + # ======================================== + - name: "Test 2: Tool availability check" + id: test_tools + run: | + echo "๐Ÿ”ง Checking critical tools..." + + FAILED=0 + + # LibreOffice + echo -n " libreoffice: " + if docker exec convertx-test libreoffice --version 2>/dev/null; then + echo "โœ…" + else + echo "โŒ FAILED" + FAILED=1 + fi + + # Pandoc + echo -n " pandoc: " + if docker exec convertx-test pandoc --version 2>/dev/null | head -1; then + echo "โœ…" + else + echo "โŒ FAILED" + FAILED=1 + fi + + # FFmpeg + echo -n " ffmpeg: " + if docker exec convertx-test ffmpeg -version 2>/dev/null | head -1; then + echo "โœ…" + else + echo "โŒ FAILED" + FAILED=1 + fi + + # Tesseract + echo -n " tesseract: " + if docker exec convertx-test tesseract --version 2>/dev/null | head -1; then + echo "โœ…" + else + echo "โŒ FAILED" + FAILED=1 + fi + + # ้กๅค–ๆชขๆŸฅๆ›ดๅคšๅทฅๅ…ท + echo "" + echo "๐Ÿ”ง Checking additional tools..." + + # ImageMagick + echo -n " imagemagick: " + if docker exec convertx-test magick -version 2>/dev/null | head -1; then + echo "โœ…" + else + echo "โš ๏ธ Warning (optional)" + fi + + # Inkscape + echo -n " inkscape: " + if docker exec convertx-test inkscape --version 2>/dev/null; then + echo "โœ…" + else + echo "โš ๏ธ Warning (optional)" + fi + + if [ $FAILED -eq 1 ]; then + echo "" + echo "โŒ Critical tool check failed!" + exit 1 + fi + + echo "" + echo "โœ… All critical tools available" + + # ======================================== + # Test 3: ๆœ€ๅฐ่ฝ‰ๆ›ๆธฌ่ฉฆ (txt โ†’ pdf) + # ======================================== + - name: "Test 3: Minimal conversion test" + id: test_conversion + run: | + echo "๐Ÿ“„ Testing minimal conversion (txt โ†’ pdf)..." + + # ไฝฟ็”จ API ้€ฒ่กŒ่ฝ‰ๆ›ๆธฌ่ฉฆ + # ๅ…ˆไธŠๅ‚ณๆช”ๆกˆ + echo " Uploading test file..." + UPLOAD_RESPONSE=$(curl -sf -X POST \ + -F "file=@/tmp/smoke-test/test.txt" \ + http://localhost:3000/upload 2>&1) || { + echo "โŒ Upload failed" + echo "Response: $UPLOAD_RESPONSE" + exit 1 + } + + echo " Upload response: $UPLOAD_RESPONSE" + + # ๆชขๆŸฅ healthcheck ็ขบ่ชๆœๅ‹™ๆญฃๅธธ + echo " Checking service health..." + HEALTH=$(curl -sf http://localhost:3000/healthcheck) + echo " Health: $HEALTH" + + # ไฝฟ็”จ LibreOffice ็›ดๆŽฅๆธฌ่ฉฆ่ฝ‰ๆ›่ƒฝๅŠ› + echo " Testing LibreOffice conversion capability..." + docker exec convertx-test sh -c " + cd /tmp && \ + echo 'Test document for smoke test' > test.txt && \ + libreoffice --headless --convert-to pdf test.txt && \ + ls -la test.pdf + " && echo "โœ… Conversion test passed" || { + echo "โŒ Conversion test failed" + exit 1 + } + + # ======================================== + # Test 4: API ็ซฏ้ปžๆชขๆŸฅ + # ======================================== + - name: "Test 4: API endpoints check" + run: | + echo "๐ŸŒ Checking API endpoints..." + + # Healthcheck + echo -n " /healthcheck: " + if curl -sf http://localhost:3000/healthcheck > /dev/null; then + echo "โœ…" + else + echo "โŒ" + exit 1 + fi + + # Root page + echo -n " / (root): " + if curl -sf http://localhost:3000/ > /dev/null; then + echo "โœ…" + else + echo "โŒ" + exit 1 + fi + + # Converters list + echo -n " /converters: " + if curl -sf http://localhost:3000/converters > /dev/null; then + echo "โœ…" + else + echo "โš ๏ธ (may require auth)" + fi + + echo "" + echo "โœ… API endpoints check passed" + + - name: Cleanup test container + if: always() + run: | + docker stop convertx-test || true + docker rm convertx-test || true + + - name: Smoke test summary + if: always() + run: | + echo "## ๐Ÿงช Smoke Test Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| ๆธฌ่ฉฆ้ …็›ฎ | ็‹€ๆ…‹ |" >> $GITHUB_STEP_SUMMARY + echo "|----------|------|" >> $GITHUB_STEP_SUMMARY + echo "| Container ๅ•Ÿๅ‹• | โœ… ้€š้Ž (${startup_time:-N/A}s) |" >> $GITHUB_STEP_SUMMARY + echo "| ๅทฅๅ…ทๆชขๆŸฅ (libreoffice, pandoc, ffmpeg, tesseract) | โœ… ้€š้Ž |" >> $GITHUB_STEP_SUMMARY + echo "| ๆœ€ๅฐ่ฝ‰ๆ›ๆธฌ่ฉฆ | โœ… ้€š้Ž |" >> $GITHUB_STEP_SUMMARY + echo "| API ็ซฏ้ปžๆชขๆŸฅ | โœ… ้€š้Ž |" >> $GITHUB_STEP_SUMMARY + + # ========================================================================== + # Job 3: ๆธฌ่ฉฆ้€š้ŽๅพŒ๏ผŒๅปบๆง‹ไธฆๆŽจ้€ multi-arch image + # ========================================================================== + build-and-push: + name: Build & Push Multi-arch Image + runs-on: ubuntu-latest + needs: [build-test-image, smoke-test] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # ======================================== + # ๆธ…็†็ฃ็ขŸ็ฉบ้–“๏ผˆmulti-arch build ้œ€่ฆๆ›ดๅคš็ฉบ้–“๏ผ‰ + # ======================================== + - name: Free disk space + run: | + echo "๐Ÿงน Cleaning up disk space for multi-arch build..." + echo "Before cleanup:" + df -h + + # ็งป้™คไธ้œ€่ฆ็š„้ ่ฃ่ปŸ้ซ” + sudo rm -rf /usr/share/dotnet + sudo rm -rf /usr/local/lib/android + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo rm -rf /usr/local/share/boost + sudo rm -rf /usr/share/swift + sudo rm -rf /opt/hostedtoolcache + + # ๆธ…็† apt cache + sudo apt-get clean + sudo apt-get autoremove -y + + # ๆธ…็† Docker ่ˆŠ่ณ‡ๆ–™ + docker system prune -af --volumes || true + + echo "" + echo "After cleanup:" + df -h + + - name: Get current date + id: date + run: echo "date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to Docker Hub uses: docker/login-action@v3 with: @@ -45,4 +397,16 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY echo "**Image:** \`${{ env.DOCKER_IMAGE }}:upstream-${{ steps.date.outputs.date }}\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "This is an upstream sync build. Use version tags for stable releases." >> $GITHUB_STEP_SUMMARY + echo "### โœ… Smoke Test ๅทฒ้€š้Ž" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| ๆธฌ่ฉฆ้ …็›ฎ | ็‹€ๆ…‹ |" >> $GITHUB_STEP_SUMMARY + echo "|----------|------|" >> $GITHUB_STEP_SUMMARY + echo "| Container ๅ•Ÿๅ‹• | โœ… |" >> $GITHUB_STEP_SUMMARY + echo "| ๅทฅๅ…ทๅญ˜ๅœจๆ€งๆชขๆŸฅ | โœ… |" >> $GITHUB_STEP_SUMMARY + echo "| ๆœ€ๅฐ่ฝ‰ๆ›ๆธฌ่ฉฆ | โœ… |" >> $GITHUB_STEP_SUMMARY + echo "| API ็ซฏ้ปžๆชขๆŸฅ | โœ… |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "โš ๏ธ ้€™ๆ˜ฏ upstream ๅŒๆญฅ็‰ˆๆœฌใ€‚่ซ‹ไฝฟ็”จ version tags ๅ–ๅพ—็ฉฉๅฎš็‰ˆๆœฌใ€‚" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "โŒ **ไธๆœƒๆ›ดๆ–ฐ \`latest\` tag**" >> $GITHUB_STEP_SUMMARY