devfolio-page 0.1.4 → 0.2.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.
- package/README.md +25 -4
- package/dist/cli/commands/dev.js +165 -0
- package/dist/cli/index.js +10 -1
- package/dist/cli/postinstall.js +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -18,11 +18,11 @@ devfolio-page init
|
|
|
18
18
|
cd my-portfolio
|
|
19
19
|
nano portfolio.yaml
|
|
20
20
|
|
|
21
|
-
#
|
|
22
|
-
devfolio-page
|
|
21
|
+
# Start development server with auto-rebuild
|
|
22
|
+
devfolio-page dev
|
|
23
23
|
|
|
24
|
-
#
|
|
25
|
-
|
|
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,165 @@
|
|
|
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 { renderCommand } from './render.js';
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
let isBuilding = false;
|
|
9
|
+
let buildQueued = false;
|
|
10
|
+
async function rebuild(file, options) {
|
|
11
|
+
// If already building, queue another build
|
|
12
|
+
if (isBuilding) {
|
|
13
|
+
buildQueued = true;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
isBuilding = true;
|
|
17
|
+
try {
|
|
18
|
+
console.log();
|
|
19
|
+
console.log(chalk.dim(`[${new Date().toLocaleTimeString()}]`) + ' File changed: ' + chalk.cyan(file));
|
|
20
|
+
console.log(chalk.dim('Rebuilding...'));
|
|
21
|
+
console.log();
|
|
22
|
+
await renderCommand('portfolio.yaml', {
|
|
23
|
+
theme: options.theme,
|
|
24
|
+
output: options.output || './site',
|
|
25
|
+
});
|
|
26
|
+
console.log();
|
|
27
|
+
console.log(chalk.green('✓') + ' Rebuild complete!');
|
|
28
|
+
console.log();
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
console.error(chalk.red('✗') + ' Build failed:');
|
|
32
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
33
|
+
console.log();
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
isBuilding = false;
|
|
37
|
+
// If a build was queued while we were building, run it now
|
|
38
|
+
if (buildQueued) {
|
|
39
|
+
buildQueued = false;
|
|
40
|
+
setTimeout(() => rebuild(file, options), 100);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function startServer(outputDir, port) {
|
|
45
|
+
const server = http.createServer(async (req, res) => {
|
|
46
|
+
try {
|
|
47
|
+
// Default to index.html
|
|
48
|
+
let filePath = req.url === '/' ? '/index.html' : req.url || '/index.html';
|
|
49
|
+
// Remove query string
|
|
50
|
+
filePath = filePath.split('?')[0];
|
|
51
|
+
const fullPath = path.join(outputDir, filePath);
|
|
52
|
+
// Security: prevent directory traversal
|
|
53
|
+
const normalizedPath = path.normalize(fullPath);
|
|
54
|
+
if (!normalizedPath.startsWith(path.normalize(outputDir))) {
|
|
55
|
+
res.writeHead(403);
|
|
56
|
+
res.end('Forbidden');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Check if file exists
|
|
60
|
+
if (!existsSync(fullPath)) {
|
|
61
|
+
res.writeHead(404);
|
|
62
|
+
res.end('Not found');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// Read file
|
|
66
|
+
const content = await fs.readFile(fullPath);
|
|
67
|
+
// Set content type
|
|
68
|
+
const ext = path.extname(fullPath);
|
|
69
|
+
const contentTypes = {
|
|
70
|
+
'.html': 'text/html',
|
|
71
|
+
'.css': 'text/css',
|
|
72
|
+
'.js': 'application/javascript',
|
|
73
|
+
'.json': 'application/json',
|
|
74
|
+
'.png': 'image/png',
|
|
75
|
+
'.jpg': 'image/jpeg',
|
|
76
|
+
'.jpeg': 'image/jpeg',
|
|
77
|
+
'.gif': 'image/gif',
|
|
78
|
+
'.svg': 'image/svg+xml',
|
|
79
|
+
'.ico': 'image/x-icon',
|
|
80
|
+
'.woff': 'font/woff',
|
|
81
|
+
'.woff2': 'font/woff2',
|
|
82
|
+
'.ttf': 'font/ttf',
|
|
83
|
+
'.otf': 'font/otf',
|
|
84
|
+
};
|
|
85
|
+
res.writeHead(200, { 'Content-Type': contentTypes[ext] || 'application/octet-stream' });
|
|
86
|
+
res.end(content);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.error('Server error:', error);
|
|
90
|
+
res.writeHead(500);
|
|
91
|
+
res.end('Internal server error');
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
server.listen(port);
|
|
95
|
+
return server;
|
|
96
|
+
}
|
|
97
|
+
export async function devCommand(file = 'portfolio.yaml', options = {}) {
|
|
98
|
+
const port = options.port || 3000;
|
|
99
|
+
const outputDir = path.resolve(options.output || './site');
|
|
100
|
+
// Check if portfolio.yaml exists
|
|
101
|
+
if (!existsSync(file)) {
|
|
102
|
+
console.error(chalk.red('✗') + ` File not found: ${file}`);
|
|
103
|
+
console.log();
|
|
104
|
+
console.log('Make sure you are in a portfolio directory with a portfolio.yaml file.');
|
|
105
|
+
console.log('Run ' + chalk.cyan('devfolio-page init') + ' to create a new portfolio.');
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
console.log();
|
|
109
|
+
console.log(chalk.cyan('┌─────────────────────────────────────────────────────────┐'));
|
|
110
|
+
console.log(chalk.cyan('│') + ' ' + chalk.bold('devfolio.page') + ' - Development Mode ' + chalk.cyan('│'));
|
|
111
|
+
console.log(chalk.cyan('└─────────────────────────────────────────────────────────┘'));
|
|
112
|
+
console.log();
|
|
113
|
+
// Initial build
|
|
114
|
+
console.log(chalk.bold('Building initial site...'));
|
|
115
|
+
console.log();
|
|
116
|
+
try {
|
|
117
|
+
await renderCommand(file, {
|
|
118
|
+
theme: options.theme,
|
|
119
|
+
output: outputDir,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.error(chalk.red('✗') + ' Initial build failed:');
|
|
124
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
// Start server
|
|
128
|
+
const server = startServer(outputDir, port);
|
|
129
|
+
console.log();
|
|
130
|
+
console.log(chalk.green('✓') + ' Dev server started!');
|
|
131
|
+
console.log();
|
|
132
|
+
console.log(' ' + chalk.bold('Local:') + ' ' + chalk.cyan(`http://localhost:${port}`));
|
|
133
|
+
console.log(' ' + chalk.bold('Output:') + ' ' + chalk.dim(outputDir));
|
|
134
|
+
console.log();
|
|
135
|
+
console.log(chalk.dim('Watching for changes...'));
|
|
136
|
+
console.log(chalk.dim('Press Ctrl+C to stop'));
|
|
137
|
+
console.log();
|
|
138
|
+
// Watch for file changes
|
|
139
|
+
const watcher = chokidar.watch([file, 'images/**/*'], {
|
|
140
|
+
persistent: true,
|
|
141
|
+
ignoreInitial: true,
|
|
142
|
+
awaitWriteFinish: {
|
|
143
|
+
stabilityThreshold: 100,
|
|
144
|
+
pollInterval: 100,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
watcher.on('change', (changedFile) => {
|
|
148
|
+
rebuild(changedFile, options);
|
|
149
|
+
});
|
|
150
|
+
watcher.on('add', (changedFile) => {
|
|
151
|
+
rebuild(changedFile, options);
|
|
152
|
+
});
|
|
153
|
+
watcher.on('unlink', (changedFile) => {
|
|
154
|
+
rebuild(changedFile, options);
|
|
155
|
+
});
|
|
156
|
+
// Handle process exit
|
|
157
|
+
process.on('SIGINT', () => {
|
|
158
|
+
console.log();
|
|
159
|
+
console.log();
|
|
160
|
+
console.log(chalk.dim('Shutting down...'));
|
|
161
|
+
watcher.close();
|
|
162
|
+
server.close();
|
|
163
|
+
process.exit(0);
|
|
164
|
+
});
|
|
165
|
+
}
|
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.
|
|
13
|
+
.version('0.2.0');
|
|
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');
|
package/dist/cli/postinstall.js
CHANGED
|
@@ -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');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devfolio-page",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|