Files
SuperCharged-Claude-Code-Up…/scripts/migrate-to-projects.js
uroma 91e4835e03 feat: add database migration script for projects
Add migration script to backfill existing sessions into projects:
- Scans session files from Claude Sessions directory
- Extracts unique project names from metadata.project field
- Creates projects with random icons and colors
- Links sessions to their respective projects in database
- Provides detailed progress reporting and summary

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 17:12:31 +00:00

304 lines
8.7 KiB
JavaScript
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const fs = require('fs');
const path = require('path');
const Database = require('better-sqlite3');
/**
* Database Migration Script: Migrate existing sessions to projects
*
* This script:
* 1. Scans existing session files from Claude Sessions directory
* 2. Extracts unique project names from metadata.project field
* 3. Creates projects with random icons and colors
* 4. Updates sessions table with projectId references
*/
const VAULT_PATH = '/home/uroma/obsidian-vault';
const CLAUDE_SESSIONS_DIR = path.join(VAULT_PATH, 'Claude Sessions');
const DB_PATH = path.join(__dirname, '..', 'database.sqlite');
const ICONS = ['🚀', '📱', '🎨', '💻', '🔧', '📊', '🎯', '🔮', '⚡', '🌟'];
const COLORS = ['#4a9eff', '#ff6b6b', '#51cf66', '#ffd43b', '#cc5de8', '#ff922b', '#20c997', '#339af0'];
/**
* Get random item from array
*/
function getRandomItem(array) {
return array[Math.floor(Math.random() * array.length)];
}
/**
* Parse frontmatter from markdown content
*/
function parseFrontmatter(content) {
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
if (!frontmatterMatch) {
return null;
}
const frontmatter = {};
frontmatterMatch[1].split('\n').forEach(line => {
const [key, ...valueParts] = line.split(':');
if (key && valueParts.length > 0) {
frontmatter[key.trim()] = valueParts.join(':').trim();
}
});
return frontmatter;
}
/**
* Scan session files and extract project information
*/
function scanSessionFiles() {
console.log('📂 Scanning session files...');
console.log(` Directory: ${CLAUDE_SESSIONS_DIR}`);
if (!fs.existsSync(CLAUDE_SESSIONS_DIR)) {
console.log('⚠️ Claude Sessions directory does not exist');
return [];
}
const files = fs.readdirSync(CLAUDE_SESSIONS_DIR);
const sessionFiles = files.filter(f => f.endsWith('.md') && f.includes('session-'));
console.log(` Found ${sessionFiles.length} session files`);
const sessions = [];
const projectMap = new Map(); // projectName -> { sessions: [], path: null }
sessionFiles.forEach(file => {
const filepath = path.join(CLAUDE_SESSIONS_DIR, file);
const content = fs.readFileSync(filepath, 'utf-8');
const frontmatter = parseFrontmatter(content);
if (!frontmatter) {
console.log(` ⚠️ Skipping ${file}: No frontmatter found`);
return;
}
const sessionId = frontmatter.session_id;
const projectName = frontmatter.project;
const workingDir = frontmatter.working_dir;
const createdAt = frontmatter.created_at;
if (!sessionId) {
console.log(` ⚠️ Skipping ${file}: No session_id in frontmatter`);
return;
}
// Add session to list
sessions.push({
id: sessionId,
projectName,
workingDir,
createdAt,
file
});
// Group by project
if (projectName) {
if (!projectMap.has(projectName)) {
projectMap.set(projectName, {
sessions: [],
path: workingDir,
earliestDate: createdAt
});
}
projectMap.get(projectName).sessions.push(sessionId);
// Update earliest date if this session is older
const currentDate = new Date(createdAt);
const existingDate = new Date(projectMap.get(projectName).earliestDate);
if (currentDate < existingDate) {
projectMap.get(projectName).earliestDate = createdAt;
}
}
});
console.log(` ✅ Processed ${sessions.length} sessions`);
console.log(` 📊 Found ${projectMap.size} unique projects`);
return { sessions, projectMap };
}
/**
* Create projects in database
*/
function createProjects(db, projectMap) {
console.log('\n🎨 Creating projects in database...');
const insertProject = db.prepare(`
INSERT INTO projects (name, description, icon, color, path, createdAt, lastActivity)
VALUES (?, ?, ?, ?, ?, ?, ?)
`);
const projects = {};
let createdCount = 0;
for (const [projectName, data] of projectMap.entries()) {
const icon = getRandomItem(ICONS);
const color = getRandomItem(COLORS);
const now = new Date().toISOString();
try {
const result = insertProject.run(
projectName,
`Sessions for ${projectName}`,
icon,
color,
data.path || VAULT_PATH,
data.earliestDate,
now
);
projects[projectName] = {
id: result.lastInsertRowid,
name: projectName,
icon,
color,
sessionCount: data.sessions.length
};
createdCount++;
console.log(` ✅ Created project: ${projectName} (${icon})`);
console.log(` - ID: ${result.lastInsertRowid}`);
console.log(` - Sessions: ${data.sessions.length}`);
console.log(` - Path: ${data.path || VAULT_PATH}`);
} catch (error) {
console.error(` ❌ Error creating project ${projectName}:`, error.message);
}
}
console.log(`\n ✅ Created ${createdCount} projects`);
return projects;
}
/**
* Update sessions with projectId
*/
function updateSessions(db, sessions, projects) {
console.log('\n🔗 Linking sessions to projects...');
const insertSession = db.prepare(`
INSERT OR REPLACE INTO sessions (id, projectId)
VALUES (?, ?)
`);
let updatedCount = 0;
let unlinkedCount = 0;
sessions.forEach(session => {
const projectName = session.projectName;
if (projectName && projects[projectName]) {
const projectId = projects[projectName].id;
try {
insertSession.run(session.id, projectId);
updatedCount++;
console.log(` ✅ Linked ${session.id} -> ${projectName} (ID: ${projectId})`);
} catch (error) {
console.error(` ❌ Error linking session ${session.id}:`, error.message);
}
} else {
// Session without a project - add to sessions table without projectId
try {
insertSession.run(session.id, null);
unlinkedCount++;
console.log(` ⚠️ Unlinked session: ${session.id} (no project)`);
} catch (error) {
console.error(` ❌ Error adding session ${session.id}:`, error.message);
}
}
});
console.log(`\n ✅ Linked ${updatedCount} sessions to projects`);
console.log(` ${unlinkedCount} sessions without projects`);
return { updatedCount, unlinkedCount };
}
/**
* Display migration summary
*/
function displaySummary(sessions, projects, stats) {
console.log('\n' + '='.repeat(60));
console.log('📊 MIGRATION SUMMARY');
console.log('='.repeat(60));
console.log(`\nSessions Processed: ${sessions.length}`);
console.log(`Projects Created: ${Object.keys(projects).length}`);
console.log(`Sessions Linked: ${stats.updatedCount}`);
console.log(`Sessions Unlinked: ${stats.unlinkedCount}`);
console.log('\n📁 Projects Created:');
Object.values(projects).forEach(project => {
console.log(` ${project.icon} ${project.name} (${project.color})`);
console.log(` - ID: ${project.id}`);
console.log(` - Sessions: ${project.sessionCount}`);
});
console.log('\n' + '='.repeat(60));
console.log('✅ Migration completed successfully!');
console.log('='.repeat(60) + '\n');
}
/**
* Main migration function
*/
function migrate() {
console.log('🚀 Starting database migration for projects...\n');
// Check if database exists
if (!fs.existsSync(DB_PATH)) {
console.error('❌ Database file not found:', DB_PATH);
console.error('Please run the application first to initialize the database.');
process.exit(1);
}
// Open database
console.log('📦 Opening database...');
const db = new Database(DB_PATH);
db.pragma('journal_mode = WAL');
try {
// Step 1: Scan session files
const { sessions, projectMap } = scanSessionFiles();
if (sessions.length === 0) {
console.log('\n⚠ No sessions found to migrate');
db.close();
return;
}
// Step 2: Create projects
const projects = createProjects(db, projectMap);
if (Object.keys(projects).length === 0) {
console.log('\n⚠ No projects found to create');
db.close();
return;
}
// Step 3: Update sessions
const stats = updateSessions(db, sessions, projects);
// Step 4: Display summary
displaySummary(sessions, projects, stats);
} catch (error) {
console.error('\n❌ Migration failed:', error);
throw error;
} finally {
db.close();
console.log('📦 Database connection closed\n');
}
}
// Run migration if executed directly
if (require.main === module) {
migrate();
}
module.exports = { migrate };