Co-authored-by: rfc1437 <774975+rfc1437@users.noreply.github.com>
This commit is contained in:
@@ -255,7 +255,8 @@ export class MetaEngine extends EventEmitter {
|
|||||||
try {
|
try {
|
||||||
await this.ensureMetaDirExists();
|
await this.ensureMetaDirExists();
|
||||||
const filePath = this.getProjectMetadataFilePath();
|
const filePath = this.getProjectMetadataFilePath();
|
||||||
const content = JSON.stringify(this.projectMetadata, null, 2);
|
const { dataPath: _dataPath, ...persistedMetadata } = this.projectMetadata || {};
|
||||||
|
const content = JSON.stringify(persistedMetadata, null, 2);
|
||||||
await fs.writeFile(filePath, content, 'utf-8');
|
await fs.writeFile(filePath, content, 'utf-8');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[MetaEngine] Failed to save project metadata:', error);
|
console.error('[MetaEngine] Failed to save project metadata:', error);
|
||||||
@@ -439,22 +440,11 @@ export class MetaEngine extends EventEmitter {
|
|||||||
// Handle project metadata
|
// Handle project metadata
|
||||||
if (projectMetadataFileExists) {
|
if (projectMetadataFileExists) {
|
||||||
await this.loadProjectMetadata();
|
await this.loadProjectMetadata();
|
||||||
|
if (this.projectMetadata?.dataPath !== undefined) {
|
||||||
// Keep dataPath authoritative in database (selected folder path on create/open).
|
const { dataPath: _dataPath, ...metadataWithoutDataPath } = this.projectMetadata;
|
||||||
// If project.json has a stale dataPath, update project.json from database.
|
this.projectMetadata = metadataWithoutDataPath;
|
||||||
const projectData = await this.fetchProjectFromDatabase();
|
|
||||||
if (!projectData) {
|
|
||||||
throw new Error(`Project not found in database: ${this.currentProjectId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const databaseDataPath = projectData.dataPath || undefined;
|
|
||||||
if (this.projectMetadata && this.projectMetadata.dataPath !== databaseDataPath) {
|
|
||||||
this.projectMetadata = {
|
|
||||||
...this.projectMetadata,
|
|
||||||
dataPath: databaseDataPath,
|
|
||||||
};
|
|
||||||
await this.saveProjectMetadata();
|
await this.saveProjectMetadata();
|
||||||
console.log(`[MetaEngine] Synced dataPath from database to project.json: ${databaseDataPath || '(default)'}`);
|
console.log('[MetaEngine] Removed deprecated dataPath from project.json');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No file exists, fetch project data from database and create file
|
// No file exists, fetch project data from database and create file
|
||||||
@@ -465,7 +455,6 @@ export class MetaEngine extends EventEmitter {
|
|||||||
this.projectMetadata = {
|
this.projectMetadata = {
|
||||||
name: projectData.name,
|
name: projectData.name,
|
||||||
description: projectData.description || undefined,
|
description: projectData.description || undefined,
|
||||||
dataPath: projectData.dataPath || undefined,
|
|
||||||
maxPostsPerPage: DEFAULT_MAX_POSTS_PER_PAGE,
|
maxPostsPerPage: DEFAULT_MAX_POSTS_PER_PAGE,
|
||||||
};
|
};
|
||||||
await this.saveProjectMetadata();
|
await this.saveProjectMetadata();
|
||||||
|
|||||||
@@ -284,9 +284,9 @@ function buildCanonicalPostPath(post: PostData): string {
|
|||||||
return `/${year}/${month}/${day}/${post.slug}`;
|
return `/${year}/${month}/${day}/${post.slug}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPageHtml(content: string, title: string): string {
|
function getPageHtml(content: string, title: string, language: string): string {
|
||||||
return `<!doctype html>
|
return `<!doctype html>
|
||||||
<html lang="en">
|
<html lang="${escapeHtml(language)}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
@@ -452,7 +452,8 @@ export class PreviewServer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.respond(res, 200, getPageHtml(result, resolvePageTitle(metadata, context.projectName, context.projectDescription)));
|
const language = metadata?.mainLanguage?.trim() || 'en';
|
||||||
|
this.respond(res, 200, getPageHtml(result, resolvePageTitle(metadata, context.projectName, context.projectDescription), language));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[PreviewServer] Request failed:', error);
|
console.error('[PreviewServer] Request failed:', error);
|
||||||
this.respond(res, 500, 'Internal Server Error');
|
this.respond(res, 500, 'Internal Server Error');
|
||||||
|
|||||||
@@ -472,6 +472,20 @@ describe('MetaEngine', () => {
|
|||||||
expect(parsed.description).toBe('Test description');
|
expect(parsed.description).toBe('Test description');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not persist dataPath to filesystem project metadata', async () => {
|
||||||
|
await metaEngine.setProjectMetadata({
|
||||||
|
name: 'Test Project',
|
||||||
|
dataPath: '/custom/project/path',
|
||||||
|
});
|
||||||
|
|
||||||
|
const metaDir = metaEngine.getMetaDir();
|
||||||
|
const projectPath = normalizePath(`${metaDir}/project.json`);
|
||||||
|
const content = mockFiles.get(projectPath);
|
||||||
|
const parsed = JSON.parse(content!);
|
||||||
|
|
||||||
|
expect(parsed.dataPath).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it('should load project metadata from filesystem', async () => {
|
it('should load project metadata from filesystem', async () => {
|
||||||
const metaDir = metaEngine.getMetaDir();
|
const metaDir = metaEngine.getMetaDir();
|
||||||
const projectPath = normalizePath(`${metaDir}/project.json`);
|
const projectPath = normalizePath(`${metaDir}/project.json`);
|
||||||
@@ -757,7 +771,7 @@ describe('MetaEngine', () => {
|
|||||||
expect(normalizePath(metaDir)).toContain(normalizePath(customDataDir));
|
expect(normalizePath(metaDir)).toContain(normalizePath(customDataDir));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sync dataPath from database to project.json if different', async () => {
|
it('should ignore and remove dataPath from project.json during syncOnStartup', async () => {
|
||||||
const metaDir = metaEngine.getMetaDir();
|
const metaDir = metaEngine.getMetaDir();
|
||||||
const oldPath = path.join('old', 'path', 'from', 'file');
|
const oldPath = path.join('old', 'path', 'from', 'file');
|
||||||
const newPath = path.join('new', 'path', 'from', 'database');
|
const newPath = path.join('new', 'path', 'from', 'database');
|
||||||
@@ -783,7 +797,7 @@ describe('MetaEngine', () => {
|
|||||||
const savedProjectJson = mockFiles.get(normalizePath(`${metaDir}/project.json`));
|
const savedProjectJson = mockFiles.get(normalizePath(`${metaDir}/project.json`));
|
||||||
expect(savedProjectJson).toBeDefined();
|
expect(savedProjectJson).toBeDefined();
|
||||||
const parsed = JSON.parse(savedProjectJson!);
|
const parsed = JSON.parse(savedProjectJson!);
|
||||||
expect(normalizePath(parsed.dataPath)).toBe(normalizePath(newPath));
|
expect(parsed.dataPath).toBeUndefined();
|
||||||
expect(mockLocalDb.update).not.toHaveBeenCalled();
|
expect(mockLocalDb.update).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type PostEngineLike = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type SettingsEngineLike = {
|
type SettingsEngineLike = {
|
||||||
getProjectMetadata: () => Promise<{ maxPostsPerPage?: number } | null>;
|
getProjectMetadata: () => Promise<{ maxPostsPerPage?: number; mainLanguage?: string } | null>;
|
||||||
setProjectContext: (projectId: string, dataDir?: string) => void;
|
setProjectContext: (projectId: string, dataDir?: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -335,6 +335,30 @@ describe('PreviewServer', () => {
|
|||||||
expect(html).not.toContain('<title>Blog Preview</title>');
|
expect(html).not.toContain('<title>Blog Preview</title>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses mainLanguage from metadata for html lang attribute', async () => {
|
||||||
|
server = new PreviewServer({
|
||||||
|
postEngine: makeEngine([makePost()]),
|
||||||
|
settingsEngine: {
|
||||||
|
setProjectContext: vi.fn(),
|
||||||
|
async getProjectMetadata() {
|
||||||
|
return {
|
||||||
|
name: 'My Great Blog',
|
||||||
|
mainLanguage: 'de',
|
||||||
|
maxPostsPerPage: 50,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getActiveProjectContext: async () => ({ projectId: 'default' }),
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.start(0);
|
||||||
|
|
||||||
|
const response = await fetch(`${server.getBaseUrl()}/`);
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
const html = await response.text();
|
||||||
|
expect(html).toContain('<html lang="de">');
|
||||||
|
});
|
||||||
|
|
||||||
it('falls back to active project name in page title when metadata is unavailable', async () => {
|
it('falls back to active project name in page title when metadata is unavailable', async () => {
|
||||||
server = new PreviewServer({
|
server = new PreviewServer({
|
||||||
postEngine: makeEngine([makePost()]),
|
postEngine: makeEngine([makePost()]),
|
||||||
@@ -587,4 +611,4 @@ describe('PreviewServer', () => {
|
|||||||
|
|
||||||
expect(getPost).toHaveBeenCalledTimes(50);
|
expect(getPost).toHaveBeenCalledTimes(50);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user