devfolio-page 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +219 -0
  2. package/dist/cli/commands/init.js +282 -0
  3. package/dist/cli/commands/render.js +105 -0
  4. package/dist/cli/commands/themes.js +40 -0
  5. package/dist/cli/commands/validate.js +86 -0
  6. package/dist/cli/helpers/validate.js +99 -0
  7. package/dist/cli/index.js +51 -0
  8. package/dist/cli/postinstall.js +20 -0
  9. package/dist/cli/schemas/portfolio.schema.js +299 -0
  10. package/dist/generator/builder.js +551 -0
  11. package/dist/generator/markdown.js +57 -0
  12. package/dist/generator/themes/dark-academia/partials/education.html +15 -0
  13. package/dist/generator/themes/dark-academia/partials/experience.html +23 -0
  14. package/dist/generator/themes/dark-academia/partials/hero.html +15 -0
  15. package/dist/generator/themes/dark-academia/partials/projects.html +17 -0
  16. package/dist/generator/themes/dark-academia/partials/skills.html +11 -0
  17. package/dist/generator/themes/dark-academia/partials/writing.html +15 -0
  18. package/dist/generator/themes/dark-academia/script.js +91 -0
  19. package/dist/generator/themes/dark-academia/styles.css +913 -0
  20. package/dist/generator/themes/dark-academia/template.html +46 -0
  21. package/dist/generator/themes/dark-academia/templates/experiments-index.html +80 -0
  22. package/dist/generator/themes/dark-academia/templates/homepage.html +125 -0
  23. package/dist/generator/themes/dark-academia/templates/project.html +101 -0
  24. package/dist/generator/themes/dark-academia/templates/projects-index.html +80 -0
  25. package/dist/generator/themes/dark-academia/templates/writing-index.html +75 -0
  26. package/dist/generator/themes/modern/partials/education.html +14 -0
  27. package/dist/generator/themes/modern/partials/experience.html +21 -0
  28. package/dist/generator/themes/modern/partials/hero.html +15 -0
  29. package/dist/generator/themes/modern/partials/projects.html +17 -0
  30. package/dist/generator/themes/modern/partials/skills.html +11 -0
  31. package/dist/generator/themes/modern/partials/writing.html +14 -0
  32. package/dist/generator/themes/modern/script.js +136 -0
  33. package/dist/generator/themes/modern/styles.css +835 -0
  34. package/dist/generator/themes/modern/template.html +59 -0
  35. package/dist/generator/themes/modern/templates/experiments-index.html +78 -0
  36. package/dist/generator/themes/modern/templates/homepage.html +125 -0
  37. package/dist/generator/themes/modern/templates/project.html +98 -0
  38. package/dist/generator/themes/modern/templates/projects-index.html +79 -0
  39. package/dist/generator/themes/modern/templates/writing-index.html +73 -0
  40. package/dist/generator/themes/srcl/partials/education.html +27 -0
  41. package/dist/generator/themes/srcl/partials/experience.html +25 -0
  42. package/dist/generator/themes/srcl/partials/hero.html +22 -0
  43. package/dist/generator/themes/srcl/partials/projects.html +24 -0
  44. package/dist/generator/themes/srcl/partials/sections/code.html +8 -0
  45. package/dist/generator/themes/srcl/partials/sections/demo.html +8 -0
  46. package/dist/generator/themes/srcl/partials/sections/gallery.html +12 -0
  47. package/dist/generator/themes/srcl/partials/sections/image.html +6 -0
  48. package/dist/generator/themes/srcl/partials/sections/interactive.html +8 -0
  49. package/dist/generator/themes/srcl/partials/sections/metrics.html +10 -0
  50. package/dist/generator/themes/srcl/partials/sections/outcomes.html +5 -0
  51. package/dist/generator/themes/srcl/partials/sections/overview.html +5 -0
  52. package/dist/generator/themes/srcl/partials/sections/process.html +5 -0
  53. package/dist/generator/themes/srcl/partials/skills.html +21 -0
  54. package/dist/generator/themes/srcl/partials/writing.html +14 -0
  55. package/dist/generator/themes/srcl/script.js +354 -0
  56. package/dist/generator/themes/srcl/styles.css +1260 -0
  57. package/dist/generator/themes/srcl/template.html +46 -0
  58. package/dist/generator/themes/srcl/templates/experiments-index.html +66 -0
  59. package/dist/generator/themes/srcl/templates/homepage.html +136 -0
  60. package/dist/generator/themes/srcl/templates/project.html +96 -0
  61. package/dist/generator/themes/srcl/templates/projects-index.html +70 -0
  62. package/dist/generator/themes/srcl/templates/writing-index.html +61 -0
  63. package/dist/types/portfolio.js +4 -0
  64. package/package.json +58 -0
  65. package/src/generator/themes/dark-academia/partials/education.html +15 -0
  66. package/src/generator/themes/dark-academia/partials/experience.html +23 -0
  67. package/src/generator/themes/dark-academia/partials/hero.html +15 -0
  68. package/src/generator/themes/dark-academia/partials/projects.html +17 -0
  69. package/src/generator/themes/dark-academia/partials/skills.html +11 -0
  70. package/src/generator/themes/dark-academia/partials/writing.html +15 -0
  71. package/src/generator/themes/dark-academia/script.js +91 -0
  72. package/src/generator/themes/dark-academia/styles.css +913 -0
  73. package/src/generator/themes/dark-academia/template.html +46 -0
  74. package/src/generator/themes/dark-academia/templates/experiments-index.html +80 -0
  75. package/src/generator/themes/dark-academia/templates/homepage.html +125 -0
  76. package/src/generator/themes/dark-academia/templates/project.html +101 -0
  77. package/src/generator/themes/dark-academia/templates/projects-index.html +80 -0
  78. package/src/generator/themes/dark-academia/templates/writing-index.html +75 -0
  79. package/src/generator/themes/modern/partials/education.html +14 -0
  80. package/src/generator/themes/modern/partials/experience.html +21 -0
  81. package/src/generator/themes/modern/partials/hero.html +15 -0
  82. package/src/generator/themes/modern/partials/projects.html +17 -0
  83. package/src/generator/themes/modern/partials/skills.html +11 -0
  84. package/src/generator/themes/modern/partials/writing.html +14 -0
  85. package/src/generator/themes/modern/script.js +136 -0
  86. package/src/generator/themes/modern/styles.css +835 -0
  87. package/src/generator/themes/modern/template.html +59 -0
  88. package/src/generator/themes/modern/templates/experiments-index.html +78 -0
  89. package/src/generator/themes/modern/templates/homepage.html +125 -0
  90. package/src/generator/themes/modern/templates/project.html +98 -0
  91. package/src/generator/themes/modern/templates/projects-index.html +79 -0
  92. package/src/generator/themes/modern/templates/writing-index.html +73 -0
  93. package/src/generator/themes/srcl/partials/education.html +27 -0
  94. package/src/generator/themes/srcl/partials/experience.html +25 -0
  95. package/src/generator/themes/srcl/partials/hero.html +22 -0
  96. package/src/generator/themes/srcl/partials/projects.html +24 -0
  97. package/src/generator/themes/srcl/partials/sections/code.html +8 -0
  98. package/src/generator/themes/srcl/partials/sections/demo.html +8 -0
  99. package/src/generator/themes/srcl/partials/sections/gallery.html +12 -0
  100. package/src/generator/themes/srcl/partials/sections/image.html +6 -0
  101. package/src/generator/themes/srcl/partials/sections/interactive.html +8 -0
  102. package/src/generator/themes/srcl/partials/sections/metrics.html +10 -0
  103. package/src/generator/themes/srcl/partials/sections/outcomes.html +5 -0
  104. package/src/generator/themes/srcl/partials/sections/overview.html +5 -0
  105. package/src/generator/themes/srcl/partials/sections/process.html +5 -0
  106. package/src/generator/themes/srcl/partials/skills.html +21 -0
  107. package/src/generator/themes/srcl/partials/writing.html +14 -0
  108. package/src/generator/themes/srcl/script.js +354 -0
  109. package/src/generator/themes/srcl/styles.css +1260 -0
  110. package/src/generator/themes/srcl/template.html +46 -0
  111. package/src/generator/themes/srcl/templates/experiments-index.html +66 -0
  112. package/src/generator/themes/srcl/templates/homepage.html +136 -0
  113. package/src/generator/themes/srcl/templates/project.html +96 -0
  114. package/src/generator/themes/srcl/templates/projects-index.html +70 -0
  115. package/src/generator/themes/srcl/templates/writing-index.html +61 -0
