parent
b12da23918
commit
cffc6c4b50
|
|
@ -61,7 +61,7 @@ Process:
|
||||||
- Preserve the existing changelog style and sections: `Added`, `Changed`, `Fixed`, `Removed`, and `Breaking Changes` when applicable.
|
- Preserve the existing changelog style and sections: `Added`, `Changed`, `Fixed`, `Removed`, and `Breaking Changes` when applicable.
|
||||||
|
|
||||||
7. Audit next-release public docs.
|
7. Audit next-release public docs.
|
||||||
- Treat root `README.md` and the current stable docs source as the latest released public docs. While `docs/versions/manifest.json` has `stable_source: legacy`, that source is `website/src/content/docs/`; afterward it is the current immutable snapshot under `docs/versions/<current>/website/src/content/docs/`.
|
- Treat root `README.md` and the version selected by `docs/versions/manifest.json` under `docs/versions/<current>/website/src/content/docs/` as the latest released public docs. Published version docs may contain factual corrections made after the release tag.
|
||||||
- Treat `docs/next/README.md` as the next-release root README and `docs/next/website/src/content/docs/` as the complete unpublished website-doc draft.
|
- Treat `docs/next/README.md` as the next-release root README and `docs/next/website/src/content/docs/` as the complete unpublished website-doc draft.
|
||||||
- Treat `docs/preview/website/` as bot-owned output for the active preview release. Never edit it during release review and never use it as the stable release source.
|
- Treat `docs/preview/website/` as bot-owned output for the active preview release. Never edit it during release review and never use it as the stable release source.
|
||||||
- Compare meaningful user-facing changes in the range against next-release docs first.
|
- Compare meaningful user-facing changes in the range against next-release docs first.
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
bun-version: 1.3.14
|
bun-version: 1.3.14
|
||||||
|
|
||||||
- name: Validate published snapshots
|
- name: Validate published documentation
|
||||||
run: |
|
run: |
|
||||||
node website/scripts/docs-versions.mjs check
|
node website/scripts/docs-versions.mjs check
|
||||||
node website/scripts/docs-preview.mjs check
|
node website/scripts/docs-preview.mjs check
|
||||||
|
|
|
||||||
|
|
@ -163,9 +163,9 @@ Unreleased docs live in `docs/next/website/src/content/docs/`. Update those when
|
||||||
|
|
||||||
The active preview release docs live in `docs/preview/website/`. Preview CI owns this mutable snapshot and commits it atomically with `website/preview.json`; never edit it manually. Validate it with `node website/scripts/docs-preview.mjs check`.
|
The active preview release docs live in `docs/preview/website/`. Preview CI owns this mutable snapshot and commits it atomically with `website/preview.json`; never edit it manually. Validate it with `node website/scripts/docs-preview.mjs check`.
|
||||||
|
|
||||||
Immutable stable release snapshots live in `docs/versions/`. The website build generates `/docs/preview/` from the active preview snapshot and `/docs/<version>/` from stable snapshots. The current stable `/docs/` temporarily uses the tracked legacy `website/src/content/docs/` tree; the next stable release switches it to the current immutable snapshot and removes the legacy copy. Do not edit generated preview, version, or snapshot-backed stable files under `website/src/content/docs/`.
|
Published stable-release documentation lives in `docs/versions/`. Release CI seeds each version from the tagged `docs/next` tree, and maintainers may correct factual documentation errors in a published version afterward. Apply a correction separately to `docs/next` when it also applies to future releases; never replace a published tree with the current draft. The website build generates `/docs/preview/` from the active preview snapshot, `/docs/<version>/` from the maintained version directories, and `/docs/` from the version selected by `docs/versions/manifest.json`. Do not edit generated files under `website/src/content/docs/`.
|
||||||
|
|
||||||
During release review, finalize `docs/next` and run `just release-docs-check`. Do not copy draft docs into preview or stable manually. Preview CI snapshots the selected commit. After a stable GitHub Release succeeds, release CI snapshots the exact tag, updates `latest.json`, and deploys them together. Normal feature/fix work should not edit root `README.md`, root `CHANGELOG.md`, legacy stable website docs, or `website/latest.json` unless explicitly requested.
|
During release review, finalize `docs/next` and run `just release-docs-check`. Do not copy draft docs into preview or published versions manually. Preview CI snapshots the selected commit. After a stable GitHub Release succeeds, release CI seeds a new version from the exact tag, updates `latest.json`, and deploys them together. Normal feature/fix work should not edit root `README.md`, root `CHANGELOG.md`, published version docs, or `website/latest.json` unless it is a focused correction to already-published documentation or explicitly requested.
|
||||||
|
|
||||||
Put local PRDs, planning notes, and exploratory specs under `.local/prd/`; `.local/` is ignored and locally controlled.
|
Put local PRDs, planning notes, and exploratory specs under `.local/prd/`; `.local/` is ignored and locally controlled.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,9 +65,9 @@ If your proposal changes the visual language, interaction model, workflow, persi
|
||||||
|
|
||||||
## Documentation for unreleased changes
|
## Documentation for unreleased changes
|
||||||
|
|
||||||
The root `README.md`, root `CHANGELOG.md`, and public website docs describe released Herdr builds. Do not update root `README.md`, root `CHANGELOG.md`, `docs/preview/`, `docs/versions/`, or `website/src/content/docs/` for normal PRs.
|
The root `README.md`, root `CHANGELOG.md`, and public website docs describe released Herdr builds. Do not update root `README.md`, root `CHANGELOG.md`, `docs/preview/`, `docs/versions/`, or `website/src/content/docs/` for normal code PRs. A focused correction to already-published documentation may update the affected `docs/versions/<version>/` files and should make the same correction under `docs/next` when it remains relevant to future releases.
|
||||||
|
|
||||||
If your PR changes user-facing behavior, mention the needed public-doc update in the PR. Update `docs/next/README.md` only when the root README needs to change for the next stable release. Update the draft under `docs/next/website/src/content/docs/` when website docs need to change. Draft changes stay unpublished until preview CI snapshots a selected commit or stable release CI snapshots a tag; contributors and maintainers do not copy them into public docs manually.
|
If your PR changes user-facing behavior, mention the needed public-doc update in the PR. Update `docs/next/README.md` only when the root README needs to change for the next stable release. Update the draft under `docs/next/website/src/content/docs/` when website docs need to change. Draft changes stay unpublished until preview CI snapshots a selected commit or stable release CI seeds a new version from a tag; contributors and maintainers do not copy an entire draft tree into published docs manually.
|
||||||
|
|
||||||
You do not need to edit the changelog for normal PRs. Maintainers prepare `docs/next/CHANGELOG.md` during release review.
|
You do not need to edit the changelog for normal PRs. Maintainers prepare `docs/next/CHANGELOG.md` during release review.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -373,6 +373,8 @@ position = "bottom-right"
|
||||||
|
|
||||||
Choose `herdr` for an in-app toast, `terminal` for an outer-terminal notification that works well over SSH, `system` for the local OS notification service, or `off` to disable popups. Herdr suppresses popups for the active tab. Search `ui.toast` in the [Config reference](/docs/config-reference/) for positions, delay behavior, and clipboard feedback settings.
|
Choose `herdr` for an in-app toast, `terminal` for an outer-terminal notification that works well over SSH, `system` for the local OS notification service, or `off` to disable popups. Herdr suppresses popups for the active tab. Search `ui.toast` in the [Config reference](/docs/config-reference/) for positions, delay behavior, and clipboard feedback settings.
|
||||||
|
|
||||||
|
On macOS, `system` tries `terminal-notifier` first and falls back to `/usr/bin/osascript` when it is unavailable or fails. The fallback appears as Script Editor in Notification Center and cannot activate the hosting terminal. Install `terminal-notifier` with `brew install terminal-notifier`. For a supported, detected terminal, it can activate the terminal app when you click the notification. Alternatively, choose `terminal` to let a supported outer terminal own the notification.
|
||||||
|
|
||||||
## Sound
|
## Sound
|
||||||
|
|
||||||
Sound notifications play through the local Herdr client. Custom sounds must be mp3 files; relative paths are resolved from the config file's directory.
|
Sound notifications play through the local Herdr client. Custom sounds must be mp3 files; relative paths are resolved from the config file's directory.
|
||||||
|
|
|
||||||
|
|
@ -367,6 +367,8 @@ position = "bottom-right"
|
||||||
|
|
||||||
アプリ内トーストには `herdr`、SSH 越しでも使いやすい外側のターミナル通知には `terminal`、ローカル OS の通知サービスには `system`、ポップアップを無効にするには `off` を選びます。Herdr はアクティブなタブのポップアップを抑制します。位置、遅延の挙動、クリップボードのフィードバック設定については、[設定リファレンス](/docs/config-reference/)で `ui.toast` を検索してください。
|
アプリ内トーストには `herdr`、SSH 越しでも使いやすい外側のターミナル通知には `terminal`、ローカル OS の通知サービスには `system`、ポップアップを無効にするには `off` を選びます。Herdr はアクティブなタブのポップアップを抑制します。位置、遅延の挙動、クリップボードのフィードバック設定については、[設定リファレンス](/docs/config-reference/)で `ui.toast` を検索してください。
|
||||||
|
|
||||||
|
macOS では、`system` は最初に `terminal-notifier` を試し、利用できない場合や失敗した場合は `/usr/bin/osascript` にフォールバックします。このフォールバックは通知センターで Script Editor として表示され、ホストターミナルをアクティブにできません。`brew install terminal-notifier` で `terminal-notifier` をインストールできます。対応するターミナルを Herdr が検出できた場合、通知のクリック時にそのターミナルアプリをアクティブにできます。または、`terminal` を選んで対応する外側のターミナルに通知を送信させてください。
|
||||||
|
|
||||||
## サウンド
|
## サウンド
|
||||||
|
|
||||||
サウンド通知はローカルの Herdr クライアントで再生されます。カスタムサウンドは mp3 ファイルでなければなりません。相対パスは設定ファイルのディレクトリから解決されます。
|
サウンド通知はローカルの Herdr クライアントで再生されます。カスタムサウンドは mp3 ファイルでなければなりません。相対パスは設定ファイルのディレクトリから解決されます。
|
||||||
|
|
|
||||||
|
|
@ -367,6 +367,8 @@ position = "bottom-right"
|
||||||
|
|
||||||
选择 `herdr` 可使用应用内 toast,选择 `terminal` 可使用适合 SSH 场景的外层终端通知,选择 `system` 可使用本地操作系统通知服务,选择 `off` 可禁用弹出通知。Herdr 会抑制活动标签页的弹出通知。在[配置参考](/docs/config-reference/)中搜索 `ui.toast`,可查看位置、延迟行为和剪贴板反馈设置。
|
选择 `herdr` 可使用应用内 toast,选择 `terminal` 可使用适合 SSH 场景的外层终端通知,选择 `system` 可使用本地操作系统通知服务,选择 `off` 可禁用弹出通知。Herdr 会抑制活动标签页的弹出通知。在[配置参考](/docs/config-reference/)中搜索 `ui.toast`,可查看位置、延迟行为和剪贴板反馈设置。
|
||||||
|
|
||||||
|
在 macOS 上,`system` 会先尝试 `terminal-notifier`;如果它不可用或执行失败,则回退到 `/usr/bin/osascript`。该回退方式会在通知中心显示为 Script Editor,并且无法激活承载 Herdr 的终端。可运行 `brew install terminal-notifier` 安装 `terminal-notifier`。如果 Herdr 检测到受支持的终端,点击通知时它可以激活该终端应用。也可以选择 `terminal`,让受支持的外层终端负责发送通知。
|
||||||
|
|
||||||
## 声音
|
## 声音
|
||||||
|
|
||||||
声音通知通过本地 Herdr 客户端播放。自定义声音必须是 mp3 文件;相对路径从配置文件所在目录解析。
|
声音通知通过本地 Herdr 客户端播放。自定义声音必须是 mp3 文件;相对路径从配置文件所在目录解析。
|
||||||
|
|
|
||||||
|
|
@ -373,6 +373,8 @@ position = "bottom-right"
|
||||||
|
|
||||||
Choose `herdr` for an in-app toast, `terminal` for an outer-terminal notification that works well over SSH, `system` for the local OS notification service, or `off` to disable popups. Herdr suppresses popups for the active tab. Search `ui.toast` in the [Config reference](/docs/config-reference/) for positions, delay behavior, and clipboard feedback settings.
|
Choose `herdr` for an in-app toast, `terminal` for an outer-terminal notification that works well over SSH, `system` for the local OS notification service, or `off` to disable popups. Herdr suppresses popups for the active tab. Search `ui.toast` in the [Config reference](/docs/config-reference/) for positions, delay behavior, and clipboard feedback settings.
|
||||||
|
|
||||||
|
On macOS, `system` tries `terminal-notifier` first and falls back to `/usr/bin/osascript` when it is unavailable or fails. The fallback appears as Script Editor in Notification Center and cannot activate the hosting terminal. Install `terminal-notifier` with `brew install terminal-notifier`. For a supported, detected terminal, it can activate the terminal app when you click the notification. Alternatively, choose `terminal` to let a supported outer terminal own the notification.
|
||||||
|
|
||||||
## Sound
|
## Sound
|
||||||
|
|
||||||
Sound notifications play through the local Herdr client. Custom sounds must be mp3 files; relative paths are resolved from the config file's directory.
|
Sound notifications play through the local Herdr client. Custom sounds must be mp3 files; relative paths are resolved from the config file's directory.
|
||||||
|
|
|
||||||
|
|
@ -367,6 +367,8 @@ position = "bottom-right"
|
||||||
|
|
||||||
アプリ内トーストには `herdr`、SSH 越しでも使いやすい外側のターミナル通知には `terminal`、ローカル OS の通知サービスには `system`、ポップアップを無効にするには `off` を選びます。Herdr はアクティブなタブのポップアップを抑制します。位置、遅延の挙動、クリップボードのフィードバック設定については、[設定リファレンス](/docs/config-reference/)で `ui.toast` を検索してください。
|
アプリ内トーストには `herdr`、SSH 越しでも使いやすい外側のターミナル通知には `terminal`、ローカル OS の通知サービスには `system`、ポップアップを無効にするには `off` を選びます。Herdr はアクティブなタブのポップアップを抑制します。位置、遅延の挙動、クリップボードのフィードバック設定については、[設定リファレンス](/docs/config-reference/)で `ui.toast` を検索してください。
|
||||||
|
|
||||||
|
macOS では、`system` は最初に `terminal-notifier` を試し、利用できない場合や失敗した場合は `/usr/bin/osascript` にフォールバックします。このフォールバックは通知センターで Script Editor として表示され、ホストターミナルをアクティブにできません。`brew install terminal-notifier` で `terminal-notifier` をインストールできます。対応するターミナルを Herdr が検出できた場合、通知のクリック時にそのターミナルアプリをアクティブにできます。または、`terminal` を選んで対応する外側のターミナルに通知を送信させてください。
|
||||||
|
|
||||||
## サウンド
|
## サウンド
|
||||||
|
|
||||||
サウンド通知はローカルの Herdr クライアントで再生されます。カスタムサウンドは mp3 ファイルでなければなりません。相対パスは設定ファイルのディレクトリから解決されます。
|
サウンド通知はローカルの Herdr クライアントで再生されます。カスタムサウンドは mp3 ファイルでなければなりません。相対パスは設定ファイルのディレクトリから解決されます。
|
||||||
|
|
|
||||||
|
|
@ -367,6 +367,8 @@ position = "bottom-right"
|
||||||
|
|
||||||
选择 `herdr` 可使用应用内 toast,选择 `terminal` 可使用适合 SSH 场景的外层终端通知,选择 `system` 可使用本地操作系统通知服务,选择 `off` 可禁用弹出通知。Herdr 会抑制活动标签页的弹出通知。在[配置参考](/docs/config-reference/)中搜索 `ui.toast`,可查看位置、延迟行为和剪贴板反馈设置。
|
选择 `herdr` 可使用应用内 toast,选择 `terminal` 可使用适合 SSH 场景的外层终端通知,选择 `system` 可使用本地操作系统通知服务,选择 `off` 可禁用弹出通知。Herdr 会抑制活动标签页的弹出通知。在[配置参考](/docs/config-reference/)中搜索 `ui.toast`,可查看位置、延迟行为和剪贴板反馈设置。
|
||||||
|
|
||||||
|
在 macOS 上,`system` 会先尝试 `terminal-notifier`;如果它不可用或执行失败,则回退到 `/usr/bin/osascript`。该回退方式会在通知中心显示为 Script Editor,并且无法激活承载 Herdr 的终端。可运行 `brew install terminal-notifier` 安装 `terminal-notifier`。如果 Herdr 检测到受支持的终端,点击通知时它可以激活该终端应用。也可以选择 `terminal`,让受支持的外层终端负责发送通知。
|
||||||
|
|
||||||
## 声音
|
## 声音
|
||||||
|
|
||||||
声音通知通过本地 Herdr 客户端播放。自定义声音必须是 mp3 文件;相对路径从配置文件所在目录解析。
|
声音通知通过本地 Herdr 客户端播放。自定义声音必须是 mp3 文件;相对路径从配置文件所在目录解析。
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
# Versioned documentation
|
# Versioned documentation
|
||||||
|
|
||||||
This directory contains immutable documentation snapshots for stable Herdr releases.
|
This directory contains the published documentation for stable Herdr releases.
|
||||||
|
|
||||||
Do not edit snapshot files manually. They must match the release tag recorded in `manifest.json`. Validate them with:
|
Release CI creates each version from the tagged `docs/next` tree after the GitHub Release succeeds. Maintainers can correct published documentation in its version directory afterward. When a correction also applies to future releases, make the same focused change under `docs/next`; do not replace a published tree with the current draft.
|
||||||
|
|
||||||
|
Validate the manifest and build every published version with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node website/scripts/docs-versions.mjs check
|
node website/scripts/docs-versions.mjs check
|
||||||
|
cd website && bun run build
|
||||||
```
|
```
|
||||||
|
|
||||||
Release CI creates a new snapshot from the tagged `docs/next` tree after the GitHub Release succeeds. `website/scripts/prepare-docs.mjs` renders these snapshots at `/docs/<version>/` and uses the current snapshot for `/docs/` after the legacy stable-doc migration completes. `/docs/preview/` comes only from the active preview release snapshot in `docs/preview/`, never directly from `docs/next/`.
|
`website/scripts/prepare-docs.mjs` renders each maintained version at `/docs/<version>/` and uses the version selected by `manifest.json` for `/docs/`. Generated files under `website/src/content/docs/` are not editable sources. `/docs/preview/` comes only from the active preview release snapshot in `docs/preview/`, never directly from `docs/next/`.
|
||||||
|
|
||||||
|
The `tag`, `commit`, and `source` fields in `manifest.json` record where release CI initially published a version. Git history records later documentation corrections.
|
||||||
|
|
||||||
The historical backfill starts at v0.5.11, the first release that included the Astro/Starlight documentation site.
|
The historical backfill starts at v0.5.11, the first release that included the Astro/Starlight documentation site.
|
||||||
|
|
|
||||||
|
|
@ -211,7 +211,7 @@ file: ../../../public/assets/logo.svg
|
||||||
self.assertIn("[Install](/docs/0.7.4/install/)", output)
|
self.assertIn("[Install](/docs/0.7.4/install/)", output)
|
||||||
self.assertIn("file: ../../../../../public/assets/logo.svg", output)
|
self.assertIn("file: ../../../../../public/assets/logo.svg", output)
|
||||||
self.assertIn("from '../../../../components/ConfigReference.astro'", output)
|
self.assertIn("from '../../../../components/ConfigReference.astro'", output)
|
||||||
self.assertIn("blob/v0.7.4/docs/next/website/src/content/docs/index.mdx", output)
|
self.assertIn("blob/master/docs/versions/0.7.4/website/src/content/docs/index.mdx", output)
|
||||||
self.assertIn("blob/v0.7.4/SKILL.md", output)
|
self.assertIn("blob/v0.7.4/SKILL.md", output)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@ Documentation has three lifecycle states:
|
||||||
|
|
||||||
- `../docs/next/website/` is the committed, author-edited draft. Production builds never read it.
|
- `../docs/next/website/` is the committed, author-edited draft. Production builds never read it.
|
||||||
- `../docs/preview/website/` is the latest preview release snapshot, rendered at `/docs/preview/`.
|
- `../docs/preview/website/` is the latest preview release snapshot, rendered at `/docs/preview/`.
|
||||||
- `../docs/versions/<version>/website/` contains immutable stable release snapshots, rendered at `/docs/<version>/`.
|
- `../docs/versions/<version>/website/` contains maintained stable-release documentation, rendered at `/docs/<version>/`. Release CI seeds a new version from its tag; later factual corrections are made directly in that version directory and mirrored to `docs/next` when they also apply to future releases.
|
||||||
|
|
||||||
The current stable site temporarily remains tracked under `src/content/docs/` because it contains post-v0.7.5 documentation corrections. The next stable release switches `docs/versions/manifest.json` to snapshot-backed stable docs and removes that legacy copy. From then on, `src/content/docs/` is entirely generated and ignored.
|
The version selected by `docs/versions/manifest.json` is also rendered at `/docs/`. `src/content/docs/` is entirely generated and ignored.
|
||||||
|
|
||||||
Preview CI snapshots the selected commit and updates `preview.json` in one commit:
|
Preview CI snapshots the selected commit and updates `preview.json` in one commit:
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ node website/scripts/docs-preview.mjs snapshot <commit>
|
||||||
node website/scripts/docs-preview.mjs check
|
node website/scripts/docs-preview.mjs check
|
||||||
```
|
```
|
||||||
|
|
||||||
Stable release CI snapshots the exact tag after the GitHub Release succeeds:
|
Stable release CI seeds a new maintained version from the exact tag after the GitHub Release succeeds:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node website/scripts/docs-versions.mjs publish <tag>
|
node website/scripts/docs-versions.mjs publish <tag>
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ assertIncludes(preview, 'name="robots" content="noindex, nofollow"');
|
||||||
assertIncludes(archived, `data-pagefind-filter="version[content]" content="${versions.current}"`);
|
assertIncludes(archived, `data-pagefind-filter="version[content]" content="${versions.current}"`);
|
||||||
assertIncludes(archived, 'name="robots" content="noindex, nofollow"');
|
assertIncludes(archived, 'name="robots" content="noindex, nofollow"');
|
||||||
if (archived.includes(`This page documents Herdr ${versions.current}`)) {
|
if (archived.includes(`This page documents Herdr ${versions.current}`)) {
|
||||||
throw new Error('the current immutable snapshot must not be labeled as outdated');
|
throw new Error('the current documentation version must not be labeled as outdated');
|
||||||
}
|
}
|
||||||
const versionSelect = stable.match(/<select[^>]*aria-label="Documentation version"[^>]*>([\s\S]*?)<\/select>/)?.[1];
|
const versionSelect = stable.match(/<select[^>]*aria-label="Documentation version"[^>]*>([\s\S]*?)<\/select>/)?.[1];
|
||||||
if (!versionSelect) throw new Error('stable docs are missing the version selector');
|
if (!versionSelect) throw new Error('stable docs are missing the version selector');
|
||||||
|
|
@ -68,7 +68,7 @@ if (previous) {
|
||||||
const sitemap = await readFile(resolve(distDir, 'sitemap-0.xml'), 'utf8');
|
const sitemap = await readFile(resolve(distDir, 'sitemap-0.xml'), 'utf8');
|
||||||
assertIncludes(sitemap, 'https://herdr.dev/docs/');
|
assertIncludes(sitemap, 'https://herdr.dev/docs/');
|
||||||
if (nonCanonicalDocsUrl.test(sitemap)) {
|
if (nonCanonicalDocsUrl.test(sitemap)) {
|
||||||
throw new Error('preview or immutable documentation URLs must not appear in the sitemap');
|
throw new Error('preview or versioned documentation URLs must not appear in the sitemap');
|
||||||
}
|
}
|
||||||
|
|
||||||
const build = await inspectFiles(distDir);
|
const build = await inspectFiles(distDir);
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ export async function compareGitTree(git, ref, sourceRoot, snapshotRoot) {
|
||||||
assertRelativePath(relativePath, entry.path);
|
assertRelativePath(relativePath, entry.path);
|
||||||
expected.set(relativePath, entry);
|
expected.set(relativePath, entry);
|
||||||
}
|
}
|
||||||
const actualPaths = await listSnapshotPaths(snapshotRoot);
|
const actualPaths = await listDocumentationPaths(snapshotRoot);
|
||||||
const expectedPaths = [...expected.keys()].sort();
|
const expectedPaths = [...expected.keys()].sort();
|
||||||
if (JSON.stringify(actualPaths) !== JSON.stringify(expectedPaths)) {
|
if (JSON.stringify(actualPaths) !== JSON.stringify(expectedPaths)) {
|
||||||
throw new Error(`${snapshotRoot} file list differs from ${ref}:${sourceRoot}`);
|
throw new Error(`${snapshotRoot} file list differs from ${ref}:${sourceRoot}`);
|
||||||
|
|
@ -89,7 +89,7 @@ export async function compareGitTree(git, ref, sourceRoot, snapshotRoot) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listSnapshotPaths(root) {
|
export async function listDocumentationPaths(root) {
|
||||||
const paths = [];
|
const paths = [];
|
||||||
async function walk(directory) {
|
async function walk(directory) {
|
||||||
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { afterEach, describe, expect, test } from 'bun:test';
|
import { afterEach, describe, expect, test } from 'bun:test';
|
||||||
import { execFileSync } from 'node:child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
import { mkdtemp, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises';
|
||||||
import { join, resolve } from 'node:path';
|
import { join, resolve } from 'node:path';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
|
|
||||||
|
|
@ -21,7 +21,8 @@ describe('documentation release publishing', () => {
|
||||||
await write(root, 'website/latest.json', '{"version":"0.9.0"}\n');
|
await write(root, 'website/latest.json', '{"version":"0.9.0"}\n');
|
||||||
await write(root, 'README.md', 'stable readme\n');
|
await write(root, 'README.md', 'stable readme\n');
|
||||||
await write(root, 'README.zh-CN.md', 'stable readme zh-cn\n');
|
await write(root, 'README.zh-CN.md', 'stable readme zh-cn\n');
|
||||||
await write(root, 'docs/next/website/src/content/docs/index.mdx', 'next docs\n');
|
const nextDocs = '---\ntitle: Documentation\n---\n\nnext docs\n';
|
||||||
|
await write(root, 'docs/next/website/src/content/docs/index.mdx', nextDocs);
|
||||||
await write(root, 'docs/next/website/src/data/config-reference.json', '{"next":true}\n');
|
await write(root, 'docs/next/website/src/data/config-reference.json', '{"next":true}\n');
|
||||||
await write(root, 'docs/next/README.md', 'next readme\n');
|
await write(root, 'docs/next/README.md', 'next readme\n');
|
||||||
await write(root, 'docs/next/README.zh-CN.md', 'next readme zh-cn\n');
|
await write(root, 'docs/next/README.zh-CN.md', 'next readme zh-cn\n');
|
||||||
|
|
@ -40,7 +41,7 @@ describe('documentation release publishing', () => {
|
||||||
await expect(read(root, 'website/src/data/config-reference.json')).rejects.toThrow();
|
await expect(read(root, 'website/src/data/config-reference.json')).rejects.toThrow();
|
||||||
expect(await read(root, 'README.md')).toBe('next readme\n');
|
expect(await read(root, 'README.md')).toBe('next readme\n');
|
||||||
expect(await read(root, 'README.zh-CN.md')).toBe('next readme zh-cn\n');
|
expect(await read(root, 'README.zh-CN.md')).toBe('next readme zh-cn\n');
|
||||||
expect(await read(root, 'docs/versions/1.0.0/website/src/content/docs/index.mdx')).toBe('next docs\n');
|
expect(await read(root, 'docs/versions/1.0.0/website/src/content/docs/index.mdx')).toBe(nextDocs);
|
||||||
|
|
||||||
const manifest = JSON.parse(await read(root, 'docs/versions/manifest.json'));
|
const manifest = JSON.parse(await read(root, 'docs/versions/manifest.json'));
|
||||||
expect(manifest.current).toBe('1.0.0');
|
expect(manifest.current).toBe('1.0.0');
|
||||||
|
|
@ -65,7 +66,19 @@ describe('documentation release publishing', () => {
|
||||||
`${JSON.stringify({ build_id: 'preview-test', commit: previewCommit })}\n`,
|
`${JSON.stringify({ build_id: 'preview-test', commit: previewCommit })}\n`,
|
||||||
);
|
);
|
||||||
runPrepare(root);
|
runPrepare(root);
|
||||||
expect(await read(root, 'website/src/content/docs/index.mdx')).toBe('next docs\n');
|
const stableDocs = await read(root, 'website/src/content/docs/index.mdx');
|
||||||
|
expect(stableDocs).toContain('next docs');
|
||||||
|
expect(stableDocs).toContain(
|
||||||
|
'editUrl: https://github.com/herdrdev/herdr/blob/master/docs/versions/1.0.0/website/src/content/docs/index.mdx',
|
||||||
|
);
|
||||||
|
const versionedDocs = await read(
|
||||||
|
root,
|
||||||
|
'website/src/content/docs/_versions/1.0.0/index.mdx',
|
||||||
|
);
|
||||||
|
expect(versionedDocs).toContain('next docs');
|
||||||
|
expect(versionedDocs).toContain(
|
||||||
|
'editUrl: https://github.com/herdrdev/herdr/blob/master/docs/versions/1.0.0/website/src/content/docs/index.mdx',
|
||||||
|
);
|
||||||
expect(await read(root, 'website/src/content/docs/preview/index.mdx')).toContain(
|
expect(await read(root, 'website/src/content/docs/preview/index.mdx')).toContain(
|
||||||
'Preview build `preview-test`',
|
'Preview build `preview-test`',
|
||||||
);
|
);
|
||||||
|
|
@ -74,10 +87,36 @@ describe('documentation release publishing', () => {
|
||||||
await write(root, 'website/latest.json', '{"version":"1.0.0"}\n');
|
await write(root, 'website/latest.json', '{"version":"1.0.0"}\n');
|
||||||
runScript(root, ['check']);
|
runScript(root, ['check']);
|
||||||
|
|
||||||
|
const correctedDocs = '---\ntitle: Documentation\n---\n\ncorrected docs\n';
|
||||||
|
await write(
|
||||||
|
root,
|
||||||
|
'docs/versions/1.0.0/website/src/content/docs/index.mdx',
|
||||||
|
correctedDocs,
|
||||||
|
);
|
||||||
|
await write(
|
||||||
|
root,
|
||||||
|
'docs/versions/1.0.0/website/src/data/config-reference.json',
|
||||||
|
'{"corrected":true}\n',
|
||||||
|
);
|
||||||
|
runScript(root, ['check']);
|
||||||
|
|
||||||
await write(root, 'README.md', 'post-release correction\n');
|
await write(root, 'README.md', 'post-release correction\n');
|
||||||
runScript(root, ['publish', 'v1.0.0']);
|
runScript(root, ['publish', 'v1.0.0']);
|
||||||
expect(await read(root, 'README.md')).toBe('post-release correction\n');
|
expect(await read(root, 'README.md')).toBe('post-release correction\n');
|
||||||
|
|
||||||
|
runPrepare(root);
|
||||||
|
expect(await read(root, 'website/src/content/docs/index.mdx')).toContain('corrected docs');
|
||||||
|
expect(await read(root, 'website/src/content/docs/_versions/1.0.0/index.mdx')).toContain(
|
||||||
|
'corrected docs',
|
||||||
|
);
|
||||||
|
expect(await read(root, 'website/src/data/config-reference.json')).toBe(
|
||||||
|
'{"corrected":true}\n',
|
||||||
|
);
|
||||||
|
const versionReferences = JSON.parse(
|
||||||
|
await read(root, 'website/src/data/config-reference-versions.json'),
|
||||||
|
);
|
||||||
|
expect(versionReferences['1.0.0']).toEqual({ corrected: true });
|
||||||
|
|
||||||
runScript(root, ['publish', 'v0.9.0']);
|
runScript(root, ['publish', 'v0.9.0']);
|
||||||
const archivedManifest = JSON.parse(await read(root, 'docs/versions/manifest.json'));
|
const archivedManifest = JSON.parse(await read(root, 'docs/versions/manifest.json'));
|
||||||
expect(archivedManifest.current).toBe('1.0.0');
|
expect(archivedManifest.current).toBe('1.0.0');
|
||||||
|
|
@ -85,6 +124,34 @@ describe('documentation release publishing', () => {
|
||||||
expect(await read(root, 'README.md')).toBe('post-release correction\n');
|
expect(await read(root, 'README.md')).toBe('post-release correction\n');
|
||||||
runScript(root, ['check']);
|
runScript(root, ['check']);
|
||||||
|
|
||||||
|
const correctedArchivedDocs = '---\ntitle: Documentation\n---\n\ncorrected archived docs\n';
|
||||||
|
await write(
|
||||||
|
root,
|
||||||
|
'docs/versions/0.9.0/website/src/content/docs/index.mdx',
|
||||||
|
correctedArchivedDocs,
|
||||||
|
);
|
||||||
|
runScript(root, ['publish', 'v0.9.0']);
|
||||||
|
expect(await read(root, 'docs/versions/0.9.0/website/src/content/docs/index.mdx')).toBe(
|
||||||
|
correctedArchivedDocs,
|
||||||
|
);
|
||||||
|
|
||||||
|
const archivedDocsRoot = resolve(
|
||||||
|
root,
|
||||||
|
'docs/versions/0.9.0/website/src/content/docs',
|
||||||
|
);
|
||||||
|
await rm(archivedDocsRoot, { recursive: true, force: true });
|
||||||
|
expect(() => runScript(root, ['check'])).toThrow();
|
||||||
|
await write(
|
||||||
|
root,
|
||||||
|
'docs/versions/0.9.0/website/src/content/docs/index.mdx',
|
||||||
|
correctedArchivedDocs,
|
||||||
|
);
|
||||||
|
const linkedPage = resolve(archivedDocsRoot, 'linked.mdx');
|
||||||
|
await symlink('index.mdx', linkedPage);
|
||||||
|
expect(() => runScript(root, ['check'])).toThrow();
|
||||||
|
await rm(linkedPage);
|
||||||
|
runScript(root, ['check']);
|
||||||
|
|
||||||
delete archivedManifest.versions[0].commit;
|
delete archivedManifest.versions[0].commit;
|
||||||
await write(root, 'docs/versions/manifest.json', `${JSON.stringify(archivedManifest)}\n`);
|
await write(root, 'docs/versions/manifest.json', `${JSON.stringify(archivedManifest)}\n`);
|
||||||
expect(() => runScript(root, ['check'])).toThrow();
|
expect(() => runScript(root, ['check'])).toThrow();
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
||||||
import { dirname, resolve } from 'node:path';
|
import { dirname, resolve } from 'node:path';
|
||||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||||
import {
|
import {
|
||||||
compareGitTree,
|
|
||||||
createGit,
|
createGit,
|
||||||
extractGitTree,
|
extractGitTree,
|
||||||
gitPathExists,
|
gitPathExists,
|
||||||
gitTreesEqual,
|
gitTreesEqual,
|
||||||
|
listDocumentationPaths,
|
||||||
resolveCommit,
|
resolveCommit,
|
||||||
} from './docs-snapshot.mjs';
|
} from './docs-snapshot.mjs';
|
||||||
|
|
||||||
|
|
@ -167,38 +167,26 @@ export async function checkVersions() {
|
||||||
if (!['website/src/content/docs', 'docs/next/website/src/content/docs'].includes(entry.source)) {
|
if (!['website/src/content/docs', 'docs/next/website/src/content/docs'].includes(entry.source)) {
|
||||||
throw new Error(`docs version ${entry.version} has unsupported source ${entry.source}`);
|
throw new Error(`docs version ${entry.version} has unsupported source ${entry.source}`);
|
||||||
}
|
}
|
||||||
const sourceRoot = entry.source;
|
|
||||||
const snapshotRoot = resolve(versionsDir, entry.version, 'website/src/content/docs');
|
|
||||||
await compareGitTree(git, entry.tag, sourceRoot, snapshotRoot);
|
|
||||||
|
|
||||||
const referenceSource = sourceRoot.includes('docs/next/')
|
const versionRoot = resolve(versionsDir, entry.version, 'website');
|
||||||
? 'docs/next/website/src/data/config-reference.json'
|
const documentationPaths = await listDocumentationPaths(
|
||||||
: 'website/src/data/config-reference.json';
|
resolve(versionRoot, 'src/content/docs'),
|
||||||
const referenceSnapshot = resolve(
|
|
||||||
versionsDir,
|
|
||||||
entry.version,
|
|
||||||
'website/src/data/config-reference.json',
|
|
||||||
);
|
);
|
||||||
if (gitPathExists(git, entry.tag, referenceSource)) {
|
if (!documentationPaths.includes('index.md') && !documentationPaths.includes('index.mdx')) {
|
||||||
const actualReference = await readFile(referenceSnapshot);
|
throw new Error(`docs version ${entry.version} has no root index`);
|
||||||
const taggedReference = git(['show', `${entry.tag}:${referenceSource}`], { binary: true });
|
}
|
||||||
if (!actualReference.equals(taggedReference)) {
|
|
||||||
throw new Error(`${entry.version} config reference differs from ${entry.tag}:${referenceSource}`);
|
try {
|
||||||
}
|
JSON.parse(await readFile(resolve(versionRoot, 'src/data/config-reference.json'), 'utf8'));
|
||||||
} else {
|
} catch (error) {
|
||||||
try {
|
if (error.code !== 'ENOENT') throw error;
|
||||||
await readFile(referenceSnapshot);
|
|
||||||
throw new Error(`${entry.version} has an unexpected config reference snapshot`);
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== 'ENOENT') throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!seen.has(manifest.current)) {
|
if (!seen.has(manifest.current)) {
|
||||||
throw new Error(`current docs version ${manifest.current} has no snapshot`);
|
throw new Error(`current docs version ${manifest.current} has no snapshot`);
|
||||||
}
|
}
|
||||||
process.stdout.write(`validated ${manifest.versions.length} documentation snapshots\n`);
|
process.stdout.write(`validated ${manifest.versions.length} documentation versions\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function publishVersion(tag) {
|
export async function publishVersion(tag) {
|
||||||
|
|
@ -217,8 +205,7 @@ export async function publishVersion(tag) {
|
||||||
if (!existing?.commit || existing.commit !== release.commit) {
|
if (!existing?.commit || existing.commit !== release.commit) {
|
||||||
throw new Error(`published documentation ${release.version} has mismatched commit provenance`);
|
throw new Error(`published documentation ${release.version} has mismatched commit provenance`);
|
||||||
}
|
}
|
||||||
await compareGitTree(git, release.commit, existing.source, resolve(versionsDir, release.version, 'website/src/content/docs'));
|
process.stdout.write(`documentation ${release.tag} is already published\n`);
|
||||||
process.stdout.write(`documentation snapshot ${release.tag} is already published\n`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (manifest.current && compareVersions(release.version, manifest.current) < 0) {
|
if (manifest.current && compareVersions(release.version, manifest.current) < 0) {
|
||||||
|
|
@ -229,13 +216,7 @@ export async function publishVersion(tag) {
|
||||||
if (existing.commit && existing.commit !== release.commit) {
|
if (existing.commit && existing.commit !== release.commit) {
|
||||||
throw new Error(`version ${release.version} has mismatched commit provenance`);
|
throw new Error(`version ${release.version} has mismatched commit provenance`);
|
||||||
}
|
}
|
||||||
await compareGitTree(
|
process.stdout.write(`documentation ${release.tag} is already archived\n`);
|
||||||
git,
|
|
||||||
release.commit,
|
|
||||||
existing.source,
|
|
||||||
resolve(versionsDir, release.version, 'website/src/content/docs'),
|
|
||||||
);
|
|
||||||
process.stdout.write(`documentation snapshot ${release.tag} is already archived\n`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const archived = await snapshotTag(tag, 'docs/next/website/src/content/docs');
|
const archived = await snapshotTag(tag, 'docs/next/website/src/content/docs');
|
||||||
|
|
@ -251,7 +232,7 @@ export async function publishVersion(tag) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (existing) {
|
if (existing) {
|
||||||
throw new Error(`version ${release.version} already has a documentation snapshot`);
|
throw new Error(`version ${release.version} already has published documentation`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata = await snapshotTag(tag, 'docs/next/website/src/content/docs');
|
const metadata = await snapshotTag(tag, 'docs/next/website/src/content/docs');
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ if (process.argv[2] === '--rewrite-preview-doc-fixture') {
|
||||||
rewriteVersionDocContent(Buffer.concat(chunks).toString('utf8'), {
|
rewriteVersionDocContent(Buffer.concat(chunks).toString('utf8'), {
|
||||||
version: process.argv[3] ?? '0.7.5',
|
version: process.argv[3] ?? '0.7.5',
|
||||||
tag: `v${process.argv[3] ?? '0.7.5'}`,
|
tag: `v${process.argv[3] ?? '0.7.5'}`,
|
||||||
sourceRoot: 'docs/next/website/src/content/docs',
|
|
||||||
relativePath: 'index.mdx',
|
relativePath: 'index.mdx',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -118,8 +117,8 @@ async function prepareDocs({ draft }) {
|
||||||
stableDocsDir,
|
stableDocsDir,
|
||||||
(content, relativePath) =>
|
(content, relativePath) =>
|
||||||
rewriteStableDocContent(content, {
|
rewriteStableDocContent(content, {
|
||||||
|
version: currentEntry.version,
|
||||||
tag: currentEntry.tag,
|
tag: currentEntry.tag,
|
||||||
sourceRoot: currentEntry.source,
|
|
||||||
relativePath,
|
relativePath,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -162,7 +161,6 @@ async function prepareDocs({ draft }) {
|
||||||
rewriteVersionDocContent(content, {
|
rewriteVersionDocContent(content, {
|
||||||
version,
|
version,
|
||||||
tag: entry.tag,
|
tag: entry.tag,
|
||||||
sourceRoot: entry.source,
|
|
||||||
relativePath,
|
relativePath,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -269,24 +267,22 @@ export function rewritePreviewDocContent(
|
||||||
return insertPreviewNotice(withSourceLink, relativePath, { buildId, commit });
|
return insertPreviewNotice(withSourceLink, relativePath, { buildId, commit });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rewriteStableDocContent(content, { tag, sourceRoot, relativePath }) {
|
export function rewriteStableDocContent(content, { version, tag, relativePath }) {
|
||||||
const taggedContent = rewriteRepositoryLinks(content, tag);
|
const taggedContent = rewriteRepositoryLinks(content, tag);
|
||||||
return setGeneratedEditUrl(
|
return setGeneratedEditUrl(taggedContent, versionedDocSourceUrl(version, relativePath));
|
||||||
taggedContent,
|
|
||||||
`https://github.com/herdrdev/herdr/blob/${tag}/${sourceRoot}/${relativePath}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rewriteVersionDocContent(content, { version, tag, sourceRoot, relativePath }) {
|
export function rewriteVersionDocContent(content, { version, tag, relativePath }) {
|
||||||
const taggedContent = rewriteRepositoryLinks(
|
const taggedContent = rewriteRepositoryLinks(
|
||||||
content.replaceAll('/docs/', `/docs/${version}/`),
|
content.replaceAll('/docs/', `/docs/${version}/`),
|
||||||
tag,
|
tag,
|
||||||
);
|
);
|
||||||
const rewritten = rewriteRelativeDocPaths(taggedContent, 2);
|
const rewritten = rewriteRelativeDocPaths(taggedContent, 2);
|
||||||
return setGeneratedEditUrl(
|
return setGeneratedEditUrl(rewritten, versionedDocSourceUrl(version, relativePath));
|
||||||
rewritten,
|
}
|
||||||
`https://github.com/herdrdev/herdr/blob/${tag}/${sourceRoot}/${relativePath}`,
|
|
||||||
);
|
function versionedDocSourceUrl(version, relativePath) {
|
||||||
|
return `https://github.com/herdrdev/herdr/blob/master/docs/versions/${version}/website/src/content/docs/${relativePath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function rewriteRepositoryLinks(content, ref) {
|
function rewriteRepositoryLinks(content, ref) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue