Skip to content
113 changes: 113 additions & 0 deletions .github/workflows/auto-bump-version.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: Auto Bump Version on Merge to main

on:
pull_request:
types: [closed]

permissions:
contents: write

jobs:
bump-version:
if: >
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'main' &&
github.actor != 'github-actions[bot]'
runs-on: ubuntu-latest
steps:
- name: Checkout main
uses: actions/checkout@v4
with:
ref: main

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Detect if package.json version changed in PR
id: ver_changed
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request.number;
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr,
per_page: 100
});
const touched = files.some(f => f.filename === 'package.json' && /"version"\s*:/.test(f.patch || ''));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

버전 변경 감지 로직이 키 존재 여부만 확인 - 실제 값 변경 미감지

현재 정규식 /"version"\s*:/.test(f.patch || '')"version" 키가 있는지만 확인하고, 실제로 버전 이 변경되었는지는 검증하지 않습니다. JSON 포매팅 변경이나 다른 필드 수정으로도 거짓 양성이 발생할 수 있습니다.

patch 컨텐츠에서 이전/새 버전값을 직접 추출하여 비교하도록 개선하세요:

const versionMatch = (f.patch || '').match(/^-.*"version"\s*:\s*"([^"]+)"/m);
const newVersionMatch = (f.patch || '').match(/^\+.*"version"\s*:\s*"([^"]+)"/m);
const touched = versionMatch && newVersionMatch && versionMatch[1] !== newVersionMatch[1];

🏁 Script executed:

#!/bin/bash
# Examine the workflow file to verify the version detection logic
cat .github/workflows/auto-bump-version.yaml | head -60

Length of output: 1827


🏁 Script executed:

#!/bin/bash
# Search for context around the version detection logic
rg -n "touched.*files.some" -A 5 -B 5 .github/workflows/auto-bump-version.yaml

Length of output: 479


🏁 Script executed:

#!/bin/bash
# Look for any other version detection or comparison logic in the workflow
rg -n "version" -i .github/workflows/auto-bump-version.yaml | head -20

Length of output: 769


라인 42의 버전 변경 감지 로직이 키 존재 여부만 확인 - 실제 값 변경 미감지

현재 정규식 /"version"\s*:/.test(f.patch || '')"version" 키가 diff의 어디든 나타나면 참을 반환합니다. GitHub API의 patch 형식은 변경사항(-, +)뿐만 아니라 컨텍스트 라인(변경 없는 라인)도 포함하므로, 버전 필드가 diff 컨텍스트에 나타나기만 해도 거짓 양성이 발생합니다.

실제 버전 값 변경을 감지하도록 개선하세요:

const versionMatch = (f.patch || '').match(/^-.*"version"\s*:\s*"([^"]+)"/m);
const newVersionMatch = (f.patch || '').match(/^\+.*"version"\s*:\s*"([^"]+)"/m);
const touched = versionMatch && newVersionMatch && versionMatch[1] !== newVersionMatch[1];
🤖 Prompt for AI Agents
.github/workflows/auto-bump-version.yaml lines 42: the current check only tests
for the presence of a "version" key in the patch and can produce false positives
because GitHub patch includes context lines; update the logic to extract the old
and new version strings from lines that start with '-' and '+' respectively
(using regex anchored to those prefixes), then set touched to true only when
both matches exist and the extracted versions differ, and ensure you safely
handle empty or undefined f.patch before matching.

core.setOutput('changed', touched ? 'true' : 'false');
Comment on lines +30 to +43
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Line 41: 버전 변경 감지 로직 개선 - 정확성 고려

현재 정규식 패턴 /"version"\s*:/.test(f.patch || '')"version" 키의 존재 여부만 확인하며, 실제 버전 이 변경되었는지는 검증하지 않습니다. JSON 포매팅 변경이나 다른 필드 수정으로도 거짓 양성(false positive)이 발생할 수 있습니다.

patch 컨텐츠에서 이전 버전과 새 버전의 실제 semver 문자열을 추출하여 비교하면 더 정확합니다:

const versionMatch = (f.patch || '').match(/^-.*"version"\s*:\s*"([^"]+)"[\s,]/m);
const newVersionMatch = (f.patch || '').match(/^\+.*"version"\s*:\s*"([^"]+)"[\s,]/m);
const touched = versionMatch && newVersionMatch && versionMatch[1] !== newVersionMatch[1];
🤖 Prompt for AI Agents
In .github/workflows/auto-bump-version.yaml around lines 29 to 42, the current
regex only detects the "version" key and can yield false positives; instead
parse the patch to extract the old and new version values and compare them.
Update the script to search the patch for a line removed (starts with '-')
containing "version": "oldVersion" and a line added (starts with '+') containing
"version": "newVersion", ensure both matches are found and non-null, compare
oldVersion !== newVersion, and set the 'changed' output to 'true' only if they
differ; include safe null checks to avoid runtime errors.


