Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
d8e274c
docs: README ์—…๋ฐ์ดํŠธ
sunjae0902 Feb 1, 2026
8b3ca98
Merge pull request #244 from boostcampwm2025/docs/#223-readme
sunjae0902 Feb 1, 2026
c6dddc5
refactor: ์Šคํฌ๋ฆฝํŠธ ์ด๋ฆ„ ์ˆ˜์ •
sunjae0902 Feb 2, 2026
6786304
fix: ๋นŒ๋“œ ๋„˜๋ฒ„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ฐ€์ ธ์˜ค๋„๋ก ์ˆ˜์ •
sunjae0902 Feb 2, 2026
44bc14a
add: test target ์ถ”๊ฐ€
dongglehada Feb 2, 2026
3bb9c13
test: dataRace ํ™•์ธ์šฉ test์ฝ”๋“œ ์ถ”๊ฐ€
dongglehada Feb 2, 2026
39c2dae
fix: ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ถ”๊ฐ€ ๋ฐ main์— ๋จธ์ง€๋œ ์ปค๋ฐ‹ ๊ธฐ์ค€์œผ๋กœ PR ์ถ”์ ํ•˜๋„๋ก ๋ณ€๊ฒฝ
sunjae0902 Feb 2, 2026
c19313f
fix: ํ˜„์žฌ PR๋„ ํฌํ•จํ•˜๋„๋ก ๋ณ€๊ฒฝ
sunjae0902 Feb 2, 2026
1c507b3
fix: ์–ธ์–ด ๋งž์ถ”๊ธฐ ๊ฒŒ์ž„ ์ข…๋ฃŒ ํ›„ ๋ฒ„ํŠผ ํƒญ ํฌ๋ž˜์‹œ ๋ฐฉ์ง€ ์ฝ”๋“œ ์ถ”๊ฐ€
snughnu Feb 2, 2026
5e98be7
refactor: ๊ฐœ๋ณ„ ๊ฒŒ์ž„ ์‹œ๊ฐ„ ๊ธฐ๋ก ์‚ญ์ œ
tomchoi95 Feb 3, 2026
7c5afa8
refector: ๊ฒŒ์ž„ ์‹คํ–‰ ์‹œ๊ฐ„ ๊ธฐ๋ก ๋กœ์ง ์ถ”๊ฐ€
tomchoi95 Feb 3, 2026
64067ce
feat: ํ”Œ๋ ˆ์ด ํƒ€์ž„ ๊ธฐ๋ก ๋กœ์ง ์ถ”๊ฐ€
tomchoi95 Feb 3, 2026
88c52c8
fix: ์ˆ˜์ •๋œ ํ”Œ๋ ˆ์ด ํƒ€์ž„ ๊ธฐ๋ก ๋กœ์ง์œผ๋กœ ์—…๋ฐ์ดํŠธ
tomchoi95 Feb 3, 2026
48d6c9c
feat: ์กฐ๋‹จ์œ„ ํฌ๋ฉ”ํŒ… ์ถ”๊ฐ€
tomchoi95 Feb 3, 2026
6c42bc6
fix: actor -> mainActor๋กœ ๋ณ€๊ฒฝ
dongglehada Feb 3, 2026
ef85181
remove: gcdํ…Œ์ŠคํŠธ ์ œ๊ฑฐ
dongglehada Feb 3, 2026
7026afb
refactor: ๋ฏธ์…˜ ๋ณด์ƒ ์ˆ˜์ •
tomchoi95 Feb 3, 2026
e162b2b
refactor: ์žฅ๋น„ ๋ฐธ๋Ÿฐ์Šค ์ˆ˜์ •
tomchoi95 Feb 3, 2026
84a3599
refactor: ๊ฒŒ์ž„ ์ •์ฑ… ๋ฐ ๋ฐธ๋Ÿฐ์Šค ์กฐ์ •
tomchoi95 Feb 3, 2026
3548235
fix: ์•„์ดํ…œ ๊ตฌ๋งค ๋กœ์ง์— ์ค‘๋ณต ์ฒ˜๋ฆฌ ๋ฐฉ์ง€ ๋กœ์ง ์ถ”๊ฐ€
sunjae0902 Feb 3, 2026
0942f7f
fix: PriceButton์˜ ๊ฐ€๊ฒฉ์„ ํ•œ ์ค„๋กœ ํ‘œ์‹œํ•˜๊ฒŒ๋” ์ˆ˜์ •
snughnu Feb 3, 2026
3a1c5b4
fix: ์ œ์Šค์ฒ˜ ์ƒํƒœ ๋ณ€์ˆ˜ ์ˆ˜์ •: ๋‘ ์†๊ฐ€๋ฝ ํ„ฐ์น˜ ๋ฌธ์ œ ํ•ด๊ฒฐ
snughnu Feb 3, 2026
49b9560
remove: ๋ถˆํ•„์š” await ์ œ๊ฑฐ
dongglehada Feb 3, 2026
7074142
feat: LongPressRepeatModifier ๋ฐ View+ longPressRepeat ์ถ”๊ฐ€
tomchoi95 Feb 3, 2026
be02f70
fix: ์Šคํ‚ฌ ์—…๊ทธ๋ ˆ์ด๋“œ ์‹œ ์ตœ๊ณ  ๋ ˆ๋ฒจ ์ƒํƒœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ˆ˜์ •
sunjae0902 Feb 3, 2026
2739955
feat: PriceButton์— ๋กฑํ”„๋ ˆ์Šค ๋ฐ˜๋ณต ์—ฐ๋™
tomchoi95 Feb 3, 2026
3d36148
feat: ItemRow์— ๋กฑํ”„๋ ˆ์Šค ์•ก์…˜ ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€
tomchoi95 Feb 3, 2026
e5d02e7
remove: ๋ถˆํ•„์š” await์ œ๊ฑฐ
dongglehada Feb 3, 2026
b9b7e43
feat: SkillView์— ์Šคํ‚ฌ ๋กฑํ”„๋ ˆ์Šค ์—ฐ์† ์—…๊ทธ๋ ˆ์ด๋“œ ์—ฐ๊ฒฐ
tomchoi95 Feb 3, 2026
85dc057
fix: ์žฅ๋น„ ์•„์ดํ…œ ์ตœ๊ณ  ๋ ˆ๋ฒจ ๋„๋‹ฌ ์ƒํƒœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ˆ˜์ •
sunjae0902 Feb 3, 2026
6a75387
refector: ๋ฏธ์…˜ ์™„๋ฃŒ ์ƒํƒœ ์†Œ์ˆซ์  2์ž๋ฆฌ๊นŒ์ง€ ๋ฐ˜์˜ํ•˜๋„๋ก ๋ณ€๊ฒฝ
tomchoi95 Feb 3, 2026
894eb88
fix: ํŒ์—… ์„ธ๋กœ ๊ฐ„๊ฒฉ ์ถ”๊ฐ€
sunjae0902 Feb 3, 2026
0131a9c
fix: ์–ธ์–ด ๋ฒ„ํŠผ ํด๋ฆญ Task ๊ด€๋ฆฌ ์ˆ˜์ •
snughnu Feb 3, 2026
b94e385
test: ์Šคํ‚ฌ ์—…๊ทธ๋ ˆ์ด๋“œ UIํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ถ”๊ฐ€
sunjae0902 Feb 3, 2026
bcfec2d
Merge pull request #264 from boostcampwm2025/fix/#256-purchasing-doubโ€ฆ
sunjae0902 Feb 3, 2026
af1f1b3
Merge pull request #260 from boostcampwm2025/fix/#255-languageGame-crash
snughnu Feb 3, 2026
8e13b50
Merge pull request #262 from boostcampwm2025/feat/#252-play-time
tomchoi95 Feb 3, 2026
64f1614
Merge pull request #266 from boostcampwm2025/feat/#246-skill-long-press
tomchoi95 Feb 3, 2026
4dd299d
Merge branch 'dev' into fix/#153-priceButton-bug
snughnu Feb 3, 2026
25e574f
fix: PriceButton ์ƒํƒœ GestureState๋กœ ๋ณ€๊ฒฝ
snughnu Feb 3, 2026
9b80ac8
fix: ๊ฐ€๊ฒฉ ๋ฒ„ํŠผ ๋น„ํ™œ์„ฑํ™” ์‹œ ๋ˆŒ๋ฆผ ์ƒํƒœ ํ•ด์ œ
snughnu Feb 3, 2026
34306fd
refactor: ํƒญ ๊ฒŒ์ž„ ํ”ผ๋ฒ„ ํ–ฅ์ƒ
tomchoi95 Feb 3, 2026
1ea858e
fix: ์ฝ”๋“œ์งœ๊ธฐ ์†Œ๋ฆฌ ์ˆ˜์ •
dongglehada Feb 3, 2026
f472e2f
fix: block bomb ์†Œ๋ฆฌ ๋ณ€๊ฒฝ
dongglehada Feb 3, 2026
ce46b87
Merge pull request #259 from boostcampwm2025/fix/#243-release-note
sunjae0902 Feb 3, 2026
6156c6a
Merge pull request #265 from boostcampwm2025/fix/#247-concurrency-issue
dongglehada Feb 3, 2026
42deaf9
Merge pull request #263 from boostcampwm2025/refector/#248-balance
snughnu Feb 3, 2026
65397a5
Merge pull request #267 from boostcampwm2025/fix/#153-priceButton-bug
snughnu Feb 3, 2026
5ae8b53
Merge pull request #268 from boostcampwm2025/fix/#249-sound
dongglehada Feb 3, 2026
21423d9
Merge branch 'dev' into fix/#257-max-item-state
snughnu Feb 3, 2026
b850e7f
Merge pull request #269 from boostcampwm2025/fix/#257-max-item-state
snughnu Feb 3, 2026
6cedac7
chore: IPHONEOS_DEPLOYMENT_TARGET ์„ค์ •
snughnu Feb 3, 2026
ea955c1
chore: v1.1.1 ๋ฒ„์ „ ์„ค์ •
snughnu Feb 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 143 additions & 118 deletions .github/workflows/release-note.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Release Drafter
name: Release Note Generator

