feat: user-managed templates

This commit is contained in:
2026-02-27 20:00:53 +01:00
parent e25a0d85a5
commit f3364999ee
47 changed files with 3664 additions and 40 deletions

View File

@@ -81,12 +81,73 @@ export class ProjectEngine extends EventEmitter {
// - If custom dataPath is provided, all project data lives there (allows cloud storage backup)
// - If no dataPath (default project), use internal userData storage
const dataDir = this.getDataDir(projectId, dataPath);
// Create all project directories in the data directory
await fs.mkdir(path.join(dataDir, 'posts'), { recursive: true });
await fs.mkdir(path.join(dataDir, 'media'), { recursive: true });
await fs.mkdir(path.join(dataDir, 'meta'), { recursive: true });
await fs.mkdir(path.join(dataDir, 'thumbnails'), { recursive: true });
await fs.mkdir(path.join(dataDir, 'templates'), { recursive: true });
}
private async copyStarterTemplates(projectId: string, dataPath?: string | null): Promise<void> {
const dataDir = this.getDataDir(projectId, dataPath);
const destDir = path.join(dataDir, 'templates');
// Resolve the bundled templates directory
const bundledRoots = [
path.resolve(__dirname, 'templates'),
path.resolve(process.cwd(), 'dist', 'main', 'engine', 'templates'),
path.resolve(process.cwd(), 'src', 'main', 'engine', 'templates'),
];
if (typeof process.resourcesPath === 'string' && process.resourcesPath.length > 0) {
bundledRoots.unshift(path.resolve(process.resourcesPath, 'templates'));
}
let sourceDir: string | null = null;
for (const root of bundledRoots) {
try {
const stat = await fs.stat(root);
if (stat.isDirectory()) {
sourceDir = root;
break;
}
} catch {
// Directory doesn't exist, try next
}
}
if (!sourceDir) {
return;
}
try {
await this.copyDirectoryRecursive(sourceDir, destDir);
} catch (error) {
console.error('[ProjectEngine] Failed to copy starter templates:', error);
}
}
private async copyDirectoryRecursive(src: string, dest: string): Promise<void> {
await fs.mkdir(dest, { recursive: true });
const entries = await fs.readdir(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
await this.copyDirectoryRecursive(srcPath, destPath);
} else if (entry.name.endsWith('.liquid')) {
try {
await fs.access(destPath);
// File already exists, skip
} catch {
await fs.copyFile(srcPath, destPath);
}
}
}
}
async createProject(data: { name: string; description?: string; slug?: string; dataPath?: string }): Promise<ProjectData> {
@@ -119,6 +180,9 @@ export class ProjectEngine extends EventEmitter {
// Create directories using project ID (not slug)
await this.ensureProjectDirectories(id, data.dataPath);
// Copy bundled templates as starter templates
await this.copyStarterTemplates(id, data.dataPath);
// Insert into database
const dbProject: NewProject = {
id: project.id,