devfolio-page 0.1.4 → 0.2.1

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.
package/README.md CHANGED
@@ -18,11 +18,11 @@ devfolio-page init
18
18
  cd my-portfolio
19
19
  nano portfolio.yaml
20
20
 
21
- # Generate the static site
22
- devfolio-page render
21
+ # Start development server with auto-rebuild
22
+ devfolio-page dev
23
23
 
24
- # Open in browser
25
- open site/index.html
24
+ # Or generate the static site once
25
+ devfolio-page render
26
26
  ```
27
27
 
28
28
  ## Folder Structure
@@ -48,6 +48,27 @@ Create a new portfolio folder with config and images directory.
48
48
  devfolio-page init
49
49
  ```
50
50
 
51
+ ### `devfolio-page dev`
52
+
53
+ Start development server with file watching and auto-rebuild.
54
+
55
+ ```bash
56
+ devfolio-page dev # Start server on port 3000
57
+ devfolio-page dev --port 8080 # Use custom port
58
+ devfolio-page dev --theme modern # Use a specific theme
59
+ ```
60
+
61
+ **Options:**
62
+
63
+ - `-p, --port <port>` - Port for dev server (default: 3000)
64
+ - `-t, --theme <theme>` - Theme to use (srcl, modern, dark-academia)
65
+ - `-o, --output <dir>` - Output directory (default: ./site)
66
+
67
+ The dev server will:
68
+ - Watch `portfolio.yaml` and `images/` for changes
69
+ - Automatically rebuild your site when files change
70
+ - Serve your site at `http://localhost:3000`
71
+
51
72
  ### `devfolio-page render`
52
73
 
53
74
  Generate a static website. Run from inside your portfolio folder.