on:
pull_request:
Expand All @@ -11,7 +11,7 @@ permissions:
pull-requests: read

jobs:
update_release_draft:
create_release_note:
# PR์ด ์‹ค์ œ๋กœ merge๋˜์—ˆ์„ ๋•Œ๋งŒ ์‹คํ–‰
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
Expand All @@ -24,12 +24,14 @@ jobs:
- name: Extract Xcode version info
id: xcode_version
run: |
PROJECT_NAME="SoloDeveloperTraining" # ํ”„๋กœ์ ํŠธ ์ด๋ฆ„
# project.pbxproj์—์„œ MARKETING_VERSION ์ถ”์ถœ (์˜ˆ: 1.0.0)
# ํ”„๋กœ์ ํŠธ ์ด๋ฆ„
PROJECT_NAME="SoloDeveloperTraining"

# MARKETING_VERSION ์ถ”์ถœ
MARKETING_VERSION=$(grep -m 1 "MARKETING_VERSION = " "${PROJECT_NAME}/${PROJECT_NAME}.xcodeproj/project.pbxproj" | sed 's/.*MARKETING_VERSION = \(.*\);/\1/' | tr -d ' ')

# project.pbxproj์—์„œ CURRENT_PROJECT_VERSION ์ถ”์ถœ (๋นŒ๋“œ ๋ฒˆํ˜ธ, ์˜ˆ: 42)
BUILD_NUMBER=$(grep -m 1 "CURRENT_PROJECT_VERSION = " "${PROJECT_NAME}/${PROJECT_NAME}.xcodeproj/project.pbxproj" | sed 's/.*MARKETING_VERSION = \(.*\);/\1/' | tr -d ' ')
# BUILD_NUMBER ์ถ”์ถœ
BUILD_NUMBER=$(grep -m 1 "CURRENT_PROJECT_VERSION = " "${PROJECT_NAME}/${PROJECT_NAME}.xcodeproj/project.pbxproj" | awk -F'[ ;]' '{print $3}')