package/README.md ADDED
@@ -0,0 +1,219 @@
1
+ # devfolio.page
2
+
3
+ > Your portfolio as code. Version control it like software.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g devfolio-page
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Create a new portfolio folder
15
+ devfolio-page init
16
+
17
+ # Edit the portfolio config
18
+ cd my-portfolio
19
+ nano portfolio.yaml
20
+
21
+ # Generate the static site
22
+ devfolio-page render
23
+
24
+ # Open in browser
25
+ open site/index.html
26
+ ```
27
+
28
+ ## Folder Structure
29
+
30
+ After running `devfolio-page init`, you get:
31
+
32
+ ```
33
+ my-portfolio/
34
+ ├── portfolio.yaml # Your config
35
+ ├── images/ # Add images here
36
+ └── site/ # Generated after render
37
+ ├── index.html
38
+ └── assets/
39
+ ```
40
+
41
+ ## Commands
42
+
43
+ ### `devfolio-page init`
44
+
45
+ Create a new portfolio folder with config and images directory.
46
+
47
+ ```bash
48
+ devfolio-page init
49
+ ```
50
+
51
+ ### `devfolio-page render`
52
+
53
+ Generate a static website. Run from inside your portfolio folder.
54
+
55
+ ```bash
56
+ devfolio-page render # Uses portfolio.yaml → site/
57
+ devfolio-page render --theme modern # Use a different theme
58
+ devfolio-page render --output ./dist # Custom output directory
59
+ devfolio-page render other.yaml # Use a different YAML file
60
+ ```
61
+
62
+ **Options:**
63
+
64
+ - `-t, --theme <theme>` - Theme to use (srcl, modern, dark-academia)
65
+ - `-o, --output <dir>` - Output directory (default: ./site)
66
+
67
+ ### `devfolio-page validate <file>`
68
+
69
+ Validate your portfolio YAML file.
70
+
71
+ ```bash
72
+ devfolio-page validate portfolio.yaml
73
+ ```
74
+
75
+ ## YAML Schema
76
+
77
+ ```yaml
78
+ meta:
79
+ name: Your Name
80
+ title: Your Title
81
+ location: Your Location
82
+ timezone: America/Los_Angeles # optional
83
+
84
+ contact:
85
+ email: you@example.com
86
+ github: username # optional
87
+ linkedin: username # optional
88
+ twitter: username # optional
89
+ website: https://... # optional
90
+
91
+ bio: |
92
+ A few sentences about yourself.
93
+ Can be multiple lines.
94
+
95
+ sections:
96
+ experience:
97
+ - company: Company Name
98
+ role: Your Role
99
+ date:
100
+ start: 2024-01
101
+ end: present
102
+ location: City, State # optional
103
+ highlights:
104
+ - Achievement 1
105
+ - Achievement 2
106
+
107
+ projects:
108
+ - name: Project Name
109
+ url: https://github.com/... # optional
110
+ description: What the project does
111
+ tags: [React, TypeScript]
112
+ featured: true # optional
113
+
114
+ skills:
115
+ Frontend: [React, TypeScript, Next.js]
116
+ Backend: [Node.js, Python, Go]
117
+ Tools: [Git, Docker, AWS]
118
+
119
+ writing:
120
+ - title: Article Title
121
+ url: https://...
122
+ date: 2024-12
123
+ description: Brief description # optional
124
+
125
+ education:
126
+ - institution: University Name
127
+ degree: B.S. Computer Science
128
+ date:
129
+ start: 2016-09
130
+ end: 2020-05
131
+ location: City, State # optional
132
+ highlights: # optional
133
+ - Dean's List
134
+ - Relevant coursework
135
+
136
+ theme: srcl # srcl, modern, dark-academia
137
+
138
+ settings:
139
+ show_grid: false # show character grid overlay
140
+ enable_hotkeys: true # enable keyboard shortcuts
141
+ color_scheme: dark # dark or light
142
+ animate: subtle # none, subtle, or full
143
+ ```
144
+
145
+ ## Themes
146
+
147
+ ### SRCL (Default)
148
+
149
+ Terminal-inspired aesthetic from [sacred.computer](https://sacred.computer).
150
+
151
+ - Monospace typography
152
+ - Character-based spacing
153
+ - Keyboard navigation (Ctrl+T, Ctrl+G)
154
+ - Dark/light mode toggle
155
+
156
+ ### Modern
157
+
158
+ Clean, contemporary design.
159
+
160
+ - Sans-serif fonts
161
+ - Card-based layout
162
+ - Smooth animations
163
+
164
+ ### Dark Academia
165
+
166
+ Classical, scholarly design.
167
+
168
+ - Serif typography
169
+ - Warm color palette
170
+ - Book-like layout
171
+
172
+ ## Keyboard Shortcuts
173
+
174
+ When `enable_hotkeys: true`:
175
+
176
+ | Key | Action |
177
+ |-----|--------|
178
+ | `Ctrl+T` | Toggle dark/light theme |
179
+ | `Ctrl+G` | Toggle grid overlay |
180
+ | `1-9` | Jump to section |
181
+ | `Esc` | Close accordions |
182
+
183
+ ## Philosophy
184
+
185
+ Inspired by [RenderCV](https://github.com/sinaatalay/rendercv). Your portfolio should be:
186
+
187
+ - **Version controlled** - Track changes with Git
188
+ - **Content-first** - Separate content from presentation
189
+ - **Portable** - Deploy anywhere (Vercel, Netlify, GitHub Pages)
190
+ - **Fast** - Static HTML, no JavaScript required
191
+ - **Accessible** - Semantic HTML, keyboard navigation
192
+
193
+ ## Development
194
+
195
+ ```bash
196
+ # Install dependencies
197
+ npm install
198
+
199
+ # Run in dev mode
200
+ npm run dev
201
+
202
+ # Build
203
+ npm run build
204
+
205
+ # Link globally for testing
206
+ npm link
207
+ ```
208
+
209
+ ## Examples
210
+
211
+ See the `examples/` directory:
212
+
213
+ - `minimal.yaml` - Bare minimum required fields
214
+ - `full.yaml` - All sections and options
215
+ - `engineer.yaml` - Realistic software engineer example
216
+
217
+ ---
218
+
219
+ Built with [SRCL](https://sacred.computer).
@@ -0,0 +1,282 @@
1
+ import chalk from 'chalk';
2
+ import * as readline from 'readline';
3
+ import { writeFileSync, existsSync, mkdirSync } from 'fs';
4
+ import path from 'path';
5
+ const THEMES = [
6
+ {
7
+ id: 'srcl',
8
+ name: 'SRCL',
9
+ description: 'Terminal aesthetic with monospace fonts',
10
+ example: 'https://devfolio.page/portfolios/engineer-srcl/',
11
+ },
12
+ {
13
+ id: 'modern',
14
+ name: 'Modern',
15
+ description: 'Clean, contemporary design',
16
+ example: 'https://devfolio.page/portfolios/designer-modern/',
17
+ },
18
+ {
19
+ id: 'dark-academia',
20
+ name: 'Dark Academia',
21
+ description: 'Scholarly, vintage aesthetic',
22
+ example: 'https://devfolio.page/portfolios/researcher-academia/',
23
+ },
24
+ ];
25
+ function createInterface() {
26
+ return readline.createInterface({
27
+ input: process.stdin,
28
+ output: process.stdout,
29
+ });
30
+ }
31
+ function question(rl, prompt) {
32
+ return new Promise((resolve) => {
33
+ rl.question(prompt, (answer) => {
34
+ resolve(answer.trim());
35
+ });
36
+ });
37
+ }
38
+ function printHeader() {
39
+ console.log();
40
+ console.log(chalk.cyan('┌────────────────────────────────────────┐'));
41
+ console.log(chalk.cyan('│') + ' Welcome to ' + chalk.bold('devfolio.page') + '! ' + chalk.cyan('│'));
42
+ console.log(chalk.cyan('│') + ' Let\'s create your portfolio ' + chalk.cyan('│'));
43
+ console.log(chalk.cyan('└────────────────────────────────────────┘'));
44
+ console.log();
45
+ console.log('This will create a portfolio folder with your YAML config.');
46
+ console.log('You can add projects, experiments, and writing later.');
47
+ console.log();
48
+ }
49
+ function generateYaml(input) {
50
+ return `# Portfolio configuration for devfolio.page
51
+ # Run: devfolio render (from this folder)
52
+
53
+ meta:
54
+ name: ${input.name}
55
+ title: ${input.title}
56
+ location: ${input.location}
57
+ # avatar: /images/avatar.jpg
58
+ # hero_image: /images/hero.jpg
59
+
60
+ contact:
61
+ email: ${input.email}
62
+ ${input.website ? `website: ${input.website}` : '# website: https://yoursite.com'}
63
+ ${input.github ? `github: ${input.github}` : '# github: yourusername'}
64
+ ${input.linkedin ? `linkedin: ${input.linkedin}` : '# linkedin: yourusername'}
65
+ ${input.twitter ? `twitter: ${input.twitter}` : '# twitter: yourusername'}
66
+
67
+ bio: |
68
+ Write a brief introduction about yourself here.
69
+ What do you do? What are you passionate about?
70
+
71
+ about:
72
+ short: |
73
+ A brief description about yourself. This shows on the homepage.
74
+
75
+ long: |
76
+ ### Background
77
+
78
+ Tell your story here. Use markdown formatting.
79
+
80
+ ### What I Do
81
+
82
+ Describe your work and expertise.
83
+
84
+ ### Interests
85
+
86
+ Share what drives you.
87
+
88
+ # Add your projects here
89
+ # Each project can have multiple sections: overview, images, code, demos, etc.
90
+ projects:
91
+ - id: example-project # URL slug
92
+ title: Example Project
93
+ subtitle: A brief description
94
+ featured: true # Show on homepage
95
+
96
+ thumbnail: /images/projects/example/thumbnail.png
97
+ # hero: /images/projects/example/hero.png
98
+
99
+ meta:
100
+ year: 2024
101
+ role: Solo Developer
102
+ timeline: 2 weeks
103
+ tech: [React, TypeScript]
104
+ links:
105
+ github: https://github.com/yourusername/project
106
+ # demo: https://project-demo.com
107
+
108
+ sections:
109
+ - type: overview
110
+ content: |
111
+ ## The Problem
112
+
113
+ Describe the problem you solved.
114
+
115
+ ## The Solution
116
+
117
+ Explain your approach.
118
+
119
+ # Add more sections: image, gallery, code, metrics, etc.
120
+ # See documentation for all section types
121
+
122
+ # Add side projects and experiments
123
+ experiments:
124
+ - title: Cool Experiment
125
+ description: A fun side project
126
+ # image: /images/experiments/cool.png
127
+ github: https://github.com/yourusername/experiment
128
+ tags: [javascript, fun]
129
+
130
+ sections:
131
+ # Add your work experience
132
+ experience:
133
+ - company: Company Name
134
+ role: Your Role
135
+ date:
136
+ start: 2024-01
137
+ end: present
138
+ location: City, Country
139
+ highlights:
140
+ - Achievement 1
141
+ - Achievement 2
142
+
143
+ # Add your skills by category
144
+ skills:
145
+ Languages: [TypeScript, Python]
146
+ Tools: [Git, Docker]
147
+
148
+ # Add articles or blog posts
149
+ writing:
150
+ - title: Blog Post Title
151
+ url: https://yourblog.com/post
152
+ date: 2024-12
153
+ excerpt: A brief excerpt of the post
154
+ # cover: /images/writing/post.png
155
+ tags: [tech, tutorial]
156
+ featured: true
157
+
158
+ # Add your education (optional)
159
+ # education:
160
+ # - institution: University Name
161
+ # degree: B.S. Computer Science
162
+ # date:
163
+ # start: 2016-09
164
+ # end: 2020-05
165
+
166
+ # Choose your theme
167
+ theme: ${input.theme} # or: srcl, modern, dark-academia
168
+
169
+ # Configure layout
170
+ layout:
171
+ homepage_style: hero # or: grid, minimal
172
+ project_layout: case-study # or: grid, list
173
+ show_experiments: true
174
+ show_timeline: false
175
+
176
+ settings:
177
+ show_grid: false
178
+ enable_hotkeys: true
179
+ color_scheme: dark
180
+ animate: subtle
181
+ `;
182
+ }
183
+ export async function initCommand() {
184
+ printHeader();
185
+ const rl = createInterface();
186
+ try {
187
+ // Ask for folder name first
188
+ let folderName = await question(rl, chalk.cyan('? ') + 'Portfolio folder name? ' + chalk.dim('(portfolio) '));
189
+ if (!folderName) {
190
+ folderName = 'portfolio';
191
+ }
192
+ if (existsSync(folderName)) {
193
+ console.log(chalk.red('✗') + ` ${folderName}/ already exists`);
194
+ console.log(chalk.dim(' Choose a different name or delete the existing folder'));
195
+ rl.close();
196
+ process.exit(1);
197
+ }
198
+ const name = await question(rl, chalk.cyan('? ') + 'What\'s your name? ');
199
+ if (!name) {
200
+ console.log(chalk.red('✗') + ' Name is required');
201
+ process.exit(1);
202
+ }
203
+ const title = await question(rl, chalk.cyan('? ') + 'What\'s your title/role? ');
204
+ if (!title) {
205
+ console.log(chalk.red('✗') + ' Title is required');
206
+ process.exit(1);
207
+ }
208
+ const location = await question(rl, chalk.cyan('? ') + 'Where are you located? ');
209
+ if (!location) {
210
+ console.log(chalk.red('✗') + ' Location is required');
211
+ process.exit(1);
212
+ }
213
+ const email = await question(rl, chalk.cyan('? ') + 'What\'s your email? ');
214
+ if (!email) {
215
+ console.log(chalk.red('✗') + ' Email is required');
216
+ process.exit(1);
217
+ }
218
+ // Theme selection
219
+ console.log();
220
+ console.log(chalk.cyan('? ') + 'Which theme would you like?');
221
+ console.log();
222
+ THEMES.forEach((theme, index) => {
223
+ console.log(` ${chalk.cyan(`[${index + 1}]`)} ${chalk.bold(theme.name)} - ${theme.description}`);
224
+ console.log(` ${chalk.dim('See example:')} ${chalk.dim.underline(theme.example)}`);
225
+ });
226
+ console.log();
227
+ let themeChoice = await question(rl, chalk.cyan('? ') + `Enter 1-${THEMES.length} ` + chalk.dim('(1) '));
228
+ if (!themeChoice)
229
+ themeChoice = '1';
230
+ const themeIndex = parseInt(themeChoice, 10) - 1;
231
+ let theme = THEMES[0].id;
232
+ if (themeIndex >= 0 && themeIndex < THEMES.length) {
233
+ theme = THEMES[themeIndex].id;
234
+ }
235
+ else {
236
+ console.log(chalk.yellow(' Invalid choice, using SRCL'));
237
+ }
238
+ console.log(chalk.dim('\n Optional fields (press Enter to skip)'));
239
+ const github = await question(rl, chalk.cyan('? ') + 'GitHub username? ' + chalk.dim('(optional) '));
240
+ const linkedin = await question(rl, chalk.cyan('? ') + 'LinkedIn username? ' + chalk.dim('(optional) '));
241
+ const twitter = await question(rl, chalk.cyan('? ') + 'Twitter username? ' + chalk.dim('(optional) '));
242
+ const website = await question(rl, chalk.cyan('? ') + 'Website URL? ' + chalk.dim('(optional) '));
243
+ rl.close();
244
+ const input = {
245
+ name,
246
+ title,
247
+ location,
248
+ email,
249
+ github: github || undefined,
250
+ linkedin: linkedin || undefined,
251
+ twitter: twitter || undefined,
252
+ website: website || undefined,
253
+ theme,
254
+ };
255
+ // Create folder structure
256
+ mkdirSync(folderName, { recursive: true });
257
+ mkdirSync(path.join(folderName, 'images'), { recursive: true });
258
+ const yamlPath = path.join(folderName, 'portfolio.yaml');
259
+ const yaml = generateYaml(input);
260
+ writeFileSync(yamlPath, yaml);
261
+ console.log();
262
+ console.log(chalk.green('✓') + ' Created ' + chalk.bold(folderName + '/'));
263
+ console.log();
264
+ console.log(chalk.dim(' Structure:'));
265
+ console.log(chalk.dim(' ├── ') + chalk.cyan('portfolio.yaml') + chalk.dim(' (your config)'));
266
+ console.log(chalk.dim(' ├── ') + chalk.cyan('images/') + chalk.dim(' (add images here)'));
267
+ console.log(chalk.dim(' └── ') + chalk.cyan('site/') + chalk.dim(' (generated after render)'));
268
+ console.log();
269
+ console.log(chalk.bold('Next steps:'));
270
+ console.log(chalk.dim(' 1.') + ' Edit ' + chalk.cyan(`${folderName}/portfolio.yaml`) + ' and add your projects');
271
+ console.log(chalk.dim(' 2.') + ' Add images to ' + chalk.cyan(`${folderName}/images/`));
272
+ console.log(chalk.dim(' 3.') + ' Run: ' + chalk.cyan(`cd ${folderName} && devfolio render`));
273
+ console.log(chalk.dim(' 4.') + ' Open: ' + chalk.cyan(`${folderName}/site/index.html`) + ' to view');
274
+ console.log();
275
+ console.log('Tip: Check ' + chalk.cyan('https://devfolio.page/portfolios/') + ' for examples');
276
+ console.log();
277
+ }
278
+ catch (err) {
279
+ rl.close();
280
+ throw err;
281
+ }
282
+ }
@@ -0,0 +1,105 @@
1
+ import chalk from 'chalk';
2
+ import path from 'path';
3
+ import { existsSync } from 'fs';
4
+ import { validatePortfolio, ValidationError, FileError, ParseError, } from '../helpers/validate.js';
5
+ import { buildStaticSite } from '../../generator/builder.js';
6
+ const TOTAL_STEPS = 4;
7
+ function logStep(step, message) {
8
+ console.log(chalk.cyan(`[${step}/${TOTAL_STEPS}]`) + ' ' + message);
9
+ }
10
+ function findAvailableOutputDir(baseDir) {
11
+ const resolved = path.resolve(baseDir);
12
+ // If it doesn't exist, use it as-is
13
+ if (!existsSync(resolved)) {
14
+ return resolved;
15
+ }
16
+ // Find next available numbered directory
17
+ let counter = 1;
18
+ while (existsSync(`${resolved}${counter}`)) {
19
+ counter++;
20
+ }
21
+ return `${resolved}${counter}`;
22
+ }
23
+ export async function renderCommand(file, options) {
24
+ console.log();
25
+ try {
26
+ // Step 1: Validate
27
+ logStep(1, 'Validating portfolio...');
28
+ const portfolio = validatePortfolio(file);
29
+ console.log(chalk.green(' ✓') + ' Valid');
30
+ // Step 2: Load theme
31
+ const theme = options.theme || portfolio.theme || 'srcl';
32
+ logStep(2, `Loading theme: ${theme}`);
33
+ console.log(chalk.green(' ✓') + ' Theme loaded');
34
+ // Step 3: Generate HTML
35
+ const outputDir = findAvailableOutputDir(options.output);
36
+ logStep(3, 'Generating HTML...');
37
+ const result = await buildStaticSite(portfolio, {
38
+ outputDir,
39
+ theme,
40
+ });
41
+ console.log(chalk.green(' ✓') + ` Generated ${result.files.length} files`);
42
+ // Step 4: Done
43
+ logStep(4, 'Copying assets...');
44
+ console.log(chalk.green(' ✓') + ' Assets copied');
45
+ // Success output
46
+ console.log();
47
+ console.log(chalk.green('✓') + ' Portfolio rendered successfully!');
48
+ console.log();
49
+ console.log(' Output: ' + chalk.cyan(outputDir));
50
+ console.log(' Theme: ' + chalk.cyan(theme));
51
+ console.log();
52
+ console.log(chalk.bold('Files created:'));
53
+ result.files.forEach((f) => {
54
+ console.log(chalk.dim(' •') + ` ${f}`);
55
+ });
56
+ console.log();
57
+ console.log(chalk.bold('Next steps:'));
58
+ console.log(' • Open ' + chalk.cyan(`${outputDir}/index.html`) + ' in your browser');
59
+ console.log(' • Or run ' + chalk.cyan(`npx serve ${outputDir}`) + ' for a local server');
60
+ console.log(' • Deploy the ' + chalk.cyan('site/') + ' folder to Vercel, Netlify, or GitHub Pages');
61
+ console.log();
62
+ }
63
+ catch (err) {
64
+ console.log();
65
+ if (err instanceof ValidationError) {
66
+ console.log(chalk.red('✗') + ' Validation failed\n');
67
+ for (const error of err.errors) {
68
+ console.log(` ${chalk.red('•')} ${chalk.yellow(error.path)} - ${error.message}`);
69
+ }
70
+ if (err.errors[0]?.hint) {
71
+ console.log();
72
+ console.log(chalk.dim(' Expected format:'));
73
+ err.errors[0].hint.split('\n').forEach((line) => {
74
+ console.log(chalk.dim(` ${line}`));
75
+ });
76
+ }
77
+ console.log();
78
+ process.exit(1);
79
+ }
80
+ if (err instanceof FileError) {
81
+ console.log(chalk.red('✗') + ` ${err.message}`);
82
+ process.exit(1);
83
+ }
84
+ if (err instanceof ParseError) {
85
+ console.log(chalk.red('✗') + ' YAML syntax error');
86
+ if (err.line !== undefined) {
87
+ console.log(` Line ${err.line + 1}: ${err.message}`);
88
+ }
89
+ else {
90
+ console.log(` ${err.message}`);
91
+ }
92
+ console.log();
93
+ process.exit(1);
94
+ }
95
+ // Build errors
96
+ if (err instanceof Error) {
97
+ console.log(chalk.red('✗') + ' Build failed');
98
+ console.log();
99
+ console.log(` ${err.message}`);
100
+ console.log();
101
+ process.exit(1);
102
+ }
103
+ throw err;
104
+ }
105
+ }
@@ -0,0 +1,40 @@
1
+ import chalk from 'chalk';
2
+ const THEMES = [
3
+ {
4
+ id: 'srcl',
5
+ name: 'SRCL',
6
+ description: 'Terminal aesthetic with monospace fonts',
7
+ example: 'https://devfolio.page/portfolios/engineer-srcl/',
8
+ },
9
+ {
10
+ id: 'modern',
11
+ name: 'Modern',
12
+ description: 'Clean, contemporary design',
13
+ example: 'https://devfolio.page/portfolios/designer-modern/',
14
+ },
15
+ {
16
+ id: 'dark-academia',
17
+ name: 'Dark Academia',
18
+ description: 'Scholarly, vintage aesthetic',
19
+ example: 'https://devfolio.page/portfolios/researcher-academia/',
20
+ },
21
+ ];
22
+ export function themesCommand() {
23
+ console.log();
24
+ console.log(chalk.bold('Available Themes'));
25
+ console.log();
26
+ THEMES.forEach((theme) => {
27
+ console.log(` ${chalk.cyan(theme.id)}`);
28
+ console.log(` ${theme.description}`);
29
+ console.log(` ${chalk.dim('Example:')} ${chalk.dim.underline(theme.example)}`);
30
+ console.log();
31
+ });
32
+ console.log(chalk.dim('Usage:'));
33
+ console.log(` devfolio render ${chalk.cyan('--theme srcl')}`);
34
+ console.log(` devfolio render ${chalk.cyan('--theme modern')}`);
35
+ console.log(` devfolio render ${chalk.cyan('--theme dark-academia')}`);
36
+ console.log();
37
+ console.log(chalk.dim('Or set the theme in your portfolio.yaml:'));
38
+ console.log(chalk.dim(' theme: srcl'));
39
+ console.log();
40
+ }