- name: Bump patch version (+0.0.1) if not changed in PR
if: steps.ver_changed.outputs.changed != 'true'
run: |
node -e "
const fs = require('fs');
const pr_title = context.payload.pull_request.title;
const pkg = JSON.parse(fs.readFileSync('package.json','utf8'));
const [a,b,c] = pkg.version.split('.').map(Number);
let new_version;

if (pr_title.toLowerCase().startsWith('feat')) {
new_version = [a,b+1,0].join('.');
} else {
new_version = [a,b,c+1].join('.');
}

pkg.version = new_version;
fs.writeFileSync('package.json', JSON.stringify(pkg,null,2)+'\n');
console.log('Bumped to', pkg.version);
"

- name: Sync UI exports to dist/shared/index.d.ts
run: |
node -e "
const fs = require('fs');
const path = require('path');
const posix = path.posix;
const root = 'dist/shared';
const indexPath = posix.join(root, 'index.d.ts');
const uiRoot = posix.join(root, 'ui');
if (!fs.existsSync(uiRoot)) { console.log('No dist/shared/ui found. Skip.'); process.exit(0); }
const indexContent = fs.existsSync(indexPath) ? fs.readFileSync(indexPath,'utf8') : '';
const findIndexFiles = (dir) => {
return fs.readdirSync(dir, { withFileTypes: true }).flatMap(d => {
const p = posix.join(dir, d.name);
if (d.isDirectory()) return findIndexFiles(p);
return d.name === 'index.d.ts' ? [p] : [];
});
};
const uiFiles = findIndexFiles(uiRoot);
const adds = [];
for (const f of uiFiles) {
const content = fs.readFileSync(f, 'utf8');
for (const m of content.matchAll(/export\s+\{([^}]+)\}\s+from\s+'\.\/([^']+)'/g)) {
const names = m[1].trim();
const fromRel = m[2].trim();
const base = f.replace(/\/index\.d\.ts$/, '');
const line = \`export { \${names} } from '\${base.replace(root,'').replace(/^\//,'').length ? './' + base.slice(root.length+1) : '.'}/\${fromRel}';\`;
if (!indexContent.includes(names)) adds.push(line);
}
}
Comment on lines +112 to +139
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

export 감지 정규식 패턴이 제한적 - 오류 위험 존재

Lines 113-115의 정규식이 특정 형식만 감지하며, 예상치 못한 형식에서 .match()[1] 접근 시 오류가 발생할 수 있습니다:

(indexContent.match(/from\\s+['\\\"](\\.\\/|\\.\\.\\/)([^'\\\"]+)['\\\"]/g) || [])
  .map(line => line.match(/from\\s+['\\\"]([^'\\\"]+)['\\\"]/)[1])

매칭이 null인 경우를 안전하게 처리하도록 개선하세요:

             const existingExports = new Set(
-              (indexContent.match(/from\\s+['\\\"](\\.\\/|\\.\\.\\/)([^'\\\"]+)['\\\"]/g) || [])
-                .map(line => line.match(/from\\s+['\\\"]([^'\\\"]+)['\\\"]/)[1])
+              (indexContent.match(/from\s+['"]([^'"]+)['"]/g) || [])
+                .map(line => {
+                  const match = line.match(/from\s+['"]([^'"]+)['"]/);
+                  return match ? match[1] : null;
+                })
+                .filter(Boolean)
             );

또한 Line 136의 중복 검사 로직도 개선이 필요합니다. 현재 existingExports.has(exportPath) 는 정확하지만, 만약 이름 기반 검사를 한다면 includes(componentName) 같은 부분 매칭은 "Button"과 "PrimaryButton"을 구분하지 못할 수 있습니다.

🤖 Prompt for AI Agents
In .github/workflows/auto-bump-version.yaml around lines 112 to 139, the current
export-detection uses .match(...).map(...)[1] which can throw if a match is null
and only handles a narrow import/export pattern; also the duplicate check using
includes could produce false positives for similarly named components. Fix by
running the global regex, iterating matches with a loop or Array.from to extract
the captured group only after verifying the match is non-null (guard against
null before accessing [1]), and construct a Set of normalized export paths; then
change the duplicate check to test the exact exportPath (and/or exact
componentName) against that Set (avoid substring/includes checks). Ensure
normalization of paths (remove extensions, resolve './' prefixes) before
comparing.

if (adds.length) {
const next = (indexContent.trim() ? indexContent.trim()+'\n' : '') + adds.join('\n') + '\n';
fs.writeFileSync(indexPath, next);
console.log('Added exports:', adds);
} else {
console.log('No new exports found.');
}
"

- name: Commit & Push
run: |
if git diff --quiet; then
echo "No changes to commit."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add package.json dist/shared/index.d.ts || true
git commit -m "chore: bump version and sync UI exports [skip ci]"
git push