Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ You never write `META.json`, `VERIFICATION.md`, or `index.json` by hand.
validates the new key and every key inheriting from it, and rebuilds the index.
It refuses mismatched versions, maps, leaf values, shallow CDP runs,
inconsistent summaries, low hit rates, and existing keys. If any step fails, it
rolls back both the key and `index.json`. Run `pnpm publish-key --help` for
every flag.
rolls back both the key and `index.json`. When the CDP report records the
overlay it applied, that overlay must be the one being published; otherwise
`VERIFICATION.md` notes that the overlay's names were not checked live. Run
`pnpm publish-key --help` for every flag.

The CLI commands below run from a spicetify/cli checkout next to this
repository. They need the stock `xpui.spa` of each Spotify build involved, taken
Expand All @@ -101,7 +103,7 @@ against the new build and publish it as inherited:

node scripts/classmap-cdp-verify.mjs --port 9229 --mode both --deep \
--classmap ../classmaps/1020094/classmap.json --css-map css-map.json \
--out /tmp/1020096-cdp.json
--overlay ../classmaps/1020094/css-map.json --out /tmp/1020096-cdp.json
```

2. From this repository, publish the inherited key:
Expand Down Expand Up @@ -146,7 +148,8 @@ and publish the result as a derived key:

4. Verify the candidate with the same two commands as an unchanged release,
passing `--classmap /tmp/1030002.json`, `--target-version 1.3.2.100`, and
`--report /tmp/1030002-migrate.json` to `verify`. Alternatively, run the whole
`--report /tmp/1030002-migrate.json` to `verify`, and
`--overlay /tmp/1030002-overlay.json` to the CDP verifier. Alternatively, run the whole
CLI side in one command with
`SPOTIFY_VERSION=1.3.2.100 BASE_CLASSMAP=... BASE_CSS_DIR=... OUT_DIR=... scripts/classmap-e2e.sh --deep`.

Expand Down
24 changes: 23 additions & 1 deletion scripts/publish-key.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ test("--replace refuses a map that would break the keys inheriting from it", ()

test("publishes a derived key from a candidate map", () => {
const overlay = path.join(root, "overlay.json");
writeFileSync(overlay, '{"topbarHashAA":"Root__globalNav"}');
writeFileSync(overlay, renderJson({ topbarHashAA: "Root__globalNav" }));
const target = publishRelease(
options(derived(classmap, { overlayPath: overlay, notes: ["The retired leaf has no rendered instance."] })),
);
Expand All @@ -362,6 +362,28 @@ test("publishes a derived key from a candidate map", () => {
assert.ok(readVerification(target).includes("- The retired leaf has no rendered instance."));
});

test("binds the published overlay to the one the CDP run applied", () => {
const overlay = path.join(root, "overlay.json");
writeFileSync(overlay, renderJson({ topbarHashAA: "Root__globalNav" }));
cdpReport.cssMap = { sha256: "c".repeat(64), overlaySha256: sha256(readFileSync(overlay)) };
const target = publishRelease(options(derived(classmap, { overlayPath: overlay })));
assert.ok(!readVerification(target).includes("did not apply this key's overlay"));
rmSync(target, { recursive: true });

cdpReport.cssMap.overlaySha256 = "d".repeat(64);
assert.throws(() => publishRelease(options(derived(classmap, { overlayPath: overlay }))), /different overlay/);

delete cdpReport.cssMap;
const unchecked = publishRelease(options(derived(classmap, { overlayPath: overlay })));
assert.ok(readVerification(unchecked).includes("The CDP run did not apply this key's overlay"));
});

test("refuses an overlay that is not in canonical form", () => {
const overlay = path.join(root, "overlay.json");
writeFileSync(overlay, '{"topbarHashAA":"Root__globalNav"}');
assert.throws(() => publishRelease(options(derived(classmap, { overlayPath: overlay }))), /overlay is not in canonical form/);
});

test("a derived key keeps the leaves the migrate report kept as stale", () => {
writeSourceMeta({ stale_leaves: [], required_paths: { "main.topbar.wrapper": "verified" } });
const migrateReport = { unmatched: [{ path: "main.playbar.indicator", old: "indicatorHashCC", kept: "indicatorHashCC", stale: true }] };
Expand Down
21 changes: 16 additions & 5 deletions scripts/publish-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,14 +337,18 @@ export function publishRelease(o: PublishOptions, index = false): string {
if (sourceKey) checkSource(sourceKey, targetKey, sourceMeta, inherited);

let classmapBytes: Buffer;
let overlay: Record<string, string> | undefined;
let overlayText: string | null = null;
if (inherited) {
classmapBytes = readFileSync(path.join(o.root, o.inheritFrom as string, "classmap.json"));
const sourceOverlay = path.join(o.root, o.inheritFrom as string, "css-map.json");
if (isFile(sourceOverlay)) overlay = JSON.parse(readUtf8(sourceOverlay));
if (isFile(sourceOverlay)) overlayText = readUtf8(sourceOverlay);
} else {
classmapBytes = readFileSync(o.classmapPath as string);
if (o.overlayPath) overlay = JSON.parse(readUtf8(o.overlayPath));
if (o.overlayPath) overlayText = readUtf8(o.overlayPath);
}
const overlay: Record<string, string> | undefined = overlayText === null ? undefined : JSON.parse(overlayText);
if (overlayText !== null && overlayText !== renderJson(overlay)) {
throw new Error("the overlay is not in canonical form; run pnpm fix on it before the CDP run");
}
const classmap: Classmap = JSON.parse(classmapBytes.toString("utf8"));
if (classmapBytes.toString("utf8") !== renderJson(classmap)) {
Expand All @@ -357,6 +361,8 @@ export function publishRelease(o: PublishOptions, index = false): string {

const evidence = checkReports(o.spotifyVersion, classmap, sha256(classmapBytes), o.staticReport, o.cdpReport, o.minHitRate);
const { missing, live, deadHashes } = evidence;
const overlaySha = overlayText === null ? null : sha256(overlayText);
const liveOverlaySha: unknown = o.cdpReport.cssMap?.overlaySha256;

const notes = [...(o.notes ?? [])];
let stale: Set<string>;
Expand Down Expand Up @@ -393,10 +399,15 @@ export function publishRelease(o: PublishOptions, index = false): string {
const observedStale = [...stale].filter((p) => live.has(p)).sort();
if (observedStale.length) notes.push(`Marked stale although observed live: ${observedStale.join(", ")}.`);
if (exists) notes.push(`Replaces an earlier verification of this key; its history is in git.`);
if (liveOverlaySha !== undefined && liveOverlaySha !== overlaySha) {
throw new Error("the CDP report was run with a different overlay than the one being published");
}
if (overlaySha && liveOverlaySha === undefined) {
notes.push("The CDP run did not apply this key's overlay, so its names were not checked live.");
}
const unverified = new Set([...doubted].filter((p) => !live.has(p) && !stale.has(p)));

const method = inherited ? "inherited" : "derived";
const overlayText = overlay ? renderJson(overlay) : null;
const meta = {
schema_version: 2,
classmap_key: targetKey,
Expand All @@ -420,7 +431,7 @@ export function publishRelease(o: PublishOptions, index = false): string {
cdpReport: o.cdpReport,
staticReport: o.staticReport,
classmapSha: sha256(classmapBytes),
overlaySha: overlayText ? sha256(overlayText) : null,
overlaySha,
overlayEntries: overlay ? Object.keys(overlay).length : 0,
notes,
});
Expand Down
Loading