echo "marketing_version=$MARKETING_VERSION" >> $GITHUB_OUTPUT
echo "build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT
Expand All @@ -49,126 +51,149 @@ jobs:
echo "โœ… Tag v${{ steps.xcode_version.outputs.marketing_version }} does not exist"
fi

# PR ์กฐํšŒ
- name: Get merged PRs since last release
# ๋™์ผํ•œ ๋ฒ„์ „์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ๋งŒ ์‹คํ–‰
# main ๋ธŒ๋žœ์น˜์— ํฌํ•จ๋œ ์ปค๋ฐ‹์„ ๊ธฐ์ค€์œผ๋กœ PR ์กฐํšŒ
- name: Get merged PRs in main branch
# ๋™์ผํ•œ ํƒœ๊ทธ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ๋งŒ ์‹คํ–‰
if: steps.check_tag.outputs.exists == 'false'
id: get_prs
uses: actions/github-script@v7
with:
script: |
const latestRelease = await github.rest.repos.getLatestRelease({
owner: context.repo.owner,
repo: context.repo.repo
}).catch(() => null);

const since = latestRelease?.data.published_at || '';

const { data: pulls } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'closed',
base: 'main',
sort: 'updated',
direction: 'desc'
});

const mergedPRs = pulls.filter(pr => {
if (!pr.merged_at) return false;
if (!since) return true; // ์ฒซ ๋ฆด๋ฆฌ์ฆˆ๋ฉด ๋ชจ๋“  PR ํฌํ•จ
return new Date(pr.merged_at) > new Date(since);
});

