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
9 changes: 0 additions & 9 deletions .githooks/pre-commit

This file was deleted.

13 changes: 0 additions & 13 deletions .githooks/pre-push

This file was deleted.

58 changes: 58 additions & 0 deletions .github/workflows/docs-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Docs Checks

on:
push:
branches:
- main
paths: &docs-check-paths
- 'content/**'
- 'content.config.ts'
- 'redirects.json'
- 'scripts/_content-lib.ts'
- 'scripts/check-stable-ids.ts'
- 'scripts/ensure-stable-ids.ts'
- 'scripts/redirects-sync.ts'
- 'package.json'
- 'pnpm-lock.yaml'
- '.github/workflows/docs-checks.yml'
pull_request:
branches:
- main
paths: *docs-check-paths
workflow_dispatch:

permissions:
contents: read

jobs:
docs-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts

- name: Typecheck scripts
run: pnpm typecheck:scripts

- name: Check stable IDs
run: pnpm stable-ids:check

# Redirect check diffs against origin/main, so it only makes sense as a
# pre-merge gate on PRs. On push to main the merge already landed and
# origin/main points at the pushed commit (self-comparison), so skip it.
- name: Fetch base ref for redirect diff
if: github.event_name == 'pull_request'
run: git fetch --depth=1 origin main

- name: Check redirects
if: github.event_name == 'pull_request'
run: pnpm redirects:check
45 changes: 45 additions & 0 deletions .github/workflows/search-index-cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Search Index Cleanup

on:
pull_request:
branches:
- main
types:
- closed
workflow_dispatch:
inputs:
branch:
description: Branch name for the preview index to delete
required: true
type: string

permissions:
contents: read

jobs:
cleanup-preview-index:
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
concurrency:
group: search-index-preview-${{ github.event.pull_request.number || inputs.branch }}
cancel-in-progress: true
env:
TYPESENSE_URL: ${{ secrets.TYPESENSE_URL }}
TYPESENSE_PRIVATE_API_KEY: ${{ secrets.TYPESENSE_PRIVATE_API_KEY }}
TYPESENSE_PREVIEW_BRANCH: ${{ github.event.pull_request.head.ref || inputs.branch }}
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
# This job runs one repository script only; skip package postinstall/build scripts.
run: pnpm install --frozen-lockfile --ignore-scripts

- name: Delete preview collection
run: pnpm typesense:cleanup-preview --branch "$TYPESENSE_PREVIEW_BRANCH"
28 changes: 21 additions & 7 deletions .github/workflows/search-index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,37 @@ on:
- 'content/**'
- 'scripts/index-docs.ts'
- 'scripts/index-docs-chunker.ts'
- 'scripts/_content-lib.ts'
- 'shared/utils/parseTypesenseUrl.ts'
- 'shared/utils/docsSections.ts'
- 'app/utils/slugify.ts'
- 'server/data/synonyms.ts'
- 'lib/typesenseAlias.ts'
- 'package.json'
- 'pnpm-lock.yaml'
- '.github/workflows/search-index.yml'
pull_request:
branches:
- main
types:
- opened
- synchronize
- reopened
paths: *index-paths
workflow_dispatch:

concurrency:
group: search-index-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read

jobs:
preview-index:
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
if: github.event_name == 'pull_request' && github.event.action != 'closed' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
# Cancel superseded preview runs so the alias never ends up pointing at a
# stale index from an out-of-order swap.
concurrency:
group: search-index-preview-${{ github.event.pull_request.number }}
cancel-in-progress: true
env:
TYPESENSE_URL: ${{ secrets.TYPESENSE_URL }}
TYPESENSE_PUBLIC_API_KEY: ${{ secrets.TYPESENSE_PUBLIC_API_KEY }}
Expand All @@ -47,14 +54,20 @@ jobs:
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile
# These jobs run repository scripts only; skip package postinstall/build scripts.
run: pnpm install --frozen-lockfile --ignore-scripts

- name: Index preview collection
run: pnpm index:docs

