Skip to content

Commit 80a803b

Browse files
committed
release: 1.0.1
1 parent cb54be2 commit 80a803b

336 files changed

Lines changed: 34545 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gemini/styleguide.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## Review Guidelines
2+
3+
- Write all review comments in Korean.
4+
- Keep review comments concise and high-signal.
5+
- Prioritize findings about bugs, performance, and readability.
6+
- Do not explain obvious, trivial, or low-signal issues.
7+
- When useful, begin the review with a short summary of the main changes.
8+
- Focus on actionable feedback rather than broad commentary.
9+
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Install Private Config
2+
description: Fetch Config.xcconfig and GoogleService-Info.plist from a private repository.
3+
4+
inputs:
5+
git_url:
6+
description: Private repository git URL
7+
required: true
8+
git_basic_authorization:
9+
description: Base64-encoded basic authorization header value
10+
required: true
11+
ref:
12+
description: Git ref to fetch from the private repository
13+
required: false
14+
default: iOS
15+
16+
runs:
17+
using: composite
18+
steps:
19+
- name: Install private config files
20+
shell: bash
21+
env:
22+
PRIVATE_CONFIG_GIT_URL: ${{ inputs.git_url }}
23+
PRIVATE_CONFIG_GIT_BASIC_AUTHORIZATION: ${{ inputs.git_basic_authorization }}
24+
PRIVATE_CONFIG_REF: ${{ inputs.ref }}
25+
run: |
26+
set -euo pipefail
27+
28+
privateConfigCheckoutPath="$RUNNER_TEMP/private-config"
29+
trap 'rm -rf "$privateConfigCheckoutPath"' EXIT
30+
configSourcePath="$privateConfigCheckoutPath/resources/DevLog/Config.xcconfig"
31+
googleServiceInfoSourcePath="$privateConfigCheckoutPath/resources/DevLog/GoogleService-Info.plist"
32+
configDestinationPath="$GITHUB_WORKSPACE/DevLog/Resource/Config.xcconfig"
33+
googleServiceInfoDestinationPath="$GITHUB_WORKSPACE/DevLog/Resource/GoogleService-Info.plist"
34+
35+
rm -rf "$privateConfigCheckoutPath"
36+
mkdir -p "$privateConfigCheckoutPath"
37+
38+
git init "$privateConfigCheckoutPath"
39+
git -C "$privateConfigCheckoutPath" remote add origin "$PRIVATE_CONFIG_GIT_URL"
40+
git -C "$privateConfigCheckoutPath" config core.sparseCheckout true
41+
printf '%s\n' \
42+
'resources/DevLog/Config.xcconfig' \
43+
'resources/DevLog/GoogleService-Info.plist' \
44+
> "$privateConfigCheckoutPath/.git/info/sparse-checkout"
45+
46+
git -C "$privateConfigCheckoutPath" \
47+
-c http.extraheader="Authorization: Basic $PRIVATE_CONFIG_GIT_BASIC_AUTHORIZATION" \
48+
fetch --depth 1 origin "$PRIVATE_CONFIG_REF"
49+
git -C "$privateConfigCheckoutPath" checkout FETCH_HEAD
50+
51+
if [ ! -f "$configSourcePath" ]; then
52+
echo "Missing private config file at $configSourcePath" >&2
53+
exit 1
54+
fi
55+
56+
if [ ! -f "$googleServiceInfoSourcePath" ]; then
57+
echo "Missing private GoogleService-Info.plist at $googleServiceInfoSourcePath" >&2
58+
exit 1
59+
fi
60+
61+
install -m 600 "$configSourcePath" "$configDestinationPath"
62+
install -m 600 "$googleServiceInfoSourcePath" "$googleServiceInfoDestinationPath"