// ๋ผ๋ฒจ๋ณ„๋กœ PR ๋ถ„๋ฅ˜
const features = mergedPRs.filter(pr =>
pr.labels.some(l => l.name === 'Feature' || l.name === 'UI' || l.name === 'Design')
);
const bugfixes = mergedPRs.filter(pr =>
pr.labels.some(l => l.name === 'Fix' || l.name === 'Bug')
);
const maintenance = mergedPRs.filter(pr =>
pr.labels.some(l => l.name === 'Chore' || l.name === 'Refactor' || l.name === 'Remove' || l.name === 'Docs' || l.name === 'Test')
);
// ์ œ์™ธํ•  ๋ผ๋ฒจ: Someday, Release๋Š” ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ์— ํฌํ•จํ•˜์ง€ ์•Š์Œ
const others = mergedPRs.filter(pr =>
!pr.labels.some(l => ['Feature', 'UI', 'Design', 'Fix', 'Bug', 'Chore', 'Refactor', 'Remove', 'Docs', 'Test', 'Someday', 'Release'].includes(l.name))
);

let releaseNotes = '## What\'s Changed\n\n';

if (features.length > 0) {
releaseNotes += '### ๐Ÿš€ New Features\n';
features.forEach(pr => {
releaseNotes += `- ${pr.title} @${pr.user.login} (#${pr.number})\n`;
});
releaseNotes += '\n';
}

if (bugfixes.length > 0) {
releaseNotes += '### ๐Ÿ› Bug Fixes\n';
bugfixes.forEach(pr => {
releaseNotes += `- ${pr.title} @${pr.user.login} (#${pr.number})\n`;
});
releaseNotes += '\n';
}

