Date screenshots by commit, so a fresh clone can pass check:shots
Some checks are pending
build-gate / build (push) Waiting to run
check:shots compared mtimes, and a checkout writes files in arbitrary order: every clone failed, pristine main included (11 shots "2s behind"). Committed, unmodified files are now dated by their last commit; files with local edits, or no git at all, still use mtime. With the honest clock the same 11 shots really were stale (3h to 31h behind their bundles), so they are retaken here with ONLY=. Emoji now render: the shot machine has an emoji font. Negative-tested: a byte appended to the doxa-simple-map bundle fails exactly the three cap2 shots.
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 621 KiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 633 KiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 495 KiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 686 KiB After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 925 KiB After Width: | Height: | Size: 1.1 MiB |
|
|
@ -25,10 +25,19 @@
|
|||
* Shots whose path is not a /doxa-maps/ leaf (the synthetic iframe host) are REPORTED as skipped
|
||||
* rather than dropped — an unchecked shot must be visible, not invisible.
|
||||
*
|
||||
* NEGATIVE-TESTED: `touch public/doxa-maps/doxa-simple-map/doxa-simple-map.js` makes this FAIL
|
||||
* naming the three cap2-* shots; re-running readme-shots.mjs returns it to PASS.
|
||||
* WHICH CLOCK: GIT, NOT THE FILESYSTEM, for any file that is committed and unmodified.
|
||||
* This compared mtimes, and a clone (or a new worktree) writes files in whatever order git checks
|
||||
* them out — so every fresh checkout failed, pristine main included (11 shots "2s behind" their
|
||||
* bundles). A gate that cannot pass on a clone teaches the recipient to ignore it. A committed,
|
||||
* clean file is dated by its last commit; a file with local edits (or no git at all, e.g. a
|
||||
* tarball) falls back to its mtime, because then the working copy IS the newest version.
|
||||
*
|
||||
* NEGATIVE-TESTED: appending a byte to public/doxa-maps/doxa-simple-map/doxa-simple-map.js makes
|
||||
* this FAIL naming the three cap2-* shots (a dirty file is dated by mtime); reverting it returns it
|
||||
* to PASS. A bare `touch` no longer fails it — a touch changes no content, so nothing is stale.
|
||||
*/
|
||||
import { readFileSync, existsSync, statSync } from 'node:fs'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
|
|
@ -42,6 +51,23 @@ if (!existsSync(SHOTS_SRC) || !existsSync(OUT)) {
|
|||
process.exit(0)
|
||||
}
|
||||
|
||||
// Dirty = modified or untracked under the two trees compared. null = git unavailable -> mtimes only.
|
||||
const git = (...a) => execFileSync('git', ['-C', ROOT, ...a], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
|
||||
let dirty = null
|
||||
try {
|
||||
dirty = new Set(git('status', '--porcelain', '--', 'public/doxa-maps', 'documentation/readme-images')
|
||||
.split('\n').filter(Boolean).map((l) => resolve(ROOT, l.slice(3).replace(/^"|"$/g, ''))))
|
||||
} catch { /* not a git checkout */ }
|
||||
function stamp(file) {
|
||||
if (dirty && !dirty.has(file)) {
|
||||
try {
|
||||
const ct = git('log', '-1', '--format=%ct', '--', file).trim()
|
||||
if (ct) return { ms: Number(ct) * 1000, by: 'commit' }
|
||||
} catch { /* fall through */ }
|
||||
}
|
||||
return { ms: statSync(file).mtimeMs, by: 'mtime' }
|
||||
}
|
||||
|
||||
// Parse `await shot('name', '/path/...')` — the generator is the source of truth for its own map.
|
||||
const src = readFileSync(SHOTS_SRC, 'utf8')
|
||||
const shots = [...src.matchAll(/shot\(\s*'([^']+)'\s*,\s*'([^']*)'/g)].map(m => ({ name: m[1], path: m[2] }))
|
||||
|
|
@ -60,10 +86,10 @@ for (const { name, path } of shots) {
|
|||
if (!existsSync(bundle)) { skipped.push(`${name} (bundle ${m[1]} not built)`); continue }
|
||||
|
||||
checked++
|
||||
const bMs = statSync(bundle).mtimeMs, pMs = statSync(png).mtimeMs
|
||||
if (bMs > pMs) {
|
||||
const b = stamp(bundle), p = stamp(png)
|
||||
if (b.ms > p.ms) {
|
||||
stale.push(`${name}.png is OLDER than the bundle it shows (${m[1]}) — ` +
|
||||
`${Math.round((bMs - pMs) / 1000)}s behind`)
|
||||
`${Math.round((b.ms - p.ms) / 1000)}s behind (bundle by ${b.by}, png by ${p.by})`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||