.github/pull_request_template.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## 🔗 연관된 이슈
2+
> 이슈 번호를 입력해주세요. (예: #12)
3+
> 이슈가 완전히 해결되었다면 아래에 예약어를 남겨주세요.
4+
- closed #이슈번호
5+
6+
## 📝 작업 내용
7+
8+
### 📌 요약
9+
10+
### 🔍 상세
11+
12+
## 📸 영상 / 이미지 (Optional)

.github/workflows/build.yml

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
name: iOS CI
2+
3+
on:
4+
pull_request:
5+
6+
env:
7+
SCHEME: DevLog
8+
XCODE_VERSION: latest
9+
MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}
10+
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
11+
12+
permissions:
13+
contents: read
14+
issues: write
15+
pull-requests: write
16+
checks: write
17+
18+
jobs:
19+
build:
20+
runs-on: macos-latest
21+
timeout-minutes: 30
22+
steps:
23+
- uses: actions/checkout@v5
24+
25+
- name: Install private config files
26+
uses: ./.github/actions/install-private-config
27+
with:
28+
git_url: ${{ env.MATCH_GIT_URL }}
29+
git_basic_authorization: ${{ env.MATCH_GIT_BASIC_AUTHORIZATION }}
30+
31+
- name: Select Xcode
32+
shell: bash
33+
run: |
34+
set -euo pipefail
35+
36+
if [ "$XCODE_VERSION" = "latest" ]; then
37+
XCODE_APP="$(find /Applications -maxdepth 1 -name 'Xcode*.app' -type d | sort -V | tail -n 1)"
38+
else
39+
XCODE_APP="/Applications/Xcode_${XCODE_VERSION}.app"
40+
if [ ! -d "$XCODE_APP" ]; then
41+
XCODE_APP="/Applications/Xcode-${XCODE_VERSION}.app"
42+
fi
43+
fi
44+
45+
if [ ! -d "${XCODE_APP:-}" ]; then
46+
echo "Requested Xcode not found for version: $XCODE_VERSION" >&2
47+
exit 1
48+
fi
49+
50+
sudo xcode-select -s "$XCODE_APP/Contents/Developer"
51+
xcodebuild -version
52+
53+
- name: Cache SwiftPM
54+
uses: actions/cache@v5
55+
with:
56+
path: |
57+
~/.swiftpm
58+
~/Library/Caches/org.swift.swiftpm
59+
~/Library/Developer/Xcode/SourcePackages
60+
.spm
61+
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
62+
restore-keys: |
63+
${{ runner.os }}-spm-
64+
65+
- name: Select iOS Simulator Runtime (installed)
66+
id: pick_ios
67+
shell: bash
68+
run: |
69+
set -euo pipefail
70+
71+
RESULT=$(python3 - <<'PY'
72+
import re, subprocess, sys
73+
74+
def ver_key(version):
75+
return tuple(int(part) for part in version.split('.'))
76+
77+
text = subprocess.check_output(["xcrun", "simctl", "list", "devices"], text=True)
78+
lines = text.splitlines()
79+
current_ver = None
80+
candidates = []
81+
82+
for line in lines:
83+
header = re.match(r"^-- iOS ([0-9]+(?:\.[0-9]+)*) --$", line.strip())
84+
if header:
85+
current_ver = header.group(1)
86+
continue
87+
if current_ver is None:
88+
continue
89+
if "(unavailable)" in line:
90+
continue
91+
if "iPhone" not in line:
92+
continue
93+
94+
raw = line.strip()
95+
if "platform:" in raw and "name:" in raw and "OS:" in raw:
96+
kv = {}
97+
for part in raw.split(","):
98+
if ":" not in part:
99+
continue
100+
k, v = part.split(":", 1)
101+
kv[k.strip()] = v.strip()
102+
name = kv.get("name", raw)
103+
else:
104+
name = raw
105+
name = re.sub(r"\s+\([0-9A-Fa-f-]{36}\)\s+\(.*\)$", "", name)
106+
107+
candidates.append((current_ver, name))
108+
109+
if len(candidates) <= 0:
110+
print("No available iPhone simulators found", file=sys.stderr)
111+
sys.exit(1)
112+
113+
latest_version = max((candidate[0] for candidate in candidates), key=ver_key)
114+
latest_candidates = [
115+
candidate for candidate in candidates
116+
if candidate[0] == latest_version
117+
]
118+
chosen_version, chosen_device_name = min(
119+
latest_candidates,
120+
key=lambda candidate: candidate[1]
121+
)
122+
123+
print(f"{chosen_version}|{chosen_device_name}")
124+
sys.exit(0)
125+
PY
126+
)
127+
128+
if [ -z "${RESULT:-}" ]; then
129+
echo "No iPhone simulator devices detected." >&2
130+
exit 1
131+
fi
132+
133+
IFS='|' read -r IOS_VER DEVICE_NAME <<< "$RESULT"
134+
135+
echo "Chosen iOS runtime version (iPhone): $IOS_VER"
136+
echo "Chosen simulator: $DEVICE_NAME"
137+
138+
echo "ios_version=$IOS_VER" >> "$GITHUB_OUTPUT"
139+
echo "device_name=$DEVICE_NAME" >> "$GITHUB_OUTPUT"
140+
141+
- name: Build
142+
shell: bash
143+
env:
144+
IOS_VER: ${{ steps.pick_ios.outputs.ios_version }}
145+
DEVICE_NAME: ${{ steps.pick_ios.outputs.device_name }}
146+
run: |
147+
set -euo pipefail
148+
set -x
149+
SPM_DIR="$GITHUB_WORKSPACE/.spm"
150+
mkdir -p "$SPM_DIR"
151+
152+
xcodebuild -version
153+
154+
echo "Using scheme: $SCHEME"
155+
echo "Using simulator: $DEVICE_NAME (iOS ${IOS_VER})"
156+
157+
set -o pipefail
158+
set +e
159+
echo "== Resolving Swift Package dependencies =="
160+
xcodebuild \
161+
-scheme "$SCHEME" \
162+
-configuration Debug \
163+
-clonedSourcePackagesDirPath "$SPM_DIR" \
164+
-resolvePackageDependencies
165+
echo "== Starting xcodebuild build =="
166+
xcodebuild \
167+
-scheme "$SCHEME" \
168+
-configuration Debug \
169+
-destination "platform=iOS Simulator,OS=${IOS_VER},name=${DEVICE_NAME}" \
170+
-clonedSourcePackagesDirPath "$SPM_DIR" \
171+
-skipPackagePluginValidation \
172+
-skipMacroValidation \
173+
-showBuildTimingSummary \
174+
build \
175+
| tee build.log
176+
echo "== xcodebuild finished =="
177+
XC_STATUS=${PIPESTATUS[0]}
178+
set -e
179+
180+
exit $XC_STATUS
181+
182+
- name: Comment build failure on PR
183+
if: failure() && github.event.pull_request.head.repo.fork == false
184+
uses: actions/github-script@v7
185+
with:
186+
script: |
187+
const fs = require('fs');
188+
const path = 'build.log';
189+
let body = '❌ iOS CI build failed.\n\n';
190+
if (fs.existsSync(path)) {
191+
const log = fs.readFileSync(path, 'utf8');
192+
const lines = log.split(/\r?\n/);
193+
const errorLines = lines.filter((line) => /^(.*?):(\d+):(\d+):\s+error:/i.test(line));
194+
if (errorLines.length > 0) {
195+
body += "Compiler error lines:\n\n```\n" + errorLines.join('\n') + '\n```\n';
196+
197+
const repoRoot = process.env.GITHUB_WORKSPACE || process.cwd();
198+
const pathMod = require('path');
199+
const snippets = [];
200+
for (const line of errorLines) {
201+
const match = line.match(/^(.*?):(\d+):(\d+):\s+error:/);
202+
if (!match) continue;
203+
const filePath = match[1];
204+
const lineNum = parseInt(match[2], 10);
205+
const absPath = filePath.startsWith('/') ? filePath : pathMod.join(repoRoot, filePath);
206+
if (!fs.existsSync(absPath)) continue;
207+
const fileLines = fs.readFileSync(absPath, 'utf8').split(/\r?\n/);
208+
const start = Math.max(0, lineNum - 3);
209+
const end = Math.min(fileLines.length, lineNum + 2);
210+
const snippet = fileLines
211+
.slice(start, end)
212+
.map((l, idx) => {
213+
const ln = start + idx + 1;
214+
return `${ln.toString().padStart(4, ' ')}| ${l}`;
215+
})
216+
.join('\n');
217+
snippets.push(`File: ${filePath}:${lineNum}\n${snippet}`);
218+
}
219+
if (snippets.length > 0) {
220+
body += "\nCode excerpts:\n\n```\n" + snippets.join('\n\n') + "\n```\n";
221+
}
222+
} else {
223+
body += "No compiler-style error diagnostics were found in build.log.";
224+
}
225+
} else {
226+
body += 'build.log not found.';
227+
}
228+
if (!context.payload.pull_request) {
229+
core.info('No PR context; skipping comment.');
230+
return;
231+
}
232+
await github.rest.issues.createComment({
233+
owner: context.repo.owner,
234+
repo: context.repo.repo,
235+
issue_number: context.payload.pull_request.number,
236+
body
237+
});

