mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-02 10:33:39 +00:00
d91e6088ff
The `mutation-pr`/`mutation-full` CI jobs were failing outright, and a full `stryker run` never got past the initial dry run, so none of this was actually usable yet. - `mutation.yml`: pin all actions to a commit SHA, add a `permissions` block, set `persist-credentials: false`, and pass the base ref/changed files through `env` instead of interpolating them into the shell script directly (zizmor was flagging all of these). Also switch to the project's own `./.github/actions/setup-node` composite action instead of `pnpm/action-setup`/`actions/setup-node` (which additionally referenced a non-existent `.node-version` file), and fix the `pnpm mutation`/`pnpm stryker run --mutate` calls to use the actual `test:mutation`/`test:mutation:changed` script names. - Drop the now-unused `@stryker-mutator/typescript-checker` dependency, left over from when `stryker.config.json` still had `checkers: ["typescript"]`. - `lib/util/timestamp.ts`: the local-timezone-offset fallback used in `asTimestamp()` was cached in a module-level constant computed once at import time. Combined with vitest's `threads` pool (used by Stryker's vitest-runner) not reliably honouring a `process.env.TZ` set from our own `test/setup.ts`, this made `asTimestamp()` parse plain dates (e.g. Jenkins' `buildDate: 'Jan 02, 2020'`) using the host's real timezone instead of UTC. Compute the offset per call instead, and set `TZ=UTC` in the actual environment via the `test:mutation*` scripts, since that's the one thing that reliably works across pools. - `test/setup.ts`: restore real timers in a top-level `afterAll`, so a spec that leaves fake timers running (there's at least one) can't leak a frozen clock into whichever spec runs next in the same worker - something that's only reachable under low-concurrency/single-threaded execution. - Raise `dryRunTimeoutMinutes` and the global vitest `testTimeout`: our full suite's per-test coverage instrumentation run took ~25 minutes end-to-end, well past Stryker's 5 minute default, and a couple of CPU-heavy specs (WASM PGP crypto) timed out under that contention too. - Ignore `.stryker-tmp/` (Stryker's sandbox dir, which can survive an interrupted run) in ls-lint/oxlint/biome/prettier/vitest, since a leftover sandbox copy of the repo was otherwise picked up as extra source/test files and broke those tools outright. Co-Authored-By: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
103 lines
2.6 KiB
TypeScript
103 lines
2.6 KiB
TypeScript
import { isString } from '@sindresorhus/is';
|
|
import { DateTime } from 'luxon';
|
|
import { z } from 'zod/v4';
|
|
|
|
export type Timestamp = string & { __timestamp: never };
|
|
|
|
const millenium = 946684800000; // 2000-01-01T00:00:00.000Z
|
|
const tomorrowOffset = 86400000; // 24 * 60 * 60 * 1000;
|
|
|
|
function isValid(date: DateTime): boolean {
|
|
if (!date.isValid) {
|
|
return false;
|
|
}
|
|
const tomorrow = DateTime.now().toMillis() + tomorrowOffset; // 24 * 60 * 60 * 1000;
|
|
const ts = date.toMillis();
|
|
return ts > millenium && ts < tomorrow;
|
|
}
|
|
|
|
export function asTimestamp(input: unknown): Timestamp | null {
|
|
if (input instanceof Date) {
|
|
const date = DateTime.fromJSDate(input, { zone: 'UTC' });
|
|
if (isValid(date)) {
|
|
return date.toISO() as Timestamp;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
if (typeof input === 'number') {
|
|
const millisDate = DateTime.fromMillis(input, { zone: 'UTC' });
|
|
if (isValid(millisDate)) {
|
|
return millisDate.toISO() as Timestamp;
|
|
}
|
|
|
|
const secondsDate = DateTime.fromSeconds(input, { zone: 'UTC' });
|
|
if (isValid(secondsDate)) {
|
|
return secondsDate.toISO() as Timestamp;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
if (isString(input)) {
|
|
const isoDate = DateTime.fromISO(input, { zone: 'UTC' });
|
|
if (isValid(isoDate)) {
|
|
return isoDate.toISO() as Timestamp;
|
|
}
|
|
|
|
const httpDate = DateTime.fromHTTP(input, { zone: 'UTC' });
|
|
if (isValid(httpDate)) {
|
|
return httpDate.toISO() as Timestamp;
|
|
}
|
|
|
|
const sqlDate = DateTime.fromSQL(input, { zone: 'UTC' });
|
|
if (isValid(sqlDate)) {
|
|
return sqlDate.toISO() as Timestamp;
|
|
}
|
|
|
|
const numberLikeDate = DateTime.fromFormat(input, 'yyyyMMddHHmmss', {
|
|
zone: 'UTC',
|
|
});
|
|
if (isValid(numberLikeDate)) {
|
|
return numberLikeDate.toISO() as Timestamp;
|
|
}
|
|
|
|
const numberLikeOffsetDate = DateTime.fromFormat(
|
|
input,
|
|
'yyyyMMddHHmmssZZZ',
|
|
{ zone: 'UTC' },
|
|
);
|
|
if (isValid(numberLikeOffsetDate)) {
|
|
return numberLikeOffsetDate.toISO() as Timestamp;
|
|
}
|
|
|
|
const timezoneOffset = new Date().getTimezoneOffset() * 60000;
|
|
const fallbackDate = DateTime.fromMillis(
|
|
Date.parse(input) - timezoneOffset,
|
|
{ zone: 'UTC' },
|
|
);
|
|
if (isValid(fallbackDate)) {
|
|
return fallbackDate.toISO() as Timestamp;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export const Timestamp = z.unknown().transform((input, ctx) => {
|
|
const timestamp = asTimestamp(input);
|
|
if (!timestamp) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
message: 'Invalid timestamp',
|
|
});
|
|
return z.NEVER;
|
|
}
|
|
|
|
return timestamp;
|
|
});
|
|
export const MaybeTimestamp = Timestamp.nullable().catch(null);
|