Skip to content
Open
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
5 changes: 3 additions & 2 deletions .amp/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ services:
port: 31420
env:
NODE_OPTIONS: --max-old-space-size=3072
health: /agentic-batch-changes
# YAML cannot read DOCS_BASE_PATH from docs.config.js; keep these in step.
health: /docs/agentic-batch-changes
portal:
url: /agentic-batch-changes
url: /docs/agentic-batch-changes
title: Sourcegraph Docs
11 changes: 9 additions & 2 deletions .github/workflows/preview-links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,20 @@ jobs:
if: github.event.client_payload.environment != 'production'
runs-on: ubuntu-latest
steps:
# The PR's own docs.config.js, for the basePath the preview serves under.
- uses: actions/checkout@v4
with:
ref: ${{ github.event.client_payload.git.sha || inputs.sha }}
sparse-checkout: docs.config.js
sparse-checkout-cone-mode: false
- uses: actions/github-script@v7
env:
PREVIEW_URL: ${{ github.event.client_payload.url || inputs.url }}
COMMIT_SHA: ${{ github.event.client_payload.git.sha || inputs.sha }}
with:
script: |
const {PREVIEW_URL, COMMIT_SHA} = process.env;
const {DOCS_BASE_PATH} = require(`${process.env.GITHUB_WORKSPACE}/docs.config.js`);
if (!PREVIEW_URL || !/^[0-9a-f]{40}$/i.test(COMMIT_SHA ?? '')) {
core.setFailed(`Need a preview URL and a full commit SHA; got url=${JSON.stringify(PREVIEW_URL)} sha=${JSON.stringify(COMMIT_SHA)}. `
+ `Vercel sends these as client_payload.url and client_payload.git.sha; workflow_dispatch takes them as the url and sha inputs.`);
Expand All @@ -60,11 +67,11 @@ jobs:
});

// Mirror contentlayer's flattenedPath: docs/<path>.mdx -> /<path>,
// with a trailing /index dropped. Preview deployments have no basePath.
// with a trailing /index dropped, under the basePath.
const pages = files
.filter(f => f.filename.startsWith('docs/') && f.filename.endsWith('.mdx') && f.status !== 'removed')
.map(f => f.filename.slice('docs/'.length, -'.mdx'.length).replace(/^index$/, '').replace(/\/index$/, ''))
.map(path => ({path, url: `${previewUrl}/${path}`}));
.map(path => ({path, url: `${previewUrl}${DOCS_BASE_PATH}/${path}`}));

if (pages.length === 0) {
core.info(`PR #${pr.number} changes no docs pages; not commenting.`);
Expand Down
12 changes: 11 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
script header for what it checks). Not part of `pnpm run check`: main has
hundreds of pre-existing findings, and CI only reports the ones a PR adds
- **Prove changed links resolve on a deploy**:
`node dev/verify-links-live.mjs --site <vercel-preview-url>` prints a
`node dev/verify-links-live.mjs --site <vercel-preview-url>/docs` prints a
Markdown table for the PR description
- **Spell check**: `.github/workflows/spellcheck.yml` runs
`dev/check-spelling.mjs` on the lines a PR adds plus its title and
Expand Down Expand Up @@ -83,3 +83,13 @@ next run.
<https://api.slack.com/apps?new_app=1> (From a manifest), install it, copy its
Bot User OAuth Token into the secret, and `/invite @Vercel build log` to the
channel.

<!-- BEGIN:nextjs-agent-rules -->

# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.

This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.

<!-- END:nextjs-agent-rules -->
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Next, run the development server:
pnpm run dev
```

Finally, open [`http://localhost:3000`](http://localhost:3000) in your browser
Finally, open [`http://localhost:3000/docs`](http://localhost:3000/docs) in your browser
to view the website.

## Writing and contributing to Sourcegraph Docs
Expand Down Expand Up @@ -225,7 +225,7 @@ instructions visit

As you make changes to the documentation, the development server will
automatically update. Review your changes by navigating to
`http://localhost:3000` in your browser.
`http://localhost:3000/docs` in your browser.

### Previewing Vercel Deployments

Expand Down
1 change: 1 addition & 0 deletions cspell-allow-list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ horsegraph
horsten
hostmatcher
hostpath
hrefs
HSTS
httptest
huggingface
Expand Down
19 changes: 13 additions & 6 deletions dev/check-links.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import fs from 'fs';
import path from 'path';
import GithubSlugger from 'github-slugger';
import { fileURLToPath, pathToFileURL } from 'url';
import config from '../docs.config.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand Down Expand Up @@ -279,11 +280,17 @@ function extractLinks(content, filePath) {
}

