From 25a485d64d9c9a9aa6d95c6a1b0e39e0af2a3c30 Mon Sep 17 00:00:00 2001 From: Jiyong Jung Date: Thu, 26 Mar 2026 06:32:43 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20workflow=20=EA=B0=9C=EC=84=A0?= =?UTF-8?q?=20=EB=B0=8F=20Notion=20=EC=A0=9C=EA=B1=B0=20-=20#215?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/setup-build-env/action.yml | 13 ++- .github/scripts/create-notion-page.js | 106 --------------------- .github/workflows/cd_develop.yml | 3 + .github/workflows/cd_main.yml | 3 + .github/workflows/ci_pr.yml | 27 +++++- .github/workflows/create-notion-page.yml | 30 ------ .github/workflows/issue-automation.yml | 2 +- fastlane/Fastfile | 16 ++-- 8 files changed, 50 insertions(+), 150 deletions(-) delete mode 100644 .github/scripts/create-notion-page.js delete mode 100644 .github/workflows/create-notion-page.yml diff --git a/.github/actions/setup-build-env/action.yml b/.github/actions/setup-build-env/action.yml index 37684cfc..e7d8d391 100644 --- a/.github/actions/setup-build-env/action.yml +++ b/.github/actions/setup-build-env/action.yml @@ -39,7 +39,15 @@ runs: ruby-version: "3.3.0" bundler-cache: true + - name: πŸ“¦ Cache Tuist binary + id: tuist-binary-cache + uses: actions/cache@v4 + with: + path: ~/.tuist-bin + key: tuist-binary-${{ runner.os }}-4.118.0 + - name: πŸ›  Install Tuist (manual binary) + if: steps.tuist-binary-cache.outputs.cache-hit != 'true' shell: bash run: | TUIST_VERSION=4.118.0 @@ -47,7 +55,10 @@ runs: curl -L "https://github.com/tuist/tuist/releases/download/${TUIST_VERSION}/tuist.zip" -o tuist.zip unzip -o tuist.zip -d ~/.tuist-bin chmod +x ~/.tuist-bin/tuist - echo "$HOME/.tuist-bin" >> $GITHUB_PATH + + - name: πŸ”§ Add Tuist to PATH + shell: bash + run: echo "$HOME/.tuist-bin" >> $GITHUB_PATH - name: πŸ“¦ Cache SPM dependencies uses: actions/cache@v4 diff --git a/.github/scripts/create-notion-page.js b/.github/scripts/create-notion-page.js deleted file mode 100644 index dfce0931..00000000 --- a/.github/scripts/create-notion-page.js +++ /dev/null @@ -1,106 +0,0 @@ -const { Client } = require("@notionhq/client"); - -const notion = new Client({ auth: process.env.NOTION_TOKEN }); - -function rt(text) { - return [{ type: "text", text: { content: text } }]; -} - -function mdToNotionBlocks(md) { - const lines = (md ?? "").replace(/\r\n/g, "\n").split("\n"); - - const blocks = []; - for (const raw of lines) { - const line = raw.trim(); - if (!line) continue; - - // ### Heading 3 to 2 - const h3 = line.match(/^###\s+(.*)$/); - if (h3) { - blocks.push({ - object: "block", - type: "heading_2", - heading_2: { rich_text: rt(h3[1]) }, - }); - continue; - } - - // - [ ] todo (unchecked) - const todoUnchecked = line.match(/^- \[\s\]\s+(.*)$/); - if (todoUnchecked) { - blocks.push({ - object: "block", - type: "to_do", - to_do: { rich_text: rt(todoUnchecked[1]), checked: false }, - }); - continue; - } - - // - [x] todo (checked) - const todoChecked = line.match(/^- \[[xX]\]\s+(.*)$/); - if (todoChecked) { - blocks.push({ - object: "block", - type: "to_do", - to_do: { rich_text: rt(todoChecked[1]), checked: true }, - }); - continue; - } - - // fallback paragraph - blocks.push({ - object: "block", - type: "paragraph", - paragraph: { rich_text: rt(line) }, - }); - } - return blocks; -} - -async function main() { - const dbId = process.env.NOTION_DATABASE_ID; - const bodyMd = process.env.ISSUE_BODY_MD; - const titleText = process.env.ISSUE_TITLE ?? "Untitled"; - const issueNumber = process.env.ISSUE_NUMBER ?? 0; - const githubAssignee = process.env.GITHUB_ASSIGNEE || process.env.GITHUB_ISSUE_AUTHOR; - - const db = await notion.databases.retrieve({ database_id: dbId }); - const dataSourceId = db.data_sources?.[0]?.id; - if (!dataSourceId) throw new Error("No data_sources in this database"); - - const notionAssigneeIdMap = { - "clxxrlove": process.env.NOTION_USER_ID_CLXXRLOVE, - "jihun32": process.env.NOTION_USER_ID_JIHUN32, - }; - - const properties = { - "μž‘μ—… 이름": { title: rt(`#${issueNumber} ${titleText}`) }, - "μƒνƒœ": { status: { name: "μ§„ν–‰ 쀑" } }, - "μž‘μ—… μœ ν˜•": { multi_select: [{ name: "νŒ€ μž‘μ—…" }] }, - "마감일": { date: { start: "2026-03-01" } }, - }; - - const notionUserId = notionAssigneeIdMap[githubAssignee]; - if (notionUserId) { - properties["λ‹΄λ‹Ήμž"] = { people: [{ id: notionUserId }] }; - } - - const children = mdToNotionBlocks(bodyMd); - - await notion.pages.create({ - parent: { data_source_id: dataSourceId }, - icon: { - type: "external", - external: { - url: "https://github.com/user-attachments/assets/7059c317-215f-4116-91b8-4fdc92a13ce1", - }, - }, - properties, - children, - }); -} - -main().catch((e) => { - console.error(e); - process.exit(1); -}); \ No newline at end of file diff --git a/.github/workflows/cd_develop.yml b/.github/workflows/cd_develop.yml index acb110fe..93f8dc4c 100644 --- a/.github/workflows/cd_develop.yml +++ b/.github/workflows/cd_develop.yml @@ -14,6 +14,8 @@ jobs: steps: - name: πŸ”„ Checkout source code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: πŸ›  Setup Build Environment uses: ./.github/actions/setup-build-env @@ -37,6 +39,7 @@ jobs: ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} ASC_KEY_P8_BASE64: ${{ secrets.ASC_KEY_P8_BASE64 }} + GITHUB_RUN_NUMBER: ${{ github.run_number }} run: bundle exec fastlane deploy_develop - name: 🧹 Teardown() diff --git a/.github/workflows/cd_main.yml b/.github/workflows/cd_main.yml index b0ded6bd..70c85274 100644 --- a/.github/workflows/cd_main.yml +++ b/.github/workflows/cd_main.yml @@ -14,6 +14,8 @@ jobs: steps: - name: πŸ”„ Checkout source code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: πŸ›  Setup Build Environment uses: ./.github/actions/setup-build-env @@ -38,6 +40,7 @@ jobs: ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} ASC_KEY_P8_BASE64: ${{ secrets.ASC_KEY_P8_BASE64 }} + GITHUB_RUN_NUMBER: ${{ github.run_number }} run: bundle exec fastlane deploy_main - name: 🧹 Teardown() diff --git a/.github/workflows/ci_pr.yml b/.github/workflows/ci_pr.yml index fbefd04e..3520fe45 100644 --- a/.github/workflows/ci_pr.yml +++ b/.github/workflows/ci_pr.yml @@ -3,8 +3,23 @@ name: Fastlane CI (PR) on: pull_request: +concurrency: + group: ci-pr-${{ github.head_ref || github.ref }} + cancel-in-progress: true + jobs: - ci_pr: + swiftlint: + runs-on: ubuntu-latest + steps: + - name: πŸ”„ Checkout source code + uses: actions/checkout@v4 + + - name: 🧹 Run SwiftLint + uses: norio-nomura/action-swiftlint@3.2.1 + with: + args: --strict + + build: runs-on: macos-15 steps: - name: πŸ”„ Checkout source code @@ -15,7 +30,15 @@ jobs: with: keychain-name: ${{ secrets.KEYCHAIN_NAME }} keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }} - install-swiftlint: "true" + install-swiftlint: "false" + + - name: πŸ“¦ Cache DerivedData + uses: actions/cache@v4 + with: + path: ~/Library/Developer/Xcode/DerivedData + key: deriveddata-${{ runner.os }}-${{ hashFiles('**/*.swift', '**/Project.swift', 'Tuist/Package.resolved') }} + restore-keys: | + deriveddata-${{ runner.os }}- - name: πŸ§ͺ Run PR Checks (build + test) env: diff --git a/.github/workflows/create-notion-page.yml b/.github/workflows/create-notion-page.yml deleted file mode 100644 index 2662c68b..00000000 --- a/.github/workflows/create-notion-page.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Create Notion page on new issue - -on: - issues: - types: [opened] - -jobs: - notion: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: 20 - - - run: npm i @notionhq/client - - run: node .github/scripts/create-notion-page.js - env: - NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }} - NOTION_DATABASE_ID: ${{ secrets.NOTION_DATABASE_ID }} - ISSUE_TITLE: ${{ github.event.issue.title }} - ISSUE_URL: ${{ github.event.issue.html_url }} - ISSUE_NUMBER: ${{ github.event.issue.number }} - REPO: ${{ github.repository }} - ISSUE_BODY_MD: ${{ github.event.issue.body }} - GITHUB_ASSIGNEE: ${{ github.event.issue.assignee.login }} - GITHUB_ISSUE_AUTHOR: ${{ github.event.issue.user.login }} - NOTION_USER_ID_CLXXRLOVE: ${{ secrets.NOTION_USER_ID_CLXXRLOVE }} - NOTION_USER_ID_JIHUN32: ${{ secrets.NOTION_USER_ID_JIHUN32 }} \ No newline at end of file diff --git a/.github/workflows/issue-automation.yml b/.github/workflows/issue-automation.yml index b41419f9..40985445 100644 --- a/.github/workflows/issue-automation.yml +++ b/.github/workflows/issue-automation.yml @@ -52,7 +52,7 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, }); - const baseBranch = repo.default_branch; + const baseBranch = 'develop'; const { data: baseRef } = await github.rest.git.getRef({ owner: context.repo.owner, diff --git a/fastlane/Fastfile b/fastlane/Fastfile index cd0b12b3..a99660ee 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -170,11 +170,9 @@ platform :ios do is_key_content_base64: true ) - current_build = latest_testflight_build_number( - api_key: api_key, - app_identifier: BUNDLE_ID - ) - new_build = current_build + 1 + commit_count = sh("git rev-list --count HEAD").strip.to_i + run_number = (ENV["GITHUB_RUN_NUMBER"] || "0").to_i + new_build = commit_count * 100 + (run_number % 100) sh("cd .. && /usr/libexec/PlistBuddy -c 'Set :CFBundleVersion #{new_build}' Projects/App/Derived/InfoPlists/TwixDebug-Info.plist") @@ -209,11 +207,9 @@ platform :ios do is_key_content_base64: true ) - current_build = latest_testflight_build_number( - api_key: api_key, - app_identifier: BUNDLE_ID - ) - new_build = current_build + 1 + commit_count = sh("git rev-list --count HEAD").strip.to_i + run_number = (ENV["GITHUB_RUN_NUMBER"] || "0").to_i + new_build = commit_count * 100 + (run_number % 100) sh("cd .. && /usr/libexec/PlistBuddy -c 'Set :CFBundleVersion #{new_build}' Projects/App/Derived/InfoPlists/Twix-Info.plist") From ccffd43385e1f41a6eeb3648fbc3ea45d21ac974 Mon Sep 17 00:00:00 2001 From: Jiyong Jung Date: Thu, 26 Mar 2026 10:29:32 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=EB=B8=8C=EB=9E=9C=EC=B9=98?= =?UTF-8?q?=EB=AA=85=EC=97=90=20Linear=20=EC=9D=B4=EC=8A=88=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=B6=94=EA=B0=80=20-=20#215?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/issue-automation.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/issue-automation.yml b/.github/workflows/issue-automation.yml index 40985445..18054698 100644 --- a/.github/workflows/issue-automation.yml +++ b/.github/workflows/issue-automation.yml @@ -44,7 +44,13 @@ jobs: let type = (m?.[1] ?? "chore").toLowerCase(); if (!allowed.has(type)) type = "chore"; - const branchName = `${type}/#${issueNumber}`; + // Parse Linear issue ID from title (e.g., [TWI-123]) + const linearMatch = title.match(/\[(TWI-\d+)\]/); + const linearId = linearMatch?.[1]; + + const branchName = linearId + ? `${type}/#${issueNumber}/${linearId}` + : `${type}/#${issueNumber}`; console.log(`🌿 Target branch: ${branchName}`); // base branch SHA From 71729b9707a74a3d48480121efcf5403503e1d4d Mon Sep 17 00:00:00 2001 From: Jiyong Jung Date: Thu, 26 Mar 2026 12:21:11 +0900 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20CI=20=EC=BA=90=EC=8B=9C=20?= =?UTF-8?q?=EC=B5=9C=EC=A0=81=ED=99=94=20=EB=B0=8F=20SwiftLint=20job=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20-=20#215?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/setup-build-env/action.yml | 43 ++++------------------ .github/workflows/cd_develop.yml | 9 ++++- .github/workflows/cd_main.yml | 9 ++++- .github/workflows/ci_pr.yml | 14 +------ 4 files changed, 25 insertions(+), 50 deletions(-) diff --git a/.github/actions/setup-build-env/action.yml b/.github/actions/setup-build-env/action.yml index e7d8d391..e6235c29 100644 --- a/.github/actions/setup-build-env/action.yml +++ b/.github/actions/setup-build-env/action.yml @@ -8,10 +8,6 @@ inputs: keychain-password: description: "Keychain password" required: true - install-swiftlint: - description: "Whether to install SwiftLint" - required: false - default: "true" include-pulse: description: "Whether to include Pulse in cache (false for production builds)" required: false @@ -61,6 +57,7 @@ runs: run: echo "$HOME/.tuist-bin" >> $GITHUB_PATH - name: πŸ“¦ Cache SPM dependencies + id: spm-cache uses: actions/cache@v4 with: path: Tuist/.build @@ -69,49 +66,25 @@ runs: tuist-spm-${{ runner.os }}- - name: πŸ›  Install tuist dependencies + if: steps.spm-cache.outputs.cache-hit != 'true' shell: bash run: tuist install - - name: πŸ“¦ Cache Tuist build artifacts - id: tuist-cache - uses: actions/cache@v4 - with: - path: ~/.cache/tuist - key: tuist-build-cache-${{ runner.os }}-${{ inputs.include-pulse }}-${{ hashFiles('Tuist/Package.resolved', 'Tuist/Package.swift') }} - restore-keys: | - tuist-build-cache-${{ runner.os }}-${{ inputs.include-pulse }}- - - - name: πŸ“Š Tuist cache status + - name: πŸ“Š SPM cache status shell: bash run: | - if [ "${{ steps.tuist-cache.outputs.cache-hit }}" == "true" ]; then - echo "βœ… Tuist cache HIT" + if [ "${{ steps.spm-cache.outputs.cache-hit }}" == "true" ]; then + echo "βœ… SPM cache HIT - skipping tuist cache warm up" else - echo "❌ Tuist cache MISS - will build dependencies" + echo "❌ SPM cache MISS - will build dependencies" fi - name: πŸ— Warm up external dependencies (with Pulse) - if: ${{ inputs.include-pulse == 'true' }} + if: steps.spm-cache.outputs.cache-hit != 'true' && inputs.include-pulse == 'true' shell: bash run: tuist cache ComposableArchitecture Kingfisher Pulse KakaoSDKAuth KakaoSDKCommon GoogleSignIn GoogleSignInSwift - name: πŸ— Warm up external dependencies (without Pulse) - if: ${{ inputs.include-pulse != 'true' }} + if: steps.spm-cache.outputs.cache-hit != 'true' && inputs.include-pulse != 'true' shell: bash run: tuist cache ComposableArchitecture Kingfisher KakaoSDKAuth KakaoSDKCommon GoogleSignIn GoogleSignInSwift - - - name: πŸ“¦ Cache SwiftLint - if: ${{ inputs.install-swiftlint == 'true' }} - uses: actions/cache@v4 - with: - path: /opt/homebrew/bin/swiftlint - key: swiftlint-${{ runner.os }}-0.57.0 - - - name: 🧰 Install SwiftLint - if: ${{ inputs.install-swiftlint == 'true' }} - shell: bash - run: | - if ! command -v swiftlint &> /dev/null; then - brew install swiftlint - fi - swiftlint version diff --git a/.github/workflows/cd_develop.yml b/.github/workflows/cd_develop.yml index 93f8dc4c..b02892df 100644 --- a/.github/workflows/cd_develop.yml +++ b/.github/workflows/cd_develop.yml @@ -22,7 +22,14 @@ jobs: with: keychain-name: ${{ secrets.KEYCHAIN_NAME }} keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }} - install-swiftlint: "false" + + - name: πŸ“¦ Cache DerivedData + uses: actions/cache@v4 + with: + path: ~/Library/Developer/Xcode/DerivedData + key: deriveddata-${{ runner.os }}-${{ hashFiles('Tuist/Package.resolved') }} + restore-keys: | + deriveddata-${{ runner.os }}- - name: πŸš€ Deploy to TestFlight (TwixDebug) env: diff --git a/.github/workflows/cd_main.yml b/.github/workflows/cd_main.yml index 70c85274..52ec6856 100644 --- a/.github/workflows/cd_main.yml +++ b/.github/workflows/cd_main.yml @@ -22,9 +22,16 @@ jobs: with: keychain-name: ${{ secrets.KEYCHAIN_NAME }} keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }} - install-swiftlint: "false" include-pulse: "false" + - name: πŸ“¦ Cache DerivedData + uses: actions/cache@v4 + with: + path: ~/Library/Developer/Xcode/DerivedData + key: deriveddata-${{ runner.os }}-${{ hashFiles('Tuist/Package.resolved') }} + restore-keys: | + deriveddata-${{ runner.os }}- + - name: πŸš€ Deploy to App Store Connect (Twix) env: MATCH_REPO_URL: ${{ secrets.MATCH_REPO_URL }} diff --git a/.github/workflows/ci_pr.yml b/.github/workflows/ci_pr.yml index 3520fe45..dc0b55ba 100644 --- a/.github/workflows/ci_pr.yml +++ b/.github/workflows/ci_pr.yml @@ -8,17 +8,6 @@ concurrency: cancel-in-progress: true jobs: - swiftlint: - runs-on: ubuntu-latest - steps: - - name: πŸ”„ Checkout source code - uses: actions/checkout@v4 - - - name: 🧹 Run SwiftLint - uses: norio-nomura/action-swiftlint@3.2.1 - with: - args: --strict - build: runs-on: macos-15 steps: @@ -30,13 +19,12 @@ jobs: with: keychain-name: ${{ secrets.KEYCHAIN_NAME }} keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }} - install-swiftlint: "false" - name: πŸ“¦ Cache DerivedData uses: actions/cache@v4 with: path: ~/Library/Developer/Xcode/DerivedData - key: deriveddata-${{ runner.os }}-${{ hashFiles('**/*.swift', '**/Project.swift', 'Tuist/Package.resolved') }} + key: deriveddata-${{ runner.os }}-${{ hashFiles('Tuist/Package.resolved') }} restore-keys: | deriveddata-${{ runner.os }}-