mirror of
https://github.com/aljazceru/claude-code-viewer.git
synced 2026-02-07 14:54:34 +01:00
fix: Handle missing Claude projects directory and Node.js compatibility issues
- Add proper error handling for missing ~/.claude/projects directory - Fix TypeError when d.parentPath is undefined (Node.js compatibility) - Replace toSorted() with sort() for Node.js 18 compatibility - Handle readdir errors gracefully and return empty arrays These changes prevent the application from crashing with 'Internal Server Error' when the Claude projects directory doesn't exist or when using Node.js < 20.
This commit is contained in:
9353
package-lock.json
generated
Normal file
9353
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "run-p 'dev:*'",
|
||||
"dev:next": "PORT=3400 next dev --turbopack",
|
||||
"dev:next": "next dev --turbopack",
|
||||
"start": "node dist/index.js",
|
||||
"build": "./scripts/build.sh",
|
||||
"lint": "run-s 'lint:*'",
|
||||
|
||||
@@ -6,7 +6,7 @@ if [ -d "dist/.next" ]; then
|
||||
rm -rf dist/.next
|
||||
fi
|
||||
|
||||
pnpm exec next build
|
||||
npx next build
|
||||
cp -r public .next/standalone/
|
||||
cp -r .next/static .next/standalone/.next/
|
||||
|
||||
|
||||
@@ -50,11 +50,11 @@ export const getProjectMeta = async (
|
||||
.map(
|
||||
(d) =>
|
||||
({
|
||||
fullPath: resolve(d.parentPath, d.name),
|
||||
stats: statSync(resolve(d.parentPath, d.name)),
|
||||
fullPath: resolve(claudeProjectPath, d.name),
|
||||
stats: statSync(resolve(claudeProjectPath, d.name)),
|
||||
}) as const,
|
||||
)
|
||||
.toSorted((a, b) => {
|
||||
.sort((a, b) => {
|
||||
return a.stats.ctime.getTime() - b.stats.ctime.getTime();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { readdir, access } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { constants } from "node:fs";
|
||||
|
||||
import { claudeProjectPath } from "../paths";
|
||||
import type { Project } from "../types";
|
||||
@@ -7,28 +8,42 @@ import { getProjectMeta } from "./getProjectMeta";
|
||||
import { encodeProjectId } from "./id";
|
||||
|
||||
export const getProjects = async (): Promise<{ projects: Project[] }> => {
|
||||
const dirents = await readdir(claudeProjectPath, { withFileTypes: true });
|
||||
const projects = await Promise.all(
|
||||
dirents
|
||||
.filter((d) => d.isDirectory())
|
||||
.map(async (d) => {
|
||||
const fullPath = resolve(d.parentPath, d.name);
|
||||
const id = encodeProjectId(fullPath);
|
||||
try {
|
||||
// Check if the claude projects directory exists
|
||||
await access(claudeProjectPath, constants.F_OK);
|
||||
} catch (error) {
|
||||
// Directory doesn't exist, return empty array
|
||||
console.warn(`Claude projects directory not found at ${claudeProjectPath}`);
|
||||
return { projects: [] };
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
claudeProjectPath: fullPath,
|
||||
meta: await getProjectMeta(fullPath),
|
||||
};
|
||||
try {
|
||||
const dirents = await readdir(claudeProjectPath, { withFileTypes: true });
|
||||
const projects = await Promise.all(
|
||||
dirents
|
||||
.filter((d) => d.isDirectory())
|
||||
.map(async (d) => {
|
||||
const fullPath = resolve(claudeProjectPath, d.name);
|
||||
const id = encodeProjectId(fullPath);
|
||||
|
||||
return {
|
||||
id,
|
||||
claudeProjectPath: fullPath,
|
||||
meta: await getProjectMeta(fullPath),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
projects: projects.sort((a, b) => {
|
||||
return (
|
||||
(b.meta.lastModifiedAt?.getTime() ?? 0) -
|
||||
(a.meta.lastModifiedAt?.getTime() ?? 0)
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
projects: projects.sort((a, b) => {
|
||||
return (
|
||||
(b.meta.lastModifiedAt?.getTime() ?? 0) -
|
||||
(a.meta.lastModifiedAt?.getTime() ?? 0)
|
||||
);
|
||||
}),
|
||||
};
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error reading projects:', error);
|
||||
return { projects: [] };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,24 +15,29 @@ export const getSessions = async (
|
||||
): Promise<{ sessions: Session[] }> => {
|
||||
const claudeProjectPath = decodeProjectId(projectId);
|
||||
|
||||
const dirents = await readdir(claudeProjectPath, { withFileTypes: true });
|
||||
const sessions = await Promise.all(
|
||||
dirents
|
||||
.filter((d) => d.isFile() && d.name.endsWith(".jsonl"))
|
||||
.map(async (d): Promise<Session> => {
|
||||
const fullPath = resolve(d.parentPath, d.name);
|
||||
try {
|
||||
const dirents = await readdir(claudeProjectPath, { withFileTypes: true });
|
||||
const sessions = await Promise.all(
|
||||
dirents
|
||||
.filter((d) => d.isFile() && d.name.endsWith(".jsonl"))
|
||||
.map(async (d): Promise<Session> => {
|
||||
const fullPath = resolve(claudeProjectPath, d.name);
|
||||
|
||||
return {
|
||||
id: basename(fullPath, extname(fullPath)),
|
||||
jsonlFilePath: fullPath,
|
||||
meta: await getSessionMeta(fullPath),
|
||||
};
|
||||
return {
|
||||
id: basename(fullPath, extname(fullPath)),
|
||||
jsonlFilePath: fullPath,
|
||||
meta: await getSessionMeta(fullPath),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
sessions: sessions.sort((a, b) => {
|
||||
return getTime(b.meta.lastModifiedAt) - getTime(a.meta.lastModifiedAt);
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
sessions: sessions.sort((a, b) => {
|
||||
return getTime(b.meta.lastModifiedAt) - getTime(a.meta.lastModifiedAt);
|
||||
}),
|
||||
};
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn(`Failed to read sessions for project ${projectId}:`, error);
|
||||
return { sessions: [] };
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user