@mtldev514/retro-portfolio-engine 1.0.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 +408 -0
- package/bin/cli.js +103 -0
- package/engine/admin/admin.css +720 -0
- package/engine/admin/admin.html +801 -0
- package/engine/admin/admin_api.py +230 -0
- package/engine/admin/scripts/backup.sh +116 -0
- package/engine/admin/scripts/config_loader.py +180 -0
- package/engine/admin/scripts/init.sh +141 -0
- package/engine/admin/scripts/manager.py +308 -0
- package/engine/admin/scripts/restore.sh +121 -0
- package/engine/admin/scripts/server.py +41 -0
- package/engine/admin/scripts/update.sh +321 -0
- package/engine/admin/scripts/validate_json.py +62 -0
- package/engine/fonts.css +37 -0
- package/engine/index.html +190 -0
- package/engine/js/config-loader.js +370 -0
- package/engine/js/config.js +173 -0
- package/engine/js/counter.js +17 -0
- package/engine/js/effects.js +97 -0
- package/engine/js/i18n.js +68 -0
- package/engine/js/init.js +107 -0
- package/engine/js/media.js +264 -0
- package/engine/js/render.js +282 -0
- package/engine/js/router.js +133 -0
- package/engine/js/sparkle.js +123 -0
- package/engine/js/themes.js +607 -0
- package/engine/style.css +2037 -0
- package/index.js +35 -0
- package/package.json +48 -0
- package/scripts/admin.js +67 -0
- package/scripts/build.js +142 -0
- package/scripts/init.js +237 -0
- package/scripts/post-install.js +16 -0
- package/scripts/serve.js +54 -0
- package/templates/user-portfolio/.github/workflows/deploy.yml +57 -0
- package/templates/user-portfolio/config/app.json +36 -0
- package/templates/user-portfolio/config/categories.json +241 -0
- package/templates/user-portfolio/config/languages.json +15 -0
- package/templates/user-portfolio/config/media-types.json +59 -0
- package/templates/user-portfolio/data/painting.json +3 -0
- package/templates/user-portfolio/data/projects.json +3 -0
- package/templates/user-portfolio/lang/en.json +114 -0
- package/templates/user-portfolio/lang/fr.json +114 -0
package/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @retro-portfolio/engine
|
|
3
|
+
* Main entry point for the package
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
// Path to engine files
|
|
10
|
+
enginePath: path.join(__dirname, 'engine'),
|
|
11
|
+
|
|
12
|
+
// Package version
|
|
13
|
+
version: require('./package.json').version,
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get path to engine directory
|
|
17
|
+
*/
|
|
18
|
+
getEnginePath() {
|
|
19
|
+
return this.enginePath;
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get package version
|
|
24
|
+
*/
|
|
25
|
+
getVersion() {
|
|
26
|
+
return this.version;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get path to a specific engine file
|
|
31
|
+
*/
|
|
32
|
+
getEngineFile(filePath) {
|
|
33
|
+
return path.join(this.enginePath, filePath);
|
|
34
|
+
}
|
|
35
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mtldev514/retro-portfolio-engine",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Retro portfolio site engine - Package as a Service. Users install this package and provide their own data.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"retro-portfolio": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"prepare": "node scripts/post-install.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"portfolio",
|
|
14
|
+
"retro",
|
|
15
|
+
"static-site-generator",
|
|
16
|
+
"site-as-a-package",
|
|
17
|
+
"90s-aesthetic",
|
|
18
|
+
"personal-website"
|
|
19
|
+
],
|
|
20
|
+
"author": "Alex",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/mtldev514/retro-portfolio-engine"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"engine/",
|
|
28
|
+
"scripts/",
|
|
29
|
+
"bin/",
|
|
30
|
+
"templates/",
|
|
31
|
+
"index.js"
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=14.0.0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"chalk": "^4.1.2",
|
|
38
|
+
"commander": "^11.0.0",
|
|
39
|
+
"fs-extra": "^11.1.1"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"http-server": "^14.1.1"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "restricted"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/scripts/admin.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Script
|
|
3
|
+
* Launches admin interface for content management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { spawn } = require('child_process');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs-extra');
|
|
10
|
+
|
|
11
|
+
async function admin(options = {}) {
|
|
12
|
+
const port = options.port || 5001;
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
|
|
15
|
+
console.log(chalk.blue('š§ Starting admin interface...\n'));
|
|
16
|
+
|
|
17
|
+
// Check if admin_api.py exists in engine
|
|
18
|
+
const adminApiPath = path.join(__dirname, '../engine/admin_api.py');
|
|
19
|
+
const userDataPath = cwd;
|
|
20
|
+
|
|
21
|
+
if (!fs.existsSync(adminApiPath)) {
|
|
22
|
+
console.log(chalk.red('ā Admin API not found in engine.'));
|
|
23
|
+
console.log(chalk.yellow('\nThe admin interface will be added in a future update.'));
|
|
24
|
+
console.log(chalk.gray('For now, you can edit JSON files in data/ and lang/ directly.\n'));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Set environment variable to point to user data
|
|
29
|
+
const env = {
|
|
30
|
+
...process.env,
|
|
31
|
+
DATA_DIR: path.join(userDataPath, 'data'),
|
|
32
|
+
CONFIG_DIR: path.join(userDataPath, 'config'),
|
|
33
|
+
LANG_DIR: path.join(userDataPath, 'lang'),
|
|
34
|
+
PORT: port
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
console.log(chalk.cyan('Starting admin API...'));
|
|
38
|
+
|
|
39
|
+
// Start Flask API
|
|
40
|
+
const apiProcess = spawn('python3', [adminApiPath], {
|
|
41
|
+
env,
|
|
42
|
+
stdio: 'inherit'
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
console.log(chalk.green('\n⨠Admin interface running!\n'));
|
|
46
|
+
console.log(chalk.cyan('URL:'), `http://localhost:${port}/admin.html`);
|
|
47
|
+
console.log(chalk.gray('\nPress CTRL+C to stop\n'));
|
|
48
|
+
|
|
49
|
+
// Handle process termination
|
|
50
|
+
process.on('SIGINT', () => {
|
|
51
|
+
console.log(chalk.yellow('\n\nš Shutting down admin...'));
|
|
52
|
+
apiProcess.kill();
|
|
53
|
+
process.exit(0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Auto-open browser if requested
|
|
57
|
+
if (options.open) {
|
|
58
|
+
setTimeout(async () => {
|
|
59
|
+
const open = require('open');
|
|
60
|
+
await open(`http://localhost:${port}/admin.html`);
|
|
61
|
+
}, 2000); // Wait for Flask to start
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return apiProcess;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = admin;
|
package/scripts/build.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build Script
|
|
3
|
+
* Merges engine files with user data to generate static site
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs-extra');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
|
|
10
|
+
async function build(options = {}) {
|
|
11
|
+
console.log(chalk.blue('šļø Building your portfolio...\n'));
|
|
12
|
+
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
const outputDir = path.join(cwd, options.output || 'dist');
|
|
15
|
+
|
|
16
|
+
// Validate user has required directories
|
|
17
|
+
const requiredDirs = ['config', 'data', 'lang'];
|
|
18
|
+
for (const dir of requiredDirs) {
|
|
19
|
+
if (!fs.existsSync(path.join(cwd, dir))) {
|
|
20
|
+
throw new Error(`Missing required directory: ${dir}/`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log(chalk.cyan('š Validating data files...'));
|
|
25
|
+
|
|
26
|
+
// Validate config files exist
|
|
27
|
+
const requiredConfigs = ['app.json', 'languages.json', 'categories.json'];
|
|
28
|
+
for (const config of requiredConfigs) {
|
|
29
|
+
const configPath = path.join(cwd, 'config', config);
|
|
30
|
+
if (!fs.existsSync(configPath)) {
|
|
31
|
+
throw new Error(`Missing config file: config/${config}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(chalk.green(' ā All data files present\n'));
|
|
36
|
+
|
|
37
|
+
// Clean output directory
|
|
38
|
+
console.log(chalk.cyan('š§¹ Cleaning output directory...'));
|
|
39
|
+
await fs.emptyDir(outputDir);
|
|
40
|
+
console.log(chalk.green(' ā Output directory cleaned\n'));
|
|
41
|
+
|
|
42
|
+
// Copy engine files
|
|
43
|
+
console.log(chalk.cyan('š¦ Copying engine files...'));
|
|
44
|
+
|
|
45
|
+
const enginePath = path.join(__dirname, '../engine');
|
|
46
|
+
|
|
47
|
+
if (!fs.existsSync(enginePath)) {
|
|
48
|
+
throw new Error('Engine files not found. Package may be corrupted.');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await fs.copy(enginePath, outputDir);
|
|
52
|
+
|
|
53
|
+
// Count files copied
|
|
54
|
+
const engineFiles = await fs.readdir(enginePath);
|
|
55
|
+
console.log(chalk.green(` ā Copied ${engineFiles.length} engine files\n`));
|
|
56
|
+
|
|
57
|
+
// Copy user data
|
|
58
|
+
console.log(chalk.cyan('š Copying your data...'));
|
|
59
|
+
|
|
60
|
+
const dataDirs = ['config', 'data', 'lang'];
|
|
61
|
+
for (const dir of dataDirs) {
|
|
62
|
+
const srcPath = path.join(cwd, dir);
|
|
63
|
+
const destPath = path.join(outputDir, dir);
|
|
64
|
+
|
|
65
|
+
await fs.copy(srcPath, destPath);
|
|
66
|
+
|
|
67
|
+
const fileCount = (await fs.readdir(srcPath)).length;
|
|
68
|
+
console.log(chalk.green(' ā'), `${dir}/ (${fileCount} files)`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Copy assets if exists
|
|
72
|
+
const assetsPath = path.join(cwd, 'assets');
|
|
73
|
+
if (fs.existsSync(assetsPath)) {
|
|
74
|
+
await fs.copy(assetsPath, path.join(outputDir, 'assets'));
|
|
75
|
+
const assetCount = (await fs.readdir(assetsPath)).length;
|
|
76
|
+
console.log(chalk.green(' ā'), `assets/ (${assetCount} files)`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Create build info
|
|
80
|
+
const buildInfo = {
|
|
81
|
+
buildDate: new Date().toISOString(),
|
|
82
|
+
engine: {
|
|
83
|
+
name: '@retro-portfolio/engine',
|
|
84
|
+
version: require('../package.json').version
|
|
85
|
+
},
|
|
86
|
+
site: {
|
|
87
|
+
name: path.basename(cwd)
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
await fs.writeJson(path.join(outputDir, 'build-info.json'), buildInfo, {
|
|
92
|
+
spaces: 2
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
console.log(chalk.green('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
96
|
+
console.log(chalk.green('⨠Build completed successfully!\n'));
|
|
97
|
+
console.log(chalk.cyan('Output:'), outputDir);
|
|
98
|
+
console.log(chalk.cyan('Size:'), await getDirSize(outputDir));
|
|
99
|
+
console.log(chalk.green('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
100
|
+
|
|
101
|
+
console.log(chalk.gray('Next steps:'));
|
|
102
|
+
console.log(chalk.gray(' ⢠Test locally: npm run dev'));
|
|
103
|
+
console.log(chalk.gray(' ⢠Deploy: npm run deploy\n'));
|
|
104
|
+
|
|
105
|
+
return outputDir;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get directory size in human-readable format
|
|
110
|
+
*/
|
|
111
|
+
async function getDirSize(dirPath) {
|
|
112
|
+
let size = 0;
|
|
113
|
+
|
|
114
|
+
async function calcSize(dir) {
|
|
115
|
+
const items = await fs.readdir(dir);
|
|
116
|
+
|
|
117
|
+
for (const item of items) {
|
|
118
|
+
const itemPath = path.join(dir, item);
|
|
119
|
+
const stats = await fs.stat(itemPath);
|
|
120
|
+
|
|
121
|
+
if (stats.isDirectory()) {
|
|
122
|
+
await calcSize(itemPath);
|
|
123
|
+
} else {
|
|
124
|
+
size += stats.size;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
await calcSize(dirPath);
|
|
130
|
+
|
|
131
|
+
// Convert to human-readable
|
|
132
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
133
|
+
let unitIndex = 0;
|
|
134
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
135
|
+
size /= 1024;
|
|
136
|
+
unitIndex++;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return `${size.toFixed(2)} ${units[unitIndex]}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = build;
|
package/scripts/init.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init Script
|
|
3
|
+
* Creates a new portfolio with template data structure
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs-extra');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
|
|
10
|
+
async function init(targetDir, options = {}) {
|
|
11
|
+
console.log(chalk.blue('šØ Initializing Retro Portfolio...\n'));
|
|
12
|
+
|
|
13
|
+
const targetPath = path.resolve(process.cwd(), targetDir);
|
|
14
|
+
const templatePath = path.join(__dirname, '../templates/user-portfolio');
|
|
15
|
+
|
|
16
|
+
// Check if directory exists
|
|
17
|
+
if (fs.existsSync(targetPath) && fs.readdirSync(targetPath).length > 0) {
|
|
18
|
+
if (!options.force) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Directory ${targetDir} is not empty. Use --force to overwrite.`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Create directory structure
|
|
26
|
+
console.log(chalk.cyan('š Creating directory structure...'));
|
|
27
|
+
|
|
28
|
+
const dirs = ['config', 'data', 'lang', 'assets'];
|
|
29
|
+
for (const dir of dirs) {
|
|
30
|
+
const dirPath = path.join(targetPath, dir);
|
|
31
|
+
await fs.ensureDir(dirPath);
|
|
32
|
+
console.log(chalk.green(' ā'), dir + '/');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Copy template files
|
|
36
|
+
console.log(chalk.cyan('\nš Creating template files...'));
|
|
37
|
+
|
|
38
|
+
// package.json
|
|
39
|
+
const packageJson = {
|
|
40
|
+
name: path.basename(targetPath),
|
|
41
|
+
version: '1.0.0',
|
|
42
|
+
private: true,
|
|
43
|
+
scripts: {
|
|
44
|
+
build: 'retro-portfolio build',
|
|
45
|
+
dev: 'retro-portfolio dev',
|
|
46
|
+
admin: 'retro-portfolio admin',
|
|
47
|
+
deploy: 'retro-portfolio deploy'
|
|
48
|
+
},
|
|
49
|
+
dependencies: {
|
|
50
|
+
'@retro-portfolio/engine': '^1.0.0'
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
await fs.writeJson(path.join(targetPath, 'package.json'), packageJson, {
|
|
55
|
+
spaces: 2
|
|
56
|
+
});
|
|
57
|
+
console.log(chalk.green(' ā'), 'package.json');
|
|
58
|
+
|
|
59
|
+
// .gitignore
|
|
60
|
+
const gitignore = `# Dependencies
|
|
61
|
+
node_modules/
|
|
62
|
+
|
|
63
|
+
# Build output
|
|
64
|
+
dist/
|
|
65
|
+
|
|
66
|
+
# Environment
|
|
67
|
+
.env
|
|
68
|
+
.env.local
|
|
69
|
+
|
|
70
|
+
# Editor
|
|
71
|
+
.vscode/
|
|
72
|
+
.idea/
|
|
73
|
+
*.swp
|
|
74
|
+
|
|
75
|
+
# OS
|
|
76
|
+
.DS_Store
|
|
77
|
+
Thumbs.db
|
|
78
|
+
|
|
79
|
+
# Temp
|
|
80
|
+
temp_uploads/
|
|
81
|
+
.cache/
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
await fs.writeFile(path.join(targetPath, '.gitignore'), gitignore);
|
|
85
|
+
console.log(chalk.green(' ā'), '.gitignore');
|
|
86
|
+
|
|
87
|
+
// .env.example
|
|
88
|
+
const envExample = `# Cloudinary Configuration (for media uploads)
|
|
89
|
+
CLOUDINARY_CLOUD_NAME=your_cloud_name
|
|
90
|
+
CLOUDINARY_API_KEY=your_api_key
|
|
91
|
+
CLOUDINARY_API_SECRET=your_api_secret
|
|
92
|
+
|
|
93
|
+
# Optional: Custom engine version
|
|
94
|
+
# ENGINE_VERSION=1.0.0
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
await fs.writeFile(path.join(targetPath, '.env.example'), envExample);
|
|
98
|
+
console.log(chalk.green(' ā'), '.env.example');
|
|
99
|
+
|
|
100
|
+
// Create config files
|
|
101
|
+
const configFiles = {
|
|
102
|
+
'config/app.json': {
|
|
103
|
+
site: {
|
|
104
|
+
name: 'My Retro Portfolio',
|
|
105
|
+
description: 'A nostalgic web presence',
|
|
106
|
+
author: 'Your Name'
|
|
107
|
+
},
|
|
108
|
+
theme: {
|
|
109
|
+
default: 'jr16'
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
'config/languages.json': {
|
|
113
|
+
defaultLanguage: 'en',
|
|
114
|
+
supportedLanguages: [
|
|
115
|
+
{ code: 'en', name: 'English', flag: 'š¬š§' },
|
|
116
|
+
{ code: 'fr', name: 'FranƧais', flag: 'š«š·' }
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
'config/categories.json': {
|
|
120
|
+
contentTypes: [
|
|
121
|
+
{
|
|
122
|
+
id: 'painting',
|
|
123
|
+
name: { en: 'Painting', fr: 'Peinture' },
|
|
124
|
+
icon: 'šØ',
|
|
125
|
+
mediaType: 'image',
|
|
126
|
+
dataFile: 'data/painting.json'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'projects',
|
|
130
|
+
name: { en: 'Projects', fr: 'Projets' },
|
|
131
|
+
icon: 'š»',
|
|
132
|
+
mediaType: 'code',
|
|
133
|
+
dataFile: 'data/projects.json'
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
'config/media-types.json': {
|
|
138
|
+
mediaTypes: [
|
|
139
|
+
{
|
|
140
|
+
id: 'image',
|
|
141
|
+
name: 'Image',
|
|
142
|
+
supportsGallery: true,
|
|
143
|
+
acceptedFormats: ['jpg', 'png', 'gif', 'webp']
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'code',
|
|
147
|
+
name: 'Code Project',
|
|
148
|
+
supportsGallery: false
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
for (const [filePath, content] of Object.entries(configFiles)) {
|
|
155
|
+
const fullPath = path.join(targetPath, filePath);
|
|
156
|
+
await fs.writeJson(fullPath, content, { spaces: 2 });
|
|
157
|
+
console.log(chalk.green(' ā'), filePath);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Create empty data files
|
|
161
|
+
const dataFiles = ['painting', 'projects'];
|
|
162
|
+
for (const file of dataFiles) {
|
|
163
|
+
const filePath = path.join(targetPath, 'data', `${file}.json`);
|
|
164
|
+
await fs.writeJson(filePath, { items: [] }, { spaces: 2 });
|
|
165
|
+
console.log(chalk.green(' ā'), `data/${file}.json`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Create language files
|
|
169
|
+
const langFiles = {
|
|
170
|
+
'lang/en.json': {
|
|
171
|
+
header_title: 'My Portfolio',
|
|
172
|
+
nav_painting: 'Painting',
|
|
173
|
+
nav_projects: 'Projects',
|
|
174
|
+
footer_copy: 'Ā© 2026 My Portfolio'
|
|
175
|
+
},
|
|
176
|
+
'lang/fr.json': {
|
|
177
|
+
header_title: 'Mon Portfolio',
|
|
178
|
+
nav_painting: 'Peinture',
|
|
179
|
+
nav_projects: 'Projets',
|
|
180
|
+
footer_copy: 'Ā© 2026 Mon Portfolio'
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
for (const [filePath, content] of Object.entries(langFiles)) {
|
|
185
|
+
const fullPath = path.join(targetPath, filePath);
|
|
186
|
+
await fs.writeJson(fullPath, content, { spaces: 2 });
|
|
187
|
+
console.log(chalk.green(' ā'), filePath);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Create README
|
|
191
|
+
const readme = `# My Retro Portfolio
|
|
192
|
+
|
|
193
|
+
A retro-styled portfolio powered by @retro-portfolio/engine.
|
|
194
|
+
|
|
195
|
+
## Quick Start
|
|
196
|
+
|
|
197
|
+
\`\`\`bash
|
|
198
|
+
# Install dependencies
|
|
199
|
+
npm install
|
|
200
|
+
|
|
201
|
+
# Start development server
|
|
202
|
+
npm run dev
|
|
203
|
+
|
|
204
|
+
# Build for production
|
|
205
|
+
npm run build
|
|
206
|
+
|
|
207
|
+
# Launch admin interface
|
|
208
|
+
npm run admin
|
|
209
|
+
\`\`\`
|
|
210
|
+
|
|
211
|
+
## Project Structure
|
|
212
|
+
|
|
213
|
+
- \`config/\` - Site configuration
|
|
214
|
+
- \`data/\` - Your portfolio content (JSON)
|
|
215
|
+
- \`lang/\` - Translations
|
|
216
|
+
- \`assets/\` - Your images, files, etc.
|
|
217
|
+
|
|
218
|
+
## Documentation
|
|
219
|
+
|
|
220
|
+
Visit [retro-portfolio documentation](https://github.com/YOUR_USERNAME/retro-portfolio-engine) for more info.
|
|
221
|
+
`;
|
|
222
|
+
|
|
223
|
+
await fs.writeFile(path.join(targetPath, 'README.md'), readme);
|
|
224
|
+
console.log(chalk.green(' ā'), 'README.md');
|
|
225
|
+
|
|
226
|
+
// Success message
|
|
227
|
+
console.log(chalk.green('\n⨠Portfolio initialized successfully!\n'));
|
|
228
|
+
console.log(chalk.cyan('Next steps:\n'));
|
|
229
|
+
console.log(` cd ${targetDir}`);
|
|
230
|
+
console.log(' npm install');
|
|
231
|
+
console.log(' npm run dev\n');
|
|
232
|
+
console.log(chalk.gray('Happy creating! šØ\n'));
|
|
233
|
+
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = init;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-install script
|
|
3
|
+
* Runs after npm install (optional setup)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Nothing required for now - this can be used later for:
|
|
7
|
+
// - Creating default config files
|
|
8
|
+
// - Setting up initial directories
|
|
9
|
+
// - Showing welcome message
|
|
10
|
+
|
|
11
|
+
console.log('ā
@retro-portfolio/engine installed successfully!');
|
|
12
|
+
console.log('\nGet started with:');
|
|
13
|
+
console.log(' npx retro-portfolio init my-portfolio');
|
|
14
|
+
console.log(' cd my-portfolio');
|
|
15
|
+
console.log(' npm install');
|
|
16
|
+
console.log(' npm run dev\n');
|
package/scripts/serve.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serve Script
|
|
3
|
+
* Local development server with live reload
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { spawn } = require('child_process');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs-extra');
|
|
10
|
+
|
|
11
|
+
async function serve(options = {}) {
|
|
12
|
+
const port = options.port || 8000;
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
const distPath = path.join(cwd, 'dist');
|
|
15
|
+
|
|
16
|
+
console.log(chalk.blue('š Starting development server...\n'));
|
|
17
|
+
|
|
18
|
+
// Check if dist exists, if not build first
|
|
19
|
+
if (!fs.existsSync(distPath)) {
|
|
20
|
+
console.log(chalk.yellow('ā ļø No build found. Building first...\n'));
|
|
21
|
+
const build = require('./build');
|
|
22
|
+
await build({ output: 'dist' });
|
|
23
|
+
console.log('');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Start HTTP server
|
|
27
|
+
console.log(chalk.cyan('Starting server...'));
|
|
28
|
+
|
|
29
|
+
const serverProcess = spawn('python3', ['-m', 'http.server', port], {
|
|
30
|
+
cwd: distPath,
|
|
31
|
+
stdio: 'inherit'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
console.log(chalk.green('\n⨠Server running!\n'));
|
|
35
|
+
console.log(chalk.cyan('Local:'), `http://localhost:${port}`);
|
|
36
|
+
console.log(chalk.gray('\nPress CTRL+C to stop\n'));
|
|
37
|
+
|
|
38
|
+
// Handle process termination
|
|
39
|
+
process.on('SIGINT', () => {
|
|
40
|
+
console.log(chalk.yellow('\n\nš Shutting down server...'));
|
|
41
|
+
serverProcess.kill();
|
|
42
|
+
process.exit(0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Auto-open browser if requested
|
|
46
|
+
if (options.open) {
|
|
47
|
+
const open = require('open');
|
|
48
|
+
await open(`http://localhost:${port}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return serverProcess;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = serve;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Build and Deploy to GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pages: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: "pages"
|
|
15
|
+
cancel-in-progress: false
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build-and-deploy:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- name: š¦ Checkout
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: š§ Setup Node.js
|
|
26
|
+
uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: '18'
|
|
29
|
+
cache: 'npm'
|
|
30
|
+
|
|
31
|
+
- name: š„ Install dependencies
|
|
32
|
+
run: npm ci
|
|
33
|
+
|
|
34
|
+
- name: šļø Build site
|
|
35
|
+
run: npm run build
|
|
36
|
+
|
|
37
|
+
- name: š Build summary
|
|
38
|
+
run: |
|
|
39
|
+
echo "### šØ Build Complete" >> $GITHUB_STEP_SUMMARY
|
|
40
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
41
|
+
echo "**Build date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> $GITHUB_STEP_SUMMARY
|
|
42
|
+
echo "**Output:** dist/" >> $GITHUB_STEP_SUMMARY
|
|
43
|
+
|
|
44
|
+
- name: š¤ Upload artifact
|
|
45
|
+
uses: actions/upload-pages-artifact@v3
|
|
46
|
+
with:
|
|
47
|
+
path: ./dist
|
|
48
|
+
|
|
49
|
+
- name: š Deploy to GitHub Pages
|
|
50
|
+
id: deployment
|
|
51
|
+
uses: actions/deploy-pages@v4
|
|
52
|
+
|
|
53
|
+
- name: ā
Deployment complete
|
|
54
|
+
run: |
|
|
55
|
+
echo "### š Site deployed!" >> $GITHUB_STEP_SUMMARY
|
|
56
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
57
|
+
echo "**URL:** ${{ steps.deployment.outputs.page_url }}" >> $GITHUB_STEP_SUMMARY
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"app": {
|
|
3
|
+
"name": "Retro Portfolio",
|
|
4
|
+
"version": "1.0",
|
|
5
|
+
"adminTitle": "PORTFOLIO MANAGER"
|
|
6
|
+
},
|
|
7
|
+
"api": {
|
|
8
|
+
"host": "127.0.0.1",
|
|
9
|
+
"port": 5001,
|
|
10
|
+
"baseUrl": "http://127.0.0.1:5001"
|
|
11
|
+
},
|
|
12
|
+
"paths": {
|
|
13
|
+
"dataDir": "data",
|
|
14
|
+
"langDir": "lang",
|
|
15
|
+
"pagesDir": "pages"
|
|
16
|
+
},
|
|
17
|
+
"github": {
|
|
18
|
+
"username": "yourusername",
|
|
19
|
+
"repoName": "retro-portfolio",
|
|
20
|
+
"mediaReleaseTag": "media",
|
|
21
|
+
"uploadCategories": [
|
|
22
|
+
"music"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"counter": {
|
|
26
|
+
"apiUrl": "https://api.counterapi.dev/v1/retro-portfolio/visits/up"
|
|
27
|
+
},
|
|
28
|
+
"winamp": {
|
|
29
|
+
"title": "My Playlist",
|
|
30
|
+
"bitrate": "192",
|
|
31
|
+
"frequency": "44"
|
|
32
|
+
},
|
|
33
|
+
"pagination": {
|
|
34
|
+
"pageSize": 24
|
|
35
|
+
}
|
|
36
|
+
}
|