mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-07 05:42:53 +00:00
fix(migrate): guard report timestamp formatting
This commit is contained in:
18
src/commands/migrate/context.test.ts
Normal file
18
src/commands/migrate/context.test.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildMigrationReportDir } from "./context.js";
|
||||
|
||||
describe("migration context helpers", () => {
|
||||
it("builds report directories with filename-safe timestamps", () => {
|
||||
const now = Date.parse("2026-02-23T12:34:56.000Z");
|
||||
expect(buildMigrationReportDir("codex", "/state", now)).toBe(
|
||||
path.join("/state", "migration", "codex", "2026-02-23T12-34-56.000Z"),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back instead of throwing for out-of-range report timestamps", () => {
|
||||
expect(buildMigrationReportDir("codex", "/state", 9_000_000_000_000_000)).toMatch(
|
||||
/[/\\]migration[/\\]codex[/\\]\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.\d{3}Z$/,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import { resolveStateDir } from "../../config/paths.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import type { MigrationProviderContext } from "../../plugins/types.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { timestampMsToIsoFileStamp } from "../../shared/number-coercion.js";
|
||||
|
||||
export function createMigrationLogger(runtime: RuntimeEnv, opts: { json?: boolean } = {}) {
|
||||
const info = opts.json ? runtime.error : runtime.log;
|
||||
@@ -24,7 +25,7 @@ export function buildMigrationReportDir(
|
||||
stateDir: string,
|
||||
nowMs = Date.now(),
|
||||
): string {
|
||||
const stamp = new Date(nowMs).toISOString().replaceAll(":", "-");
|
||||
const stamp = timestampMsToIsoFileStamp(nowMs);
|
||||
return path.join(stateDir, "migration", providerId, stamp);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { timestampMsToIsoString } from "../../shared/number-coercion.js";
|
||||
import { timestampMsToIsoFileStamp } from "../../shared/number-coercion.js";
|
||||
import { escapeRegExp } from "../../shared/regexp.js";
|
||||
|
||||
export type SessionArchiveReason = "bak" | "reset" | "deleted";
|
||||
@@ -123,11 +123,7 @@ export function parseUsageCountedSessionIdFromFileName(fileName: string): string
|
||||
}
|
||||
|
||||
export function formatSessionArchiveTimestamp(nowMs = Date.now()): string {
|
||||
const iso =
|
||||
timestampMsToIsoString(nowMs) ??
|
||||
timestampMsToIsoString(Date.now()) ??
|
||||
"1970-01-01T00:00:00.000Z";
|
||||
return iso.replaceAll(":", "-");
|
||||
return timestampMsToIsoFileStamp(nowMs);
|
||||
}
|
||||
|
||||
function restoreSessionArchiveTimestamp(raw: string): string {
|
||||
|
||||
@@ -25,6 +25,8 @@ import {
|
||||
parseStrictNonNegativeInteger,
|
||||
parseStrictPositiveInteger,
|
||||
resolveTimerTimeoutMs,
|
||||
resolveTimestampMsToIsoString,
|
||||
timestampMsToIsoFileStamp,
|
||||
timestampMsToIsoString,
|
||||
} from "./number-coercion.js";
|
||||
|
||||
@@ -132,6 +134,22 @@ describe("number-coercion", () => {
|
||||
expect(timestampMsToIsoString("0")).toBeUndefined();
|
||||
});
|
||||
|
||||
test("timestamp fallback helpers resolve Date-invalid timestamps", () => {
|
||||
expect(resolveTimestampMsToIsoString(0)).toBe("1970-01-01T00:00:00.000Z");
|
||||
expect(resolveTimestampMsToIsoString(Number.POSITIVE_INFINITY, 1_000)).toBe(
|
||||
"1970-01-01T00:00:01.000Z",
|
||||
);
|
||||
expect(resolveTimestampMsToIsoString(Number.POSITIVE_INFINITY, Number.NaN)).toBe(
|
||||
"1970-01-01T00:00:00.000Z",
|
||||
);
|
||||
expect(timestampMsToIsoFileStamp(Date.parse("2026-02-23T12:34:56.000Z"))).toBe(
|
||||
"2026-02-23T12-34-56.000Z",
|
||||
);
|
||||
expect(timestampMsToIsoFileStamp(9_000_000_000_000_000, 1_000)).toBe(
|
||||
"1970-01-01T00-00-01.000Z",
|
||||
);
|
||||
});
|
||||
|
||||
test("expiry helpers resolve safe absolute timestamps", () => {
|
||||
expect(
|
||||
resolveExpiresAtMsFromDurationSeconds("3600", {
|
||||
|
||||
@@ -96,6 +96,7 @@ export function asPositiveSafeInteger(value: unknown): number | undefined {
|
||||
export const MAX_TIMER_TIMEOUT_MS = 2_147_000_000;
|
||||
export const MAX_TIMER_TIMEOUT_SECONDS = Math.floor(MAX_TIMER_TIMEOUT_MS / 1000);
|
||||
export const MAX_DATE_TIMESTAMP_MS = 8_640_000_000_000_000;
|
||||
export const UNIX_EPOCH_ISO_STRING = "1970-01-01T00:00:00.000Z";
|
||||
|
||||
export function asDateTimestampMs(value: unknown): number | undefined {
|
||||
return asFiniteNumberInRange(value, {
|
||||
@@ -109,6 +110,22 @@ export function timestampMsToIsoString(value: unknown): string | undefined {
|
||||
return timestampMs === undefined ? undefined : new Date(timestampMs).toISOString();
|
||||
}
|
||||
|
||||
export function resolveTimestampMsToIsoString(
|
||||
value: unknown,
|
||||
fallbackValue: unknown = Date.now(),
|
||||
): string {
|
||||
return (
|
||||
timestampMsToIsoString(value) ?? timestampMsToIsoString(fallbackValue) ?? UNIX_EPOCH_ISO_STRING
|
||||
);
|
||||
}
|
||||
|
||||
export function timestampMsToIsoFileStamp(
|
||||
value: unknown,
|
||||
fallbackValue: unknown = Date.now(),
|
||||
): string {
|
||||
return resolveTimestampMsToIsoString(value, fallbackValue).replaceAll(":", "-");
|
||||
}
|
||||
|
||||
export function clampTimerTimeoutMs(valueMs: unknown, minMs = 1): number | undefined {
|
||||
const value = asFiniteNumber(valueMs);
|
||||
if (value === undefined) {
|
||||
|
||||
Reference in New Issue
Block a user