if (maintenance.length > 0) {
releaseNotes += '### ๐Ÿšฉ Maintenance\n';
maintenance.forEach(pr => {
releaseNotes += `- ${pr.title} @${pr.user.login} (#${pr.number})\n`;
});
releaseNotes += '\n';
}

if (others.length > 0) {
releaseNotes += '### ๐Ÿ“ Others\n';
others.forEach(pr => {
releaseNotes += `- ${pr.title} @${pr.user.login} (#${pr.number})\n`;
});
try {
const owner = context.repo.owner;
const repo = context.repo.repo;
const pull_number = context.payload.pull_request.number;

const prNumbers = new Set();

console.log(`๐Ÿ”Ž Analyzing commits from merged PR #${pull_number}...`);

// 1. ํ˜„์žฌ main์œผ๋กœ ๋จธ์ง€๋œ PR์— ํฌํ•จ๋œ ๋ชจ๋“  ์ปค๋ฐ‹ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
const commits = await github.paginate(
github.rest.pulls.listCommits,
{
owner,
repo,
pull_number: pull_number
}
);

// 2. ๊ฐ ์ปค๋ฐ‹๊ณผ ์—ฐ๊ฒฐ๋œ PR๋“ค์„ ์—ญ์ถ”์ ํ•ฉ๋‹ˆ๋‹ค.
for (const commit of commits) {
const linkedPRs = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner,
repo,
commit_sha: commit.sha
});

linkedPRs.data.forEach(pr => {
// ๋จธ์ง€๋œ PR์ด๊ณ , ํ˜„์žฌ main์œผ๋กœ ๋จธ์ง€๋œ PR ์ž์ฒด๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ(ํ•˜์œ„ PR์ธ ๊ฒฝ์šฐ) ์ถ”๊ฐ€
if (pr.merged_at && pr.number !== pull_number) {
prNumbers.add(pr.number);
}
});
}

// 3. ํ˜„์žฌ PR์„ ํฌํ•จ์‹œํ‚ต๋‹ˆ๋‹ค.
prNumbers.add(pull_number);

console.log(`โœ… Found ${prNumbers.size} unique sub-PRs.`);

const prDetails = [];
for (const number of Array.from(prNumbers)) {
const { data: pr } = await github.rest.pulls.get({
owner,
repo,
pull_number: number
});
prDetails.push(pr);
}

prDetails.sort((a, b) => new Date(b.merged_at) - new Date(a.merged_at));

// 4. PR ๋ผ๋ฒจ์— ๋”ฐ๋ผ ์นดํ…Œ๊ณ ๋ผ์ด์ง• ํ•ฉ๋‹ˆ๋‹ค.
const features = prDetails.filter(pr => pr.labels.some(l => ['Feature','UI','Design'].includes(l.name)));
const bugfixes = prDetails.filter(pr => pr.labels.some(l => ['Fix','Bug'].includes(l.name)));
const maintenance = prDetails.filter(pr => pr.labels.some(l => ['Chore','Refactor','Remove','Docs','Test'].includes(l.name)));
const others = prDetails.filter(pr => !pr.labels.some(l => ['Feature','UI','Design','Fix','Bug','Chore','Refactor','Remove','Docs','Test','Someday','Release'].includes(l.name)));

console.log(`๐Ÿ“Š PR Breakdown - Features: ${features.length}, Fixes: ${bugfixes.length}, Maint: ${maintenance.length}, Others: ${others.length}`);

// 5. ๋ฌธ์„œ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
let releaseNotes = '## What\'s Changed\n\n';
const formatPR = (pr) => `- ${pr.title} @${pr.user.login} ([#${pr.number}](${pr.html_url}))\n`;

