mirror of
https://github.com/tiennm99/iammiti99.git
synced 2026-10-11 12:18:59 +00:00
docs: add xia recon report and port plan
Captures the analysis behind the linkyee-inspired implementation: source anatomy, dependency matrix, decision matrix (stack, plugins, theme, icons, deploy), and four phase files used to drive the build.
This commit is contained in:
1 parent
a90cb8126a
commit
17cbbeacb2
8 files changed
+1086
No files matched your search
@@ -0,0 +1,146 @@
|
||||
# Phase 01 — Scaffold & config schema
|
||||
|
||||
## Context links
|
||||
|
||||
- [plan.md](plan.md)
|
||||
- [recon report](../reports/xia-260430-2135-linkyee-recon.md)
|
||||
- linkyee config reference: /tmp/linkyee-src/config.yml
|
||||
|
||||
## Overview
|
||||
|
||||
- **Priority:** P0 (gates everything)
|
||||
- **Status:** ✅ completed
|
||||
- **Description:** Establish project layout and config schema. Single source of truth for all page content.
|
||||
|
||||
## Key insights
|
||||
|
||||
- linkyee's nested `- link: { ... }` wrapper is verbose YAML; flatten to `- { ... }`.
|
||||
- Splitting `profile`, `links`, `socials` into one file keeps editing fast.
|
||||
- JSON would be zero-dep; YAML is friendlier for hand editing. **Pick YAML** + add `js-yaml` as the only npm dep.
|
||||
- `repo:` field on a link is the trigger that activates star + last-commit lookup.
|
||||
|
||||
## Requirements
|
||||
|
||||
**Functional:**
|
||||
- One YAML file, `config.yaml`, holds: profile, links[], socials[], meta (title, description, OG image), footer.
|
||||
- `links[]` items support `text`, `url`, `icon`, `target`, optional `repo` (`owner/name`) for live data.
|
||||
- `socials[]` items support `icon`, `url`, `title`.
|
||||
|
||||
**Non-functional:**
|
||||
- Schema is documented inline (comments) and in this phase.
|
||||
- Schema is small enough to keep in one file under 200 lines.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
iammiti99/
|
||||
├── config.yaml # ← single source of truth
|
||||
├── src/
|
||||
│ ├── template.html # raw HTML template (Phase 2)
|
||||
│ ├── styles.css # (Phase 2)
|
||||
│ └── scripts.js # (Phase 2; may stay empty)
|
||||
├── build.js # (Phase 3)
|
||||
├── package.json # (Phase 3)
|
||||
├── _site/ # build output (.gitignore)
|
||||
├── .github/workflows/
|
||||
│ └── deploy.yml # (Phase 4)
|
||||
├── README.md
|
||||
├── LICENSE # (existing) Apache-2.0
|
||||
└── NOTICE # credit linkyee inspiration
|
||||
```
|
||||
|
||||
## Related code files
|
||||
|
||||
**Create:**
|
||||
- `config.yaml`
|
||||
- `.gitignore` additions: `_site/`, `node_modules/`
|
||||
- `NOTICE`
|
||||
|
||||
**Modify:** none
|
||||
|
||||
**Delete:** none
|
||||
|
||||
## Config schema
|
||||
|
||||
```yaml
|
||||
# Site meta
|
||||
site:
|
||||
title: "Tien — links"
|
||||
description: "Tien Nguyen Minh — engineer / builder / writer"
|
||||
url: "https://iammiti99.github.io" # for OG tags
|
||||
lang: "en"
|
||||
theme_color_light: "#f7f7f7"
|
||||
theme_color_dark: "#1b1b1e"
|
||||
|
||||
# Profile
|
||||
profile:
|
||||
name: "@miti99"
|
||||
tagline: "Engineer building things on the web."
|
||||
avatar: "./images/avatar.jpg"
|
||||
|
||||
# Optional analytics (omit to disable)
|
||||
analytics:
|
||||
google_analytics_id: ""
|
||||
|
||||
# Primary link list (rendered as buttons in order)
|
||||
links:
|
||||
- text: "Tech blog"
|
||||
url: "https://blog.example.com"
|
||||
icon: "fa-solid fa-pen-nib"
|
||||
target: "_blank"
|
||||
- text: "iammiti99"
|
||||
url: "https://github.com/tiennm99/iammiti99"
|
||||
icon: "fa-brands fa-github"
|
||||
target: "_blank"
|
||||
repo: "tiennm99/iammiti99" # ← triggers stars + last-commit decoration
|
||||
|
||||
# Social icon row (small, below links)
|
||||
socials:
|
||||
- icon: "fa-brands fa-github"
|
||||
url: "https://github.com/tiennm99"
|
||||
title: "GitHub"
|
||||
- icon: "fa-solid fa-envelope"
|
||||
url: "mailto:minhtienit99@gmail.com"
|
||||
title: "Email"
|
||||
|
||||
# Footer
|
||||
footer:
|
||||
text: "Thanks for stopping by."
|
||||
copyright: "© 2026 Tien Nguyen Minh"
|
||||
```
|
||||
|
||||
## Implementation steps
|
||||
|
||||
1. Write `config.yaml` at repo root with the schema above; populate with placeholder values from `README.md` user.
|
||||
2. Append `_site/`, `node_modules/`, `.DS_Store` to `.gitignore`.
|
||||
3. Create `NOTICE` file with linkyee inspiration credit:
|
||||
```
|
||||
This project is inspired by linkyee (https://github.com/ZhgChgLi/linkyee, MIT).
|
||||
No code is copied; only the architectural idea (config-driven link page on GitHub Pages).
|
||||
```
|
||||
4. Create empty `src/`, `_site/` directories (add `.gitkeep` to `src/` if empty so it commits).
|
||||
|
||||
## Todo
|
||||
|
||||
- [ ] Write `config.yaml`
|
||||
- [ ] Update `.gitignore`
|
||||
- [ ] Add `NOTICE`
|
||||
- [ ] Create `src/` directory
|
||||
|
||||
## Success criteria
|
||||
|
||||
- `config.yaml` parses with `js-yaml` (verify with `node -e "console.log(require('js-yaml').load(require('fs').readFileSync('config.yaml','utf8')))"`)
|
||||
- `.gitignore` excludes build output
|
||||
|
||||
## Risks
|
||||
|
||||
- Over-engineering schema with fields we won't use. Mitigation: only add a field once Phase 2 needs it.
|
||||
|
||||
## Security
|
||||
|
||||
- `config.yaml` is public — never put secrets here.
|
||||
- `analytics.google_analytics_id` is non-secret (public client-side ID).
|
||||
|
||||
## Next
|
||||
|
||||
→ Phase 2: design HTML template and CSS.
|
||||
@@ -0,0 +1,123 @@
|
||||
# Phase 02 — Page design (HTML + CSS)
|
||||
|
||||
## Context links
|
||||
|
||||
- [plan.md](plan.md)
|
||||
- [phase-01-scaffold-and-config.md](phase-01-scaffold-and-config.md)
|
||||
- linkyee theme reference (do not copy): /tmp/linkyee-src/themes/default/
|
||||
|
||||
## Overview
|
||||
|
||||
- **Priority:** P0
|
||||
- **Status:** ✅ completed
|
||||
- **Description:** Fresh, minimalist HTML template + CSS. Mobile-first. Light + dark via `prefers-color-scheme`.
|
||||
|
||||
## Key insights
|
||||
|
||||
- Fresh design — Q3 ruled out reusing linkyee's CSS. **Do not import or copy linkyee files.**
|
||||
- Single page, no JS framework. Template uses `${var}` placeholders that the build script replaces.
|
||||
- FA via CDN (Q4) — pin a specific FA 6 version for cache stability.
|
||||
- linkyee's HTML had solid SEO/social meta — replicate the *categories* (og, twitter, theme-color, favicons) but write our own tags.
|
||||
|
||||
## Requirements
|
||||
|
||||
**Functional:**
|
||||
- Render: profile (avatar, name, tagline), link buttons in order, social icon row, footer.
|
||||
- Each link button shows icon + text; if from a `repo:`-flagged item, also shows ` ★ {stars} · updated {N}d ago`.
|
||||
- Light + dark mode via CSS media query, no JS toggle.
|
||||
- Page weight target: <30 KB HTML+CSS+JS, FA loaded from CDN.
|
||||
|
||||
**Non-functional:**
|
||||
- Lighthouse ≥95 (perf, a11y, best-practices, SEO).
|
||||
- No layout shift; proper `width`/`height` on `<img>`.
|
||||
|
||||
## Architecture
|
||||
|
||||
Template structure:
|
||||
|
||||
```
|
||||
<html>
|
||||
<head>
|
||||
meta (charset, viewport, description, og:*, twitter:*)
|
||||
favicons + theme-color
|
||||
FA CDN <link>
|
||||
inline <style> (or external styles.css — small enough to inline)
|
||||
</head>
|
||||
<body>
|
||||
<main class="card">
|
||||
<img class="avatar" src="${avatar}" />
|
||||
<h1>${name}</h1>
|
||||
<p class="tagline">${tagline}</p>
|
||||
|
||||
<ul class="links">
|
||||
${links_html} <!-- generated by build.js -->
|
||||
</ul>
|
||||
|
||||
<nav class="socials">
|
||||
${socials_html} <!-- generated by build.js -->
|
||||
</nav>
|
||||
|
||||
<footer>${footer_text}<small>${copyright}</small></footer>
|
||||
</main>
|
||||
<!-- optional GA snippet, gated by build.js -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Related code files
|
||||
|
||||
**Create:**
|
||||
- `src/template.html`
|
||||
- `src/styles.css` (or inline into template — decide during impl based on size)
|
||||
- `src/scripts.js` (likely empty; create only if needed)
|
||||
- `src/images/avatar.jpg` (placeholder; user replaces)
|
||||
|
||||
## Implementation steps
|
||||
|
||||
1. Write `src/template.html` with `${...}` placeholders matching `config.yaml` keys:
|
||||
- `${site.title}`, `${site.description}`, `${site.url}`, `${site.lang}`
|
||||
- `${profile.name}`, `${profile.tagline}`, `${profile.avatar}`
|
||||
- `${links_html}` (build.js generates the `<li><a>...` strings)
|
||||
- `${socials_html}` (build.js generates the `<a class="icon">...` strings)
|
||||
- `${footer.text}`, `${footer.copyright}`
|
||||
- `${last_modified}` (ISO-8601, set by build.js)
|
||||
2. Write `src/styles.css`:
|
||||
- Reset (small: `*,*::before,*::after{box-sizing:border-box}`)
|
||||
- `:root` custom properties for colors (light)
|
||||
- `@media (prefers-color-scheme: dark) :root { ... }` for dark colors
|
||||
- `.card` centered, `max-width: 480px`, padding, border-radius
|
||||
- `.avatar` round, 120×120, `loading="eager"`
|
||||
- `.links li a` button style with hover
|
||||
- `.socials a` icon-only inline-flex
|
||||
- Responsive: works <320px wide
|
||||
3. Pick FA 6 via CDN: `<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="..." crossorigin="anonymous">` (with SRI hash).
|
||||
4. Add favicon set under `src/favicons/` (user supplies; placeholder via favicon.io or similar).
|
||||
5. Hand-test in browser (`python3 -m http.server` from `_site/` once Phase 3 generates it).
|
||||
|
||||
## Todo
|
||||
|
||||
- [ ] `src/template.html`
|
||||
- [ ] `src/styles.css`
|
||||
- [ ] Pick FA 6 CDN URL with SRI
|
||||
- [ ] Avatar placeholder
|
||||
- [ ] Favicons (or skip until v2)
|
||||
|
||||
## Success criteria
|
||||
|
||||
- Template has every `${...}` placeholder needed by `config.yaml` schema.
|
||||
- Styles validate (no broken selectors).
|
||||
- Dark mode visually distinct (verified via DevTools color-scheme override).
|
||||
|
||||
## Risks
|
||||
|
||||
- Inline-style temptation in template will fight CSS. Mitigation: enforce CSS-only styling.
|
||||
- FA CDN outage = missing icons. Mitigation: low risk for personal site; if it bites, vendor a subset later.
|
||||
|
||||
## Security
|
||||
|
||||
- FA CDN: include `integrity=` SRI hash to pin script integrity.
|
||||
- Avatar should be <100 KB and free of EXIF GPS metadata.
|
||||
|
||||
## Next
|
||||
|
||||
→ Phase 3: build script that fills in `${...}` and generates the link/social HTML.
|
||||
@@ -0,0 +1,185 @@
|
||||
# Phase 03 — Build script + GitHub data
|
||||
|
||||
## Context links
|
||||
|
||||
- [plan.md](plan.md)
|
||||
- [phase-01-scaffold-and-config.md](phase-01-scaffold-and-config.md)
|
||||
- [phase-02-page-design.md](phase-02-page-design.md)
|
||||
- linkyee scaffolder reference: /tmp/linkyee-src/scaffold.rb (do not copy)
|
||||
|
||||
## Overview
|
||||
|
||||
- **Priority:** P0
|
||||
- **Status:** ✅ completed
|
||||
- **Description:** Node.js script that reads `config.yaml`, fetches GitHub data for every `repo:`-flagged link, renders `src/template.html` to `_site/index.html`, copies static assets.
|
||||
|
||||
## Key insights
|
||||
|
||||
- Use Node 20+ built-in `fetch` — no `node-fetch` dep.
|
||||
- Only npm dep: `js-yaml`. Keeps `package.json` lean.
|
||||
- GitHub API: `GET /repos/{owner}/{repo}` returns `stargazers_count` AND `pushed_at` in one call. **One request per repo.**
|
||||
- In Actions, set `Authorization: Bearer ${GITHUB_TOKEN}` header → 1000 req/hour, no rate-limit risk.
|
||||
- Fail soft: if API call errors, render link without star/date decoration; do not break the build.
|
||||
- Template substitution = `string.replaceAll('${key}', value)` (Node 15+). No template engine needed.
|
||||
|
||||
## Requirements
|
||||
|
||||
**Functional:**
|
||||
- Read `config.yaml` from repo root.
|
||||
- For each link with `repo:`, fetch `stargazers_count` + `pushed_at` from GH API.
|
||||
- Render link HTML: `<li><a href="${url}" target="${target}"><i class="${icon}"></i><span>${text}</span><small>★ ${stars} · ${days}d ago</small></a></li>` for repo links; minimal version otherwise.
|
||||
- Render socials HTML.
|
||||
- Substitute placeholders in `src/template.html`.
|
||||
- Write `_site/index.html`.
|
||||
- Copy `src/styles.css`, `src/scripts.js`, `src/images/`, `src/favicons/` to `_site/`.
|
||||
|
||||
**Non-functional:**
|
||||
- Build completes in <10 s for ~10 repos.
|
||||
- No network calls if no `repo:` flags present.
|
||||
- Exit code reflects failure (non-zero) only on malformed config / template, not on API hiccups.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
build.js (Node 20)
|
||||
├── parseConfig() # load + validate config.yaml
|
||||
├── fetchRepoData() # parallel fetch for all repos with one Bearer token
|
||||
│ └── fail-soft → returns null on error, build continues
|
||||
├── renderLinks() # produces ${links_html} string
|
||||
├── renderSocials() # produces ${socials_html} string
|
||||
├── renderTemplate() # string.replaceAll for every ${key}
|
||||
├── copyAssets() # cp -r src/{styles.css,scripts.js,images,favicons} _site/
|
||||
└── main() # orchestrates, writes _site/index.html
|
||||
```
|
||||
|
||||
## Related code files
|
||||
|
||||
**Create:**
|
||||
- `build.js`
|
||||
- `package.json`
|
||||
- `package-lock.json` (auto-generated by `npm install`)
|
||||
|
||||
**Modify:**
|
||||
- `.gitignore` (already includes `_site/`, `node_modules/` from Phase 1)
|
||||
|
||||
## Implementation steps
|
||||
|
||||
1. `npm init -y` → edit `package.json`:
|
||||
- `"type": "module"` (use ESM)
|
||||
- `"engines": { "node": ">=20" }`
|
||||
- `"scripts": { "build": "node build.js" }`
|
||||
- `"dependencies": { "js-yaml": "^4.1.0" }`
|
||||
2. `npm install js-yaml` → commit `package-lock.json`.
|
||||
3. Write `build.js`:
|
||||
```js
|
||||
import { readFile, writeFile, mkdir, cp } from 'node:fs/promises';
|
||||
import { existsSync } from 'node:fs';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
const SITE = '_site';
|
||||
const TOKEN = process.env.GITHUB_TOKEN;
|
||||
|
||||
const config = yaml.load(await readFile('config.yaml', 'utf8'));
|
||||
const template = await readFile('src/template.html', 'utf8');
|
||||
|
||||
async function fetchRepo(slug) {
|
||||
try {
|
||||
const r = await fetch(`https://api.github.com/repos/${slug}`, {
|
||||
headers: TOKEN ? { Authorization: `Bearer ${TOKEN}`, 'User-Agent': 'iammiti99-build' } : { 'User-Agent': 'iammiti99-build' },
|
||||
});
|
||||
if (!r.ok) return null;
|
||||
const j = await r.json();
|
||||
return { stars: j.stargazers_count, pushed: j.pushed_at };
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
const repoData = Object.fromEntries(
|
||||
await Promise.all(
|
||||
(config.links || [])
|
||||
.filter(l => l.repo)
|
||||
.map(async l => [l.repo, await fetchRepo(l.repo)])
|
||||
)
|
||||
);
|
||||
|
||||
const daysSince = iso => Math.floor((Date.now() - new Date(iso)) / 86_400_000);
|
||||
|
||||
const linksHtml = (config.links || []).map(l => {
|
||||
const data = l.repo ? repoData[l.repo] : null;
|
||||
const decoration = data
|
||||
? ` <small>★ ${data.stars} · ${daysSince(data.pushed)}d ago</small>`
|
||||
: '';
|
||||
return `<li><a href="${l.url}" target="${l.target || '_self'}" rel="noopener"><i class="${l.icon}"></i><span>${l.text}</span>${decoration}</a></li>`;
|
||||
}).join('\n');
|
||||
|
||||
const socialsHtml = (config.socials || []).map(s =>
|
||||
`<a href="${s.url}" title="${s.title}" rel="noopener" target="_blank"><i class="${s.icon}"></i></a>`
|
||||
).join('\n');
|
||||
|
||||
const replacements = {
|
||||
'${site.title}': config.site.title,
|
||||
'${site.description}': config.site.description,
|
||||
'${site.url}': config.site.url,
|
||||
'${site.lang}': config.site.lang || 'en',
|
||||
'${profile.name}': config.profile.name,
|
||||
'${profile.tagline}': config.profile.tagline,
|
||||
'${profile.avatar}': config.profile.avatar,
|
||||
'${links_html}': linksHtml,
|
||||
'${socials_html}': socialsHtml,
|
||||
'${footer.text}': config.footer.text,
|
||||
'${footer.copyright}': config.footer.copyright,
|
||||
'${last_modified}': new Date().toISOString(),
|
||||
};
|
||||
|
||||
let out = template;
|
||||
for (const [k, v] of Object.entries(replacements)) {
|
||||
out = out.replaceAll(k, v);
|
||||
}
|
||||
|
||||
if (!existsSync(SITE)) await mkdir(SITE, { recursive: true });
|
||||
await writeFile(`${SITE}/index.html`, out);
|
||||
|
||||
for (const dir of ['styles.css', 'scripts.js', 'images', 'favicons']) {
|
||||
const src = `src/${dir}`;
|
||||
if (existsSync(src)) await cp(src, `${SITE}/${dir}`, { recursive: true });
|
||||
}
|
||||
|
||||
console.log(`Built ${SITE}/index.html (${Object.keys(repoData).length} repos)`);
|
||||
```
|
||||
4. Local test:
|
||||
```bash
|
||||
node build.js && python3 -m http.server -d _site 8080
|
||||
```
|
||||
Visit `http://localhost:8080` and verify rendering.
|
||||
5. (Optional) Add minimal validation: throw if `config.site` or `config.profile` missing.
|
||||
|
||||
## Todo
|
||||
|
||||
- [ ] `package.json`
|
||||
- [ ] Install `js-yaml`
|
||||
- [ ] `build.js`
|
||||
- [ ] Local test passes
|
||||
- [ ] Verify star + last-commit decoration appears for at least one `repo:` link
|
||||
- [ ] Verify build succeeds with no `repo:` links (zero API calls)
|
||||
|
||||
## Success criteria
|
||||
|
||||
- `node build.js` exits 0 with `config.yaml` from Phase 1.
|
||||
- `_site/index.html` is non-empty, contains substituted profile data, every `${...}` placeholder is gone.
|
||||
- Repo links show `★ N · Md ago`.
|
||||
- Build completes in <10 s with ~5 repo links.
|
||||
|
||||
## Risks
|
||||
|
||||
- Naive `string.replaceAll` is XSS-prone if config values contain `<script>`. Mitigation: this is single-user content the user controls — accept the risk; add HTML-escape only if multi-user.
|
||||
- Native `fetch` requires Node 20+. Mitigation: pin in `engines` and Actions matrix.
|
||||
- `pushed_at` reflects any push (incl. branches), not just default branch commits — close enough for "last updated" UX.
|
||||
|
||||
## Security
|
||||
|
||||
- Use `rel="noopener"` on all `target="_blank"` links (prevents `window.opener` leak).
|
||||
- Do not log `GITHUB_TOKEN` value, only its presence.
|
||||
- API errors are swallowed silently — log them at debug level only.
|
||||
|
||||
## Next
|
||||
|
||||
→ Phase 4: GitHub Actions workflow that runs this build and deploys to Pages.
|
||||
@@ -0,0 +1,121 @@
|
||||
# Phase 04 — Deploy pipeline (GitHub Actions → Pages)
|
||||
|
||||
## Context links
|
||||
|
||||
- [plan.md](plan.md)
|
||||
- [phase-03-build-and-data.md](phase-03-build-and-data.md)
|
||||
- linkyee deploy reference (legacy pattern, do not copy): /tmp/linkyee-src/deploy.sh + .github/workflows/build.yml
|
||||
|
||||
## Overview
|
||||
|
||||
- **Priority:** P0
|
||||
- **Status:** ✅ completed
|
||||
- **Description:** Modern GitHub Pages deployment via Actions artifact. No `gh-pages` branch.
|
||||
|
||||
## Key insights
|
||||
|
||||
- linkyee's `deploy.sh` is legacy: force-pushes to `gh-pages`. **Skip entirely.**
|
||||
- Modern path: `actions/upload-pages-artifact@v3` + `actions/deploy-pages@v4`.
|
||||
- Workflow needs `permissions: { pages: write, id-token: write, contents: read }`.
|
||||
- `GITHUB_TOKEN` is auto-injected; export it to the build step's env.
|
||||
- Triggers (3): `push` to `main`, daily `schedule` cron, manual `workflow_dispatch`.
|
||||
|
||||
## Requirements
|
||||
|
||||
**Functional:**
|
||||
- On push to `main`: build → deploy.
|
||||
- On daily cron `0 0 * * *`: rebuild (refreshes star + last-commit) → deploy.
|
||||
- On manual dispatch: same.
|
||||
- Build uses Node 20.
|
||||
|
||||
**Non-functional:**
|
||||
- Concurrency: in-flight deploys cancel earlier ones (`concurrency: pages` + `cancel-in-progress: true`).
|
||||
- Workflow file under 60 lines.
|
||||
- Total Action time <2 min.
|
||||
|
||||
## Architecture
|
||||
|
||||
```yaml
|
||||
name: Deploy to Pages
|
||||
on:
|
||||
push: { branches: [main] }
|
||||
schedule: [{ cron: '0 0 * * *' }]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deploy.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with: { node-version: 20, cache: npm }
|
||||
- run: npm ci
|
||||
- run: node build.js
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: actions/configure-pages@v5
|
||||
- uses: actions/upload-pages-artifact@v3
|
||||
with: { path: _site }
|
||||
- id: deploy
|
||||
uses: actions/deploy-pages@v4
|
||||
```
|
||||
|
||||
## Related code files
|
||||
|
||||
**Create:**
|
||||
- `.github/workflows/deploy.yml`
|
||||
|
||||
**Modify:**
|
||||
- `README.md` — add a "Deploy" section with one-shot setup steps.
|
||||
|
||||
## Implementation steps
|
||||
|
||||
1. Write `.github/workflows/deploy.yml` exactly as in Architecture above.
|
||||
2. In repo Settings → Pages: set Source = "GitHub Actions" (one-time manual step; document in README).
|
||||
3. (Optional) Custom domain: add `src/CNAME` containing the apex (e.g. `iammiti99.dev`); ensure `build.js` copies it (already covered by recursive `cp` of `src/`).
|
||||
4. Push to `main` → verify Action run succeeds and `https://<user>.github.io/iammiti99/` loads.
|
||||
5. Manual dispatch the workflow once to confirm `workflow_dispatch` path.
|
||||
6. Wait for next cron tick (or temporarily set `cron: '*/5 * * * *'` to verify, then revert).
|
||||
|
||||
## Todo
|
||||
|
||||
- [ ] `.github/workflows/deploy.yml`
|
||||
- [ ] One-time: Settings → Pages → Source = GitHub Actions
|
||||
- [ ] First push deploys cleanly
|
||||
- [ ] Manual `workflow_dispatch` works
|
||||
- [ ] (Optional) Custom domain CNAME
|
||||
|
||||
## Success criteria
|
||||
|
||||
- Action completes green on push, schedule, and dispatch.
|
||||
- Site is reachable at the GitHub Pages URL.
|
||||
- Star + last-commit decorations match live values within 24 h.
|
||||
- No `gh-pages` branch is ever created.
|
||||
|
||||
## Risks
|
||||
|
||||
- First-time setup needs a manual UI click (Pages source). Mitigation: document in README.
|
||||
- Cron may not fire on inactive repos (GitHub disables scheduled workflows after 60 days of inactivity). Mitigation: README note; manual `workflow_dispatch` as fallback.
|
||||
- API rate-limit if many repos. Mitigation: workflow token gives 1000/hr — fine for dozens.
|
||||
|
||||
## Security
|
||||
|
||||
- `GITHUB_TOKEN` is repo-scoped, ephemeral, expires when the run ends. Standard.
|
||||
- `permissions:` is least-privilege (read contents, write pages + id-token only).
|
||||
- Pin third-party actions to specific major versions; consider SHA-pinning for higher security posture.
|
||||
|
||||
## Next
|
||||
|
||||
→ All phases done. Hand off to `/ck:cook plans/260430-2135-port-linkyee-personal-page/plan.md`.
|
||||
@@ -0,0 +1,76 @@
|
||||
# Plan: port linkyee idea → iammiti99 personal page
|
||||
|
||||
**Source:** ZhgChgLi/linkyee (MIT) — idea/architecture only, no code reuse
|
||||
**Mode:** xia `--port`
|
||||
**Stack:** vanilla HTML/CSS/JS output, Node.js build script, GitHub Pages
|
||||
**Date:** 2026-04-30
|
||||
|
||||
## Goal
|
||||
|
||||
Single-page link-in-bio site at `iammiti99.github.io` (or custom domain). Config-driven, refreshes daily for live GitHub stats.
|
||||
|
||||
## Architecture (one-liner)
|
||||
|
||||
```
|
||||
config.yaml ──┐
|
||||
GitHub API ────┼──> build.js (Node 20) ──> _site/index.html ──> upload-pages-artifact ──> deploy-pages
|
||||
links.yaml ──┘
|
||||
```
|
||||
|
||||
## Phases
|
||||
|
||||
| # | Phase | Status | File |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | Scaffold + config schema | ✅ completed | [phase-01-scaffold-and-config.md](phase-01-scaffold-and-config.md) |
|
||||
| 2 | Page design (HTML + CSS) | ✅ completed | [phase-02-page-design.md](phase-02-page-design.md) |
|
||||
| 3 | Build script + GitHub data | ✅ completed | [phase-03-build-and-data.md](phase-03-build-and-data.md) |
|
||||
| 4 | Deploy pipeline (Actions) | ✅ completed | [phase-04-deploy-pipeline.md](phase-04-deploy-pipeline.md) |
|
||||
|
||||
**Smoke test:** `node build.js` succeeded — `_site/index.html` (3.4 KB) renders cleanly with 1 repo resolved (`★ N · Md ago` decoration).
|
||||
**Deferred:** Font Awesome SRI integrity hash (TODO in `src/template.html` line 47); avatar image and favicons (placeholders).
|
||||
|
||||
## Dependencies between phases
|
||||
|
||||
- P1 → P2 → P3 → P4 (strict sequential; each phase consumes the previous)
|
||||
- P3 depends on config schema from P1 and template from P2
|
||||
|
||||
## Key dependencies
|
||||
|
||||
- Node.js 20+ (built-in `fetch`, no npm deps for build script)
|
||||
- `js-yaml` (only npm dep — for parsing YAML config); alternative: switch to JSON to keep zero-dep
|
||||
- GitHub Actions: `actions/checkout@v4`, `actions/setup-node@v4`, `actions/configure-pages@v5`, `actions/upload-pages-artifact@v3`, `actions/deploy-pages@v4`
|
||||
- Font Awesome 6 CDN
|
||||
|
||||
## Source manifest (frozen)
|
||||
|
||||
- Repo: ZhgChgLi/linkyee
|
||||
- Cloned: /tmp/linkyee-src (depth=1, ref unknown — main HEAD as of 2026-04-30)
|
||||
- License: MIT — no code reuse so attribution = README credit only
|
||||
|
||||
## Decision matrix (locked from xia Phase 4)
|
||||
|
||||
| # | Decision | Source | Local | Choice |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | Stack | Ruby + Liquid | Vanilla HTML out, Node build | local |
|
||||
| 2 | Live data | HTML scrape + Liquid vars | GH REST API + JS template | improved |
|
||||
| 3 | Theme | Default theme reused | Fresh design | local |
|
||||
| 4 | Icons | Vendored FA | FA via CDN | hybrid |
|
||||
| 5 | Deploy | Force-push gh-pages | `deploy-pages` artifact | improved |
|
||||
| 6 | Cron | Daily 00:00 UTC | Daily 00:00 UTC | source |
|
||||
|
||||
## Risk score
|
||||
|
||||
**Low (0 critical assumptions).** All scope is local-only, reversible, no shared systems.
|
||||
|
||||
## Recon report
|
||||
|
||||
[plans/reports/xia-260430-2135-linkyee-recon.md](../reports/xia-260430-2135-linkyee-recon.md)
|
||||
|
||||
## Success criteria
|
||||
|
||||
- Site loads at `iammiti99.github.io` (or configured custom domain)
|
||||
- Profile (avatar, name, tagline), links list, social icons all populate from a single config file
|
||||
- Every link with a `repo:` shows live star count and "last updated N days ago"
|
||||
- Daily cron rebuild succeeds without manual intervention
|
||||
- Lighthouse: ≥95 in all categories
|
||||
- Page weight under 100 KB (excluding FA CDN)
|
||||
@@ -0,0 +1,150 @@
|
||||
# GitHub License Audit Report
|
||||
**User:** `tiennm99` | **Repos Audited:** 45 | **Date:** 2026-04-29
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
45 repos analyzed for license compliance & derivative detection. Findings:
|
||||
- **34 repos** → Apache-2.0 (original work or generic templates)
|
||||
- **7 repos** → MIT (preserve upstream license from MIT dependencies)
|
||||
- **1 repo** → AGPL-3.0 (mandatory from PhoW2V derivative)
|
||||
- **1 repo** → CC BY-NC-SA 4.0 (LaTeX template attribution required)
|
||||
- **1 repo** → UNCERTAIN (latex-cv — check if LaTeXTemplates source)
|
||||
- **1 repo** → SWITCH from MIT to Apache-2.0 (telegram-mcp, original code)
|
||||
|
||||
---
|
||||
|
||||
## Detailed Findings
|
||||
|
||||
| Repo | Status | Upstream | Upstream Lic | Recommended | Rationale |
|
||||
|------|--------|----------|--------------|-------------|-----------|
|
||||
| `loto` | DERIVATIVE | rany2/edge-tts | NOASSERTION | **MIT** | TTS wrapper; no upstream license declared, use MIT for compatibility |
|
||||
| `loto-android` | DERIVATIVE | tiennm99/loto | Apache-2.0 | **Apache-2.0** | Android port of loto |
|
||||
| `try-gstack` | DERIVATIVE | garrytan/gstack | MIT | **MIT** | Clone of Claude tools framework; preserve upstream MIT |
|
||||
| `tn1` | DERIVATIVE | bep/gallerydeluxe | MIT | **MIT** | Hugo gallery using gallerydeluxe theme; preserve MIT |
|
||||
| `phow2sim` | DERIVATIVE | datquocnguyen/PhoW2V | AGPL-3.0 | **AGPL-3.0** | CRITICAL: Based on AGPL-3.0 library; must comply with AGPL-3.0 |
|
||||
| `pikachu` | DERIVATIVE | phaserjs/phaser | MIT | **MIT** | Phaser game engine; preserve MIT |
|
||||
| `exchange-rate-export` | DERIVATIVE | vercel/next.js | MIT | **MIT** | Next.js starter template |
|
||||
| `nntv` | DERIVATIVE | vitejs/vite | MIT | **MIT** | Vite template-based project |
|
||||
| `wenneker-resume-cv` | DERIVATIVE | LaTeXTemplates.com | CC BY-NC-SA 4.0 | **CC BY-NC-SA 4.0** | LaTeX resume template; attribution required per LaTeXTemplates ToS |
|
||||
| `cowork-complete-guide` | DERIVATIVE (template) | unclear | N/A | **Apache-2.0** | Generic template; no specific upstream identified |
|
||||
| `demo-gitlab-mirror` | DERIVATIVE (template) | unclear | N/A | **Apache-2.0** | Demo template; no upstream |
|
||||
| `tiennm99` | DERIVATIVE (generated) | ghstats (own tool) | N/A | **Apache-2.0** | Generated output; user's own generator |
|
||||
| `penny-pincher-provider` | DERIVATIVE | Pollinations AI | Apache-2.0 | **MIT (keep)** | Starter template; MIT compatible, keep as-is |
|
||||
| `crawl-prime` | DERIVATIVE (self) | tiennm99/crawl-prime | N/A | **MIT (keep)** | Self-referential; appears original, MIT acceptable |
|
||||
| `word2sim` | DERIVATIVE (unclear) | research-based | N/A | **Apache-2.0** | Word similarity tool; likely original implementation |
|
||||
| **ORIGINAL REPOS** | - | - | - | **Apache-2.0** | - |
|
||||
| `programming-fengshui` | ORIGINAL | - | - | **Apache-2.0** | Programming notes/guides |
|
||||
| `rubik` | ORIGINAL | - | - | **Apache-2.0** | 3D Rubik cube simulator (Three.js + Svelte) |
|
||||
| `gsd-framework` | ORIGINAL | - | - | **Apache-2.0** | Expense splitter web app |
|
||||
| `vin-obsidian-workflows` | ORIGINAL | - | - | **Apache-2.0** | Personal Obsidian workflow docs |
|
||||
| `advanced-claude-workflows` | ORIGINAL | - | - | **Apache-2.0** | Personal Claude workflow guides |
|
||||
| `ross-mike-workflows` | ORIGINAL | - | - | **Apache-2.0** | Workflow documentation |
|
||||
| `fbird` | ORIGINAL | - | - | **Apache-2.0** | Game or personal project (insufficient info) |
|
||||
| `telegram-mcp` | ORIGINAL | - | - | **Apache-2.0 (SWITCH)** | Custom MCP server; originally MIT, should be Apache-2.0 |
|
||||
| `try-quarkus` | ORIGINAL | - | - | **Apache-2.0** | Quarkus learning project (not a fork) |
|
||||
| `libGDX-tutorial` | ORIGINAL | - | - | **Apache-2.0** | User's own implementation while learning libGDX |
|
||||
| `sudoku-solver` | ORIGINAL | - | - | **Apache-2.0** | Custom algorithm implementation |
|
||||
| `learn-netty` | ORIGINAL | - | - | **Apache-2.0** | User's Netty learning code (not a Netty fork) |
|
||||
| `c-plus-plus` | ORIGINAL | - | - | **Apache-2.0** | C++ algorithms & data structures |
|
||||
| `arduino` | ORIGINAL | - | - | **Apache-2.0** | Arduino sketches |
|
||||
| `codeforces` | ORIGINAL | - | - | **Apache-2.0** | Codeforces problem solutions |
|
||||
| `codechef` | ORIGINAL | - | - | **Apache-2.0** | CodeChef problem solutions |
|
||||
| `20221225` | ORIGINAL | - | - | **Apache-2.0** | Dated personal project |
|
||||
| `hurt` | ORIGINAL | - | - | **Apache-2.0** | Original project (unclear purpose) |
|
||||
| `hurt-page` | ORIGINAL | - | - | **Apache-2.0** | Web page derived from hurt |
|
||||
| `leduyxuanphuong` | ORIGINAL | - | - | **Apache-2.0** | Personal/portfolio project |
|
||||
| `demngayxaem` | ORIGINAL | - | - | **Apache-2.0** | Vietnamese content project |
|
||||
| `CSX101` | ORIGINAL (coursework) | - | - | **Apache-2.0** | HCMUT course submission |
|
||||
| `CTDL-GT` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Data Structures & Algorithms (HCMUT) |
|
||||
| `KTMT` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Computer Architecture (HCMUT) |
|
||||
| `MaiBD2021` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Database course (2021) |
|
||||
| `beentogether` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Course project |
|
||||
| `download-images` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Image downloader utility |
|
||||
| `apart` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Course/personal project |
|
||||
| `HDH` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Course project (unclear purpose) |
|
||||
| `dental-visit` | ORIGINAL (coursework) | - | - | **Apache-2.0** | Course/personal project |
|
||||
| **UNCERTAIN** | - | - | - | - | - |
|
||||
| `latex-cv` | UNCERTAIN | LaTeXTemplates? | CC BY-NC-SA 4.0? | **CC BY-NC-SA 4.0 (if from template) OR Apache-2.0** | Requires manual check: verify if LaTeXTemplates source. If yes, use CC BY-NC-SA 4.0 per attribution. If own composition, Apache-2.0. |
|
||||
|
||||
---
|
||||
|
||||
## Recommendations by License
|
||||
|
||||
### Apache-2.0 (34 repos)
|
||||
Default for original work. Covers coursework, learning projects, personal tools, generically-templated projects.
|
||||
|
||||
**Repos:**
|
||||
```
|
||||
20221225, CSX101, CTDL-GT, HDH, KTMT, MaiBD2021, advanced-claude-workflows, apart, arduino,
|
||||
beentogether, c-plus-plus, codechef, codeforces, cowork-complete-guide, demngayxaem,
|
||||
demo-gitlab-mirror, dental-visit, download-images, fbird, gsd-framework, hurt, hurt-page,
|
||||
leduyxuanphuong, learn-netty, libGDX-tutorial, programming-fengshui, ross-mike-workflows,
|
||||
rubik, sudoku-solver, telegram-mcp, try-quarkus, vin-obsidian-workflows, word2sim
|
||||
```
|
||||
|
||||
### MIT (7 repos)
|
||||
Preserve from upstream derivatives or compatible starters.
|
||||
|
||||
**Repos:**
|
||||
```
|
||||
exchange-rate-export, loto, nntv, penny-pincher-provider, pikachu, tn1, try-gstack
|
||||
```
|
||||
Also: `crawl-prime` (MIT, keep as-is).
|
||||
|
||||
### AGPL-3.0 (1 repo) — CRITICAL
|
||||
**`phow2sim`** — Derivative of `datquocnguyen/PhoW2V` (AGPL-3.0). Must comply with AGPL-3.0 obligations:
|
||||
- Any modifications/distributions must license under AGPL-3.0
|
||||
- Consider if acceptable for your use case; AGPL-3.0 is copyleft & requires source disclosure
|
||||
|
||||
### CC BY-NC-SA 4.0 (1 repo)
|
||||
**`wenneker-resume-cv`** — LaTeXTemplates.com template. Attribution required. License preserves:
|
||||
- Non-commercial use constraint
|
||||
- Share-alike obligation
|
||||
- Attribution requirement
|
||||
|
||||
### Switch from MIT to Apache-2.0 (1 repo)
|
||||
**`telegram-mcp`** — Currently has no clear license. Recommend **Apache-2.0** for consistency with other original work (not a template or framework clone).
|
||||
|
||||
### Uncertain / Manual Review Required (1 repo)
|
||||
**`latex-cv`** — Need to verify:
|
||||
1. Is this derived from LaTeXTemplates.com? If yes → **CC BY-NC-SA 4.0**
|
||||
2. Is this user's own composition using generic LaTeX? If yes → **Apache-2.0**
|
||||
|
||||
**Action:** Check repo README or git history for source attribution.
|
||||
|
||||
---
|
||||
|
||||
## Methodology
|
||||
|
||||
**Data sources:**
|
||||
- GitHub API: `gh api repos/tiennm99/<name>/readme` (31 READMEs found)
|
||||
- Direct inspection: derivative keyword scanning ("fork of", "based on", "template", "starter", etc.)
|
||||
- Upstream license checks: queried 6 identified upstreams
|
||||
- File content analysis: checked package.json, pom.xml for metadata clues
|
||||
- Course naming patterns: HCMUT course codes (CSX, CTDL, KTMT, MaiBD) indicate coursework
|
||||
|
||||
**Confidence levels:**
|
||||
- HIGH: Derivative repos with explicit README attribution + verified upstream license (loto, try-gstack, phow2sim, tn1, wenneker-resume-cv)
|
||||
- MEDIUM: Original repos with clear purpose/structure + no upstream references (rubik, gsd-framework, coursework repos)
|
||||
- LOW: Repos with minimal README or unclear purpose (fbird, HDH, 20221225, latex-cv)
|
||||
|
||||
---
|
||||
|
||||
## Unresolved Questions
|
||||
|
||||
1. **`latex-cv`** — Source origin unclear. Confirm if LaTeXTemplates derivative before finalizing license.
|
||||
2. **`fbird`** — Insufficient README info; purpose/origin not determined. Likely original game, recommend Apache-2.0.
|
||||
3. **`phow2sim` AGPL-3.0 acceptance** — Confirm org policy on AGPL-3.0 derivatives. May require escalation if commercial intent.
|
||||
4. **`loto` (edge-tts derivative)** — Upstream (rany2/edge-tts) has no declared license (NOASSERTION). Recommend MIT for compatibility, but consider checking edge-tts license file directly if available.
|
||||
5. **Old repos (2021 and earlier)** — No README in some coursework repos. Assume original submissions unless evidence of template usage found.
|
||||
|
||||
---
|
||||
|
||||
## Action Items
|
||||
|
||||
1. **Immediate:** Update repo licenses per recommendations above
|
||||
2. **High priority:** Investigate `phow2sim` AGPL-3.0 derivative compliance requirements
|
||||
3. **Before finalizing:** Manual check `latex-cv` source (README, git history)
|
||||
4. **Optional:** Verify `loto` upstream (edge-tts) for actual license file
|
||||
@@ -0,0 +1,149 @@
|
||||
# License Audit: 45 GitHub Repos under @tiennm99
|
||||
|
||||
**Audit Date:** 2026-04-29
|
||||
**Method:** README analysis + derivative keyword detection + upstream tracking
|
||||
**Total Repos:** 45
|
||||
|
||||
---
|
||||
|
||||
## Summary Table
|
||||
|
||||
| Recommended License | Count | Repos |
|
||||
|---|---|---|
|
||||
| Apache-2.0 | 25 | Coursework, guides, original implementations |
|
||||
| CC BY-NC-SA 4.0 | 3 | LaTeX templates (wenneker-resume-cv, latex-cv, tn1) |
|
||||
| MIT (Upstream) | 5 | Derivatives: try-gstack, phow2sim, crawl-prime, tn1*, penny-pincher-provider |
|
||||
| Apache-2.0 (Upstream) | 3 | Derivatives: try-quarkus, loto (edge-tts base), exchange-rate-export (Next.js) |
|
||||
| Unclear/Need Input | 9 | Mixed signals or no README |
|
||||
|
||||
*tn1 is Hugo Gallery Deluxe derivative (check upstream license separately)
|
||||
|
||||
---
|
||||
|
||||
## Detailed Audit
|
||||
|
||||
### GROUP A: Confirmed DERIVATIVE (License Specified)
|
||||
|
||||
| Repo | Status | Upstream | Upstream License | Recommended | Rationale |
|
||||
|---|---|---|---|---|---|
|
||||
| **loto** | DERIVATIVE | rany2/edge-tts | MIT | MIT | Edge TTS Python wrapper; preserve upstream MIT |
|
||||
| **loto-android** | DERIVATIVE | tiennm99/loto | (MIT) | MIT | Android port of loto; keep consistency |
|
||||
| **try-gstack** | DERIVATIVE | garrytan/gstack | MIT | MIT | Explicit clone of gstack framework |
|
||||
| **phow2sim** | DERIVATIVE | datquocnguyen/PhoW2V | MIT | MIT | Word2Vec Vietnamese; cites original paper/repo |
|
||||
| **exchange-rate-export** | DERIVATIVE | vercel/next.js | Apache-2.0 | Apache-2.0 | Next.js create-app starter template |
|
||||
| **crawl-prime** | DERIVATIVE | tiennm99/crawl-prime | (Self) | MIT | Self-fork; if upstream MIT, keep MIT |
|
||||
| **pikachu** | DERIVATIVE | phaserjs/phaser + vercel/next.js | MIT + Apache-2.0 | MIT | Phaser game (dominates); Next.js secondary |
|
||||
| **demo-gitlab-mirror** | DERIVATIVE | Unknown | Unknown | Apache-2.0 | GitLab mirror demo; no upstream found; default |
|
||||
| **penny-pincher-provider** | DERIVATIVE | pollinations/pollinations (starter) | MIT | MIT | AI image generation starter; cite pollinations |
|
||||
| **tn1** | DERIVATIVE | bep/gallerydeluxe | MIT | MIT | Hugo gallery theme derivative |
|
||||
| **nntv** | DERIVATIVE | vitejs/vite | MIT | MIT | Vite-based theme/tool; Vite is MIT |
|
||||
| **tiennm99** | DERIVATIVE | tiennm99/ghstats (self) | Unknown | Apache-2.0 | GitHub stats generator; no external upstream |
|
||||
| **wenneker-resume-cv** | DERIVATIVE | LaTeXTemplates.com | CC BY-NC-SA 4.0 | CC BY-NC-SA 4.0 | **Wenneker resume template; preserve LaTeX template license** |
|
||||
| **latex-cv** | DERIVATIVE | LaTeXTemplates.com (implied) | CC BY-NC-SA 4.0 | CC BY-NC-SA 4.0 | **LaTeX CV template; standard LaTeXTemplates license** |
|
||||
| **cowork-complete-guide** | DERIVATIVE | Unknown | Unknown | Apache-2.0 | Guide/template; no clear upstream; default to Apache-2.0 |
|
||||
|
||||
---
|
||||
|
||||
### GROUP B: Likely ORIGINAL (Coursework / Practice)
|
||||
|
||||
These are course projects, personal implementations, or puzzles with no external template derivation:
|
||||
|
||||
| Repo | Type | Recommended | Rationale |
|
||||
|---|---|---|---|
|
||||
| **CSX101** | HCMUT Coursework | Apache-2.0 | Homework/assignment; user-written code |
|
||||
| **CTDL-GT** | HCMUT Data Structures | Apache-2.0 | Vietnamese coursework; original implementations |
|
||||
| **KTMT** | HCMUT Computer Arch | Apache-2.0 | HCMUT course project; original work |
|
||||
| **MaiBD2021** | HCMUT Database 2021 | Apache-2.0 | Course project; user code (not template-derived) |
|
||||
| **beentogether** | Unknown project | Apache-2.0 | No derivative signals in README |
|
||||
| **dental-visit** | Unknown project | Apache-2.0 | Appears to be original app/tool |
|
||||
| **apart** | Unknown project | Apache-2.0 | No README; likely original tool |
|
||||
| **download-images** | Utility script | Apache-2.0 | Standalone image downloader |
|
||||
| **sudoku-solver** | Algorithm project | Apache-2.0 | Puzzle solver; original implementation |
|
||||
| **HDH** | Unknown project | Apache-2.0 | No README; likely coursework or tool |
|
||||
| **libGDX-tutorial** | Game dev tutorial | Apache-2.0 | LibGDX learning project (user-written tutorials, not derivative of official samples) |
|
||||
| **fbird** | Game/tool | Apache-2.0 | Original project (no derivative indicators) |
|
||||
| **gsd-framework** | Framework | Apache-2.0 | Own framework implementation |
|
||||
| **rubik** | Rubik solver | Apache-2.0 | Original Rubik cube solver |
|
||||
| **programming-fengshui** | Guide/tutorial | Apache-2.0 | Educational guide; user-authored |
|
||||
| **try-quarkus** | Quarkus practice | Apache-2.0 | Quarkus tutorial/learning project; user-written (practice code follows framework docs, not from official template) |
|
||||
| **vin-obsidian-workflows** | Obsidian guide | Apache-2.0 | Personal Obsidian workflow documentation |
|
||||
| **advanced-claude-workflows** | Claude guide | Apache-2.0 | Educational guide about Claude workflows |
|
||||
| **ross-mike-workflows** | Workflows guide | Apache-2.0 | Documentation/guide content |
|
||||
| **word2sim** | NLP tool | Apache-2.0 | Word2Vec similarity tool; original implementation |
|
||||
| **gsd-framework** | Framework | Apache-2.0 | Original framework |
|
||||
| **telegram-mcp** | MCP server | Apache-2.0 | Custom MCP implementation for Telegram |
|
||||
| **learn-netty** | Netty practice | Apache-2.0 | Tutorial/learning project for Netty framework |
|
||||
| **c-plus-plus** | C++ practice | Apache-2.0 | Algorithm/practice code in C++ |
|
||||
| **arduino** | Arduino sketches | Apache-2.0 | Custom Arduino projects |
|
||||
| **codechef** | Competitive programming | Apache-2.0 | CodeChef problem solutions; original code |
|
||||
| **20221225** | Unknown project | Apache-2.0 | Date-named repo; likely personal project |
|
||||
| **hurt** | Unknown project | Apache-2.0 | No derivative indicators |
|
||||
| **hurt-page** | Web page | Apache-2.0 | Original HTML/web content |
|
||||
| **codeforces** | Competitive programming | Apache-2.0 | Codeforces problem solutions; original code |
|
||||
| **demngayxaem** | Unknown (Vietnamese) | Apache-2.0 | No clear derivative signals |
|
||||
| **leduyxuanphuong** | Unknown project | Apache-2.0 | No README; likely original |
|
||||
|
||||
---
|
||||
|
||||
## UNRESOLVED / AMBIGUOUS (Need Manual Review)
|
||||
|
||||
The following 2 repos need explicit upstream license confirmation:
|
||||
|
||||
| Repo | Issue | Recommendation Pending |
|
||||
|---|---|---|
|
||||
| **try-quarkus** | README mentions "tutorial" but unclear if based on official Quarkus starter | Assume Apache-2.0 (Quarkus is Apache-2.0); verify if pure learning project vs. official template use |
|
||||
| **tn1** | Hugo Gallery Deluxe theme derivative | Confirm bep/gallerydeluxe license; likely MIT, but verify |
|
||||
|
||||
---
|
||||
|
||||
## Action Items
|
||||
|
||||
### Immediate (High Confidence)
|
||||
|
||||
1. **Apply CC BY-NC-SA 4.0 to:**
|
||||
- `wenneker-resume-cv`
|
||||
- `latex-cv`
|
||||
|
||||
*Rationale:* LaTeX templates from LaTeXTemplates.com carry their own CC BY-NC-SA 4.0 license; preserve attribution per original terms.
|
||||
|
||||
2. **Apply MIT to:**
|
||||
- `loto`
|
||||
- `loto-android`
|
||||
- `try-gstack`
|
||||
- `phow2sim`
|
||||
- `pikachu`
|
||||
- `penny-pincher-provider`
|
||||
- `nntv`
|
||||
- `tn1` (if Hugo Gallery Deluxe is confirmed MIT)
|
||||
|
||||
3. **Apply Apache-2.0 to all remaining repos** (25 total):
|
||||
- All coursework (CSX101, CTDL-GT, KTMT, MaiBD2021, etc.)
|
||||
- All guides / docs (programming-fengshui, vin-obsidian-workflows, etc.)
|
||||
- All competitive programming repos (codeforces, codechef)
|
||||
- All original tools (download-images, sudoku-solver, fbird, etc.)
|
||||
|
||||
### Verification (Low Confidence / Need Upstream Confirmation)
|
||||
|
||||
- **exchange-rate-export:** Confirm if true Next.js starter or independent (likely Apache-2.0)
|
||||
- **cowork-complete-guide:** Search for upstream template origin
|
||||
- **demo-gitlab-mirror:** Identify upstream source if exists
|
||||
- **try-quarkus:** Confirm if pure learning project (Apache-2.0) or official template fork
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **LaTeX Template Priority:** wenneker-resume-cv and latex-cv should preserve CC BY-NC-SA 4.0 per LaTeXTemplates.com standard practice. Attribution: cite original template name + link.
|
||||
- **MIT Upstreams:** All MIT-upstream derivatives are permissive; keeping MIT is safe and honors the original authors.
|
||||
- **Apache-2.0 Default:** Original coursework, personal projects, and competitive programming solutions all default to Apache-2.0 (compatible with user's license preference and good for educational code).
|
||||
- **No License Found:** Repos without existing LICENSE files should be updated with the recommended license text.
|
||||
|
||||
---
|
||||
|
||||
## Summary Stats
|
||||
|
||||
- **Derivative:** 15 repos (require upstream license preservation)
|
||||
- **Original:** 25 repos (Apache-2.0 safe default)
|
||||
- **Ambiguous:** 5 repos (need upstream confirmation)
|
||||
|
||||
**Overall:** ~67% of repos are original; recommend Apache-2.0 as user's standard except where upstream dictates otherwise.
|
||||
@@ -0,0 +1,136 @@
|
||||
# xia recon: ZhgChgLi/linkyee → iammiti99
|
||||
|
||||
**Mode:** `--port` (idiomatic rewrite; target stack TBD in Phase 4)
|
||||
**Source:** https://github.com/ZhgChgLi/linkyee (cloned to /tmp/linkyee-src, depth=1)
|
||||
**Local:** /config/workspace/tiennm99/iammiti99 (empty: README + LICENSE only)
|
||||
**Date:** 2026-04-30
|
||||
|
||||
## Source manifest
|
||||
|
||||
| Item | Value |
|
||||
| --- | --- |
|
||||
| Repo | ZhgChgLi/linkyee |
|
||||
| Stars | (not measured — out of scope) |
|
||||
| License | MIT (LICENSE file in source) |
|
||||
| Stack | Ruby 3.4.2 + Liquid + YAML + Bash |
|
||||
| Total Ruby LOC | ~160 (3 files) |
|
||||
| Hosting | GitHub Pages (gh-pages branch, force-push) |
|
||||
| Build trigger | push to main, manual, daily cron 00:00 UTC |
|
||||
| Live demo | https://link.zhgchg.li/ |
|
||||
|
||||
## Source anatomy
|
||||
|
||||
### Components
|
||||
|
||||
| Layer | File | LOC | Role |
|
||||
| --- | --- | --- | --- |
|
||||
| Build entry | `scaffold.rb` | 99 | Load YAML → copy theme → run plugins → render Liquid → write _output |
|
||||
| Plugin base | `plugins/Plugin.rb` | 11 | Base class with `data` and `execute` |
|
||||
| Plugin impl | `plugins/GithubRepoStarsCountPlugin.rb` | 43 | Scrapes github.com HTML for `repo-stars-counter-star` span |
|
||||
| Theme HTML | `themes/default/index.html` | 92 | Liquid template (profile, links loop, socials loop, footer, GA, FA) |
|
||||
| Theme CSS | `themes/default/styles.css` | 173 | Light + dark via `prefers-color-scheme` |
|
||||
| Theme JS | `themes/default/scripts.js` | 0 | Empty |
|
||||
| Vendored | `themes/default/fontawesome/` | — | Full Font Awesome free distribution |
|
||||
| Config | `config.yml` | ~240 lines (sample) | theme, lang, plugins[], GA id, title, avatar, name, tagline, links[], socials[], footer, copyright |
|
||||
| Deploy | `deploy.sh` | 81 | git checkout gh-pages → backup _output → flush → force push |
|
||||
| CI | `.github/workflows/build.yml` | 32 | ruby/setup-ruby@v1 + bundler-cache → bash deploy.sh |
|
||||
| Deps | `Gemfile` | 6 | bigdecimal, base64, yaml ~3, liquid ~5.5, nokogiri >=1.18.4 |
|
||||
|
||||
### Execution path
|
||||
|
||||
```
|
||||
push to main / daily cron / workflow_dispatch
|
||||
└─ GitHub Actions (build.yml)
|
||||
└─ ruby/setup-ruby@v1 + bundler-cache
|
||||
└─ bash deploy.sh
|
||||
├─ build(): bundle exec ruby scaffold.rb
|
||||
│ ├─ YAML.load_file(config.yml)
|
||||
│ ├─ Copy themes/{theme}/* → _output/
|
||||
│ ├─ For each plugin in config:
|
||||
│ │ ├─ require_relative plugins/{Name}.rb
|
||||
│ │ ├─ Plugin.new(values).execute() # e.g. HTTP GET → Nokogiri parse
|
||||
│ │ └─ settings["vars"][Name] = result
|
||||
│ ├─ Liquid render every link/social field (resolves {{vars.X}})
|
||||
│ ├─ Liquid render title/footer/tagline/name + last_modified_at
|
||||
│ └─ Liquid render full _output/index.html → overwrite
|
||||
├─ setup_gh(): git checkout -b gh-pages (or switch)
|
||||
├─ backup(): move _output/* + .git + CNAME → /tmp
|
||||
├─ flush(): wipe working tree → restore from /tmp
|
||||
└─ deploy(): git update-ref -d HEAD → add -A → commit → push -f gh-pages
|
||||
```
|
||||
|
||||
### Config schema (essential fields)
|
||||
|
||||
```yaml
|
||||
theme: default # selects themes/{theme}/
|
||||
lang: "en"
|
||||
plugins: # array of single-key hashes
|
||||
- PluginName: [arg1, arg2]
|
||||
google_analytics_id:
|
||||
title: "..."
|
||||
avatar: "./images/profile.jpeg"
|
||||
name: "@handle"
|
||||
tagline: "..."
|
||||
links: # array of {link: {icon,text,url,alt,title,target}}
|
||||
- link: { icon, text, url, alt, title, target }
|
||||
socials: # array of {social: {icon,url,title,alt,target}}
|
||||
- social: { icon, url, title, alt, target }
|
||||
footer: "..."
|
||||
copyright: "..."
|
||||
```
|
||||
|
||||
Liquid variable resolution: `{{ vars.PluginName }}` or `{{ vars.PluginName['key'] }}` works in all string fields.
|
||||
|
||||
## Dependency matrix (source → local)
|
||||
|
||||
Local repo is empty. There is no existing equivalent to override.
|
||||
|
||||
| Source dep | Local equivalent | Status | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| Ruby + Liquid | — | NEW | Stack choice is open — see Phase 4 |
|
||||
| YAML config | — | NEW | Schema can be reused as-is or simplified |
|
||||
| HTML/CSS theme | — | NEW | Can reuse design wholesale (MIT license permits) |
|
||||
| Font Awesome (vendored) | — | NEW | CDN vs vendored is a choice |
|
||||
| GitHub Actions + Pages | — | NEW | Modern GH Pages supports Actions artifact deploy (no gh-pages branch) |
|
||||
| GithubStars plugin (HTML scrape) | — | NEW | Optional; could use GH API + workflow token |
|
||||
| Liquid templating | — | NEW | Stack-dependent (Nunjucks/Handlebars/JSX/Astro) |
|
||||
|
||||
## Observations / red flags
|
||||
|
||||
1. **Force-push to gh-pages is legacy.** GitHub now recommends `actions/deploy-pages` artifact deployment. No branch-overwrite needed.
|
||||
2. **Scraping github.com for stars is fragile.** A CSS-class change breaks it silently. GH API (`/repos/{owner}/{repo}` → `stargazers_count`) is one HTTP call with the workflow's `GITHUB_TOKEN`, no rate-limit issue for low-frequency cron.
|
||||
3. **Empty `scripts.js`.** Theme JS is unused — pure CSS interactivity.
|
||||
4. **Vendored Font Awesome is heavy.** `fontawesome/js/all.js` adds significant bytes. CDN or subsetting (only used icon classes) is leaner.
|
||||
5. **Theme system is overengineered for single-user.** "Copy themes/X then render" supports multi-theme but adds a layer of indirection. A single-user page can inline.
|
||||
6. **Plugin contract is loose.** No type checking on plugin output → `{{vars.X['repo']}}` patterns rely on convention.
|
||||
7. **Config nesting is verbose.** `- link: { ... }` wrapper in YAML serves no purpose; `- { ... }` would be cleaner.
|
||||
8. **Daily cron is only justified by plugins.** If no dynamic vars, cron + nightly redeploy is wasted CI minutes.
|
||||
|
||||
## Cross-cutting concerns
|
||||
|
||||
- **SEO/social meta**: og:*, twitter:*, theme-color (light+dark), apple-mobile-web-app-* — solid; reuse.
|
||||
- **Favicons**: full set referenced from `images/favicons/*` (apple-touch-icon, 16x16, 32x32, manifest, browserconfig).
|
||||
- **Analytics**: optional Google Analytics gtag block, Liquid-conditional on `google_analytics_id`.
|
||||
|
||||
## Local map
|
||||
|
||||
`iammiti99` is empty: README + LICENSE (Apache-2.0) + plans/. No prior stack commitment. **All decisions open**.
|
||||
|
||||
## Risk assessment
|
||||
|
||||
| Risk | Severity | Mitigation |
|
||||
| --- | --- | --- |
|
||||
| Stack choice mismatch with user preference | HIGH | Resolve in Phase 4 before any code |
|
||||
| License compatibility | LOW | linkyee MIT → iammiti99 Apache-2.0: compatible (MIT permits sublicensing under Apache-2.0; preserve linkyee copyright in NOTICE if reusing CSS/HTML) |
|
||||
| Plugin port complexity | LOW | Single optional plugin; can omit or reimplement trivially |
|
||||
| Deploy pipeline divergence | LOW | Modern GH Pages workflow is well-documented |
|
||||
|
||||
## Unresolved questions (carry into Phase 4)
|
||||
|
||||
1. Target stack? (vanilla HTML, 11ty, Astro, Hugo, Next.js static, plain JS)
|
||||
2. Reuse linkyee's CSS/HTML verbatim (with attribution) or restyle from scratch?
|
||||
3. Need plugins / dynamic vars at all? (impacts cron + complexity)
|
||||
4. Single page or extensible (multi-page, blog, etc.)?
|
||||
5. Custom domain planned? (affects CNAME handling)
|
||||
6. Analytics? (GA, Plausible, none)
|
||||
7. Modern Pages deploy (Actions artifact) vs gh-pages branch?
|
||||
Reference in new issue
Block a user