feat(workspace): migrate legacy sisyphus state to omo
This commit is contained in:
@@ -79,6 +79,7 @@ export * from "./plugin-command-discovery"
|
||||
export { SessionCategoryRegistry } from "./session-category-registry"
|
||||
export * from "./plugin-identity"
|
||||
export * from "./log-legacy-plugin-startup-warning"
|
||||
export * from "./legacy-workspace-migration"
|
||||
export * from "./task-system-enabled"
|
||||
export * from "./parse-tools-config"
|
||||
export { parseModelString } from "./model-string-parser"
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
||||
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { tmpdir } from "node:os"
|
||||
import { join } from "node:path"
|
||||
import { migrateLegacyWorkspaceDirectory } from "./legacy-workspace-migration"
|
||||
|
||||
describe("migrateLegacyWorkspaceDirectory", () => {
|
||||
let testDirectory = ""
|
||||
|
||||
beforeEach(() => {
|
||||
testDirectory = join(tmpdir(), `omo-workspace-migration-${Date.now()}-${Math.random().toString(36).slice(2)}`)
|
||||
mkdirSync(testDirectory, { recursive: true })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(testDirectory, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
test("#given legacy workspace with nested state and no target #when migrating #then copies the tree to .omo", () => {
|
||||
// given
|
||||
const legacyPlanPath = join(testDirectory, ".sisyphus", "plans", "work.md")
|
||||
const legacyNotepadDirectory = join(testDirectory, ".sisyphus", "notepads", "work")
|
||||
const legacyNotepadPath = join(legacyNotepadDirectory, "notes.md")
|
||||
mkdirSync(legacyNotepadDirectory, { recursive: true })
|
||||
mkdirSync(join(testDirectory, ".sisyphus", "plans"), { recursive: true })
|
||||
writeFileSync(legacyPlanPath, "# Plan", "utf-8")
|
||||
writeFileSync(legacyNotepadPath, "note", "utf-8")
|
||||
|
||||
// when
|
||||
const result = migrateLegacyWorkspaceDirectory(testDirectory)
|
||||
|
||||
// then
|
||||
expect(result.migrated).toBe(true)
|
||||
expect(readFileSync(join(testDirectory, ".omo", "plans", "work.md"), "utf-8")).toBe("# Plan")
|
||||
expect(readFileSync(join(testDirectory, ".omo", "notepads", "work", "notes.md"), "utf-8")).toBe("note")
|
||||
expect(existsSync(join(testDirectory, ".sisyphus", "plans", "work.md"))).toBe(true)
|
||||
})
|
||||
|
||||
test("#given target file already exists #when migrating #then keeps the target content", () => {
|
||||
// given
|
||||
const legacyPlanPath = join(testDirectory, ".sisyphus", "plans", "work.md")
|
||||
const targetPlanPath = join(testDirectory, ".omo", "plans", "work.md")
|
||||
mkdirSync(join(testDirectory, ".sisyphus", "plans"), { recursive: true })
|
||||
mkdirSync(join(testDirectory, ".omo", "plans"), { recursive: true })
|
||||
writeFileSync(legacyPlanPath, "legacy", "utf-8")
|
||||
writeFileSync(targetPlanPath, "target", "utf-8")
|
||||
|
||||
// when
|
||||
const result = migrateLegacyWorkspaceDirectory(testDirectory)
|
||||
|
||||
// then
|
||||
expect(result.migrated).toBe(false)
|
||||
expect(result.skipped).toContain(join(".omo", "plans", "work.md"))
|
||||
expect(readFileSync(targetPlanPath, "utf-8")).toBe("target")
|
||||
})
|
||||
|
||||
test("#given target has other files #when migrating #then copies only missing legacy files", () => {
|
||||
// given
|
||||
const legacyPlanPath = join(testDirectory, ".sisyphus", "plans", "work.md")
|
||||
const targetNotepadPath = join(testDirectory, ".omo", "notepads", "work", "notes.md")
|
||||
mkdirSync(join(testDirectory, ".sisyphus", "plans"), { recursive: true })
|
||||
mkdirSync(join(testDirectory, ".omo", "notepads", "work"), { recursive: true })
|
||||
writeFileSync(legacyPlanPath, "legacy plan", "utf-8")
|
||||
writeFileSync(targetNotepadPath, "existing note", "utf-8")
|
||||
|
||||
// when
|
||||
const result = migrateLegacyWorkspaceDirectory(testDirectory)
|
||||
|
||||
// then
|
||||
expect(result.migrated).toBe(true)
|
||||
expect(readFileSync(join(testDirectory, ".omo", "plans", "work.md"), "utf-8")).toBe("legacy plan")
|
||||
expect(readFileSync(targetNotepadPath, "utf-8")).toBe("existing note")
|
||||
})
|
||||
|
||||
test("#given no legacy workspace #when migrating #then reports no migration", () => {
|
||||
// when
|
||||
const result = migrateLegacyWorkspaceDirectory(testDirectory)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({ migrated: false, skipped: [] })
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,72 @@
|
||||
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "node:fs"
|
||||
import { dirname, join, relative } from "node:path"
|
||||
|
||||
import { log } from "./logger"
|
||||
|
||||
const LEGACY_WORKSPACE_DIR = ".sisyphus"
|
||||
const WORKSPACE_DIR = ".omo"
|
||||
|
||||
export type LegacyWorkspaceMigrationResult = {
|
||||
migrated: boolean
|
||||
skipped: string[]
|
||||
}
|
||||
|
||||
function copyMissingEntries(legacyPath: string, targetPath: string, targetRoot: string, skipped: string[]): boolean {
|
||||
const legacyStat = statSync(legacyPath)
|
||||
|
||||
if (existsSync(targetPath)) {
|
||||
if (legacyStat.isDirectory() && statSync(targetPath).isDirectory()) {
|
||||
let copiedChild = false
|
||||
for (const entry of readdirSync(legacyPath)) {
|
||||
copiedChild = copyMissingEntries(join(legacyPath, entry), join(targetPath, entry), targetRoot, skipped) || copiedChild
|
||||
}
|
||||
return copiedChild
|
||||
}
|
||||
|
||||
skipped.push(join(WORKSPACE_DIR, relative(targetRoot, targetPath)))
|
||||
return false
|
||||
}
|
||||
|
||||
if (legacyStat.isDirectory()) {
|
||||
mkdirSync(targetPath, { recursive: true })
|
||||
let copiedChild = false
|
||||
for (const entry of readdirSync(legacyPath)) {
|
||||
copiedChild = copyMissingEntries(join(legacyPath, entry), join(targetPath, entry), targetRoot, skipped) || copiedChild
|
||||
}
|
||||
return copiedChild
|
||||
}
|
||||
|
||||
mkdirSync(dirname(targetPath), { recursive: true })
|
||||
copyFileSync(legacyPath, targetPath)
|
||||
return true
|
||||
}
|
||||
|
||||
export function migrateLegacyWorkspaceDirectory(directory: string): LegacyWorkspaceMigrationResult {
|
||||
const legacyDirectory = join(directory, LEGACY_WORKSPACE_DIR)
|
||||
if (!existsSync(legacyDirectory)) {
|
||||
return { migrated: false, skipped: [] }
|
||||
}
|
||||
|
||||
const targetDirectory = join(directory, WORKSPACE_DIR)
|
||||
const skipped: string[] = []
|
||||
|
||||
try {
|
||||
const migrated = copyMissingEntries(legacyDirectory, targetDirectory, targetDirectory, skipped)
|
||||
if (migrated || skipped.length > 0) {
|
||||
log("[legacy-workspace-migration] Checked legacy workspace directory", {
|
||||
legacyDirectory,
|
||||
targetDirectory,
|
||||
migrated,
|
||||
skipped,
|
||||
})
|
||||
}
|
||||
return { migrated, skipped }
|
||||
} catch (error) {
|
||||
log("[legacy-workspace-migration] Failed to migrate legacy workspace directory", {
|
||||
legacyDirectory,
|
||||
targetDirectory,
|
||||
error,
|
||||
})
|
||||
return { migrated: false, skipped }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user