fix: preserve projects on validation failures and add error logging (#129)
This commit is contained in:
@@ -39,6 +39,9 @@ const FILE_SEARCH_EXCLUDED_DIRS = new Set([
|
|||||||
'logs'
|
'logs'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Lock to prevent race conditions in persistSettings
|
||||||
|
let persistSettingsLock = Promise.resolve();
|
||||||
|
|
||||||
const normalizeDirectoryPath = (value) => {
|
const normalizeDirectoryPath = (value) => {
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return value;
|
return value;
|
||||||
@@ -696,30 +699,39 @@ const formatSettingsResponse = (settings) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const validateProjectEntries = async (projects) => {
|
const validateProjectEntries = async (projects) => {
|
||||||
|
console.log(`[validateProjectEntries] Starting validation for ${projects.length} projects`);
|
||||||
|
|
||||||
if (!Array.isArray(projects)) {
|
if (!Array.isArray(projects)) {
|
||||||
|
console.warn(`[validateProjectEntries] Input is not an array, returning empty`);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
if (!project || typeof project.path !== 'string' || project.path.length === 0) {
|
if (!project || typeof project.path !== 'string' || project.path.length === 0) {
|
||||||
|
console.error(`[validateProjectEntries] Invalid project entry: missing or empty path`, project);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const stats = await fsPromises.stat(project.path);
|
const stats = await fsPromises.stat(project.path);
|
||||||
if (!stats.isDirectory()) {
|
if (!stats.isDirectory()) {
|
||||||
|
console.error(`[validateProjectEntries] Project path is not a directory: ${project.path}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
results.push(project);
|
results.push(project);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error;
|
const err = error;
|
||||||
|
console.error(`[validateProjectEntries] Failed to validate project "${project.path}": ${err.code || err.message || err}`);
|
||||||
if (err && typeof err === 'object' && err.code === 'ENOENT') {
|
if (err && typeof err === 'object' && err.code === 'ENOENT') {
|
||||||
|
console.log(`[validateProjectEntries] Removing project with ENOENT: ${project.path}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
continue;
|
console.log(`[validateProjectEntries] Keeping project despite non-ENOENT error: ${project.path}`);
|
||||||
|
results.push(project);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[validateProjectEntries] Validation complete: ${results.length}/${projects.length} projects valid`);
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -794,27 +806,39 @@ const readSettingsFromDiskMigrated = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const persistSettings = async (changes) => {
|
const persistSettings = async (changes) => {
|
||||||
const current = await readSettingsFromDisk();
|
// Serialize concurrent calls using lock
|
||||||
const sanitized = sanitizeSettingsUpdate(changes);
|
persistSettingsLock = persistSettingsLock.then(async () => {
|
||||||
let next = mergePersistedSettings(current, sanitized);
|
console.log(`[persistSettings] Called with changes:`, JSON.stringify(changes, null, 2));
|
||||||
|
const current = await readSettingsFromDisk();
|
||||||
|
console.log(`[persistSettings] Current projects count:`, Array.isArray(current.projects) ? current.projects.length : 'N/A');
|
||||||
|
const sanitized = sanitizeSettingsUpdate(changes);
|
||||||
|
let next = mergePersistedSettings(current, sanitized);
|
||||||
|
|
||||||
if (Array.isArray(next.projects)) {
|
if (Array.isArray(next.projects)) {
|
||||||
const validated = await validateProjectEntries(next.projects);
|
console.log(`[persistSettings] Validating ${next.projects.length} projects...`);
|
||||||
next = { ...next, projects: validated };
|
const validated = await validateProjectEntries(next.projects);
|
||||||
}
|
console.log(`[persistSettings] After validation: ${validated.length} projects remain`);
|
||||||
|
next = { ...next, projects: validated };
|
||||||
if (Array.isArray(next.projects) && next.projects.length > 0) {
|
|
||||||
const activeId = typeof next.activeProjectId === 'string' ? next.activeProjectId : '';
|
|
||||||
const active = next.projects.find((project) => project.id === activeId) || null;
|
|
||||||
if (!active) {
|
|
||||||
next = { ...next, activeProjectId: next.projects[0].id };
|
|
||||||
}
|
}
|
||||||
} else if (next.activeProjectId) {
|
|
||||||
next = { ...next, activeProjectId: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
await writeSettingsToDisk(next);
|
if (Array.isArray(next.projects) && next.projects.length > 0) {
|
||||||
return formatSettingsResponse(next);
|
const activeId = typeof next.activeProjectId === 'string' ? next.activeProjectId : '';
|
||||||
|
const active = next.projects.find((project) => project.id === activeId) || null;
|
||||||
|
if (!active) {
|
||||||
|
console.log(`[persistSettings] Active project ID ${activeId} not found, switching to ${next.projects[0].id}`);
|
||||||
|
next = { ...next, activeProjectId: next.projects[0].id };
|
||||||
|
}
|
||||||
|
} else if (next.activeProjectId) {
|
||||||
|
console.log(`[persistSettings] No projects found, clearing activeProjectId ${next.activeProjectId}`);
|
||||||
|
next = { ...next, activeProjectId: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeSettingsToDisk(next);
|
||||||
|
console.log(`[persistSettings] Successfully saved ${next.projects?.length || 0} projects to disk`);
|
||||||
|
return formatSettingsResponse(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
return persistSettingsLock;
|
||||||
};
|
};
|
||||||
|
|
||||||
// HMR-persistent state via globalThis
|
// HMR-persistent state via globalThis
|
||||||
@@ -2386,11 +2410,15 @@ async function main(options = {}) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.put('/api/config/settings', async (req, res) => {
|
app.put('/api/config/settings', async (req, res) => {
|
||||||
|
console.log(`[API:PUT /api/config/settings] Received request`);
|
||||||
|
console.log(`[API:PUT /api/config/settings] Request body:`, JSON.stringify(req.body, null, 2));
|
||||||
try {
|
try {
|
||||||
const updated = await persistSettings(req.body ?? {});
|
const updated = await persistSettings(req.body ?? {});
|
||||||
|
console.log(`[API:PUT /api/config/settings] Success, returning ${updated.projects?.length || 0} projects`);
|
||||||
res.json(updated);
|
res.json(updated);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to save settings:', error);
|
console.error(`[API:PUT /api/config/settings] Failed to save settings:`, error);
|
||||||
|
console.error(`[API:PUT /api/config/settings] Error stack:`, error.stack);
|
||||||
res.status(500).json({ error: error instanceof Error ? error.message : 'Failed to save settings' });
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Failed to save settings' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user