.github/workflows/release.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: iOS Release
2+
3+
on:
4+
pull_request:
5+
types:
6+
- closed
7+
branches:
8+
- main
9+
10+
permissions:
11+
contents: write
12+
13+
jobs:
14+
release:
15+
if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main' && github.event.pull_request.head.ref == 'develop'
16+
runs-on: macos-latest
17+
timeout-minutes: 45
18+
19+
steps:
20+
- name: Checkout merge commit
21+
uses: actions/checkout@v5
22+
with:
23+
ref: ${{ github.event.pull_request.merge_commit_sha }}
24+
25+
- name: Read release version
26+
id: release_version
27+
run: |
28+
version=$(ruby -e 'project = File.read("DevLog.xcodeproj/project.pbxproj"); match = project.match(/MARKETING_VERSION = ([^;]+);/); abort("MARKETING_VERSION not found") if match.nil?; puts match[1]')
29+
echo "version=$version" >> "$GITHUB_OUTPUT"
30+
echo "tag=v$version" >> "$GITHUB_OUTPUT"
31+
32+
- name: Create GitHub Release
33+
env:
34+
GH_TOKEN: ${{ github.token }}
35+
run: |
36+
if gh release view "${{ steps.release_version.outputs.tag }}" >/dev/null 2>&1; then
37+
echo "Release already exists for ${{ steps.release_version.outputs.tag }}"
38+
exit 0
39+
fi
40+
41+
gh release create "${{ steps.release_version.outputs.tag }}" \
42+
--target "${{ github.event.pull_request.merge_commit_sha }}" \
43+
--title "${{ steps.release_version.outputs.tag }}" \
44+
--generate-notes

0 commit comments

Comments
 (0)