if (features.length) releaseNotes += `### ๐Ÿš€ New Features\n${features.map(formatPR).join('')}\n`;
if (bugfixes.length) releaseNotes += `### ๐Ÿ› Bug Fixes\n${bugfixes.map(formatPR).join('')}\n`;
if (maintenance.length) releaseNotes += `### ๐Ÿšฉ Maintenance\n${maintenance.map(formatPR).join('')}\n`;
if (others.length) releaseNotes += `### ๐Ÿ“ Others\n${others.map(formatPR).join('')}\n`;

return releaseNotes;

} catch (error) {
console.error('โŒ Error:', error.message);
core.setFailed(error.message);
throw error;
}

return releaseNotes;

- name: Create or Update Release Draft

# ๋ฆด๋ฆฌ์ฆˆ ์ƒ์„ฑ ๋ฐ ์—…๋ฐ์ดํŠธ
- name: Create or Update Release
if: steps.check_tag.outputs.exists == 'false'
uses: actions/github-script@v7
with:
script: |
const marketingVersion = '${{ steps.xcode_version.outputs.marketing_version }}';
const buildNumber = '${{ steps.xcode_version.outputs.build_number }}';
const tagName = `v${marketingVersion}`;
const releaseNotes = ${{ steps.get_prs.outputs.result }};

// ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ์— ๋ฒ„์ „ ์ •๋ณด ์ถ”๊ฐ€
const fullReleaseNotes = `**Version**: ${marketingVersion}\n**Build**: ${buildNumber}\n\n${releaseNotes}`;

// ๊ธฐ์กด draft ๋ฆด๋ฆฌ์ฆˆ ์ฐพ๊ธฐ
const { data: releases } = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo
});

const existingDraft = releases.find(r => r.draft && r.tag_name === tagName);

if (existingDraft) {
// ์—…๋ฐ์ดํŠธ
await github.rest.repos.updateRelease({
try {
const marketingVersion = '${{ steps.xcode_version.outputs.marketing_version }}';
const buildNumber = '${{ steps.xcode_version.outputs.build_number }}';
const tagName = `v${marketingVersion}`;
const releaseNotes = ${{ steps.get_prs.outputs.result }};

if (!marketingVersion || marketingVersion === '') {
throw new Error('Marketing version is empty or invalid');
}
if (!buildNumber || buildNumber === '') {
throw new Error('Build number is empty or invalid');
}

console.log(`๐Ÿ“ฆ Creating/updating release: ${tagName}`);
console.log(`๐Ÿ“ฑ Version: ${marketingVersion}`);
console.log(`๐Ÿ”ข Build: ${buildNumber}`);

const fullReleaseNotes = `**Version**: ${marketingVersion}\n**Build**: ${buildNumber}\n\n${releaseNotes}`;

const { data: releases } = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: existingDraft.id,
body: fullReleaseNotes
repo: context.repo.repo
});
console.log(`โœ… Updated draft release: ${tagName} (Build: ${buildNumber})`);
} else {
// ์ƒˆ๋กœ ์ƒ์„ฑ
await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tagName,
name: `v${marketingVersion}`,
body: fullReleaseNotes,
draft: false,
prerelease: false
});
console.log(`โœ… Created draft release: ${tagName} (Build: ${buildNumber})`);
}

const existingRelease = releases.find(r => r.tag_name === tagName);

if (existingRelease) {
console.log(`๐Ÿ”„ Updating existing release: ${tagName}`);
await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: existingRelease.id,
body: fullReleaseNotes
});
console.log(`โœ… Updated release: ${tagName} (Build: ${buildNumber})`);
} else {
console.log(`๐Ÿ†• Creating new release: ${tagName}`);
const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tagName,
name: `v${marketingVersion}`,
body: fullReleaseNotes,
draft: false,
prerelease: false
});
console.log(`โœ… Created release: ${tagName} (Build: ${buildNumber})`);
console.log(`๐Ÿ”— Release URL: ${release.data.html_url}`);
}

} catch (error) {
console.error('โŒ Error creating/updating release:', error.message);
core.setFailed(`Failed to create/update release: ${error.message}`);
throw error;
}
Loading