// Absolute links to this site, in every form the docs have used: http or https,
// scheme-relative, www., the legacy docs.sourcegraph.com host, or sourcegraph.com/docs.
// Links pinned to an old version (/@5.1/..., /v/5.1/...) are external: the
// middleware sends them to that version's own site (5.1.sourcegraph.com), whose
// pages are not in this repo, so only --check-external can validate them.
const SELF_LINK_REGEX = /^(?:https?:)?\/\/(?:www\.)?(?:docs\.sourcegraph\.com|sourcegraph\.com\/docs)(?=[/#?]|$)(?!\/@|\/v\/)/i;
// scheme-relative, www., the legacy docs.sourcegraph.com host, or the production
// URL from docs.config.js. Links pinned to an old version (/@5.1/..., /v/5.1/...)
// are external: the middleware sends them to that version's own site
// (5.1.sourcegraph.com), whose pages are not in this repo, so only
// --check-external can validate them.
const escapeRegExp = text => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const PROD_HOST_AND_PATH = config.DOCS_PROD_URL.replace(/^https?:\/\//, '');
const SELF_LINK_REGEX = new RegExp(
`^(?:https?:)?//(?:www\\.)?(?:docs\\.sourcegraph\\.com|${escapeRegExp(PROD_HOST_AND_PATH)})(?=[/#?]|$)(?!/@|/v/)`,
'i'
);

export function isSelfLink(url) {
return SELF_LINK_REGEX.test(url);
Expand Down Expand Up @@ -580,7 +587,7 @@ function markdownFindingList(findings) {

const ABSOLUTE_LINKS_ADVICE =
'Write links on this site as relative paths (`/admin/config/site-config`), ' +
'not `https://sourcegraph.com/docs/…`: absolute links leave the preview ' +
`not \`${config.DOCS_PROD_URL}/…\`: absolute links leave the preview ` +
'deployment and local dev server, and hide moved pages behind redirects.';

// Body for a pull request comment. With --diff, findings are split into
Expand Down
29 changes: 16 additions & 13 deletions dev/check-redirects.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
* - the source has no #fragment: browsers never send fragments, so such an
* entry can never match
* - the source has no earlier entry: the middleware uses the first match only
* - neither path starts with /docs: the middleware strips that prefix from
* requests and adds it to destinations
* - neither path starts with the basePath (docs.config.js): Next strips it
* from requests and the middleware adds it to destinations
* - the destination is a page, not another redirect
* - the destination page exists under docs/ (or is a file under public/)
* - when the destination has a #fragment, the heading exists on that page
Expand Down Expand Up @@ -39,11 +39,13 @@ import path from 'path';
import vm from 'vm';
import {fileURLToPath} from 'url';
import {extractHeadings, listFiles, routeFor} from './check-links.mjs';
import config from '../docs.config.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const args = process.argv.slice(2);
const ROOT_DIR = path.resolve(flagValue('--root') ?? path.dirname(__dirname));
const BASE_PATH = config.DOCS_BASE_PATH;
const FORMAT = flagValue('--format') ?? 'text';
const BASELINE_FILE = flagValue('--baseline');
const LINK_BASE = flagValue('--link-base')?.replace(/\/$/, '');
Expand Down Expand Up @@ -72,12 +74,12 @@ const PROBLEM = {
'If the redirect destination has a #fragment, it takes precedence, otherwise if the customer ' +
"clicked a link which has a #fragment, it'll be kept and tried on the destination page."
},
docsPrefix: {
heading: 'Source or destination starts with /docs',
basePathPrefix: {
heading: `Source or destination starts with ${BASE_PATH}`,
fix:
'Write paths without the /docs prefix. The site removes /docs from the requested URL before ' +
'matching sources, and adds it back in front of the destination, so a /docs/... source never ' +
'matches and a /docs/... destination lands on /docs/docs/....'
`Write paths without the ${BASE_PATH} prefix. The site removes ${BASE_PATH} from the requested URL before ` +
`matching sources, and adds it back in front of the destination, so a ${BASE_PATH}/... source never ` +
`matches and a ${BASE_PATH}/... destination lands on ${BASE_PATH}${BASE_PATH}/....`
},
duplicateSource: {
heading:
Expand Down Expand Up @@ -195,7 +197,8 @@ function findBrokenRedirects(redirects, headingsByRoute) {
const report = (redirect, problem, {detail, fix} = {}) =>
findings.push({...redirect, problem: problem.heading, detail, fix});
const withoutFragment = url => url.split('#')[0];
const withoutDocsPrefix = url => url.replace(/^\/docs(?=\/)/, '');
const withoutBasePath = url =>
url.startsWith(`${BASE_PATH}/`) ? url.slice(BASE_PATH.length) : url;
const isRedirect = pathname =>
firstBySource.has(pathname) && !headingsByRoute.has(pathname);

Expand All @@ -217,13 +220,13 @@ function findBrokenRedirects(redirects, headingsByRoute) {
report(redirect, PROBLEM.shadowsPage);
}
if (
source.pathname.startsWith('/docs/') ||
redirect.destination.startsWith('/docs/')
source.pathname.startsWith(`${BASE_PATH}/`) ||
redirect.destination.startsWith(`${BASE_PATH}/`)
) {
report(redirect, PROBLEM.docsPrefix, {
report(redirect, PROBLEM.basePathPrefix, {
fix: {
source: withoutDocsPrefix(redirect.source),
destination: withoutDocsPrefix(redirect.destination)
source: withoutBasePath(redirect.source),
destination: withoutBasePath(redirect.destination)
}
});
continue;
Expand Down
12 changes: 6 additions & 6 deletions dev/verify-links-live.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@
// Prints a Markdown table to paste into a PR. Old links point at --old-site so
// reviewers can see the current breakage.
//
// node dev/verify-links-live.mjs --site https://<preview>.vercel.app [--old-site https://sourcegraph.com/docs] [--base origin/main]
// node dev/verify-links-live.mjs --site https://<preview>.vercel.app/docs [--old-site https://sourcegraph.com/docs] [--base origin/main]
//
// Production serves under https://sourcegraph.com/docs (basePath in
// next.config.js); Vercel previews serve at the root, so pass the full prefix
// in --site.
// Every deployment serves under the basePath (docs.config.js), so --site
// includes that prefix. Both default to production.
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { extractHeadings } from './check-links.mjs';
import config from '../docs.config.js';

const args = process.argv.slice(2);
const argValue = (flag, fallback) => {
const index = args.indexOf(flag);
return index === -1 ? fallback : args[index + 1];
};
const SITE = argValue('--site', 'https://sourcegraph.com/docs').replace(/\/$/, '');
const OLD_SITE = argValue('--old-site', 'https://sourcegraph.com/docs').replace(/\/$/, '');
const SITE = argValue('--site', config.DOCS_PROD_URL).replace(/\/$/, '');
const OLD_SITE = argValue('--old-site', config.DOCS_PROD_URL).replace(/\/$/, '');
const BASE_REF = argValue('--base', 'origin/main');

const LINK = /\]\(([^)\s]+)\)|href=["']([^"']+)["']/g;
Expand Down
12 changes: 11 additions & 1 deletion docs.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
// sourcegraph.com proxies DOCS_BASE_PATH/* to this site. Previews and local dev
// use the same basePath so a root-relative URL that 404s in production 404s
// there too. To move the site, change DOCS_BASE_PATH here and the load balancer
// rule in sourcegraph/infrastructure; nothing else spells the path out.
const DOCS_BASE_PATH = '/docs';
const DOCS_PROD_ORIGIN = 'https://sourcegraph.com';

const config = {
DOCS_LATEST_VERSION: '7.7'
DOCS_LATEST_VERSION: '7.7',
DOCS_BASE_PATH,
DOCS_PROD_ORIGIN,
DOCS_PROD_URL: `${DOCS_PROD_ORIGIN}${DOCS_BASE_PATH}`
};

module.exports = config;
23 changes: 6 additions & 17 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,22 @@
const {withContentlayer} = require('next-contentlayer2');
const {DOCS_BASE_PATH} = require('./docs.config.js');
/** @type {import('next').NextConfig} */

// in prod, we serve the docs from sourcegraph.com/docs, and this requires special config on the GFE side
// in preview/development, this is not necessary.
//
// VERCEL_ENV is a system env var set by Vercel
// https://vercel.com/docs/projects/environment-variables/system-environment-variables
const basePath = process.env.VERCEL_ENV === 'production' ? '/docs' : '';

const nextConfig = {
reactStrictMode: true,
basePath,
basePath: DOCS_BASE_PATH,
// Orb portals proxy the dev server through a different hostname.
allowedDevOrigins: process.env.PUBLIC_URL
? [new URL(process.env.PUBLIC_URL).hostname]
: [],
env: {
NEXT_PUBLIC_DOCS_BASE_PATH: basePath
},
// With basePath set, nothing serves `/`, so the *.vercel.app deployment URL
// that Vercel links from Slack / GitHub 404s. sourcegraph.com never proxies
// `/` to us, so this only affects visitors opening that raw URL. Stay on the
// same host so they see this exact deployment, not whatever is live.
// Nothing serves `/`, so the deployment URLs Vercel links from Slack / GitHub
// and http://localhost:3000 would 404. sourcegraph.com never proxies `/` to
// us. Stay on the same host so visitors see this exact deployment.
async redirects() {
if (!basePath) return [];
return [
{
source: '/',
destination: basePath,
destination: DOCS_BASE_PATH,
basePath: false,
permanent: false
}
Expand Down
3 changes: 2 additions & 1 deletion src/app/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {PreviewGuard} from '@/components/PreviewGuard';
import {PrevNextLinks} from '@/components/PrevNextLinks';
import {Prose} from '@/components/Prose';
import {TableOfContents} from '@/components/Toc';
import {withBasePath} from '@/lib/utils';
import {allPosts} from 'contentlayer/generated';
import {getMDXComponent} from 'next-contentlayer2/hooks';
import {notFound} from 'next/navigation';
Expand All @@ -28,7 +29,7 @@ export const generateMetadata = async ({params}: Props) => {
const post = allPosts.find(post => post._raw.flattenedPath === path);
if (post && post.headings && post.headings.length > 0) {
const title = post.headings[0].title;
const ogImageUrl = `${process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''}/api/og/${path}`;
const ogImageUrl = withBasePath(`/api/og/${path}`);
return {
title,
openGraph: {
Expand Down
3 changes: 2 additions & 1 deletion src/app/api/versions/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {versions} from '@/data/versions';
import config from 'docs.config';
import {NextResponse} from 'next/server';

// Next 15+ no longer caches GET handlers by default; the version list is
Expand All @@ -9,7 +10,7 @@ export function GET() {
return NextResponse.json(
versions.map((version, index) =>
index === 0
? {...version, url: 'https://sourcegraph.com/docs'}
? {...version, url: config.DOCS_PROD_URL}
: version
),
{
Expand Down
9 changes: 5 additions & 4 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {Providers} from '@/app/providers';
import {Layout} from '@/components/Layout';
import {TECHNICAL_CHANGELOG_RSS_URL} from '@/data/constants';
import {withBasePath} from '@/lib/utils';
import clsx from 'clsx';
import config from 'docs.config';
import {type Metadata} from 'next';
Expand All @@ -11,7 +12,7 @@ import {Suspense} from 'react';
import '@/styles/tailwind.css';

export const metadata: Metadata = {
metadataBase: new URL('https://sourcegraph.com'),
metadataBase: new URL(config.DOCS_PROD_ORIGIN),
title: {
template: '%s - Sourcegraph docs',
default: 'Sourcegraph docs'
Expand All @@ -23,14 +24,14 @@ export const metadata: Metadata = {
'docsearch:version': `v${config.DOCS_LATEST_VERSION}`
},
alternates: {
canonical: '/docs'
canonical: config.DOCS_BASE_PATH
},
openGraph: {
images: [{url: `${process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''}/api/og/index`, width: 1200, height: 630}]
images: [{url: withBasePath('/api/og/index'), width: 1200, height: 630}]
},
twitter: {
card: 'summary_large_image',
images: [{url: `${process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''}/api/og/index`, width: 1200, height: 630}]
images: [{url: withBasePath('/api/og/index'), width: 1200, height: 630}]
}
};

Expand Down
3 changes: 2 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import MdxComponents from '@/components/MdxComponents';
import {Prose} from '@/components/Prose';
import {withBasePath} from '@/lib/utils';
import {allPosts} from 'contentlayer/generated';
import {getMDXComponent} from 'next-contentlayer2/hooks';
import {notFound} from 'next/navigation';

export const generateMetadata = () => {
const ogImageUrl = `${process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''}/api/og/index`;
const ogImageUrl = withBasePath('/api/og/index');
return {
openGraph: {
images: [{url: ogImageUrl, width: 1200, height: 630}]
Expand Down
3 changes: 2 additions & 1 deletion src/app/robots.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {MetadataRoute} from 'next';
import config from 'docs.config';

export default async function robots(): Promise<MetadataRoute.Robots> {
return {
rules: {
userAgent: '*',
allow: '/'
},
sitemap: 'https://sourcegraph.com/docs/sitemap.xml'
sitemap: `${config.DOCS_PROD_URL}/sitemap.xml`
};
}
Loading
Loading