From 6317810666c5c9b365fbe452f17476832ce4220b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Tue, 7 Apr 2026 01:13:22 +0800 Subject: [PATCH 1/2] Streamline release workflows and reuse GUI build steps --- .github/actions/build-gui-platform/action.yml | 58 +++++++ .github/workflows/build-gui-all.yml | 77 +++------ .github/workflows/release-cli.yml | 155 ++++++++++-------- .github/workflows/release-gui-linux.yml | 29 +--- .github/workflows/release-gui-macos.yml | 31 +--- .github/workflows/release-gui-win.yml | 28 +--- 6 files changed, 196 insertions(+), 182 deletions(-) create mode 100644 .github/actions/build-gui-platform/action.yml diff --git a/.github/actions/build-gui-platform/action.yml b/.github/actions/build-gui-platform/action.yml new file mode 100644 index 00000000..e6236a4f --- /dev/null +++ b/.github/actions/build-gui-platform/action.yml @@ -0,0 +1,58 @@ +name: Build GUI Platform +description: Setup, build, inspect, and upload Tauri GUI artifacts for one platform + +inputs: + version: + description: Version string to sync into Cargo.toml and tauri.conf.json + required: true + tauri-command: + description: Command used to build the GUI bundle + required: true + artifact-name: + description: Artifact name used by actions/upload-artifact + required: true + artifact-path: + description: Newline-separated artifact globs for upload-artifact + required: true + rust-targets: + description: Optional Rust targets to install (comma-separated) + required: false + default: "" + signing-private-key: + description: Tauri updater signing private key content + required: false + default: "" + signing-private-key-password: + description: Tauri updater signing private key password + required: false + default: "" + +runs: + using: composite + steps: + - uses: ./.github/actions/setup-node-pnpm + + - uses: ./.github/actions/setup-tauri + with: + rust-targets: ${{ inputs.rust-targets }} + signing-private-key: ${{ inputs.signing-private-key }} + signing-private-key-password: ${{ inputs.signing-private-key-password }} + version: ${{ inputs.version }} + + - name: Build GUI + shell: bash + working-directory: ./gui + run: ${{ inputs.tauri-command }} + + - name: List bundle output + shell: bash + run: | + echo "=== Finding all bundle artifacts ===" + find target -path '*/bundle/*' -type f 2>/dev/null || echo 'No bundle files found' + + - name: Upload artifacts + uses: actions/upload-artifact@v7 + with: + name: ${{ inputs.artifact-name }} + path: ${{ inputs.artifact-path }} + if-no-files-found: error diff --git a/.github/workflows/build-gui-all.yml b/.github/workflows/build-gui-all.yml index 72be4fd5..0d414bbf 100644 --- a/.github/workflows/build-gui-all.yml +++ b/.github/workflows/build-gui-all.yml @@ -17,70 +17,45 @@ jobs: matrix: include: - os: windows-latest - platform: windows rust-targets: '' tauri-command: pnpm tauri build + artifact-path: | + target/*/release/bundle/**/*.exe + target/release/bundle/**/*.exe + target/*/release/bundle/**/*.sig + target/release/bundle/**/*.sig - os: ubuntu-24.04 - platform: linux rust-targets: '' tauri-command: pnpm tauri build + artifact-path: | + target/*/release/bundle/**/*.AppImage + target/*/release/bundle/**/*.deb + target/*/release/bundle/**/*.rpm + target/*/release/bundle/**/*.sig + target/release/bundle/**/*.AppImage + target/release/bundle/**/*.deb + target/release/bundle/**/*.rpm + target/release/bundle/**/*.sig - os: macos-14 - platform: macos rust-targets: aarch64-apple-darwin,x86_64-apple-darwin tauri-command: pnpm tauri build --target universal-apple-darwin + artifact-path: | + target/*/release/bundle/**/*.dmg + target/*/release/bundle/**/*.tar.gz + target/*/release/bundle/**/*.sig + target/release/bundle/**/*.dmg + target/release/bundle/**/*.tar.gz + target/release/bundle/**/*.sig runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - - uses: ./.github/actions/setup-node-pnpm - - uses: ./.github/actions/setup-tauri + + - uses: ./.github/actions/build-gui-platform with: + tauri-command: ${{ matrix.tauri-command }} + artifact-name: gui-${{ matrix.os }} + artifact-path: ${{ matrix.artifact-path }} rust-targets: ${{ matrix.rust-targets }} signing-private-key: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} signing-private-key-password: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} version: ${{ inputs.version }} - - - name: Build GUI - working-directory: ./gui - run: ${{ matrix.tauri-command }} - - - name: Upload Windows artifacts - if: matrix.platform == 'windows' - uses: actions/upload-artifact@v7 - with: - name: gui-${{ matrix.os }} - path: | - target/*/release/bundle/**/*.exe - target/release/bundle/**/*.exe - target/*/release/bundle/**/*.sig - target/release/bundle/**/*.sig - if-no-files-found: error - - - name: Upload Linux artifacts - if: matrix.platform == 'linux' - uses: actions/upload-artifact@v7 - with: - name: gui-${{ matrix.os }} - path: | - target/*/release/bundle/**/*.AppImage - target/*/release/bundle/**/*.deb - target/*/release/bundle/**/*.rpm - target/*/release/bundle/**/*.sig - target/release/bundle/**/*.AppImage - target/release/bundle/**/*.deb - target/release/bundle/**/*.rpm - target/release/bundle/**/*.sig - if-no-files-found: error - - - name: Upload macOS artifacts - if: matrix.platform == 'macos' - uses: actions/upload-artifact@v7 - with: - name: gui-${{ matrix.os }} - path: | - target/*/release/bundle/**/*.dmg - target/*/release/bundle/**/*.tar.gz - target/*/release/bundle/**/*.sig - target/release/bundle/**/*.dmg - target/release/bundle/**/*.tar.gz - target/release/bundle/**/*.sig - if-no-files-found: error diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 4ffa3d63..d1a7aa87 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -43,46 +43,41 @@ permissions: contents: read jobs: - # 1. 版本检查(快速,决定是否继续) - check-version: + # 1. 发布前检查(快速,决定是否继续) + preflight: runs-on: ubuntu-24.04 timeout-minutes: 10 outputs: - publish_cli: ${{ steps.check.outputs.publish_cli }} - publish_mcp: ${{ steps.check.outputs.publish_mcp }} - publish_npm: ${{ steps.check.outputs.publish_npm }} - version: ${{ steps.check.outputs.version }} + publish_cli: ${{ steps.npm-check.outputs.publish_cli }} + publish_mcp: ${{ steps.npm-check.outputs.publish_mcp }} + publish_npm: ${{ steps.npm-check.outputs.publish_npm }} + version: ${{ steps.npm-check.outputs.version }} + gui_should_release: ${{ steps.gui-check.outputs.should_release }} + gui_version: ${{ steps.gui-check.outputs.version }} steps: - uses: actions/checkout@v6 + - uses: ./.github/actions/setup-node-pnpm with: cache: "false" install: "false" - - name: Check if should publish - id: check + + - name: Check npm packages release state + id: npm-check uses: ./.github/actions/check-release-state with: registry-url: ${{ env.NPM_REGISTRY_URL }} - # 1.5. GUI 版本检查(独立于 npm,检查 GitHub Release) - check-gui-version: - runs-on: ubuntu-24.04 - timeout-minutes: 10 - outputs: - should_release: ${{ steps.check.outputs.should_release }} - version: ${{ steps.check.outputs.version }} - steps: - - uses: actions/checkout@v6 - - name: Check if GUI should be released - id: check + - name: Check GUI release state + id: gui-check uses: ./.github/actions/check-gui-release-state with: github-token: ${{ github.token }} # 2. 构建 NAPI 二进制(5 平台矩阵) build-napi: - needs: check-version - if: needs.check-version.outputs.publish_cli == 'true' + needs: preflight + if: needs.preflight.outputs.publish_cli == 'true' timeout-minutes: 45 strategy: fail-fast: false @@ -173,8 +168,8 @@ jobs: # 3. 收集并发布 NAPI 平台子包到 npm publish-napi: - needs: [check-version, build-napi] - if: needs.check-version.outputs.publish_cli == 'true' + needs: [preflight, build-napi] + if: needs.preflight.outputs.publish_cli == 'true' runs-on: ubuntu-24.04 timeout-minutes: 45 steps: @@ -285,64 +280,95 @@ jobs: registry-url: ${{ env.NPM_REGISTRY_URL }} package-dir: cli/npm - # 4. 架构包就绪后,发布主包到 npm - publish-cli: - needs: [check-version, publish-napi] - if: needs.check-version.outputs.publish_cli == 'true' + # 4. 架构包就绪后,顺序发布 npm 包 + publish-npm-packages: + needs: [preflight, publish-napi] + if: | + needs.preflight.outputs.publish_npm == 'true' && + (needs.publish-napi.result == 'success' || needs.publish-napi.result == 'skipped') runs-on: ubuntu-24.04 - timeout-minutes: 30 + timeout-minutes: 45 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-node-pnpm with: registry-url: https://registry.npmjs.org/ - - uses: ./.github/actions/npm-auth-preflight + + - name: Preflight CLI publish + if: needs.preflight.outputs.publish_cli == 'true' + uses: ./.github/actions/npm-auth-preflight with: npm-token: ${{ secrets.NPM_TOKEN }} registry-url: ${{ env.NPM_REGISTRY_URL }} package-dir: cli package-name: "@truenine/memory-sync-cli" - - name: Build + + - name: Build CLI + if: needs.preflight.outputs.publish_cli == 'true' run: pnpm -F @truenine/memory-sync-cli run build - - uses: ./.github/actions/npm-publish-package + + - name: Publish CLI + if: needs.preflight.outputs.publish_cli == 'true' + uses: ./.github/actions/npm-publish-package with: npm-token: ${{ secrets.NPM_TOKEN }} registry-url: ${{ env.NPM_REGISTRY_URL }} package-dir: cli - # 4.5. CLI 可用后,发布 MCP 包到 npm - publish-mcp: - needs: [check-version, publish-cli] - if: | - needs.check-version.outputs.publish_mcp == 'true' && - (needs.publish-cli.result == 'success' || needs.publish-cli.result == 'skipped') - runs-on: ubuntu-24.04 - timeout-minutes: 30 - steps: - - uses: actions/checkout@v6 - - uses: ./.github/actions/setup-node-pnpm - with: - registry-url: https://registry.npmjs.org/ - - uses: ./.github/actions/npm-auth-preflight + - name: Preflight MCP publish + if: needs.preflight.outputs.publish_mcp == 'true' + uses: ./.github/actions/npm-auth-preflight with: npm-token: ${{ secrets.NPM_TOKEN }} registry-url: ${{ env.NPM_REGISTRY_URL }} package-dir: mcp package-name: "@truenine/memory-sync-mcp" - - name: Build + + - name: Build MCP + if: needs.preflight.outputs.publish_mcp == 'true' run: pnpm exec turbo run build --filter=@truenine/memory-sync-mcp - - uses: ./.github/actions/npm-publish-package + + - name: Publish MCP + if: needs.preflight.outputs.publish_mcp == 'true' + uses: ./.github/actions/npm-publish-package with: npm-token: ${{ secrets.NPM_TOKEN }} registry-url: ${{ env.NPM_REGISTRY_URL }} package-dir: mcp - # 5. 构建 CLI 独立二进制(仅 artifact,不发 Release) + # 5. 只构建一次 CLI runtime,供二进制矩阵复用 + prepare-cli-runtime: + needs: preflight + if: | + needs.preflight.outputs.publish_cli == 'true' || + needs.preflight.outputs.gui_should_release == 'true' + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/setup-node-pnpm + - uses: ./.github/actions/setup-rust + with: + cache-key: cli-runtime + - name: Build plugin-runtime + shell: bash + run: | + pnpm -F @truenine/memory-sync-cli run build + ls -la cli/dist/plugin-runtime.mjs + - name: Upload plugin-runtime + uses: actions/upload-artifact@v7 + with: + name: cli-plugin-runtime + path: cli/dist/plugin-runtime.mjs + if-no-files-found: error + + # 6. 构建 CLI 独立二进制(仅 artifact,不发 Release) build-binary: - needs: [check-version, check-gui-version, publish-napi] + needs: [preflight, publish-napi, prepare-cli-runtime] if: | - (needs.check-version.outputs.publish_cli == 'true' || needs.check-gui-version.outputs.should_release == 'true') && - (needs.publish-napi.result == 'success' || needs.publish-napi.result == 'skipped') + (needs.preflight.outputs.publish_cli == 'true' || needs.preflight.outputs.gui_should_release == 'true') && + (needs.publish-napi.result == 'success' || needs.publish-napi.result == 'skipped') && + needs.prepare-cli-runtime.result == 'success' timeout-minutes: 60 strategy: fail-fast: false @@ -372,12 +398,11 @@ jobs: runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v6 - - uses: ./.github/actions/setup-node-pnpm - - name: Build plugin-runtime - shell: bash - run: | - pnpm -F @truenine/memory-sync-cli run build - ls -la cli/dist/plugin-runtime.mjs + - name: Download plugin-runtime + uses: actions/download-artifact@v8 + with: + name: cli-plugin-runtime + path: cli/dist - uses: ./.github/actions/setup-rust with: targets: ${{ matrix.target }} @@ -414,22 +439,22 @@ jobs: path: ${{ matrix.archive }} if-no-files-found: error - # 6. 构建 GUI — 三平台并行(fail-fast: 任一失败则全部取消) + # 7. 构建 GUI — 三平台并行(fail-fast: 任一失败则全部取消) build-gui-all: - needs: [check-gui-version] - if: needs.check-gui-version.outputs.should_release == 'true' + needs: [preflight] + if: needs.preflight.outputs.gui_should_release == 'true' uses: ./.github/workflows/build-gui-all.yml with: - version: ${{ needs.check-gui-version.outputs.version }} + version: ${{ needs.preflight.outputs.gui_version }} secrets: inherit - # 7. 收集三平台产物,创建 GitHub Release + tag + # 8. 收集三平台产物,创建 GitHub Release + tag release-gui-collect: - needs: [check-gui-version, build-gui-all, build-binary] - if: needs.check-gui-version.outputs.should_release == 'true' + needs: [preflight, build-gui-all, build-binary] + if: needs.preflight.outputs.gui_should_release == 'true' permissions: contents: write uses: ./.github/workflows/release-gui-collect.yml with: - version: ${{ needs.check-gui-version.outputs.version }} + version: ${{ needs.preflight.outputs.gui_version }} secrets: inherit diff --git a/.github/workflows/release-gui-linux.yml b/.github/workflows/release-gui-linux.yml index 785a39e8..f411cebc 100644 --- a/.github/workflows/release-gui-linux.yml +++ b/.github/workflows/release-gui-linux.yml @@ -17,29 +17,12 @@ jobs: timeout-minutes: 45 steps: - uses: actions/checkout@v6 - - uses: ./.github/actions/setup-node-pnpm - - uses: ./.github/actions/setup-tauri + - uses: ./.github/actions/build-gui-platform with: - signing-private-key: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - signing-private-key-password: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - version: ${{ inputs.version }} - - - name: Build GUI - working-directory: ./gui - run: pnpm tauri build - - - name: List bundle output (debug) - shell: bash - run: | - echo "=== Finding all bundle artifacts ===" - find target -path '*/bundle/*' -type f \( -name '*.AppImage' -o -name '*.deb' -o -name '*.rpm' -o -name '*.sig' \) 2>/dev/null || echo 'No bundle files found' - - - name: Upload artifacts - uses: actions/upload-artifact@v7 - with: - name: debug-gui-ubuntu-24.04 - path: | + tauri-command: pnpm tauri build + artifact-name: debug-gui-ubuntu-24.04 + artifact-path: | target/*/release/bundle/**/*.AppImage target/release/bundle/**/*.AppImage target/*/release/bundle/**/*.deb @@ -48,4 +31,6 @@ jobs: target/release/bundle/**/*.rpm target/*/release/bundle/**/*.sig target/release/bundle/**/*.sig - if-no-files-found: error + signing-private-key: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + signing-private-key-password: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + version: ${{ inputs.version }} diff --git a/.github/workflows/release-gui-macos.yml b/.github/workflows/release-gui-macos.yml index 22d34466..21fa2042 100644 --- a/.github/workflows/release-gui-macos.yml +++ b/.github/workflows/release-gui-macos.yml @@ -17,33 +17,18 @@ jobs: timeout-minutes: 60 steps: - uses: actions/checkout@v6 - - uses: ./.github/actions/setup-node-pnpm - - uses: ./.github/actions/setup-tauri + - uses: ./.github/actions/build-gui-platform with: - rust-targets: aarch64-apple-darwin,x86_64-apple-darwin - signing-private-key: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - signing-private-key-password: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - version: ${{ inputs.version }} - - - name: Build GUI - working-directory: ./gui - run: pnpm tauri build --target universal-apple-darwin - - - name: List bundle output (debug) - shell: bash - run: | - echo "=== Finding all bundle artifacts ===" - find target -path '*/bundle/*' -type f \( -name '*.dmg' -o -name '*.tar.gz' -o -name '*.sig' \) 2>/dev/null || echo 'No bundle files found' - - - name: Upload artifacts - uses: actions/upload-artifact@v7 - with: - name: debug-gui-macos-14 - path: | + tauri-command: pnpm tauri build --target universal-apple-darwin + artifact-name: debug-gui-macos-14 + artifact-path: | target/*/release/bundle/**/*.dmg target/*/release/bundle/**/*.tar.gz target/*/release/bundle/**/*.sig target/release/bundle/**/*.dmg target/release/bundle/**/*.tar.gz target/release/bundle/**/*.sig - if-no-files-found: error + rust-targets: aarch64-apple-darwin,x86_64-apple-darwin + signing-private-key: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + signing-private-key-password: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + version: ${{ inputs.version }} diff --git a/.github/workflows/release-gui-win.yml b/.github/workflows/release-gui-win.yml index e06e8031..570fa2cf 100644 --- a/.github/workflows/release-gui-win.yml +++ b/.github/workflows/release-gui-win.yml @@ -17,30 +17,16 @@ jobs: timeout-minutes: 45 steps: - uses: actions/checkout@v6 - - uses: ./.github/actions/setup-node-pnpm - - uses: ./.github/actions/setup-tauri - with: - signing-private-key: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - signing-private-key-password: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - version: ${{ inputs.version }} - - - name: Build GUI - working-directory: ./gui - run: pnpm tauri build - - name: List bundle output (debug) - shell: bash - run: | - echo "=== Finding all bundle artifacts ===" - find target -path '*/bundle/*' -type f \( -name '*.exe' -o -name '*.sig' \) 2>/dev/null || echo 'No bundle files found' - - - name: Upload artifacts - uses: actions/upload-artifact@v7 + - uses: ./.github/actions/build-gui-platform with: - name: debug-gui-windows-latest - path: | + tauri-command: pnpm tauri build + artifact-name: debug-gui-windows-latest + artifact-path: | target/*/release/bundle/**/*.exe target/*/release/bundle/**/*.sig target/release/bundle/**/*.exe target/release/bundle/**/*.sig - if-no-files-found: error + signing-private-key: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + signing-private-key-password: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + version: ${{ inputs.version }} From d33ed347197316d97e6aeafeedfec76a5ce1e6f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Wed, 8 Apr 2026 23:00:11 +0800 Subject: [PATCH 2/2] Add base path support for docs site --- doc/app/docs/[[...mdxPath]]/layout.tsx | 4 +- doc/app/layout.tsx | 10 ++--- doc/app/manifest.ts | 3 +- doc/app/not-found.tsx | 11 ++--- doc/app/robots.ts | 4 +- doc/app/sitemap.ts | 5 +-- doc/lib/site.ts | 33 +++++++++++++- doc/next.config.ts | 22 ++++++++- doc/package.json | 10 ++--- doc/scripts/run-next.ts | 47 ++++++++++++++++++++ sdk/src/plugins/ClaudeCodeCLIOutputPlugin.ts | 7 ++- 11 files changed, 128 insertions(+), 28 deletions(-) create mode 100644 doc/scripts/run-next.ts diff --git a/doc/app/docs/[[...mdxPath]]/layout.tsx b/doc/app/docs/[[...mdxPath]]/layout.tsx index a9f8afce..50c1732d 100644 --- a/doc/app/docs/[[...mdxPath]]/layout.tsx +++ b/doc/app/docs/[[...mdxPath]]/layout.tsx @@ -3,7 +3,7 @@ import {Layout, Navbar} from 'nextra-theme-docs' import {getPageMap} from 'nextra/page-map' import {DocsSectionNav} from '../../../components/docs-section-nav' import {isDocSectionName} from '../../../lib/docs-sections' -import {siteConfig} from '../../../lib/site' +import {siteConfig, withBasePath} from '../../../lib/site' export default async function DocsLayout({ children, @@ -25,7 +25,7 @@ export default async function DocsLayout({ navbar={( Docs diff --git a/doc/app/layout.tsx b/doc/app/layout.tsx index cd34f4d1..4ee6c029 100644 --- a/doc/app/layout.tsx +++ b/doc/app/layout.tsx @@ -1,7 +1,7 @@ import type {Metadata} from 'next' import {Inter, JetBrains_Mono} from 'next/font/google' import React from 'react' -import {getSiteUrl, siteConfig} from '@/lib/site' +import {getSiteUrl, siteConfig, withBasePath} from '@/lib/site' import 'nextra-theme-docs/style.css' import './globals.scss' @@ -18,7 +18,7 @@ const mono = JetBrains_Mono({ }) export const metadata: Metadata = { - metadataBase: getSiteUrl(), + metadataBase: getSiteUrl('/'), title: { default: siteConfig.title, template: `%s | ${siteConfig.productName}` @@ -26,13 +26,13 @@ export const metadata: Metadata = { description: siteConfig.description, applicationName: siteConfig.shortName, alternates: { - canonical: '/' + canonical: withBasePath('/') }, category: 'developer tools', - manifest: '/manifest.webmanifest', + manifest: withBasePath('/manifest.webmanifest'), openGraph: { type: 'website', - url: '/', + url: withBasePath('/'), title: siteConfig.title, description: siteConfig.description, siteName: siteConfig.title, diff --git a/doc/app/manifest.ts b/doc/app/manifest.ts index 9fd19100..dd0aa773 100644 --- a/doc/app/manifest.ts +++ b/doc/app/manifest.ts @@ -1,11 +1,12 @@ import type {MetadataRoute} from 'next' +import {withBasePath} from '@/lib/site' export default function manifest(): MetadataRoute.Manifest { return { name: 'memory-sync 文档', short_name: 'memory-sync', description: 'Chinese-first manifesto-led docs for memory-sync.', - start_url: '/', + start_url: withBasePath('/'), display: 'standalone', background_color: '#090909', theme_color: '#cf5d29', diff --git a/doc/app/not-found.tsx b/doc/app/not-found.tsx index 31909777..ffd9b266 100644 --- a/doc/app/not-found.tsx +++ b/doc/app/not-found.tsx @@ -1,4 +1,5 @@ import Link from 'next/link' +import {withBasePath} from '@/lib/site' export default function NotFound() { return ( @@ -6,11 +7,11 @@ export default function NotFound() {

