mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
test(landing): guard _redirects against catalog drift (#575)
Exports a pure buildRedirects() from generate-redirects.mjs and adds a unit test asserting the committed apps/landing/public/_redirects matches it, so tool additions can't silently leave the generated redirects stale (see #573).
This commit is contained in:
@@ -1,16 +1,37 @@
|
|||||||
// Generates public/_redirects (Cloudflare Pages) mapping old flat tool URLs
|
// Generates public/_redirects (Cloudflare Pages) mapping old flat tool URLs
|
||||||
// (/tools/<id>) to the new section-nested URLs (/tools/<section>/<id>/).
|
// (/tools/<id>) to the new section-nested URLs (/tools/<section>/<id>/).
|
||||||
// Both slash variants are emitted because Cloudflare matches paths exactly.
|
// Both slash variants are emitted because Cloudflare matches paths exactly.
|
||||||
|
//
|
||||||
|
// buildRedirects() is exported (side-effect-free) so a drift test can assert the
|
||||||
|
// committed public/_redirects still matches the catalog; the file is only
|
||||||
|
// written when this script runs directly (the landing prebuild).
|
||||||
import { writeFileSync } from "node:fs";
|
import { writeFileSync } from "node:fs";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||||
import { TOOLS, toolSection } from "@snapotter/shared";
|
import { TOOLS, toolSection } from "@snapotter/shared";
|
||||||
|
|
||||||
const lines = ["# Generated by scripts/generate-redirects.mjs -- do not edit by hand"];
|
/** Build the full _redirects file contents from the shared TOOLS catalog. */
|
||||||
for (const tool of TOOLS) {
|
export function buildRedirects() {
|
||||||
const to = `/tools/${toolSection(tool)}/${tool.id}/`;
|
const lines = ["# Generated by scripts/generate-redirects.mjs -- do not edit by hand"];
|
||||||
lines.push(`/tools/${tool.id}/ ${to} 301`);
|
for (const tool of TOOLS) {
|
||||||
lines.push(`/tools/${tool.id} ${to} 301`);
|
const to = `/tools/${toolSection(tool)}/${tool.id}/`;
|
||||||
|
lines.push(`/tools/${tool.id}/ ${to} 301`);
|
||||||
|
lines.push(`/tools/${tool.id} ${to} 301`);
|
||||||
|
}
|
||||||
|
return `${lines.join("\n")}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Absolute path to the generated file. */
|
||||||
|
export const REDIRECTS_PATH = fileURLToPath(new URL("../public/_redirects", import.meta.url));
|
||||||
|
|
||||||
|
function writeRedirects() {
|
||||||
|
const content = buildRedirects();
|
||||||
|
writeFileSync(REDIRECTS_PATH, content);
|
||||||
|
const redirectLines = content.trimEnd().split("\n").length - 1;
|
||||||
|
console.log(`wrote ${REDIRECTS_PATH} (${TOOLS.length} tools, ${redirectLines} redirect lines)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write only when executed directly (prebuild); importing for the drift test
|
||||||
|
// stays side-effect-free.
|
||||||
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||||
|
writeRedirects();
|
||||||
}
|
}
|
||||||
const out = fileURLToPath(new URL("../public/_redirects", import.meta.url));
|
|
||||||
writeFileSync(out, `${lines.join("\n")}\n`);
|
|
||||||
console.log(`wrote ${out} (${TOOLS.length} tools, ${lines.length - 1} redirect lines)`);
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
buildRedirects,
|
||||||
|
REDIRECTS_PATH,
|
||||||
|
} from "../../../apps/landing/scripts/generate-redirects.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drift guard: apps/landing/public/_redirects is generated from the shared
|
||||||
|
* TOOLS catalog by scripts/generate-redirects.mjs (runs in the landing
|
||||||
|
* prebuild) but is committed, so it silently goes stale whenever a tool is
|
||||||
|
* added without regenerating it (that is how remove-gif-background's redirects
|
||||||
|
* went missing, PR #573). Adding a tool touches @snapotter/shared and runs this
|
||||||
|
* suite, so the drift fails here at PR time instead of shipping.
|
||||||
|
*/
|
||||||
|
describe("landing _redirects drift", () => {
|
||||||
|
it("committed public/_redirects matches the generator output", () => {
|
||||||
|
const committed = readFileSync(REDIRECTS_PATH, "utf8");
|
||||||
|
expect(
|
||||||
|
committed,
|
||||||
|
"apps/landing/public/_redirects is stale. Regenerate it with `pnpm --filter @snapotter/landing prebuild` and commit the file.",
|
||||||
|
).toBe(buildRedirects());
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user