@@ -0,0 +1,162 @@
1
+ import chalk from 'chalk';
2
+ import chokidar from 'chokidar';
3
+ import { existsSync } from 'fs';
4
+ import http from 'http';
5
+ import path from 'path';
6
+ import { buildStaticSiteInMemory } from '../../generator/builder.js';
7
+ import { validatePortfolio } from '../helpers/validate.js';
8
+ let isBuilding = false;
9
+ let buildQueued = false;
10
+ let siteCache = new Map();
11
+ async function rebuild(file, options) {
12
+ // If already building, queue another build
13
+ if (isBuilding) {
14
+ buildQueued = true;
15
+ return;
16
+ }
17
+ isBuilding = true;
18
+ try {
19
+ console.log();
20
+ console.log(chalk.dim(`[${new Date().toLocaleTimeString()}]`) + ' File changed: ' + chalk.cyan(file));
21
+ console.log(chalk.dim('Rebuilding in memory...'));
22
+ console.log();
23
+ // Validate and build in memory
24
+ const portfolio = validatePortfolio('portfolio.yaml');
25
+ const result = await buildStaticSiteInMemory(portfolio, {
26
+ theme: options.theme,
27
+ });
28
+ // Update cache
29
+ siteCache = result.files;
30
+ console.log();
31
+ console.log(chalk.green('✓') + ` Rebuilt ${result.fileList.length} files in memory`);
32
+ console.log();
33
+ }
34
+ catch (error) {
35
+ console.error(chalk.red('✗') + ' Build failed:');
36
+ console.error(error instanceof Error ? error.message : String(error));
37
+ console.log();
38
+ }
39
+ finally {
40
+ isBuilding = false;
41
+ // If a build was queued while we were building, run it now
42
+ if (buildQueued) {
43
+ buildQueued = false;
44
+ setTimeout(() => rebuild(file, options), 100);
45
+ }
46
+ }
47
+ }
48
+ function startServer(port) {
49
+ const server = http.createServer(async (req, res) => {
50
+ try {
51
+ // Default to index.html
52
+ let filePath = req.url === '/' ? 'index.html' : (req.url || 'index.html');
53
+ // Remove leading slash and query string
54
+ filePath = filePath.replace(/^\//, '').split('?')[0];
55
+ // Check if file exists in cache
56
+ if (!siteCache.has(filePath)) {
57
+ res.writeHead(404);
58
+ res.end('Not found');
59
+ return;
60
+ }
61
+ // Get file from cache
62
+ const content = siteCache.get(filePath);
63
+ // Set content type based on file extension
64
+ const ext = path.extname(filePath);
65
+ const contentTypes = {
66
+ '.html': 'text/html',
67
+ '.css': 'text/css',
68
+ '.js': 'application/javascript',
69
+ '.json': 'application/json',
70
+ '.png': 'image/png',
71
+ '.jpg': 'image/jpeg',
72
+ '.jpeg': 'image/jpeg',
73
+ '.gif': 'image/gif',
74
+ '.svg': 'image/svg+xml',
75
+ '.ico': 'image/x-icon',
76
+ '.woff': 'font/woff',
77
+ '.woff2': 'font/woff2',
78
+ '.ttf': 'font/ttf',
79
+ '.otf': 'font/otf',
80
+ };
81
+ res.writeHead(200, { 'Content-Type': contentTypes[ext] || 'application/octet-stream' });
82
+ res.end(content);
83
+ }
84
+ catch (error) {
85
+ console.error('Server error:', error);
86
+ res.writeHead(500);
87
+ res.end('Internal server error');
88
+ }
89
+ });
90
+ server.listen(port);
91
+ return server;
92
+ }
93
+ export async function devCommand(file = 'portfolio.yaml', options = {}) {
94
+ const port = options.port || 3000;
95
+ // Check if portfolio.yaml exists
96
+ if (!existsSync(file)) {
97
+ console.error(chalk.red('✗') + ` File not found: ${file}`);
98
+ console.log();
99
+ console.log('Make sure you are in a portfolio directory with a portfolio.yaml file.');
100
+ console.log('Run ' + chalk.cyan('devfolio-page init') + ' to create a new portfolio.');
101
+ process.exit(1);
102
+ }
103
+ console.log();
104
+ console.log(chalk.cyan('┌─────────────────────────────────────────────────────────┐'));
105
+ console.log(chalk.cyan('│') + ' ' + chalk.bold('devfolio.page') + ' - Development Mode ' + chalk.cyan('│'));
106
+ console.log(chalk.cyan('└─────────────────────────────────────────────────────────┘'));
107
+ console.log();
108
+ // Initial build
109
+ console.log(chalk.bold('Building initial site in memory...'));
110
+ console.log();
111
+ try {
112
+ const portfolio = validatePortfolio(file);
113
+ const result = await buildStaticSiteInMemory(portfolio, {
114
+ theme: options.theme,
115
+ });
116
+ siteCache = result.files;
117
+ console.log(chalk.green(' ✓') + ` Built ${result.fileList.length} files`);
118
+ }
119
+ catch (error) {
120
+ console.error(chalk.red('✗') + ' Initial build failed:');
121
+ console.error(error instanceof Error ? error.message : String(error));
122
+ process.exit(1);
123
+ }
124
+ // Start server
125
+ const server = startServer(port);
126
+ console.log();
127
+ console.log(chalk.green('✓') + ' Dev server started!');
128
+ console.log();
129
+ console.log(' ' + chalk.bold('Local:') + ' ' + chalk.cyan(`http://localhost:${port}`));
130
+ console.log(' ' + chalk.bold('Mode:') + ' ' + chalk.dim('In-memory (no site/ folder)'));
131
+ console.log();
132
+ console.log(chalk.dim('Watching for changes...'));
133
+ console.log(chalk.dim('Press Ctrl+C to stop'));
134
+ console.log();
135
+ // Watch for file changes
136
+ const watcher = chokidar.watch([file, 'images/**/*'], {
137
+ persistent: true,
138
+ ignoreInitial: true,
139
+ awaitWriteFinish: {
140
+ stabilityThreshold: 100,
141
+ pollInterval: 100,
142
+ },
143
+ });
144
+ watcher.on('change', (changedFile) => {
145
+ rebuild(changedFile, options);
146
+ });
147
+ watcher.on('add', (changedFile) => {
148
+ rebuild(changedFile, options);
149
+ });
150
+ watcher.on('unlink', (changedFile) => {
151
+ rebuild(changedFile, options);
152
+ });
153
+ // Handle process exit
154
+ process.on('SIGINT', () => {
155
+ console.log();
156
+ console.log();
157
+ console.log(chalk.dim('Shutting down...'));
158
+ watcher.close();
159
+ server.close();
160
+ process.exit(0);
161
+ });
162
+ }
package/dist/cli/index.js CHANGED
@@ -5,11 +5,12 @@ import { initCommand } from './commands/init.js';
5
5
  import { validateCommand } from './commands/validate.js';
6
6
  import { renderCommand } from './commands/render.js';
7
7
  import { themesCommand } from './commands/themes.js';
8
+ import { devCommand } from './commands/dev.js';
8
9
  const program = new Command();
9
10
  program
10
11
  .name('devfolio-page')
11
12
  .description('Your portfolio as code. Version control it like software.')
12
- .version('0.1.4');
13
+ .version('0.2.1');
13
14
  program
14
15
  .command('init')
15
16
  .description('Create a new portfolio.yaml with interactive prompts')
@@ -28,6 +29,13 @@ program
28
29
  .command('themes')
29
30
  .description('List available themes with examples')
30
31
  .action(themesCommand);
32
+ program
33
+ .command('dev [file]')
34
+ .description('Start development server with file watching')
35
+ .option('-p, --port <port>', 'Port for dev server', '3000')
36
+ .option('-t, --theme <theme>', 'Theme to use (defaults to YAML theme or srcl)')
37
+ .option('-o, --output <dir>', 'Output directory', './site')
38
+ .action((file, options) => devCommand(file || 'portfolio.yaml', options));
31
39
  // Show welcome message if no arguments provided
32
40
  if (process.argv.length === 2) {
33
41
  console.log();
@@ -38,6 +46,7 @@ if (process.argv.length === 2) {
38
46
  console.log(chalk.bold('Commands:'));
39
47
  console.log();
40
48
  console.log(' ' + chalk.cyan('devfolio-page init') + ' Create a new portfolio folder');
49
+ console.log(' ' + chalk.cyan('devfolio-page dev') + ' Start dev server with auto-rebuild');
41
50
  console.log(' ' + chalk.cyan('devfolio-page render') + ' Generate site (uses portfolio.yaml)');
42
51
  console.log(' ' + chalk.cyan('devfolio-page themes') + ' List available themes');
43
52
  console.log(' ' + chalk.cyan('devfolio-page validate') + ' <file> Validate a portfolio YAML file');
@@ -11,6 +11,7 @@ log();
11
11
  log(chalk.bold('Available commands:'));
12
12
  log();
13
13
  log(' ' + chalk.cyan('devfolio-page init') + ' Create a new portfolio folder');
14
+ log(' ' + chalk.cyan('devfolio-page dev') + ' Start dev server with auto-rebuild');
14
15
  log(' ' + chalk.cyan('devfolio-page render') + ' Generate static site from YAML');
15
16
  log(' ' + chalk.cyan('devfolio-page validate') + ' <file> Validate a portfolio YAML file');
16
17
  log(' ' + chalk.cyan('devfolio-page --help') + ' Show all options');
@@ -33,6 +33,34 @@ export async function buildStaticSite(portfolio, options) {
33
33
  return { outputDir, files };
34
34
  }
35
35
  // =============================================================================
36
+ // In-Memory Build Function (for dev mode)
37
+ // =============================================================================
38
+ export async function buildStaticSiteInMemory(portfolio, options) {
39
+ const theme = options.theme || portfolio.theme || 'srcl';
40
+ const themePath = path.join(import.meta.dirname, 'themes', theme);
41
+ const files = new Map();
42
+ const fileList = [];
43
+ // 1. Copy theme assets (CSS, JS)
44
+ const assetFiles = await copyThemeAssetsInMemory(themePath, portfolio.settings, files);
45
+ fileList.push(...assetFiles);
46
+ // 2. Copy fonts
47
+ const fontFiles = await copyFontsInMemory(files);
48
+ fileList.push(...fontFiles);
49
+ // 3. Generate pages based on portfolio structure
50
+ const hasRichProjects = (portfolio.projects?.length ?? 0) > 0;
51
+ if (hasRichProjects) {
52
+ // Multi-page site with project case studies
53
+ const pageFiles = await generateMultiPageSiteInMemory(portfolio, themePath, theme, files);
54
+ fileList.push(...pageFiles);
55
+ }
56
+ else {
57
+ // Single-page site (backwards compatible)
58
+ const pageFiles = await generateSinglePageSiteInMemory(portfolio, themePath, theme, files);
59
+ fileList.push(...pageFiles);
60
+ }
61
+ return { files, fileList };
62
+ }
63
+ // =============================================================================
36
64
  // Directory Structure
37
65
  // =============================================================================
38
66
  async function createDirectoryStructure(outputDir) {
@@ -554,3 +582,254 @@ async function fileExists(filePath) {
554
582
  return false;
555
583
  }
556
584
  }
585
+ // =============================================================================
586
+ // In-Memory Build Helpers
587
+ // =============================================================================
588
+ async function copyThemeAssetsInMemory(themePath, settings, files) {
589
+ const fileList = [];
590
+ // Copy CSS
591
+ const cssPath = path.join(themePath, 'styles.css');
592
+ if (await fileExists(cssPath)) {
593
+ const content = await fs.readFile(cssPath);
594
+ files.set('assets/styles.css', content);
595
+ fileList.push('assets/styles.css');
596
+ }
597
+ // Copy JS
598
+ const jsPath = path.join(themePath, 'script.js');
599
+ if (await fileExists(jsPath)) {
600
+ const content = await fs.readFile(jsPath);
601
+ files.set('assets/script.js', content);
602
+ fileList.push('assets/script.js');
603
+ }
604
+ return fileList;
605
+ }
606
+ async function copyFontsInMemory(files) {
607
+ const fileList = [];
608
+ const srcFontDir = '/Users/louanne/www-sacred/public/fonts';
609
+ try {
610
+ const fontFiles = await fs.readdir(srcFontDir);
611
+ for (const file of fontFiles) {
612
+ if (file.endsWith('.woff') ||
613
+ file.endsWith('.woff2') ||
614
+ file.endsWith('.ttf') ||
615
+ file.endsWith('.otf') ||
616
+ file.endsWith('.css')) {
617
+ const content = await fs.readFile(path.join(srcFontDir, file));
618
+ files.set(`assets/fonts/${file}`, content);
619
+ fileList.push(`assets/fonts/${file}`);
620
+ }
621
+ }
622
+ }
623
+ catch {
624
+ // Fonts directory doesn't exist or isn't accessible - that's fine
625
+ }
626
+ return fileList;
627
+ }
628
+ async function generateSinglePageSiteInMemory(portfolio, themePath, theme, files) {
629
+ const fileList = [];
630
+ // Load template and partials
631
+ const template = await fs.readFile(path.join(themePath, 'template.html'), 'utf-8');
632
+ const partials = await loadPartials(themePath);
633
+ // Prepare template data
634
+ const templateData = prepareTemplateData(portfolio);
635
+ // Render each partial
636
+ const renderedPartials = renderPartials(partials, templateData);
637
+ // Render main template with settings
638
+ const settings = portfolio.settings || {};
639
+ const defaultColorScheme = theme === 'dark-academia' ? 'light' : 'dark';
640
+ const html = Mustache.render(template, {
641
+ ...templateData,
642
+ ...renderedPartials,
643
+ colorScheme: settings.color_scheme || defaultColorScheme,
644
+ showGrid: settings.show_grid || false,
645
+ enableHotkeys: settings.enable_hotkeys !== false,
646
+ animate: settings.animate || 'subtle',
647
+ hasExperience: (portfolio.sections.experience?.length ?? 0) > 0,
648
+ hasProjects: (portfolio.sections.projects?.length ?? 0) > 0,
649
+ hasSkills: portfolio.sections.skills && Object.keys(portfolio.sections.skills).length > 0,
650
+ hasWriting: (portfolio.sections.writing?.length ?? 0) > 0,
651
+ hasEducation: (portfolio.sections.education?.length ?? 0) > 0,
652
+ });
653
+ files.set('index.html', Buffer.from(html, 'utf-8'));
654
+ fileList.push('index.html');
655
+ return fileList;
656
+ }
657
+ async function generateMultiPageSiteInMemory(portfolio, themePath, theme, files) {
658
+ const fileList = [];
659
+ // Check if theme has multi-page templates
660
+ const hasMultiPageTemplates = await fileExists(path.join(themePath, 'templates/homepage.html'));
661
+ if (!hasMultiPageTemplates) {
662
+ return generateSinglePageSiteInMemory(portfolio, themePath, theme, files);
663
+ }
664
+ // Generate homepage
665
+ const homepageFiles = await generateHomepageInMemory(portfolio, themePath, theme, files);
666
+ fileList.push(...homepageFiles);
667
+ // Generate project pages
668
+ const projectFiles = await generateProjectPagesInMemory(portfolio, themePath, theme, files);
669
+ fileList.push(...projectFiles);
670
+ // Generate projects index
671
+ const projectIndexFiles = await generateProjectsIndexInMemory(portfolio, themePath, theme, files);
672
+ fileList.push(...projectIndexFiles);
673
+ // Generate experiments index
674
+ const experimentsIndexFiles = await generateExperimentsIndexInMemory(portfolio, themePath, theme, files);
675
+ fileList.push(...experimentsIndexFiles);
676
+ // Generate writing index
677
+ const writingIndexFiles = await generateWritingIndexInMemory(portfolio, themePath, theme, files);
678
+ fileList.push(...writingIndexFiles);
679
+ // Copy user images
680
+ await copyUserImagesInMemory(portfolio, files);
681
+ return fileList;
682
+ }
683
+ async function generateHomepageInMemory(portfolio, themePath, theme, files) {
684
+ const templatePath = path.join(themePath, 'templates/homepage.html');
685
+ if (!(await fileExists(templatePath))) {
686
+ return [];
687
+ }
688
+ const template = await fs.readFile(templatePath, 'utf-8');
689
+ const settings = portfolio.settings || {};
690
+ const defaultColorScheme = theme === 'dark-academia' ? 'light' : 'dark';
691
+ const data = {
692
+ name: portfolio.meta.name,
693
+ title: portfolio.meta.title,
694
+ location: portfolio.meta.location,
695
+ timezone: portfolio.meta.timezone,
696
+ email: portfolio.contact.email,
697
+ website: portfolio.contact.website,
698
+ github: portfolio.contact.github,
699
+ linkedin: portfolio.contact.linkedin,
700
+ twitter: portfolio.contact.twitter,
701
+ bio: portfolio.bio,
702
+ bio_html: parseBio(portfolio.bio),
703
+ about_short: portfolio.about?.short || '',
704
+ featured_projects: portfolio.projects?.filter((p) => p.featured) || [],
705
+ featured_writing: portfolio.sections.writing?.filter((w) => 'featured' in w && w.featured) || [],
706
+ show_experiments: portfolio.layout?.show_experiments,
707
+ experiments: portfolio.experiments?.slice(0, 4) || [],
708
+ colorScheme: settings.color_scheme || defaultColorScheme,
709
+ showGrid: settings.show_grid || false,
710
+ enableHotkeys: settings.enable_hotkeys !== false,
711
+ animate: settings.animate || 'subtle',
712
+ hasProjects: (portfolio.projects?.length ?? 0) > 0,
713
+ hasExperiments: (portfolio.experiments?.length ?? 0) > 0,
714
+ hasWriting: (portfolio.sections.writing?.length ?? 0) > 0,
715
+ };
716
+ const html = Mustache.render(template, data);
717
+ files.set('index.html', Buffer.from(html, 'utf-8'));
718
+ return ['index.html'];
719
+ }
720
+ async function generateProjectPagesInMemory(portfolio, themePath, theme, files) {
721
+ if (!portfolio.projects || portfolio.projects.length === 0) {
722
+ return [];
723
+ }
724
+ const templatePath = path.join(themePath, 'templates/project.html');
725
+ if (!(await fileExists(templatePath))) {
726
+ return [];
727
+ }
728
+ const template = await fs.readFile(templatePath, 'utf-8');
729
+ const partials = await loadProjectPartials(themePath);
730
+ const settings = portfolio.settings || {};
731
+ const defaultColorScheme = theme === 'dark-academia' ? 'light' : 'dark';
732
+ const fileList = [];
733
+ for (const project of portfolio.projects) {
734
+ const sectionsHtml = project.sections
735
+ .map((section) => renderContentSection(section, partials))
736
+ .join('\n');
737
+ const data = {
738
+ ...project,
739
+ sections_html: sectionsHtml,
740
+ site_name: portfolio.meta.name,
741
+ nav_links: generateNavLinks(portfolio, true, 'project'),
742
+ colorScheme: settings.color_scheme || defaultColorScheme,
743
+ showGrid: settings.show_grid || false,
744
+ enableHotkeys: settings.enable_hotkeys !== false,
745
+ };
746
+ const html = Mustache.render(template, data);
747
+ const filename = `projects/${project.id}.html`;
748
+ files.set(filename, Buffer.from(html, 'utf-8'));
749
+ fileList.push(filename);
750
+ }
751
+ return fileList;
752
+ }
753
+ async function generateProjectsIndexInMemory(portfolio, themePath, theme, files) {
754
+ const templatePath = path.join(themePath, 'templates/projects-index.html');
755
+ if (!(await fileExists(templatePath))) {
756
+ return [];
757
+ }
758
+ const template = await fs.readFile(templatePath, 'utf-8');
759
+ const settings = portfolio.settings || {};
760
+ const defaultColorScheme = theme === 'dark-academia' ? 'light' : 'dark';
761
+ const data = {
762
+ site_name: portfolio.meta.name,
763
+ projects: portfolio.projects || [],
764
+ colorScheme: settings.color_scheme || defaultColorScheme,
765
+ nav_links: generateNavLinks(portfolio, true, 'projects'),
766
+ };
767
+ const html = Mustache.render(template, data);
768
+ files.set('projects/index.html', Buffer.from(html, 'utf-8'));
769
+ return ['projects/index.html'];
770
+ }
771
+ async function generateExperimentsIndexInMemory(portfolio, themePath, theme, files) {
772
+ if (!portfolio.experiments || portfolio.experiments.length === 0) {
773
+ return [];
774
+ }
775
+ const templatePath = path.join(themePath, 'templates/experiments-index.html');
776
+ if (!(await fileExists(templatePath))) {
777
+ return [];
778
+ }
779
+ const template = await fs.readFile(templatePath, 'utf-8');
780
+ const settings = portfolio.settings || {};
781
+ const defaultColorScheme = theme === 'dark-academia' ? 'light' : 'dark';
782
+ const data = {
783
+ site_name: portfolio.meta.name,
784
+ experiments: portfolio.experiments || [],
785
+ colorScheme: settings.color_scheme || defaultColorScheme,
786
+ nav_links: generateNavLinks(portfolio, true, 'experiments'),
787
+ };
788
+ const html = Mustache.render(template, data);
789
+ files.set('experiments/index.html', Buffer.from(html, 'utf-8'));
790
+ return ['experiments/index.html'];
791
+ }
792
+ async function generateWritingIndexInMemory(portfolio, themePath, theme, files) {
793
+ if (!portfolio.sections.writing || portfolio.sections.writing.length === 0) {
794
+ return [];
795
+ }
796
+ const templatePath = path.join(themePath, 'templates/writing-index.html');
797
+ if (!(await fileExists(templatePath))) {
798
+ return [];
799
+ }
800
+ const template = await fs.readFile(templatePath, 'utf-8');
801
+ const settings = portfolio.settings || {};
802
+ const defaultColorScheme = theme === 'dark-academia' ? 'light' : 'dark';
803
+ const data = {
804
+ site_name: portfolio.meta.name,
805
+ writing: portfolio.sections.writing || [],
806
+ colorScheme: settings.color_scheme || defaultColorScheme,
807
+ nav_links: generateNavLinks(portfolio, true, 'writing'),
808
+ };
809
+ const html = Mustache.render(template, data);
810
+ files.set('writing/index.html', Buffer.from(html, 'utf-8'));
811
+ return ['writing/index.html'];
812
+ }
813
+ async function copyUserImagesInMemory(portfolio, files) {
814
+ const imagePaths = extractAllImagePaths(portfolio);
815
+ for (const imgPath of imagePaths) {
816
+ if (!imgPath) {
817
+ continue;
818
+ }
819
+ // Skip absolute URLs
820
+ if (imgPath.startsWith('http://') || imgPath.startsWith('https://')) {
821
+ continue;
822
+ }
823
+ const srcPath = imgPath.startsWith('/')
824
+ ? path.join(process.cwd(), imgPath)
825
+ : path.join(process.cwd(), imgPath);
826
+ try {
827
+ const content = await fs.readFile(srcPath);
828
+ files.set(imgPath, content);
829
+ }
830
+ catch {
831
+ // Image doesn't exist - that's okay, it might be a placeholder
832
+ console.warn(`Warning: Could not copy image ${imgPath}`);
833
+ }
834
+ }
835
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devfolio-page",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "Your portfolio as code. Version control it like software.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,6 +41,7 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "chalk": "^5.6.2",
44
+ "chokidar": "^5.0.0",
44
45
  "commander": "^14.0.2",
45
46
  "js-yaml": "^4.1.1",
46
47
  "marked": "^17.0.1",