404 / ROUTE NOT FOUND

你摸到了一条不存在的通道。

- 当前文档已经重构为首页 ` - / - ` 加文档命名空间 ` - /docs/* - `。 + 当前文档已经重构为首页 + {withBasePath('/')} + 加文档命名空间 + {withBasePath('/docs/*')} + 。 如果你是从旧链接跳来的,很可能命中了已经移除的平铺 MDX 路径。

diff --git a/doc/app/robots.ts b/doc/app/robots.ts index 5ad9d4cc..fdc5cfe0 100644 --- a/doc/app/robots.ts +++ b/doc/app/robots.ts @@ -2,13 +2,11 @@ import type {MetadataRoute} from 'next' import {getSiteUrl} from '@/lib/site' export default function robots(): MetadataRoute.Robots { - const siteUrl = getSiteUrl() - return { rules: { userAgent: '*', allow: '/' }, - sitemap: new URL('/sitemap.xml', siteUrl).toString() + sitemap: getSiteUrl('/sitemap.xml').toString() } } diff --git a/doc/app/sitemap.ts b/doc/app/sitemap.ts index 9c782fd3..b9da0a24 100644 --- a/doc/app/sitemap.ts +++ b/doc/app/sitemap.ts @@ -27,13 +27,12 @@ async function findMdxFiles(directory: string): Promise { export default async function sitemap(): Promise { const contentDir = path.join(process.cwd(), 'content') - const siteUrl = getSiteUrl() const files = await findMdxFiles(contentDir) const routes = files.map(file => { const route = routeFromContentFile(file).replace(TRAILING_SLASHES_PATTERN, '') || '/docs' return { - url: new URL(route, siteUrl).toString(), + url: getSiteUrl(route).toString(), changeFrequency: 'weekly' as const, priority: route === '/docs' ? 0.9 : 0.7 } @@ -41,7 +40,7 @@ export default async function sitemap(): Promise { return [ { - url: new URL('/', siteUrl).toString(), + url: getSiteUrl('/').toString(), changeFrequency: 'weekly', priority: 1 }, diff --git a/doc/lib/site.ts b/doc/lib/site.ts index 19769e95..c3b3878a 100644 --- a/doc/lib/site.ts +++ b/doc/lib/site.ts @@ -11,8 +11,37 @@ export const siteConfig = { issueUrl: 'https://github.com/TrueNine/memory-sync/issues/new/choose' } as const -export function getSiteUrl(): URL { - return new URL(process.env.NEXT_PUBLIC_DOCS_SITE_URL ?? 'http://localhost:3000') +function normalizeBasePath(rawBasePath: string | undefined): string { + if (rawBasePath == null) { + return '' + } + + const trimmed = rawBasePath.trim() + + if (trimmed === '' || trimmed === '/') { + return '' + } + + return `/${trimmed.replaceAll(/^\/+|\/+$/gu, '')}` +} + +export function getBasePath(): string { + return normalizeBasePath(process.env.NEXT_PUBLIC_DOCS_BASE_PATH ?? process.env.DOCS_BASE_PATH) +} + +export function withBasePath(route: string): string { + const normalizedRoute = route === '' ? '/' : route.startsWith('/') ? route : `/${route}` + const basePath = getBasePath() + + if (normalizedRoute === '/') { + return basePath === '' ? '/' : `${basePath}/` + } + + return `${basePath}${normalizedRoute}` +} + +export function getSiteUrl(route = '/'): URL { + return new URL(withBasePath(route), process.env.NEXT_PUBLIC_DOCS_SITE_URL ?? 'http://localhost:3000') } export const heroProofPoints = [ diff --git a/doc/next.config.ts b/doc/next.config.ts index d5c42521..850b3307 100644 --- a/doc/next.config.ts +++ b/doc/next.config.ts @@ -1,5 +1,6 @@ import type {NextConfig} from 'next' import {readFileSync} from 'node:fs' +import process from 'node:process' import nextra from 'nextra' const mermaidAliasPath = '@/components/mermaid' @@ -45,6 +46,20 @@ const LEGACY_DOC_REDIRECTS = [ const DOC_PACKAGE_JSON_PATH = new URL('./package.json', import.meta.url) +function normalizeBasePath(rawBasePath: string | undefined): string { + if (rawBasePath == null) { + return '' + } + + const trimmed = rawBasePath.trim() + + if (trimmed === '' || trimmed === '/') { + return '' + } + + return `/${trimmed.replaceAll(/^\/+|\/+$/gu, '')}` +} + function readDocsVersion() { const packageJson = JSON.parse(readFileSync(DOC_PACKAGE_JSON_PATH, 'utf8')) as { version?: string @@ -58,10 +73,15 @@ function readDocsVersion() { } const docsVersion = readDocsVersion() +const docsBasePath = normalizeBasePath(process.env.DOCS_BASE_PATH ?? process.env.NEXT_PUBLIC_DOCS_BASE_PATH) const nextConfig: NextConfig = { + ...docsBasePath !== '' && { + basePath: docsBasePath + }, env: { - NEXT_PUBLIC_MEMORY_SYNC_VERSION: docsVersion + NEXT_PUBLIC_MEMORY_SYNC_VERSION: docsVersion, + NEXT_PUBLIC_DOCS_BASE_PATH: docsBasePath }, reactStrictMode: true, pageExtensions: ['tsx', 'ts', 'mdx'], diff --git a/doc/package.json b/doc/package.json index 20f8a76e..30bc169f 100644 --- a/doc/package.json +++ b/doc/package.json @@ -7,14 +7,14 @@ "node": ">=22" }, "scripts": { - "dev": "next dev", - "build": "pnpm run validate:content && next build", + "dev": "tsx scripts/run-next.ts dev", + "build": "tsx scripts/validate-content.ts && tsx scripts/run-next.ts build", "postbuild": "tsx scripts/run-pagefind.ts", "check": "run-p lint check:type", "validate:content": "tsx scripts/validate-content.ts", - "check:type": "next typegen && tsc --project tsconfig.typecheck.json --noEmit --incremental false", - "start": "next start", - "lint": "pnpm run validate:content && eslint --cache --cache-location .eslintcache ." + "check:type": "tsx scripts/run-next.ts typegen && tsc --project tsconfig.typecheck.json --noEmit --incremental false", + "start": "tsx scripts/run-next.ts start", + "lint": "tsx scripts/validate-content.ts && eslint --cache --cache-location .eslintcache ." }, "dependencies": { "@theguild/remark-mermaid": "catalog:", diff --git a/doc/scripts/run-next.ts b/doc/scripts/run-next.ts new file mode 100644 index 00000000..3691a849 --- /dev/null +++ b/doc/scripts/run-next.ts @@ -0,0 +1,47 @@ +import {spawn} from 'node:child_process' +import {createRequire} from 'node:module' +import process from 'node:process' + +const NODE_OPTIONS_SPLIT_PATTERN = /\s+/u + +const require = createRequire(import.meta.url) +const nextBinPath = require.resolve('next/dist/bin/next') +const nextArgs = process.argv.slice(2) +const nodeOptions = process.env.NODE_OPTIONS ?? '' + +function hasNodeOption(optionName: string): boolean { + return nodeOptions.split(NODE_OPTIONS_SPLIT_PATTERN).some( + option => option === optionName || option.startsWith(`${optionName}=`) + ) +} + +const shouldDisableWebStorage = !hasNodeOption('--experimental-webstorage') + && !hasNodeOption('--no-experimental-webstorage') + && !hasNodeOption('--webstorage') + && !hasNodeOption('--no-webstorage') + && !hasNodeOption('--localstorage-file') + +const mergedNodeOptions = shouldDisableWebStorage + ? `--no-experimental-webstorage ${nodeOptions}`.trim() + : nodeOptions + +const child = spawn( + process.execPath, + [nextBinPath, ...nextArgs], + { + stdio: 'inherit', + env: { + ...process.env, + NODE_OPTIONS: mergedNodeOptions + } + } +) + +child.on('exit', (code, signal) => { + if (signal != null) { + process.kill(process.pid, signal) + return + } + + process.exit(code ?? 0) +}) diff --git a/sdk/src/plugins/ClaudeCodeCLIOutputPlugin.ts b/sdk/src/plugins/ClaudeCodeCLIOutputPlugin.ts index 664cfa14..a050d696 100644 --- a/sdk/src/plugins/ClaudeCodeCLIOutputPlugin.ts +++ b/sdk/src/plugins/ClaudeCodeCLIOutputPlugin.ts @@ -38,7 +38,12 @@ export class ClaudeCodeCLIOutputPlugin extends AbstractOutputPlugin { sourceScopes: ['project'], includePrefix: true, linkSymbol: '-', - ext: '.md' + ext: '.md', + transformFrontMatter: (agent, context) => ({ + name: agent.canonicalName, + description: doubleQuoted(context.sourceFrontMatter?.description ?? ''), + memory: 'project' + }) }, skills: { subDir: SKILLS_SUBDIR