refactor: remove legacy kb compatibility
Drops the .kb/kb.db migration path, legacy backup filename handling, and backward-compat test suites. Renames internal kbDir identifiers to fusionDir and hasKbProject/isValidKbProject to their fusion equivalents. - Remove needsCentralMigration, autoMigrateToCentral, and the "needs-migration" FirstRunState; checkAndMigrate and KB_SKIP_MIGRATION env var are gone - Remove LEGACY_BACKUP_DIR and canonicalizeBackupDir; listBackups no longer matches kb-* filenames - Delete backward-compat.test.ts and store-backward-compat.test.ts; update remaining tests to new 3-state first-run model Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -189,7 +189,6 @@ vi.mock("./commands/message.js", () => ({
|
||||
|
||||
const originalArgv = process.argv;
|
||||
const originalExit = process.exit;
|
||||
const originalSkipMigration = process.env.KB_SKIP_MIGRATION;
|
||||
const originalPiPackageDir = process.env.PI_PACKAGE_DIR;
|
||||
|
||||
let importCounter = 0;
|
||||
@@ -206,7 +205,6 @@ describe("bin command routing and fallbacks", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
process.env.KB_SKIP_MIGRATION = "1";
|
||||
delete process.env.PI_PACKAGE_DIR;
|
||||
process.exit = vi.fn(((code?: number) => {
|
||||
throw new Error(`process.exit:${code ?? 0}`);
|
||||
@@ -216,11 +214,6 @@ describe("bin command routing and fallbacks", () => {
|
||||
afterEach(() => {
|
||||
process.argv = originalArgv;
|
||||
process.exit = originalExit;
|
||||
if (originalSkipMigration === undefined) {
|
||||
delete process.env.KB_SKIP_MIGRATION;
|
||||
} else {
|
||||
process.env.KB_SKIP_MIGRATION = originalSkipMigration;
|
||||
}
|
||||
|
||||
if (originalPiPackageDir === undefined) {
|
||||
delete process.env.PI_PACKAGE_DIR;
|
||||
|
||||
@@ -380,51 +380,6 @@ function getFlagValueNumber(args: string[], flag: string): number | undefined {
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if migration is needed and run it automatically.
|
||||
* This handles the transition from single-project to multi-project mode.
|
||||
*/
|
||||
async function checkAndMigrate(): Promise<void> {
|
||||
// Skip if KB_SKIP_MIGRATION is set
|
||||
if (process.env.KB_SKIP_MIGRATION === "1") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { needsCentralMigration, autoMigrateToCentral } = await import("@fusion/core");
|
||||
|
||||
// Check if migration is needed
|
||||
if (!needsCentralMigration(process.cwd())) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("\n🔄 Migrating to multi-project mode...");
|
||||
|
||||
// Get CentralCore and run migration
|
||||
const { CentralCore } = await import("@fusion/core");
|
||||
const central = new CentralCore();
|
||||
await central.init();
|
||||
|
||||
try {
|
||||
const result = await autoMigrateToCentral(process.cwd(), central);
|
||||
|
||||
if (result.success) {
|
||||
console.log(`✓ Registered project: ${result.projectsRegistered.join(", ")}`);
|
||||
if (result.errors.length > 0) {
|
||||
console.log(` Warnings: ${result.errors.join(", ")}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`⚠ Migration warnings: ${result.errors.join(", ")}`);
|
||||
}
|
||||
} finally {
|
||||
await central.close();
|
||||
}
|
||||
} catch (err) {
|
||||
// Migration errors are non-fatal - continue with legacy mode
|
||||
console.log(`⚠ Migration check failed: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { cleanedArgs: args, projectName } = extractGlobalProjectFlag(process.argv.slice(2));
|
||||
|
||||
@@ -435,12 +390,6 @@ async function main() {
|
||||
|
||||
const command = args[0];
|
||||
|
||||
// Migration check: run auto-migration for existing single-project users
|
||||
// Skip for init command itself (user is explicitly initializing)
|
||||
if (command !== "init" && command !== "dashboard") {
|
||||
await checkAndMigrate();
|
||||
}
|
||||
|
||||
const {
|
||||
runDashboard,
|
||||
runServe,
|
||||
|
||||
@@ -21,7 +21,7 @@ vi.mock("@fusion/core", () => ({
|
||||
TaskStore: vi.fn().mockImplementation(() => ({
|
||||
init: vi.fn().mockResolvedValue(undefined),
|
||||
getSettings: mockGetSettings,
|
||||
kbDir: "/cwd/.fusion",
|
||||
fusionDir: "/cwd/.fusion",
|
||||
})),
|
||||
createBackupManager: vi.fn(() => ({
|
||||
listBackups: mockListBackups,
|
||||
@@ -60,7 +60,7 @@ describe("backup commands", () => {
|
||||
projectName: "demo-project",
|
||||
projectPath: "/projects/demo",
|
||||
isRegistered: true,
|
||||
store: { getSettings: mockGetSettings, kbDir: "/projects/demo/.fusion" },
|
||||
store: { getSettings: mockGetSettings, fusionDir: "/projects/demo/.fusion" },
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -22,14 +22,14 @@ async function resolveBackupStore(projectName?: string): Promise<TaskStore> {
|
||||
async function getBackupManager(projectName?: string): Promise<{
|
||||
manager: BackupManager;
|
||||
store: TaskStore;
|
||||
kbDir: string;
|
||||
fusionDir: string;
|
||||
}> {
|
||||
const store = await resolveBackupStore(projectName);
|
||||
// Access the private kbDir property via type assertion
|
||||
const kbDir = (store as unknown as { kbDir: string }).kbDir;
|
||||
// Access the private fusionDir property via type assertion
|
||||
const fusionDir = (store as unknown as { fusionDir: string }).fusionDir;
|
||||
const settings = await store.getSettings();
|
||||
const manager = createBackupManager(kbDir, settings);
|
||||
return { manager, store, kbDir };
|
||||
const manager = createBackupManager(fusionDir, settings);
|
||||
return { manager, store, fusionDir };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,12 +37,12 @@ async function getBackupManager(projectName?: string): Promise<{
|
||||
* Usage: fn backup --create
|
||||
*/
|
||||
export async function runBackupCreate(projectName?: string): Promise<void> {
|
||||
const { kbDir, store } = await getBackupManager(projectName);
|
||||
const { fusionDir, store } = await getBackupManager(projectName);
|
||||
const settings = await store.getSettings();
|
||||
|
||||
console.log("Creating database backup...");
|
||||
|
||||
const result = await runBackupCommand(kbDir, settings);
|
||||
const result = await runBackupCommand(fusionDir, settings);
|
||||
|
||||
if (result.success) {
|
||||
console.log(result.output);
|
||||
|
||||
@@ -26,8 +26,8 @@ async function getProjectPath(projectName?: string): Promise<string> {
|
||||
*/
|
||||
async function createMessageStore(projectName?: string): Promise<{ store: MessageStore; db: Database }> {
|
||||
const projectPath = await getProjectPath(projectName);
|
||||
const kbDir = projectPath + "/.fusion";
|
||||
const db = createDatabase(kbDir);
|
||||
const fusionDir = projectPath + "/.fusion";
|
||||
const db = createDatabase(fusionDir);
|
||||
db.init();
|
||||
const store = new MessageStore(db);
|
||||
return { store, db };
|
||||
|
||||
@@ -220,12 +220,12 @@ export async function resolveProject(options: ResolveOptions = {}): Promise<Reso
|
||||
|
||||
// 2. Walk up from cwd to find .fusion/
|
||||
const cwd = options.cwd ? resolve(options.cwd) : process.cwd();
|
||||
const kbDir = findKbDir(cwd);
|
||||
const fusionDir = findKbDir(cwd);
|
||||
|
||||
if (kbDir) {
|
||||
if (fusionDir) {
|
||||
// 3. Match path against registered projects
|
||||
const allProjects = await central.listProjects();
|
||||
const normalizedKbDir = normalize(kbDir);
|
||||
const normalizedKbDir = normalize(fusionDir);
|
||||
|
||||
const match = allProjects.find((p) => normalize(p.path) === normalizedKbDir);
|
||||
|
||||
@@ -245,12 +245,12 @@ export async function resolveProject(options: ResolveOptions = {}): Promise<Reso
|
||||
|
||||
// 4. Has .fusion/ but not registered
|
||||
if (interactive) {
|
||||
console.log(`\n Found fn project at ${kbDir} but it's not registered.`);
|
||||
console.log(`\n Found fn project at ${fusionDir} but it's not registered.`);
|
||||
const shouldRegister = await promptConfirm("Register this project now?", true);
|
||||
|
||||
if (shouldRegister) {
|
||||
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
||||
const defaultName = kbDir.split("/").pop() || "unnamed";
|
||||
const defaultName = fusionDir.split("/").pop() || "unnamed";
|
||||
const name = await rl.question(` Project name [${defaultName}]: `);
|
||||
rl.close();
|
||||
|
||||
@@ -259,7 +259,7 @@ export async function resolveProject(options: ResolveOptions = {}): Promise<Reso
|
||||
try {
|
||||
const newProject = await central.registerProject({
|
||||
name: finalName,
|
||||
path: kbDir,
|
||||
path: fusionDir,
|
||||
isolationMode: "in-process",
|
||||
});
|
||||
|
||||
@@ -272,22 +272,22 @@ export async function resolveProject(options: ResolveOptions = {}): Promise<Reso
|
||||
throw new ProjectResolutionError(
|
||||
`Failed to register project: ${err.message}`,
|
||||
"NOT_REGISTERED",
|
||||
{ directory: kbDir, error: err.message }
|
||||
{ directory: fusionDir, error: err.message }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new ProjectResolutionError(
|
||||
"Project not registered. Run `fn project add <path>` to register.",
|
||||
"NOT_REGISTERED",
|
||||
{ directory: kbDir }
|
||||
{ directory: fusionDir }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new ProjectResolutionError(
|
||||
`Found fn project at ${kbDir} but it's not registered.\n\n` +
|
||||
"Run `fn project add " + kbDir + "` to register it, or use --project <name>.",
|
||||
`Found fn project at ${fusionDir} but it's not registered.\n\n` +
|
||||
"Run `fn project add " + fusionDir + "` to register it, or use --project <name>.",
|
||||
"NOT_REGISTERED",
|
||||
{ directory: kbDir }
|
||||
{ directory: fusionDir }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user