Skip to content

Commit 98f42fa

Browse files
fix(redirects): restore Discord short link (#21)
1 parent 8e76602 commit 98f42fa

5 files changed

Lines changed: 25 additions & 16 deletions

File tree

redirects.caddy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ redir /docs/self-hosting/railway /actors/self-host/control-plane/{http.request.u
175175
redir /docs/self-hosting/railway/ /actors/self-host/control-plane/{http.request.uri.prefixed_query} 301
176176
redir /docs/self-hosting/tls /actors/self-host/control-plane/{http.request.uri.prefixed_query} 301
177177
redir /docs/self-hosting/tls/ /actors/self-host/control-plane/{http.request.uri.prefixed_query} 301
178+
redir /discord https://discord.gg/rivet-developer-network-822914074136018994{http.request.uri.prefixed_query} 301
179+
redir /discord/ https://discord.gg/rivet-developer-network-822914074136018994{http.request.uri.prefixed_query} 301
178180
redir /docs/integrations/vercel-workflow /actors/integrations/workflow-sdk/{http.request.uri.prefixed_query} 301
179181
redir /docs/integrations/vercel-workflow/ /actors/integrations/workflow-sdk/{http.request.uri.prefixed_query} 301
180182
redir /integrations/vercel-workflow /actors/integrations/workflow-sdk/{http.request.uri.prefixed_query} 301

redirects.mjs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@
88
// targets should end in `/` to match the site's canonical trailing-slash form
99
// and avoid a second redirect hop.
1010
//
11-
// Absolute targets are supported but restricted to a single allowed host, so a
12-
// bad entry can never point traffic at an arbitrary domain. See
13-
// `EXTERNAL_REDIRECT_HOST` below and the matching check in
14-
// `scripts/generate-caddy-redirects.mjs`. Nothing currently uses it: agentOS
15-
// briefly had its own site and is now a vertical here.
11+
// Absolute targets are supported but restricted to explicitly allowed hosts,
12+
// so a bad entry can never point traffic at an arbitrary domain. See
13+
// `EXTERNAL_REDIRECT_HOSTS` below and the matching check in
14+
// `scripts/generate-caddy-redirects.mjs`.
1615
import { readdirSync } from 'node:fs';
1716
import path from 'node:path';
1817
import { fileURLToPath } from 'node:url';
1918

2019

2120
const explicitRedirects = {
21+
// Public community short link. Keep this slashless in authored links, while
22+
// Caddy serves both variants so copied URLs cannot fall through to a 404.
23+
'/discord': 'https://discord.gg/rivet-developer-network-822914074136018994',
2224
// Integrations moved out of the documentation URL hierarchy.
2325
'/docs/integrations/vercel-workflow': '/actors/integrations/workflow-sdk/',
2426
'/integrations/vercel-workflow': '/actors/integrations/workflow-sdk/',
@@ -249,10 +251,10 @@ function legacyDocsRedirects() {
249251
export const redirects = { ...legacyDocsRedirects(), ...explicitRedirects };
250252

251253

252-
// External host that wildcard and absolute-URL redirect targets are restricted
254+
// External hosts that wildcard and absolute-URL redirect targets are restricted
253255
// to. Used by both the Astro config and the Caddy generator so neither consumer
254256
// can accidentally emit a redirect to an arbitrary host.
255-
export const EXTERNAL_REDIRECT_HOST = 'agentos-sdk.dev';
257+
export const EXTERNAL_REDIRECT_HOSTS = ['agentos-sdk.dev', 'discord.gg'];
256258

257259
// Wildcard (prefix) redirects. Any request under `from` (at any depth) is sent
258260
// to `to`.

scripts/check-seo.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,10 +417,11 @@ for (const file of htmlFiles) {
417417
} catch {
418418
errors.push(`${route}: canonical URL is invalid: ${href}`);
419419
}
420-
if (canonical?.origin !== SITE_ORIGIN) {
420+
if (!redirect && canonical?.origin !== SITE_ORIGIN) {
421421
errors.push(`${route}: canonical must use ${SITE_ORIGIN}`);
422422
}
423423
if (
424+
!redirect &&
424425
canonical &&
425426
ensureDirectorySlash(canonical.pathname) !== canonical.pathname
426427
) {

scripts/generate-caddy-redirects.mjs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@
1010
// The output file is gitignored and regenerated during the Docker build.
1111
import { writeFileSync } from 'node:fs';
1212
import { fileURLToPath } from 'node:url';
13-
import { EXTERNAL_REDIRECT_HOST, redirects, wildcardRedirects } from '../redirects.mjs';
13+
import { EXTERNAL_REDIRECT_HOSTS, redirects, wildcardRedirects } from '../redirects.mjs';
1414

1515
// Conservative charset for paths. Blocks whitespace and any character that
1616
// could break out of a `redir` argument or inject another Caddy directive.
1717
const SAFE_PATH = /^\/[A-Za-z0-9\-._~/]*$/;
1818

19-
// Absolute external targets are restricted to the single agentOS host with the
20-
// same conservative path charset. This keeps the generator from ever emitting a
21-
// redirect to an arbitrary host while still allowing the agentOS split-out.
22-
const SAFE_EXTERNAL_TARGET = new RegExp(
23-
`^https://${EXTERNAL_REDIRECT_HOST.replace(/\./g, '\\.')}(/[A-Za-z0-9\\-._~/]*)?$`,
19+
// Absolute external targets are restricted to an explicit host allowlist with
20+
// the same conservative path charset. This keeps the generator from ever
21+
// emitting a redirect to an arbitrary host.
22+
const SAFE_EXTERNAL_TARGETS = EXTERNAL_REDIRECT_HOSTS.map(
23+
(host) => new RegExp(`^https://${host.replace(/\./g, '\\.')}(/[A-Za-z0-9\\-._~/]*)?$`),
2424
);
2525

2626
// Expands to `?query` when a query string is present, or an empty string when
@@ -39,7 +39,7 @@ function assertSafeTarget(to) {
3939
if (typeof to !== 'string') {
4040
throw new Error(`unsafe target in redirect map: ${JSON.stringify(to)}`);
4141
}
42-
if (SAFE_PATH.test(to) || SAFE_EXTERNAL_TARGET.test(to)) {
42+
if (SAFE_PATH.test(to) || SAFE_EXTERNAL_TARGETS.some((pattern) => pattern.test(to))) {
4343
return;
4444
}
4545
throw new Error(`unsafe target in redirect map: ${JSON.stringify(to)}`);

src/lib/internalHref.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const SITE_ORIGINS = new Set(["https://rivet.dev", "http://rivet.dev"]);
2+
const EDGE_REDIRECT_PATHS = new Set(["/discord"]);
23

34
function splitSuffix(value: string): { pathname: string; suffix: string } {
45
const boundary = value.search(/[?#]/);
@@ -8,14 +9,17 @@ function splitSuffix(value: string): { pathname: string; suffix: string } {
89
}
910

1011
function canonicalizePath(pathname: string): string {
12+
const barePath = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
13+
if (EDGE_REDIRECT_PATHS.has(barePath)) return barePath;
1114
if (!pathname || pathname === "/" || pathname.endsWith("/")) return pathname || "/";
1215
const lastSegment = pathname.slice(pathname.lastIndexOf("/") + 1);
1316
return lastSegment.includes(".") ? pathname : `${pathname}/`;
1417
}
1518

1619
/**
1720
* Returns the canonical trailing-slash form for site-owned directory links.
18-
* Fragments, query strings, files, and external/protocol URLs are preserved.
21+
* Edge-managed short links stay slashless; fragments, query strings, files,
22+
* and external/protocol URLs are preserved.
1923
*/
2024
export function canonicalizeInternalHref(href?: string | null): string {
2125
if (!href) return href ?? "";

0 commit comments

Comments
 (0)