Skip to content

Commit 443acad

Browse files
committed
ci: add workflow to auto-update dependencies and folder structure in README
1 parent 41431be commit 443acad

2 files changed

Lines changed: 137 additions & 0 deletions

File tree

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
name: Update README Dependencies and Structure
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths-ignore:
8+
- 'README.md'
9+
10+
permissions:
11+
contents: write
12+
13+
jobs:
14+
update-readme:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Checkout Repository
19+
uses: actions/checkout@v6
20+
21+
- name: Setup Node.js
22+
uses: actions/setup-node@v6
23+
with:
24+
node-version: 'lts/*'
25+
26+
- name: Update README.md
27+
run: |
28+
node -e "
29+
const fs = require('fs');
30+
const path = require('path');
31+
const pkg = require('./package.json');
32+
33+
// --- 1. Generate Dependencies ---
34+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
35+
const sortedDeps = Object.keys(deps).sort();
36+
let depMarkdown = '\n';
37+
for (const dep of sortedDeps) {
38+
depMarkdown += \`- [\${dep}](https://www.npmjs.com/package/\${dep}): \${deps[dep]}\\n\`;
39+
}
40+
depMarkdown += '\n';
41+
42+
// --- 2. Dynamic .env Detection ---
43+
let envFileToInject = null;
44+
// Check if .env.example exists in the root
45+
if (fs.existsSync('.env.example')) {
46+
envFileToInject = '.env/.env.local';
47+
}
48+
49+
// --- 3. Generate Folder Structure (Max Depth 1) ---
50+
const ignoreFolders = ['.git', '.next', 'node_modules', '.github', '.vscode'];
51+
const ignoreFiles = ['LICENSE'];
52+
const noExpandFolders = ['public', 'migrations'];
53+
54+
function getStructure(dir, depth = 0) {
55+
let structure = '';
56+
57+
// Read directory items
58+
let items = fs.readdirSync(dir, { withFileTypes: true });
59+
60+
// Filter out ignored folders, files, and *.md files
61+
items = items.filter(item => {
62+
if (item.isDirectory() && ignoreFolders.includes(item.name)) return false;
63+
if (item.isFile() && ignoreFiles.includes(item.name)) return false;
64+
if (item.isFile() && item.name.endsWith('.md')) return false;
65+
return true;
66+
});
67+
68+
// Artificially inject the dynamic .env string if we are at the root
69+
if (depth === 0 && envFileToInject) {
70+
items.push({
71+
name: envFileToInject,
72+
isDirectory: () => false,
73+
isFile: () => true
74+
});
75+
}
76+
77+
// Sort: Directories first, then files alphabetically
78+
items.sort((a, b) => {
79+
if (a.isDirectory() === b.isDirectory()) {
80+
return a.name.localeCompare(b.name);
81+
}
82+
return a.isDirectory() ? -1 : 1;
83+
});
84+
85+
// Build the string
86+
items.forEach((item) => {
87+
const prefix = depth === 0 ? ' |- ' : ' |-- ';
88+
89+
if (item.isDirectory()) {
90+
structure += \`\${prefix}\${item.name}/\\n\`;
91+
// Recurse only if at root AND the folder is not in noExpandFolders
92+
if (depth === 0 && !noExpandFolders.includes(item.name)) {
93+
structure += getStructure(path.join(dir, item.name), depth + 1);
94+
}
95+
} else {
96+
structure += \`\${prefix}\${item.name}\\n\`;
97+
}
98+
});
99+
return structure;
100+
}
101+
102+
const projectName = pkg.name || 'project-root';
103+
const folderMarkdown = '\\n\`\`\`bash\\n' + projectName + '/\\n' + getStructure('.') + '\`\`\`\\n';
104+
105+
// --- 4. Replace in README ---
106+
const readmePath = './README.md';
107+
let readme = fs.readFileSync(readmePath, 'utf8');
108+
109+
// Replace Dependencies
110+
const depRegex = new RegExp('<!--- DEPENDENCIES_START --->[\\\\s\\\\S]*?<!--- DEPENDENCIES_END --->');
111+
readme = readme.replace(depRegex, '<!--- DEPENDENCIES_START --->' + depMarkdown + '<!--- DEPENDENCIES_END --->');
112+
113+
// Replace Folder Structure
114+
const folderRegex = new RegExp('<!--- FOLDER_STRUCTURE_START --->[\\\\s\\\\S]*?<!--- FOLDER_STRUCTURE_END --->');
115+
if (folderRegex.test(readme)) {
116+
readme = readme.replace(folderRegex, '<!--- FOLDER_STRUCTURE_START --->' + folderMarkdown + '<!--- FOLDER_STRUCTURE_END --->');
117+
} else {
118+
console.log('Warning: FOLDER_STRUCTURE markers not found in README.md');
119+
}
120+
121+
fs.writeFileSync(readmePath, readme);
122+
"
123+
124+
- name: Commit and Push Changes
125+
run: |
126+
git config user.name "github-actions[bot]"
127+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
128+
git add README.md
129+
git commit -m "docs: update dependencies and folder structure in README" || echo "No changes to commit"
130+
git push

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343

4444
Here is the folder structure of this app.
4545

46+
<!--- FOLDER_STRUCTURE_START --->
47+
4648
```bash
4749
space-portfolio/
4850
|- app/
@@ -89,6 +91,8 @@ space-portfolio/
8991
|- tsconfig.json
9092
```
9193

94+
<!--- FOLDER_STRUCTURE_END --->
95+
9296
<br />
9397

9498
## :toolbox: Getting Started
@@ -127,6 +131,8 @@ You might encounter some bugs while using this app. You are more than welcome to
127131

128132
Useful resources and dependencies that are used in Space Portfolio.
129133

134+
<!--- DEPENDENCIES_START --->
135+
130136
- [@heroicons/react](https://www.npmjs.com/package/@heroicons/react): ^2.1.1
131137
- [@react-three/drei](https://www.npmjs.com/package/@react-three/drei): ^9.93.0
132138
- [@react-three/fiber](https://www.npmjs.com/package/@react-three/fiber): ^8.15.13
@@ -148,6 +154,7 @@ Useful resources and dependencies that are used in Space Portfolio.
148154
- [postcss](https://www.npmjs.com/package/postcss): ^8
149155
- [tailwindcss](https://www.npmjs.com/package/tailwindcss): ^3.3.0
150156
- [typescript](https://www.npmjs.com/package/typescript): ^5
157+
<!--- DEPENDENCIES_END --->
151158

152159
## :coffee: Buy Me a Coffee
153160

0 commit comments

Comments
 (0)