Files
ClaudeandMikael Finstad b1076a0258 Build losslesscut.app from this repo with Astro + Starlight, with the docs as pages
The losslesscut.app website moves into website/ (previously its own repo, deployed to S3/CloudFront) and is built with Astro + Starlight, deployed with Cloudflare Workers Builds (production from master, a preview URL per branch and pull request). See website/README.md.

- docs/*.md become pages under /docs. They stay plain markdown for GitHub: a content loader generates the front matter, remark plugins rewrite links, and the build fails on links to missing pages or headings.
- The app's links go through https://losslesscut.app/go/<name> redirects (src/common/websiteUrls.ts), so targets can move without an app update. The build checks that each target page and #anchor exists. This covers the help pages, discussions, bug reports, feature requests, thanks, Discord, the privacy policy, licenses.txt and the update check (/go/latest-release, a fetch replacing @octokit/core).
- licenses.txt is generated and attached to each GitHub release by the release workflow.
- New privacy policy at /privacy-policy.
- The homepage is a React component rendered to static HTML, with a header, Download/Documentation buttons and social icons. Its images and icons are served from the site (camera metadata stripped), without Google Fonts.
- Official links are in src/common/links.ts (app) and website/src/links.ts (website).
- The error report dialog's Discord link opened GitHub.
- Docs: fix broken links. Release notes: list each changelog line once.

Changelog: Help links open the documentation on losslesscut.app
Changelog: The Discord link in the error report dialog now opens Discord instead of GitHub

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AntZvg7M7on4kAETvpYqzp
Co-authored-by: Mikael Finstad <finstaden@gmail.com>
2026-10-21 04:34:50 +00:00

45 lines
2.4 KiB
TypeScript

// Writes the redirects for the app's links to the website (https://losslesscut.app/go/<name>, see
// src/common/websiteUrls.ts) into the built site (dist/_redirects), and checks that each target page (and #anchor) exists.
import fs from 'node:fs/promises';
// the app's list, shared through the repo rather than as a package
// eslint-disable-next-line import/no-relative-packages
import { appLinkPrefix, appLinkTargets, websiteUrl } from '../../src/common/websiteUrls.ts';
const distDir = new URL('../dist/', import.meta.url);
// with trailingSlash: 'never', /docs/cli is served from docs/cli/index.html (see astro.config.ts)
const getHtmlFile = (pathname: string) => new URL(`.${pathname.replace(/\/$/, '')}/index.html`, distDir);
const escapeRegExp = (text: string) => text.replaceAll(/[$()*+.?[\\\]^{|}]/g, String.raw`\$&`);
// an element with this id, e.g. <h2 id="faq">, not just the text id="faq" somewhere (e.g. in data-id="faq")
const hasElementWithId = (html: string, id: string) => new RegExp(`<[a-z][^>]*\\sid="${escapeRegExp(id)}"`, 'i').test(html);
const problems: string[] = [];
await Promise.all(Object.entries(appLinkTargets).map(async ([name, target]) => {
const { origin, pathname, hash } = new URL(target, websiteUrl);
// targets on other sites (like a GitHub release asset) can't be checked here
if (origin !== websiteUrl) return;
let html: string;
try {
html = await fs.readFile(getHtmlFile(pathname), 'utf8');
} catch {
problems.push(`${name}: ${target}: page not found`);
return;
}
if (hash !== '' && !hasElementWithId(html, decodeURIComponent(hash.slice(1)))) {
problems.push(`${name}: ${target}: no element with this id on the page`);
}
}));
if (problems.length > 0) {
console.error(`The app links to website pages that don't exist (see src/common/websiteUrls.ts):\n${problems.sort().join('\n')}`);
process.exitCode = 1;
} else {
// https://developers.cloudflare.com/workers/static-assets/redirects/
// 302, because the targets may change, and browsers would remember a 301
const redirects = Object.entries(appLinkTargets).map(([name, target]) => `${appLinkPrefix}${name} ${target} 302`);
await fs.appendFile(new URL('_redirects', distDir), `# Links from the app, see src/common/websiteUrls.ts\n${redirects.join('\n')}\n`);
console.log(`Wrote redirects for the ${redirects.length} links from the app, and checked that their pages exist.`);
}