Files
hyperframes/scripts/player-cdn-pin.test.ts
Miguel Ángel 590c192498 feat(catalog): add 3D objects, cursors, snow, starfield and paper transitions (#4349)
* feat(catalog): shelve 3D objects and cursors in their own catalog sections

Carries the generator grouping rules from #4310 and #4308.

* feat(registry): add a starfield ambient overlay

From #4313.

* feat(registry): add a falling snow ambient overlay

From #4331.

* feat(registry): add a torn-paper-rip transition

From #4321.

* feat(registry): add a paper-collage-reveal transition

From #4322.

* feat(registry): add a curtain-reveal transition

From #4323.

* feat(registry): add 3D objects: product turntable, glass hero, exploded stack, extruded logo

From #4310.

* feat(registry): add eight cursor animations and the dart cursor

From #4308.

* fix(catalog): find items by everyday editor words and accept a bare search word

From #4311.

* chore(registry): remove the simulated-cursor component

The Cursors section's new pointer animations replace it (from #4308).

* fix(registry): take cursor-zoom-follow's screenshot from runtime variables only

The declared default is empty, so page markup never becomes an image URL.

Clears the code-scanning alert raised on #4308.

* docs(cli): show a catalog search example that finds items

"smoke" matches nothing in the catalog; "crossfade" finds fade-through.

* chore(ci): allow the simulated-cursor removal in the deletion guard

* fix(ci): skip tracked files missing from disk in the player CDN pin check

Catalog regeneration removes a deleted item's pages before the publish PR untracks them,

so the check crashed on every catalog deletion PR.

* chore(cli): drop comments that narrate catalog search history

* fix(registry): drag-drop keeps the family arrow and shows the drop slot only while hovering

* fix(registry): agent cursor labels its steps in an accent tag, no glow or sparkles, in 4.8 s

* fix(registry): annotate cursor draws a tapered marker stroke and clears the circled value

* fix(registry): show real file thumbnails and end the context menu demo sooner

* fix(registry): give marquee select real photos, a Finder-blue marquee and a tighter loop

* fix(registry): centre the text-select toolbar on the selection and tighten its timing

* fix(registry): selection cursors default to the macOS system blue

* style(registry): format the agent and annotate cursor sources

* fix(registry): dart-cursor stops covering the choice it makes and shows the export finishing

* fix(registry): frame the product turntable fully and light it with two studio softboxes

* fix(registry): touch-indicator taps like a real phone and the post it taps gets liked

* fix(registry): press-ripple confirms with a check and small click rings at the cursor

* fix(registry): exploded stack separates at once and marks each layer in blue as it is labelled

* fix(registry): extruded logo settles facing the camera, set in Geist Bold with a neutral rim

* fix(registry): magnetic cursor docks its toolbar on the selection and ends after the press

* style(registry): format the exploded stack source

* fix(registry): glyph trail becomes a tutorial cursor path with numbered steps on a settings screen

* fix(registry): make glass-hero a clear lens that shows the headline through it

* fix(registry): snow reads as real snowfall with bokeh flakes over a winter title card

* fix(registry): oversized cursor is a 3.5x arrow that clicks a real editor toggle on

* fix(registry): starfield reads as a drifting night sky over a real title card

* style(registry): format the glass hero source

* fix(registry): make the paper tear show a white fibrous edge and lift the torn sheet away

* fix(registry): curtain reveal draws velvet folds that gather onto a title card

* fix(registry): make every zoom-follow click change the screen and read at zoom

* fix(registry): paper collage pieces stay opaque, tear with deckled grain and lift away on an arc

* refactor(registry): drop dead code and correct stale comments in reworked items

* fix(registry): agent cursor scrubs cleanly, docs match press-ripple, fictional demo names

* docs(registry): catalog notes say oversized-cursor exits and how it differs from press-ripple

* docs(registry): plain lowercase comments in oversized-cursor and touch-indicator

* docs(registry): oversized-cursor names CLICK_AT as the click cue
2026-09-28 22:03:59 +00:00

88 lines
3.3 KiB
TypeScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
/**
* Every CDN reference to the player must ask for `latest`.
*
* A pinned line here fails silently and indefinitely. The catalog page still
* renders on the build the pin names, so nothing looks broken: the only symptom
* is that a fix published to npm never appears on the docs, which surfaces as
* "that bug is still there" weeks later rather than as a red check. That is
* exactly how the pin sat one minor line behind after a release, across 175
* generated pages and three hand-written files nobody thought to grep.
*
* The reference lives in the two catalog snippets that build a preview
* `srcDoc`; this asserts on the whole tree rather than on the sources a reader
* would think to check.
*/
const ROOT = resolve(import.meta.dirname, "..");
const PLAYER_CDN = /cdn\.jsdelivr\.net\/npm\/@hyperframes\/player@([^/"'`\s]+)/g;
const TEXT_FILE = /\.(mdx?|[jt]sx?|html|json)$/;
/**
* Tracked files only, via git rather than a directory walk: it is one call, and
* it skips `node_modules` and build output for free because they are ignored.
*/
function trackedTextFiles(root = ROOT): string[] {
const listing = execFileSync(
"git",
["ls-files", "-z", "docs", "scripts", "packages", "registry"],
{
cwd: root,
encoding: "utf-8",
maxBuffer: 64 * 1024 * 1024,
},
);
// A tracked file can be gone from disk: catalog regeneration removes a deleted
// item's pages before the publish PR untracks them.
return listing.split("\0").filter((file) => TEXT_FILE.test(file) && existsSync(join(root, file)));
}
function pinnedReferences(root = ROOT): { file: string; version: string }[] {
const found: { file: string; version: string }[] = [];
for (const file of trackedTextFiles(root)) {
const text = readFileSync(join(root, file), "utf-8");
for (const match of text.matchAll(PLAYER_CDN)) {
found.push({ file, version: match[1] as string });
}
}
return found;
}
test("every player CDN reference asks for latest", () => {
const references = pinnedReferences();
// A guard that passes because it matched nothing is worse than no guard.
assert.ok(
references.length >= 2,
`expected the catalog snippets to reference the player CDN, found ${references.length}`,
);
const pinned = references.filter((r) => r.version !== "latest");
assert.deepEqual(
pinned,
[],
`pinned player versions found. Use @latest instead:\n${pinned
.map((r) => ` ${r.file}: @${r.version}`)
.join("\n")}`,
);
});
test("a tracked file missing from disk is skipped, not read", () => {
const dir = mkdtempSync(join(tmpdir(), "player-cdn-pin-"));
try {
execFileSync("git", ["init", "-q"], { cwd: dir });
mkdirSync(join(dir, "docs"));
writeFileSync(join(dir, "docs", "gone.mdx"), "removed page");
execFileSync("git", ["add", "docs"], { cwd: dir });
rmSync(join(dir, "docs", "gone.mdx"));
assert.deepEqual(pinnedReferences(dir), []);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});