[Git] RSS ๊ธ ๋ฐํ ์(ํฐ์คํ ๋ฆฌ ๋ฑ) GitHub ์๋ ์ปค๋ฐ ๋ฐฉ๋ฒ
GitHub์์ ์ฌ์ฉ์ ์ด๋ฆ(username)๊ณผ ๋์ผํ ์ด๋ฆ์ผ๋ก public ๋ ํฌ์งํ ๋ฆฌ๋ฅผ ์์ฑํ๊ณ RRADME.md ํ์ผ์ ์ถ๊ฐํ๋ฉด ํ๋กํ ํ์ด์ง ์๋จ์ README ๋ด์ฉ์ด ํ์๋๋ค. ํ๋กํ ํ์ด์ง์ ์ฃผ๋ก ์์ ์ด ์๊ฐํ๊ณ ์ถ์ ํ๋ก์ ํธ, ๊ธฐ์ ์คํ ๋ฑ์ ๊ธฐ์ฌํ๋ค. ์นดํ ๊ณ ๋ฆฌ๋ณ๋ก ์ฃผ๋ชฉํ ๋งํ ํ๋กํ ํ์ด์ง๋ฅผ ๋ชจ์๋ ์ฌ์ดํธ๋ ์๋ค.
๋๋ถ๋ถ์ ๊ฐ๋ฐ์๋ค์ด ๋ธ๋ก๊ทธ๋ฅผ ์ด์ํ๊ณ ์์ด์ ๊ทธ๋ฐ์ง Latest Blog Posts ํค๋์ ์ต๊ทผ ๊ธ ๋ชฉ๋ก์ ๋ณด์ฌ์ฃผ๋ ํ๋กํ์ด ๋ง๋ค. ์ด ๊ธฐ๋ฅ์ rss-parser ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด RSS ํผ๋ ๋ชฉ๋ก์ ๋ถ๋ฌ์ค๊ณ README.md์ ๋ฐ์ํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํ๋๋๋ฐ, ์ด ์์ ์ GitHub Actions๋ฅผ ํตํด ์ ๊ธฐ์ ์ผ๋ก ์ํํ๋๋ก ๋ง๋ค ์ ์๋ค.
RSS ์ค์
๋จผ์ ์์ ์ ๋ธ๋ก๊ทธ ํน์ ์น์ฌ์ดํธ์ RSS ์ค์ ์ ํด์ผ ํ๋ค. RSS๋ ์น์ฌ์ดํธ์ ์ฌ๋ผ์จ ์ ๋ณด๋ฅผ ๊ฐํธํ๊ฒ ์ ๊ณตํ๊ธฐ ์ํ XML ๊ธฐ๋ฐ์ ํฌ๋งท์ด๋ค. ์ฌ์ฉ์๊ฐ ์ง์ ์น์ฌ์ดํธ๋ฅผ ๋ฐฉ๋ฌธํ์ง ์์๋ ์ต์ ์ฝํ ์ธ ๋ฅผ ์๋์ผ๋ก ๋ฐ์ ์ ์๋ ์ฅ์ ์ด ์๋ค.
๋๋ถ๋ถ์ ์ฝํ
์ธ ์๋น์ค๋ RSS๋ฅผ ์ง์ํ๋ค. ํฐ์คํ ๋ฆฌ ๋ธ๋ก๊ทธ๋ ๊ด๋ฆฌ → ๋ธ๋ก๊ทธ → ๊ธฐํ ์ค์ ์์ RSS ๊ธฐ๋ฅ์ ์ผค ์ ์๋ค. RSS ํผ๋๋ ์ผ๋ฐ์ ์ผ๋ก ๋๋ฉ์ธ ์ฃผ์ ๋ค์ /rss
๋ฅผ ๋ถ์ด๋ ๋ฐฉ์์ผ๋ก ์ ๊ทผํ ์ ์๋ค.
ํ์ฑ ์คํฌ๋ฆฝํธ
๐ก ์๋ ๊ฐ์ด๋์์ pnpm ํจํค์ง ๋งค๋์ ๋ฅผ ์ฌ์ฉํ๋ค.
ํ๋ก์ ํธ ์ด๊ธฐํ
GitHub ํ๋กํ ๋ ํฌ์งํ ๋ฆฌ๋ฅผ ํด๋ก ํ ํ ์๋ ๋ช ๋ น์ด๋ก ํ๋ก์ ํธ๋ฅผ ์ด๊ธฐํํ๋ค.
pnpm init -y
๋ช
๋ น์ด๋ฅผ ์คํํ๋ฉด package.json ํ์ผ์ด ์์ฑ๋๋ค. Node.js๋ ๊ธฐ๋ณธ์ ์ผ๋ก CommonJS(require ๊ตฌ๋ฌธ)์ด๋ฏ๋ก ์ต์ํ import ๊ตฌ๋ฌธ์ ์ฌ์ฉํ ์ ์๋๋ก "type": "module"
์ ์ถ๊ฐํ๋ค. ์คํฌ๋ฆฝํธ ์คํ์ ์ํด scripts
์์ฑ์ ๋ช
๋ น์ด๋ ๋ฑ๋กํด๋๋ค.
// package.json
{
name: "username",
version: "1.0.0",
description: "",
main: "index.js",
type: "module", // ์ถ๊ฐ
scripts: {
test: 'echo "Error: no test specified" && exit 1',
"rss-to-readme": "node rss-to-readme.js", // ์ถ๊ฐ
},
keywords: [],
author: "",
license: "ISC",
};
RSS ํผ๋ ๋ชฉ๋ก์ ๋ถ๋ฌ์ค๊ณ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด๋ก ๋ณํํ๊ธฐ ์ํด rss-parser ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํ๋ค.
pnpm add rss-parser
์คํฌ๋ฆฝํธ ์์ฑ
rss-to-readme.js ํ์ผ์ ์๋ ๋ด์ฉ์ ๋ถ์ฌ๋ฃ๊ณ TODO ์ฃผ์ ๋ถ๋ถ์ ์์ ํ๋ค. ๊ธฐ๋ณธ๊ฐ์ ์ต๊ทผ 5๊ฐ ๊ธ์ ๋ถ๋ฌ์์ Latest Blog Posts ํค๋(๋ ๋ฒจ2) ์๋ ๋ถ๋ฆฟ ๋ชฉ๋ก์ผ๋ก ์ถ๊ฐํ๋ค. ๋ชฉ๋ก์ ํ๋กํ ํ์ด์ง ๊ฐ์ฅ ํ๋จ์ ์์นํ๋ค.
// rss-to-readme.js
import { readFileSync, writeFileSync } from 'node:fs';
import Parser from 'rss-parser';
// Constants
const README_PATH = './README.md';
const RSS_URL = 'https://romantech.net/rss'; // TODO - ๋ธ๋ก๊ทธ RSS ์ฃผ์
const RSS_HEADERS = { Accept: 'application/rss+xml, application/xml, text/xml; q=0.1' };
const MAX_POSTS = 5; // TODO - ํ์ํ ํฌ์คํ
๊ฐ์
const HEADER_PREFIX = '##'; // TODO - ํค๋ ๋ ๋ฒจ
const POSTS_HEADER = `${HEADER_PREFIX} Latest Blog Posts`; // TODO - ํฌ์คํ
๋ชฉ๋ก ํค๋ ์ด๋ฆ
const POSTS_REGEX = new RegExp(`${POSTS_HEADER}[\\s\\S]*?(?=\\n${HEADER_PREFIX}|\\n$)`);
const parser = new Parser({ headers: RSS_HEADERS });
// RSS ํผ๋ ๋ชฉ๋ก fetching
const getRSSFeed = async (url) => await parser.parseURL(url);
// URL์ด 'http://'๋ก ์์ํ๋ฉด 'https://'๋ก ๋ณํ
const convertToHTTPS = (url) => {
if (url.startsWith('http://')) return url.replace('http://', 'https://');
return url;
};
// RSS ํผ๋ ๋ชฉ๋ก์ ๋งํฌ๋ค์ด ํ์์ผ๋ก ๋ณํ
const createPostsMarkdown = (items, count) => {
const posts = items.slice(0, count).map(({ title, link }) => {
return `- [${title}](${convertToHTTPS(link)})`;
});
return [POSTS_HEADER, ...posts].join('\n');
};
// README.md ํ์ผ ์
๋ฐ์ดํธ
const updateReadme = (content, newPosts) => {
const hasPosts = content.includes(POSTS_HEADER);
if (hasPosts) return content.replace(POSTS_REGEX, newPosts + '\n');
return content + '\n' + newPosts;
};
// ์ 3๊ฐ์ง ์์
์ ๋ชจ๋ ์ํํ๋ ํจ์
const refreshReadme = async () => {
const readme = readFileSync(README_PATH, 'utf8');
const feed = await getRSSFeed(RSS_URL);
const newPosts = createPostsMarkdown(feed.items, MAX_POSTS);
const updatedReadme = updateReadme(readme, newPosts);
if (updatedReadme !== readme) {
writeFileSync(README_PATH, updatedReadme, 'utf8');
console.log('README.md updated successfully');
} else {
console.log('No new blog posts. README.md was not updated.');
}
};
try {
await refreshReadme();
} catch (error) {
console.error('Error during the refresh process:', error);
process.exit(1);
} finally {
process.exit(0);
}
ํ์ผ ์์ ์ ๋ชจ๋ ๋ง์น๊ณ ํฐ๋ฏธ๋์ pnpm rss-to-readme
๋ช
๋ น์ด๋ฅผ ์
๋ ฅํด๋ณด์. README.md ํ์ผ์ ๊ฐ์ฅ ์ต๊ทผ ์์ฑํ ๊ธ 5๊ฐ๊ฐ ์ถ๊ฐ๋๋ค๋ฉด ์ฑ๊ณต.
GitHub Actions ์ํฌํ๋ก์ฐ
GitHub Actions๋ ์ํํธ์จ์ด ๊ฐ๋ฐ ํ๋ก์ ํธ์ ๋น๋, ํ ์คํธ, ๋ฐฐํฌ ๋ฑ์ ์๋ํ ํ๊ธฐ ์ํ CI/CD ์๋น์ค๋ค. GitHub Actions๋ฅผ ์ด์ฉํด ์ฃผ๊ธฐ์ ์ผ๋ก rss-to-readme.js ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๊ณ README.md ๋ณ๊ฒฝ์ฌํญ์ ์๋์ผ๋ก ์ปค๋ฐํ ์ ์๋ค.
GitHub Actions ์ํฌํ๋ก์ฐ๋ yml ํ์ผ์ ํตํด์ ์ค์ ํ ์ ์์ผ๋ฉฐ, yml ํ์ผ์ ํญ์ workflows ํด๋์ ์์นํด์ผ ํ๋ค. ๋จผ์ .github/workflows
ํด๋์ rss-to-readme.yml ํ์ผ์ ์์ฑํ๋ค.
๐ฆ GitHub Profile Repository
โโ .github
โโ workflows
โโ rss-to-readme.yml
์๋ ๋ด์ฉ์ ๋ถ์ฌ ๋ฃ์ ํ your@email.com
๋ถ๋ถ์ GitHub์ ๋ฑ๋กํ ๋ฉ์ผ ์ฃผ์๋ก ๋ณ๊ฒฝํ๋ค. ๊ธฐ๋ณธ๊ฐ์ ๋งค ์๊ฐ ์ ๊ฐ๋ง๋ค ์ํฌํ๋ก์ฐ๋ฅผ ์คํํ๋๋ก ์์ฑ๋ผ ์๋ค. ์ํ๋ค๋ฉด schedule: cron:
์์ฑ์ ํตํด ๋ณ๊ฒฝํ ์ ์๋ค. ์ฐธ๊ณ ๋ก GitHub Actions๋ UTC ์๊ฐ ๊ธฐ์ค์ด๋ค. ํน์ ํ๊ตญ ์๊ฐ์ผ๋ก ์ค์ ํ๋ ค๋ฉด +9 ์๊ฐ์ ์ถ๊ฐํด์ผ ํ๋ค.
# rss-to-readme.yml
name: RSS to README
on:
schedule:
- cron: "0 */1 * * *" # ๋งค ์๊ฐ ์ ๊ฐ์ ์คํ (UTC)
push:
branches:
- main # ์ง์ ํ ๋ธ๋์น์ push ํ ๋ ์ํฌํ๋ก์ฐ ์คํ
workflow_dispatch: # ์๋ ํธ๋ฆฌ๊ฑฐ ๊ธฐ๋ฅ ํ์ฑ
jobs:
update-readme:
runs-on: ubuntu-latest
timeout-minutes: 30 # ์ํฌํ๋ก์ฐ๊ฐ 30๋ถ ๋ด์ ์๋ฃ๋์ง ์์ผ๋ฉด ์๋ ์ข
๋ฃ
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
- uses: pnpm/action-setup@v4
name: Install pnpm and dependencies
with:
version: latest
run_install: true
- name: Update README
run: pnpm rss-to-readme # ์
๋ฐ์ดํธ ์คํฌ๋ฆฝํธ ์คํ
- name: Configure Git
run: |
git config --local user.name 'GitHub Action Bot'
git config --local user.email 'your@email.com' # ์์
- name: Commit and push if changed
run: |
# Git ์ ์ฅ์์ ๋ณ๊ฒฝ์ฌํญ์ด ์์ ๋๋ง ๋ช
๋ น์ด ์คํ
if [ -n "$(git status --porcelain)" ]; then
git add README.md
git commit -m "Update README with latest articles [auto-generated]" -m "Generated at: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
git push
else
echo "No changes to commit"
fi
์ ๋ณ๊ฒฝ ๋ด์ฉ์ ์ปค๋ฐํ๊ณ main
๋ธ๋์น๋ก push ํด๋ณด์. push: branches:
์์ฑ์ main
์ผ๋ก ์ค์ ํ๊ธฐ ๋๋ฌธ์ push ์ด๋ ฅ์ ๊ฐ์งํด์ ์ํฌํ๋ก์ฐ๊ฐ ์๋์ผ๋ก ์คํ๋๋ค. ์์ธํ ์คํ ๊ณผ์ ์ Actions ํญ์์ ํ์ธํ ์ ์๋ค.
์ํฌํ๋ก์ฐ๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ์ํํ๋ฉด README.md ํ์ผ์ ์์ ํ ์ปค๋ฐ์ด ์ถ๊ฐ๋๊ฑธ ํ์ธํ ์ ์๋ค.
GPG Signatures
GPG Signatures๋ Git ์๋ช ๊ธฐ๋ฅ ์ค ํ๋๋ก ์ปค๋ฐ์ ๋ฌด๊ฒฐ์ฑ๊ณผ ์ ๋ขฐ์ฑ์ ๋ณด์ฅํ๋ ๊ธฐ์ ์ด๋ค. ์ด๋ฅผ ํตํด ์ปค๋ฐ์ด ์ ๋ขฐํ ์ ์๋ ์ถ์ฒ์์ ์์ฑ๋์์์ ํ์ธํ ์ ์๋ค. ์ปค๋ฐ ๋ชฉ๋ก์ ์ด๋ก์ Verified ๋ฐฐ์ง๊ฐ ๋ถ์ผ๋ฉด ์ปค๋ฐ์ด ์ ํจํ GPG ํค๋ก ์๋ช ๋์์์ ์๋ฏธํ๋ค.
GitHub Actions๋ฅผ ํตํ ์ปค๋ฐ์ ๊ธฐ๋ณธ์ ์ผ๋ก GPG ์๋ช ์ด ์ ์ฉ๋์ด ์์ง ์๊ธฐ ๋๋ฌธ์ ๋ณ๋ ์ค์ ์ด ํ์ํ๋ค.
GPG Key ์์ฑ
โถ GPG ํจํค์ง ์ค์น (macOS ๊ธฐ์ค)
brew install gpg
โท GPG ํค ์์ฑ. ๋ช ๋ น์ด๋ฅผ ์คํํ๋ฉด ์ต์ ์ ํ ์ง๋ฌธ๋ค์ด ๋์ค๋๋ฐ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ค์ ํด๋ ๋ฌด๋ฐฉํ๋ค. Real name์ GitHub ์ฌ์ฉ์ ์ด๋ฆ(username), Email address๋ GitHub์ ๋ฑ๋กํ ์ด๋ฉ์ผ๊ณผ ๋์ผํ๊ฒ ์ ๋ ฅ ํ๋ค.
gpg --full-generate-key
# ===== ํค ์ํธํ ๋ฐฉ์ ์ง๋ฌธ =====
# Please select what kind of key you want: (9) ECC (sign and encrypt)
# Please select which elliptic curve you want: (1) Curve 25519
# Please specify how long the key should be valid: 0 = key does not expire
# ===== ์ฌ์ฉ์ ์ ๋ณด ์ง๋ฌธ =====
# Real name: username
# Email address: your@email.com
# Comment: ์๋ต๊ฐ๋ฅ
๋ชจ๋ ์ ๋ณด๋ฅผ ์ ๋ ฅํ๋ฉด Passphrase(๋น๋ฐ๋ฒํธ)๋ฅผ ์ ๋ ฅํ๋ ํ์ ์ด ๋์จ๋ค. ์๋ตํ ์ ์์ง๋ง ๋ณด์์ ์ํด ์ค์ ํด ๋๋ ๊ฑธ ์ถ์ฒํ๋ค. ์ค์ ํ Passphrase๋ ์๋ช ์ ํฌํจํ ์ปค๋ฐ์ ์งํํ ๋ ํ์ํ๋ฏ๋ก ์ ๊ธฐ์ตํด ๋๋ค.
โธ GPG ํค๊ฐ ์ ์์ฑ๋๋์ง ํ์ธ (GPG ํค ๋ชฉ๋ก ์ถ๋ ฅ)
gpg --list-keys
# pub ed25519 2024-07-01 [SC]
# GPG ID
# uid [ultimate] ์ด๋ฆ <์ด๋ฉ์ผ>
# sub cv25519 2024-07-01 [E]
GPG ๊ณต๊ฐํค ๋ฑ๋ก
์๋ ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํด์ GPG ๊ณต๊ฐํค๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌํ๋ค.
# your@email.com ๋ถ๋ถ์ ๋ณธ์ธ ์ด๋ฉ์ผ ์ฃผ์๋ก ๋ณ๊ฒฝ
gpg --armor --export your@email.com | pbcopy
GitHub Settings - SSH and GPG keys ํ์ด์ง์์ New GPG key ๋ฒํผ์ ํด๋ฆญํ๋ค. Title์ ์๋ณํ๊ธฐ ์ฌ์ด ๊ฐ์ผ๋ก ์ ๋ ฅํ๊ณ , Key ํญ๋ชฉ์ ๋ณต์ฌํ ๊ณต๊ฐํค๋ฅผ ๋ถ์ฌ๋ฃ๋๋ค.
Secrets ๋ฑ๋ก
์ด์ PG_PRIVATE_KEY
, PASSPHRASE
๋ฅผ ๋ ํฌ์งํ ๋ฆฌ ์ํฌ๋ฆฟ์ผ๋ก ๋ฑ๋กํด์ผ ํ๋ค. ํฐ๋ฏธ๋์ ์๋ ๋ช
๋ น์ด๋ฅผ ์คํํด์ GPG ๊ฐ์ธํค๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌํด๋๋ค.
# your@email.com ๋ถ๋ถ์ ๋ณธ์ธ ์ด๋ฉ์ผ ์ฃผ์๋ก ๋ณ๊ฒฝ
gpg --armor --export-secret-key your@email.com | pbcopy
GitHub ํ๋กํ ๋ ํฌ์งํ ๋ฆฌ - Settings - Secrets and variables - Actions๋ก ์ด๋ํ ํ PG_PRIVATE_KEY
์ PASSPHRASE
์ด๋ฆ์ ์ํฌ๋ฆฟ์ ์ถ๊ฐํ๋ค. PG_PRIVATE_KEY
์ํฌ๋ฆฟ์ ๋ฐฉ๊ธ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌํ ๋ด์ฉ์ ๋ถ์ฌ๋ฃ์ผ๋ฉด ๋๊ณ , PASSPHRASE
๋ GPG Key ์์ฑ์ ์ค์ ํ๋ ๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํ๋ฉด ๋๋ค.
์์ฒ๋ผ ์ํฌ๋ฆฟ์ ๋ฑ๋กํด๋๋ฉด ์ํฌํ๋ก์ฐ ํ์ผ์์ ${{ secrets.GPG_PRIVATE_KEY }}
๊ตฌ๋ฌธ์ผ๋ก GPG ๊ฐ์ธํค๋ฅผ ์ฐธ์กฐํ ์ ์๊ฒ ๋๋ค.
์ํฌํ๋ก์ฐ ์์
ghaction-import-gpg ์ก์
์ ์ฌ์ฉํ๋ฉด GPG Key๋ฅผ ๋ ์ฝ๊ฒ ๋ถ๋ฌ์ฌ ์ ์๋ค. YML ํ์ผ์ ์๋์ฒ๋ผ ์์ ํ์. your@email.com ๋ถ๋ถ์ ๋ณธ์ธ ์ด๋ฉ์ผ ์ฃผ์๋ก ๋ณ๊ฒฝํด์ค๋ค. git commit ๋ช
๋ น์ด ๋ผ์ธ์ ๋ณด๋ฉด -S
ํ๋๊ทธ๊ฐ ์ถ๊ฐ๋๋๋ฐ, ์ปค๋ฐ์ ์์ฑํ ๋ GPG ํค๋ก ์๋ช
ํ๋ ์ต์
์ด๋ค.
# rss-to-readme.yml
name: RSS to README
on:
schedule:
- cron: "0 */1 * * *" # ๋งค ์๊ฐ ์ ๊ฐ์ ์คํ (UTC)
push:
branches:
- main # ์ง์ ํ ๋ธ๋์น์ push ํ ๋ ์ํฌํ๋ก์ฐ ์คํ
workflow_dispatch: # ์๋ ํธ๋ฆฌ๊ฑฐ ๊ธฐ๋ฅ ํ์ฑ
jobs:
update-readme:
runs-on: ubuntu-latest
timeout-minutes: 30 # ์ํฌํ๋ก์ฐ๊ฐ 30๋ถ ๋ด์ ์๋ฃ๋์ง ์์ผ๋ฉด ์๋ ์ข
๋ฃ
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PASSPHRASE }}
git_user_signingkey: true
git_commit_gpgsign: true
git_committer_name: GitHub Action Bot
git_committer_email: your@email.com # ์์
trust_level: 5 # Ultimate
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
- uses: pnpm/action-setup@v4
name: Install pnpm and dependencies
with:
version: latest
run_install: true
- name: Update README
run: pnpm rss-to-readme # ์
๋ฐ์ดํธ ์คํฌ๋ฆฝํธ ์คํ
- name: Commit and push if changed
run: |
# Git ์ ์ฅ์์ ๋ณ๊ฒฝ์ฌํญ์ด ์์ ๋๋ง ๋ช
๋ น์ด ์คํ
if [ -n "$(git status --porcelain)" ]; then
git add README.md
git commit -S -m "Update README with latest articles [auto-generated]" -m "Generated at: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
git push
else
echo "No changes to commit"
fi
์ํฌํ๋ก์ฐ ๋ณ๊ฒฝ๋ด์ฉ์ ํ๋กํ ๋ ํฌ์งํ ๋ฆฌ์ ๋ฐ์ํ๊ณ GitHub Actions๋ฅผ ํตํด ์์ฑ๋ ์ปค๋ฐ์ ํ์ธํด ๋ณด๋ฉด Verified ๋ฐฐ์ง๊ฐ ์ถ๊ฐ๋๊ฑธ ํ์ธํ ์ ์๋ค.
๊ธ ์์ ์ฌํญ์ ๋ ธ์ ํ์ด์ง์ ๊ฐ์ฅ ๋น ๋ฅด๊ฒ ๋ฐ์๋ฉ๋๋ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์
'๐ช Programming' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๋๊ธ
์ด ๊ธ ๊ณต์ ํ๊ธฐ
-
๊ตฌ๋
ํ๊ธฐ
๊ตฌ๋ ํ๊ธฐ
-
์นด์นด์คํก
์นด์นด์คํก
-
๋ผ์ธ
๋ผ์ธ
-
ํธ์ํฐ
ํธ์ํฐ
-
Facebook
Facebook
-
์นด์นด์ค์คํ ๋ฆฌ
์นด์นด์ค์คํ ๋ฆฌ
-
๋ฐด๋
๋ฐด๋
-
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
-
Pocket
Pocket
-
Evernote
Evernote
๋ค๋ฅธ ๊ธ
-
[JS] ์๋ฐ์คํฌ๋ฆฝํธ reduce() ๋ฉ์๋ ํ์ฉ ์์ ๋ชจ์
[JS] ์๋ฐ์คํฌ๋ฆฝํธ reduce() ๋ฉ์๋ ํ์ฉ ์์ ๋ชจ์
2024.07.07 -
[JS] ์๋ฐ์คํฌ๋ฆฝํธ Set ๊ฐ์ฒด์ ์งํฉ ์ฐ์ฐ ๋ฉ์๋ (๊ต์งํฉ, ํฉ์งํฉ ๋ฑ)
[JS] ์๋ฐ์คํฌ๋ฆฝํธ Set ๊ฐ์ฒด์ ์งํฉ ์ฐ์ฐ ๋ฉ์๋ (๊ต์งํฉ, ํฉ์งํฉ ๋ฑ)
2024.07.04 -
[DevTools] ESLint 9 Flat Config + Prettier ์ค์ (TypeScript, React)
[DevTools] ESLint 9 Flat Config + Prettier ์ค์ (TypeScript, React)
2024.06.30 -
[DevTools] nvm๋ณด๋ค 40๋ฐฐ ๋น ๋ฅธ ๋ ธ๋ ๋ฒ์ ๊ด๋ฆฌ ๋๊ตฌ — fnm
[DevTools] nvm๋ณด๋ค 40๋ฐฐ ๋น ๋ฅธ ๋ ธ๋ ๋ฒ์ ๊ด๋ฆฌ ๋๊ตฌ — fnm
2024.06.18