fix(upgrade): make snapshot paths Windows-safe (#167)

* fix(upgrade): make snapshot paths Windows-safe

Use a filesystem-safe UTC timestamp for new upgrade snapshot directories while retaining legacy ISO timestamp parsing. Normalize test paths with node:path so the Windows suite exercises the same behavior.

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(upgrade): sort snapshots by parsed timestamp

Order legacy colon and Windows-safe dash snapshot names chronologically so rollback and retention keep the actual newest snapshot across the migration boundary.

Co-Authored-By: claude-flow <ruv@ruv.net>

---------

Co-authored-by: nyxst4ck <nyxst4ck@users.noreply.github.com>
Co-authored-by: claude-flow <ruv@ruv.net>
Co-authored-by: nyxst4ck <289980115+nyxst4ck@users.noreply.github.com>
This commit is contained in:
nyxst4ck
2026-07-17 18:39:09 +10:00
committed by GitHub
co-authored by nyxst4ck claude-flow nyxst4ck
parent b038d3ceac
commit bafad077ff
2 changed files with 82 additions and 19 deletions
+19 -4
View File
@@ -2,7 +2,7 @@ import { mkdirSync, writeFileSync, readFileSync, copyFileSync, existsSync, readd
import { join } from "node:path";
export function writeSnapshot({ homeDir, fromCommit, fromVersion, toVersion, extraFiles = [] }) {
const ts = new Date().toISOString().replace(/\.\d+Z$/, "Z");
const ts = formatSnapshotTimestamp(new Date());
const root = join(homeDir, ".ocp", `upgrade-snapshot-${ts}`);
mkdirSync(root, { recursive: true });
@@ -48,7 +48,10 @@ export function listSnapshots(homeDir) {
return readdirSync(root)
.filter(name => name.startsWith("upgrade-snapshot-"))
.map(name => ({ name, path: join(root, name), mtime: statSync(join(root, name)).mtimeMs }))
.sort((a, b) => a.name.localeCompare(b.name));
.sort((a, b) => {
const chronological = parseSnapshotTimestamp(a.name) - parseSnapshotTimestamp(b.name);
return chronological || a.name.localeCompare(b.name);
});
}
/**
@@ -107,9 +110,21 @@ export function gcSnapshots(homeDir, opts = {}) {
}
function parseSnapshotTimestamp(name) {
// Both legacy ISO names and Windows-safe names are supported.
// upgrade-snapshot-2026-05-11T08:30:00Z → epoch ms
// upgrade-snapshot-2026-05-11T08-30-00Z → epoch ms
const m = name.match(/upgrade-snapshot-(.+)$/);
if (!m) return 0;
const t = Date.parse(m[1]);
return Number.isFinite(t) ? t : 0;
const raw = m[1];
const t = Date.parse(raw);
if (Number.isFinite(t)) return t;
const iso = raw.replace(/(T\d{2})-(\d{2})-(\d{2})Z$/, "$1:$2:$3Z");
const portable = Date.parse(iso);
return Number.isFinite(portable) ? portable : 0;
}
function formatSnapshotTimestamp(date) {
// Windows forbids ':' in directory names. Replacing only the time separators
// preserves chronological lexical order and keeps the timestamp readable.
return date.toISOString().replace(/\.\d+Z$/, "Z").replace(/:/g, "-");
}