prod-index:
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
# Run prod indexing sequentially; the blue/green slot design tolerates
# re-runs, so never cancel a swap mid-flight.
concurrency:
group: search-index-prod
cancel-in-progress: false
env:
TYPESENSE_URL: ${{ secrets.TYPESENSE_URL }}
TYPESENSE_PUBLIC_API_KEY: ${{ secrets.TYPESENSE_PUBLIC_API_KEY }}
Expand All @@ -70,7 +83,8 @@ jobs:
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile
# These jobs run repository scripts only; skip package postinstall/build scripts.
run: pnpm install --frozen-lockfile --ignore-scripts

- name: Index production collection
run: pnpm index:docs
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,30 @@ pnpm stable-ids:check # Validate stableId frontmatter
pnpm redirects:sync # Update redirects.json for moved pages
pnpm redirects:check # Check redirect coverage without writing files
pnpm index:docs # Build the search index in Typesense
pnpm typesense:cleanup-preview # Delete stale Typesense preview indexes
pnpm typecheck:scripts # Type check repository scripts
```

`pnpm install` configures `.githooks` for the repository when no custom `core.hooksPath` is set. The pre-commit hook can add missing `stableId` values to staged docs files. The pre-push hook checks redirects when docs content, redirect configuration, or content configuration changes.
Stable IDs give each public docs page a permanent identity. Nuxt Content derives
its unique page IDs from file paths, so moving a page changes its built-in ID.
Redirect sync compares the current branch to `origin/main`, so moved pages keep
their old URLs working.

CI runs `pnpm stable-ids:check` and `pnpm redirects:check` for docs changes.

- New docs page: run `pnpm stable-ids:ensure`, then commit the new `stableId`.
- Moved docs page: keep the existing `stableId`, run `pnpm redirects:sync`, then commit `redirects.json`.
- Deleted, split, or merged docs page: run `pnpm redirects:sync`, review `.docs/redirect-decisions-needed.md`, choose target redirects, then re-run `pnpm redirects:check`.
- Before opening a PR: run `pnpm stable-ids:check` and `pnpm redirects:check`.

Redirect scripts compare against `origin/main` by default. To check a release branch
or another target, fetch it first, then pass `--base` directly to the script:

```bash
git fetch origin release/v13
node scripts/redirects-sync.ts --base origin/release/v13 --no-write --fail-on-unresolved
node scripts/redirects-sync.ts --base origin/release/v13 --write-deterministic --fail-on-unresolved
```

## ✍️ Authoring Content

Expand Down Expand Up @@ -138,6 +158,23 @@ For one-off writes, override the index target with `TYPESENSE_INDEX_TARGET=...`.

The browser reads from `TYPESENSE_COLLECTION` when set. Otherwise it derives the same branch alias as the indexer. The app reads the alias, never the `-a` / `-b` slot name.

### Preview Cleanup

PR preview indexes are deleted when same-repo PRs close. The cleanup job deletes the branch alias and both fixed slots:

```bash
pnpm typesense:cleanup-preview --branch bry/foo
```

For one-time cleanup of accumulated preview indexes, run a dry run first:

```bash
pnpm typesense:cleanup-preview --stale --dry-run
pnpm typesense:cleanup-preview --stale
```

Stale cleanup keeps preview aliases for currently open PR branches and deletes the rest. It requires `TYPESENSE_URL`, `TYPESENSE_PRIVATE_API_KEY`, and authenticated `gh`.

### Ranking

Section boosts and personalization live in `buildPersonalizedSortBy` in `app/composables/useDocsSearch.ts`. The same `sectionPriority` array drives both the Typesense `_eval` boost order and the chip-bar render order in the palette.
Expand Down
7 changes: 5 additions & 2 deletions lib/typesenseAlias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { execSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import process from 'node:process';

export const TYPESENSE_PROD_ALIAS = 'directus-docs';
export const TYPESENSE_PREVIEW_ALIAS_PREFIX = 'directus-docs-preview-';

export function slugifyBranch(branch: string) {
const slug = branch
.toLowerCase()
Expand Down Expand Up @@ -38,6 +41,6 @@ export function getTypesenseBranchName() {

export function resolveBranchTypesenseAlias(branch = getTypesenseBranchName()) {
if (!branch) return null;
if (branch === 'main') return 'directus-docs';
return `directus-docs-preview-${slugifyBranch(branch)}`;
if (branch === 'main') return TYPESENSE_PROD_ALIAS;
return `${TYPESENSE_PREVIEW_ALIAS_PREFIX}${slugifyBranch(branch)}`;
}
30 changes: 15 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
"private": true,
"type": "module",
"scripts": {
"api-ref:generate": "tsx scripts/generate-api-reference.ts",
"build": "tsx scripts/generate-api-reference.ts && nuxt build",
"dev": "tsx scripts/generate-api-reference.ts && nuxt dev",
"generate": "tsx scripts/generate-api-reference.ts && nuxt generate",
"api-ref:generate": "node scripts/generate-api-reference.ts",
"build": "node scripts/generate-api-reference.ts && nuxt build",
"dev": "node scripts/generate-api-reference.ts && nuxt dev",
"generate": "node scripts/generate-api-reference.ts && nuxt generate",
"preview": "nuxt preview",
"postinstall": "node scripts/setup-hooks.ts && tsx scripts/generate-api-reference.ts && nuxt prepare",
"postinstall": "node scripts/generate-api-reference.ts && nuxt prepare",
"stable-ids:ensure": "node scripts/ensure-stable-ids.ts",
"stable-ids:check": "node scripts/check-stable-ids.ts",
"redirects:sync": "node scripts/redirects-sync.ts --write-deterministic --fail-on-unresolved",
"redirects:check": "node scripts/redirects-sync.ts --fail-on-unresolved --no-write",
"typecheck:scripts": "tsc -p scripts/tsconfig.json",
"index:docs": "tsx scripts/index-docs.ts",
"test:search": "vitest run tests/scripts/index-docs-chunker.test.ts tests/components/DocsSearchPalette.test.ts tests/shared/parseTypesenseUrl.test.ts tests/lib/typesenseAlias.test.ts tests/services/typesenseService.test.ts tests/utils/highlightHtml.test.ts"
"index:docs": "node scripts/index-docs.ts",
"typesense:cleanup-preview": "node scripts/cleanup-typesense-preview.ts",
"test:search": "vitest run tests/scripts/index-docs-chunker.test.ts tests/scripts/cleanup-typesense-preview.test.ts tests/components/DocsSearchPalette.test.ts tests/shared/parseTypesenseUrl.test.ts tests/lib/typesenseAlias.test.ts tests/services/typesenseService.test.ts tests/utils/highlightHtml.test.ts"
},
"dependencies": {
"@directus/openapi": "0.3.0",
Expand All @@ -32,19 +33,12 @@
"@nuxtjs/sitemap": "8.0.13",
"@vueuse/core": "14.2.1",
"@vueuse/nuxt": "14.2.1",
"dotenv": "^17.4.2",
"gray-matter": "^4.0.3",
"h3": "1.15.11",
"js-yaml": "^4.1.1",
"lodash-es": "4.18.1",
"nuxt": "4.4.2",
"nuxt-llms": "0.2.0",
"openapi3-ts": "4.5.0",
"posthog-js": "1.371.2",
"posthog-node": "5.29.7",
"remark": "^15.0.1",
"remark-mdc": "^3.11.0",
"remark-parse": "^11.0.0",
"sharp": "^0.34.5",
"tailwindcss": "^4.2.4",
"typesense": "^3.0.6",
Expand All @@ -61,8 +55,14 @@
"@types/lodash-es": "4.17.12",
"@types/node": "^22",
"@vue/test-utils": "^2.4.10",
"dotenv": "^17.4.2",
"gray-matter": "^4.0.3",
"happy-dom": "^20.9.0",
"tsx": "^4.22.3",
"js-yaml": "^4.1.1",
"lodash-es": "4.18.1",
"remark": "^15.0.1",
"remark-mdc": "^3.11.0",
"remark-parse": "^11.0.0",
"typescript": "6.0.3",
"vitest": "^4.1.7",
"vue-tsc": "^3.2.7"
Expand Down
Loading
Loading