From a899822e9983dfdd0759a68210bad3e09fca74b5 Mon Sep 17 00:00:00 2001 From: Jayesh Bhole <54071350+jayeshbhole@users.noreply.github.com> Date: Tue, 1 Sep 2026 10:30:13 +0530 Subject: [PATCH 1/7] fix: redirect the 225 dead URLs left by the IA revamp Adds one exact rule per dead URL recovered from the Git history, deletes the SPA_FALLBACK list that made 81 of them return 200, and moves the resolver into resolve-redirect.js so server.mjs, the vocs dev middleware and CI all agree. Adds npm run check:redirects as a CI gate. /modules/offramp and /smart-wallet/offramp stay 404 by design. --- .github/workflows/check-redirects.yml | 21 +++ package.json | 1 + redirects.config.js | 261 +++++++++++++++++++++++++- resolve-redirect.d.ts | 3 + resolve-redirect.js | 23 +++ scripts/check-redirects.mjs | 75 ++++++++ server.mjs | 27 +-- vocs.config.tsx | 14 +- 8 files changed, 390 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/check-redirects.yml create mode 100644 resolve-redirect.d.ts create mode 100644 resolve-redirect.js create mode 100644 scripts/check-redirects.mjs diff --git a/.github/workflows/check-redirects.yml b/.github/workflows/check-redirects.yml new file mode 100644 index 00000000..07a87629 --- /dev/null +++ b/.github/workflows/check-redirects.yml @@ -0,0 +1,21 @@ +name: check-redirects + +on: + pull_request: + paths: + - "redirects.config.js" + - "resolve-redirect.js" + - "scripts/check-redirects.mjs" + - "docs/pages/**" + push: + branches: [main] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: node scripts/check-redirects.mjs diff --git a/package.json b/package.json index 5dd2a339..c29f06e0 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "build": "vocs build", "start": "node server.mjs", "preview": "vocs preview", + "check:redirects": "node scripts/check-redirects.mjs", "format": "prettier --write ." }, "dependencies": { diff --git a/redirects.config.js b/redirects.config.js index 442e4dfa..1d8d7ef7 100644 --- a/redirects.config.js +++ b/redirects.config.js @@ -130,7 +130,8 @@ export const redirects = [ // Magic Account (legacy) → Chain Abstraction. Was a manual Render redirect to // /sdk/advanced/chain-abstraction, itself now legacy; collapse the hop straight - // to the live page. (/magic-account/* subpaths are handled in render.yaml.) + // to the live page. Every known /magic-account/* subpath has its own rule + // below; the prefix rule in server.mjs catches anything unknown. { from: "/magic-account", to: "/smart-accounts/chain-abstraction/overview" }, // Embedded Wallet docs live at /wallets/* (alpha — unlisted in sidebars). @@ -281,4 +282,262 @@ export const redirects = [ { from: "/blog/zerodev-acquired", to: "https://www.zerodev.app/blogs/blog-zerodev-acquired" }, { from: "/blog/zerodev-glider", to: "https://www.zerodev.app/blogs/blog-zerodev-glider" }, { from: "/blog/zerodev-litprotocol", to: "https://www.zerodev.app/blogs/blog-zerodev-litprotocol" }, + + // 2026-06 IA revamp fallout: every URL below was live in a past build and is + // dead today. Recovered by walking the Git history of docs/pages; each target + // was picked by comparing the deleted page against the live tree and each one + // resolves to a page that exists. /modules/offramp and /smart-wallet/offramp + // are deliberately absent: both were one-line stubs with no successor, so they + // 404 rather than send a reader somewhere unrelated. + + // Legacy /sdk/* tree (deleted in the 2026-06 IA revamp) (61) + { from: "/sdk", to: "/get-started/sdks/overview" }, + { from: "/sdk/advanced/magic-address", to: "/onramp/smart-routing-address" }, + { from: "/sdk/core-api/intro", to: "/onboarding/create-a-smart-account" }, + { from: "/sdk/core-api/pay-gas-in-erc20s", to: "/smart-accounts/pay-gas-with-erc20s" }, + { from: "/sdk/core-api/session-keys", to: "/smart-accounts/permissions/session-keys" }, + { from: "/sdk/core-api/sign-and-validate-messages", to: "/smart-accounts/sign-and-verify" }, + { from: "/sdk/core-api/wallet-connect", to: "/advanced/wallet-connect" }, + { from: "/sdk/faqs/why-chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, + { from: "/sdk/getting-started/wallet-connect", to: "/advanced/wallet-connect" }, + { from: "/sdk/infra/stackup", to: "/api-and-toolings/infrastructure/choose-an-infra-provider" }, + { from: "/sdk/intro", to: "/" }, + { from: "/sdk/knowledge-base/capabilities", to: "https://www.zerodev.app/blogs/sdks-are-dead-long-live-capabilities" }, + { from: "/sdk/knowledge-base/chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, + { from: "/sdk/knowledge-base/permissions", to: "/smart-accounts/permissions/intro" }, + { from: "/sdk/knowledge-base/smart-account", to: "/onboarding/create-a-smart-account" }, + { from: "/sdk/permissions/getting-started", to: "/smart-accounts/permissions/1-click-trading" }, + { from: "/sdk/permissions/signers/intro", to: "/smart-accounts/permissions/intro" }, + { from: "/sdk/plugins/ecdsa", to: "/smart-accounts/permissions/signers/ecdsa" }, + { from: "/sdk/plugins/guardians", to: "/advanced/account-recovery/sdk-recovery" }, + { from: "/sdk/plugins/intro", to: "/smart-accounts/use-plugins/overview" }, + { from: "/sdk/plugins/multisig", to: "/advanced/multisig" }, + { from: "/sdk/plugins/passkey", to: "/onboarding/passkeys/overview" }, + { from: "/sdk/plugins/passkeys", to: "/onboarding/passkeys/overview" }, + { from: "/sdk/plugins/recovery", to: "/advanced/account-recovery/sdk-recovery" }, + { from: "/sdk/plugins/session-keys", to: "/smart-accounts/permissions/session-keys" }, + { from: "/sdk/plugins/weighted-ecdsa", to: "/advanced/multisig" }, + { from: "/sdk/quickstart", to: "/get-started/quickstart" }, + { from: "/sdk/setting-up-yi", to: "/onboarding/create-a-smart-account" }, + { from: "/sdk/setting-up-yi/auth/eoa", to: "/onboarding/eoa" }, + { from: "/sdk/setting-up-yi/auth/passkeys", to: "/onboarding/passkeys/overview" }, + { from: "/sdk/setting-up-yi/auth/social", to: "/onboarding/social-login" }, + { from: "/sdk/setting-up-yi/auth/third-party-signers", to: "/onboarding/auth-providers" }, + { from: "/sdk/setting-up-yi/chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, + { from: "/sdk/setting-up-yi/eoa", to: "/onboarding/eoa" }, + { from: "/sdk/setting-up-yi/passkeys", to: "/onboarding/passkeys/overview" }, + { from: "/sdk/setting-up-yi/social", to: "/onboarding/social-login" }, + { from: "/sdk/setting-up-yi/third-party-signers", to: "/onboarding/auth-providers" }, + { from: "/sdk/setup", to: "/onboarding/create-a-smart-account" }, + { from: "/sdk/setup/eoa", to: "/onboarding/eoa" }, + { from: "/sdk/setup/passkeys", to: "/onboarding/passkeys/overview" }, + { from: "/sdk/setup/social", to: "/onboarding/social-login" }, + { from: "/sdk/setup/third-party-signers", to: "/onboarding/auth-providers" }, + { from: "/sdk/shared/mutation-result", to: "/advanced/react-hooks/getting-started" }, + { from: "/sdk/shared/query-result", to: "/advanced/react-hooks/getting-started" }, + { from: "/sdk/signers/comparison", to: "/onboarding/auth-providers" }, + { from: "/sdk/usage", to: "/smart-accounts/send-transactions" }, + { from: "/sdk/usage/automating-transactions", to: "/smart-accounts/permissions/transaction-automation" }, + { from: "/sdk/usage/batching-transactions", to: "/smart-accounts/batch-transactions" }, + { from: "/sdk/usage/chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, + { from: "/sdk/usage/one-click-trading", to: "/smart-accounts/permissions/1-click-trading" }, + { from: "/sdk/usage/paying-gas-with-erc20", to: "/smart-accounts/pay-gas-with-erc20s" }, + { from: "/sdk/usage/sponsoring-gas", to: "/smart-accounts/sponsor-gas/evm" }, + { from: "/sdk/using-yi/automating-transactions", to: "/smart-accounts/permissions/transaction-automation" }, + { from: "/sdk/using-yi/basic-usage", to: "/smart-accounts/send-transactions" }, + { from: "/sdk/using-yi/batching-transactions", to: "/smart-accounts/batch-transactions" }, + { from: "/sdk/using-yi/chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, + { from: "/sdk/using-yi/one-click-trading", to: "/smart-accounts/permissions/1-click-trading" }, + { from: "/sdk/using-yi/paying-gas-with-erc20", to: "/smart-accounts/pay-gas-with-erc20s" }, + { from: "/sdk/using-yi/sponsoring-gas", to: "/smart-accounts/sponsor-gas/evm" }, + { from: "/sdk/why-chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, + { from: "/sdk/yi-vs-others", to: "/smart-accounts/chain-abstraction/overview" }, + + // kerneljs.com docs merged into this site (34) + { from: "/kerneljs/core-api/batch-transactions", to: "/smart-accounts/batch-transactions" }, + { from: "/kerneljs/core-api/create-account", to: "/onboarding/create-a-smart-account" }, + { from: "/kerneljs/core-api/delegatecall", to: "/smart-accounts/delegatecall" }, + { from: "/kerneljs/core-api/deploy-contract", to: "/smart-accounts/deploy-contract" }, + { from: "/kerneljs/core-api/intro", to: "/onboarding/create-a-smart-account" }, + { from: "/kerneljs/core-api/pay-gas-in-erc20s", to: "/smart-accounts/pay-gas-with-erc20s" }, + { from: "/kerneljs/core-api/send-transactions", to: "/smart-accounts/send-transactions" }, + { from: "/kerneljs/core-api/sign-and-validate-messages", to: "/smart-accounts/sign-and-verify" }, + { from: "/kerneljs/core-api/sponsor-gas", to: "/smart-accounts/sponsor-gas/evm" }, + { from: "/kerneljs/getting-started/intro", to: "/" }, + { from: "/kerneljs/getting-started/quickstart", to: "/get-started/quickstart" }, + { from: "/kerneljs/plugins/ecdsa", to: "/smart-accounts/permissions/signers/ecdsa" }, + { from: "/kerneljs/plugins/intro", to: "/smart-accounts/use-plugins/overview" }, + { from: "/kerneljs/plugins/multisig", to: "/advanced/multisig" }, + { from: "/kerneljs/plugins/passkey", to: "/onboarding/passkeys/overview" }, + { from: "/kerneljs/plugins/recovery", to: "/advanced/account-recovery/sdk-recovery" }, + { from: "/kerneljs/plugins/session-keys", to: "/smart-accounts/permissions/session-keys" }, + { from: "/kerneljs/presets/intro", to: "/api-and-toolings/presets/intro" }, + { from: "/kerneljs/presets/zerodev", to: "/api-and-toolings/presets/zerodev" }, + { from: "/kerneljs/signers/arcana", to: "/onboarding/arcana" }, + { from: "/kerneljs/signers/capsule", to: "/onboarding/capsule" }, + { from: "/kerneljs/signers/custom-signer", to: "/onboarding/custom-signer" }, + { from: "/kerneljs/signers/dfns", to: "/onboarding/dfns" }, + { from: "/kerneljs/signers/dynamic", to: "/onboarding/dynamic" }, + { from: "/kerneljs/signers/eoa", to: "/onboarding/eoa" }, + { from: "/kerneljs/signers/fireblocks", to: "/onboarding/fireblocks" }, + { from: "/kerneljs/signers/intro", to: "/onboarding/auth-providers" }, + { from: "/kerneljs/signers/lit-protocol", to: "/onboarding/lit-protocol" }, + { from: "/kerneljs/signers/magic", to: "/onboarding/magic" }, + { from: "/kerneljs/signers/particle", to: "/onboarding/particle" }, + { from: "/kerneljs/signers/portal", to: "/onboarding/portal" }, + { from: "/kerneljs/signers/privy", to: "/onboarding/privy" }, + { from: "/kerneljs/signers/turnkey", to: "/onboarding/turnkey" }, + { from: "/kerneljs/signers/web3auth", to: "/onboarding/web3auth" }, + + // /wallets/* pages moved or renamed (29) + { from: "/wallets/api-reference", to: "/wallets/wallet-api/send-transaction" }, + { from: "/wallets/auth", to: "/wallets/auth/email-otp" }, + { from: "/wallets/auth/wallet-ui-kit", to: "/wallets/auth/wallet-ui-kit/getting-started" }, + { from: "/wallets/hooks/export-web-view", to: "/wallets/react-native/web" }, + { from: "/wallets/hooks/use-authenticate-oauth", to: "/wallets/auth/google-oauth" }, + { from: "/wallets/hooks/use-authenticate-oauth-with-expo-web-browser", to: "/wallets/react-native/google-oauth" }, + { from: "/wallets/hooks/use-authenticators", to: "/wallets/quickstart" }, + { from: "/wallets/hooks/use-export-private-key", to: "/wallets/export" }, + { from: "/wallets/hooks/use-export-wallet", to: "/wallets/export" }, + { from: "/wallets/hooks/use-get-user-email", to: "/wallets/quickstart" }, + { from: "/wallets/hooks/use-login-passkey", to: "/wallets/auth/passkeys" }, + { from: "/wallets/hooks/use-refresh-session", to: "/wallets/session-management" }, + { from: "/wallets/hooks/use-register-passkey", to: "/wallets/auth/passkeys" }, + { from: "/wallets/hooks/use-send-magic-link", to: "/wallets/auth/magic-link" }, + { from: "/wallets/hooks/use-send-otp", to: "/wallets/auth/email-otp" }, + { from: "/wallets/hooks/use-verify-magic-link", to: "/wallets/auth/magic-link" }, + { from: "/wallets/hooks/use-verify-otp", to: "/wallets/auth/email-otp" }, + { from: "/wallets/overview", to: "/wallets" }, + { from: "/wallets/react-kit/configuration", to: "/wallets/auth/wallet-ui-kit/installation" }, + { from: "/wallets/react-kit/features/authentication", to: "/wallets/auth/wallet-ui-kit/connect-wallet" }, + { from: "/wallets/react-kit/features/transaction-signing", to: "/wallets/wallet-api/send-transaction" }, + { from: "/wallets/react-kit/getting-started", to: "/wallets/auth/wallet-ui-kit/getting-started" }, + { from: "/wallets/react-kit/hooks/use-auth", to: "/wallets/auth/wallet-ui-kit/getting-started" }, + { from: "/wallets/react-kit/hooks/use-pending-request", to: "/wallets/auth/wallet-ui-kit/coming-soon" }, + { from: "/wallets/sdk-reference", to: "/wallets/quickstart" }, + { from: "/wallets/shared/mutation-result", to: "/wallets/quickstart" }, + { from: "/wallets/shared/query-result", to: "/wallets/quickstart" }, + { from: "/wallets/wallet-api", to: "/wallets/wallet-api/send-transaction" }, + { from: "/wallets/wallet-api/sign-typed-message", to: "/wallets/wallet-api/sign-message" }, + + // /smart-accounts/embedded-wallet/* moved to /wallets/* (24) + { from: "/smart-accounts/embedded-wallet", to: "/wallets" }, + { from: "/smart-accounts/embedded-wallet/auth/email-otp", to: "/wallets/auth/email-otp" }, + { from: "/smart-accounts/embedded-wallet/auth/google-oauth", to: "/wallets/auth/google-oauth" }, + { from: "/smart-accounts/embedded-wallet/auth/magic-link", to: "/wallets/auth/magic-link" }, + { from: "/smart-accounts/embedded-wallet/auth/passkeys", to: "/wallets/auth/passkeys" }, + { from: "/smart-accounts/embedded-wallet/export", to: "/wallets/export" }, + { from: "/smart-accounts/embedded-wallet/hooks/use-authenticate-oauth", to: "/wallets/auth/google-oauth" }, + { from: "/smart-accounts/embedded-wallet/hooks/use-export-private-key", to: "/wallets/export" }, + { from: "/smart-accounts/embedded-wallet/hooks/use-export-wallet", to: "/wallets/export" }, + { from: "/smart-accounts/embedded-wallet/hooks/use-get-user-email", to: "/wallets/quickstart" }, + { from: "/smart-accounts/embedded-wallet/hooks/use-login-passkey", to: "/wallets/auth/passkeys" }, + { from: "/smart-accounts/embedded-wallet/hooks/use-refresh-session", to: "/wallets/session-management" }, + { from: "/smart-accounts/embedded-wallet/hooks/use-register-passkey", to: "/wallets/auth/passkeys" }, + { from: "/smart-accounts/embedded-wallet/hooks/use-send-magic-link", to: "/wallets/auth/magic-link" }, + { from: "/smart-accounts/embedded-wallet/hooks/use-send-otp", to: "/wallets/auth/email-otp" }, + { from: "/smart-accounts/embedded-wallet/hooks/use-verify-magic-link", to: "/wallets/auth/magic-link" }, + { from: "/smart-accounts/embedded-wallet/hooks/use-verify-otp", to: "/wallets/auth/email-otp" }, + { from: "/smart-accounts/embedded-wallet/quickstart", to: "/wallets/quickstart" }, + { from: "/smart-accounts/embedded-wallet/session-management", to: "/wallets/session-management" }, + { from: "/smart-accounts/embedded-wallet/shared/mutation-result", to: "/wallets/quickstart" }, + { from: "/smart-accounts/embedded-wallet/shared/query-result", to: "/wallets/quickstart" }, + { from: "/smart-accounts/embedded-wallet/wallet-api/send-transaction", to: "/wallets/wallet-api/send-transaction" }, + { from: "/smart-accounts/embedded-wallet/wallet-api/sign-message", to: "/wallets/wallet-api/sign-message" }, + { from: "/smart-accounts/embedded-wallet/wallet-api/sign-typed-message", to: "/wallets/wallet-api/sign-message" }, + + // Magic Account subpaths with a closer home than the overview (19) + { from: "/magic-account/knowledge-base/capabilities", to: "https://www.zerodev.app/blogs/sdks-are-dead-long-live-capabilities" }, + { from: "/magic-account/knowledge-base/chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, + { from: "/magic-account/knowledge-base/permissions", to: "/smart-accounts/permissions/intro" }, + { from: "/magic-account/knowledge-base/smart-account", to: "/onboarding/create-a-smart-account" }, + { from: "/magic-account/quickstart", to: "/get-started/quickstart" }, + { from: "/magic-account/setup", to: "/onboarding/create-a-smart-account" }, + { from: "/magic-account/setup/eoa", to: "/onboarding/eoa" }, + { from: "/magic-account/setup/passkeys", to: "/onboarding/passkeys/overview" }, + { from: "/magic-account/setup/social", to: "/onboarding/social-login" }, + { from: "/magic-account/setup/third-party-signers", to: "/onboarding/auth-providers" }, + { from: "/magic-account/usage", to: "/smart-accounts/send-transactions" }, + { from: "/magic-account/usage/automating-transactions", to: "/smart-accounts/permissions/transaction-automation" }, + { from: "/magic-account/usage/batching-transactions", to: "/smart-accounts/batch-transactions" }, + { from: "/magic-account/usage/chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, + { from: "/magic-account/usage/one-click-trading", to: "/smart-accounts/permissions/1-click-trading" }, + { from: "/magic-account/usage/paying-gas-with-erc20", to: "/smart-accounts/pay-gas-with-erc20s" }, + { from: "/magic-account/usage/sponsoring-gas", to: "/smart-accounts/sponsor-gas/evm" }, + { from: "/magic-account/why-chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, + { from: "/magic-account/yi-vs-others", to: "/smart-accounts/chain-abstraction/overview" }, + + // /smart-recipes/* folded into the main tree (13) + { from: "/onramp/smart-recipes", to: "/onramp/smart-routing-address" }, + { from: "/onramp/smart-recipes/api-reference", to: "/onramp/smart-routing-address" }, + { from: "/onramp/smart-recipes/bridge-and-swap", to: "/onramp/smart-routing-address" }, + { from: "/onramp/smart-recipes/deposits", to: "/onramp/smart-routing-address" }, + { from: "/onramp/smart-recipes/errors", to: "/onramp/smart-routing-address" }, + { from: "/onramp/smart-recipes/how-it-works", to: "/onramp/smart-routing-address" }, + { from: "/onramp/smart-recipes/integration-guide", to: "/onramp/smart-routing-address/quickstart" }, + { from: "/onramp/smart-recipes/quickstart", to: "/onramp/smart-routing-address/quickstart" }, + { from: "/onramp/smart-recipes/quotes", to: "/onramp/smart-routing-address" }, + { from: "/onramp/smart-recipes/recipes", to: "/onramp/smart-routing-address" }, + { from: "/onramp/smart-recipes/reference", to: "/onramp/smart-routing-address" }, + { from: "/onramp/smart-recipes/tracking-status", to: "/onramp/smart-routing-address/fetching-status" }, + { from: "/onramp/smart-recipes/vault-discovery", to: "/onramp/smart-routing-address" }, + + // React WaaS SDK pages (12) + { from: "/sdk/waas/use-balance", to: "/advanced/react-hooks/use-balance" }, + { from: "/sdk/waas/use-create-basic-session", to: "/advanced/react-hooks/use-create-basic-session" }, + { from: "/sdk/waas/use-create-kernelclient-eoa", to: "/advanced/react-hooks/use-create-kernelclient-eoa" }, + { from: "/sdk/waas/use-create-kernelclient-passkey", to: "/advanced/react-hooks/use-create-kernelclient-passkey" }, + { from: "/sdk/waas/use-create-session", to: "/advanced/react-hooks/use-create-session" }, + { from: "/sdk/waas/use-disconnect-kernelclient", to: "/advanced/react-hooks/use-disconnect-kernelclient" }, + { from: "/sdk/waas/use-kernelclient", to: "/advanced/react-hooks/use-kernelclient" }, + { from: "/sdk/waas/use-send-useroperation", to: "/advanced/react-hooks/use-send-useroperation" }, + { from: "/sdk/waas/use-send-useroperation-with-session", to: "/advanced/react-hooks/use-send-useroperation-with-session" }, + { from: "/sdk/waas/use-session-kernelclient", to: "/advanced/react-hooks/use-session-kernelclient" }, + { from: "/sdk/waas/use-sessions", to: "/advanced/react-hooks/use-sessions" }, + { from: "/sdk/waas/use-set-kernelclient", to: "/advanced/react-hooks/use-set-kernelclient" }, + + // /modules/* (7579 modules) folded into /smart-accounts/* (10) + { from: "/modules", to: "/get-started/sdks/overview" }, + { from: "/modules/account-recovery", to: "/advanced/account-recovery/sdk-recovery" }, + { from: "/modules/batching", to: "/smart-accounts/batch-transactions" }, + { from: "/modules/defi-integrations", to: "/smart-accounts/defi" }, + { from: "/modules/gas", to: "/smart-accounts/sponsor-gas/evm" }, + { from: "/modules/importing-assets", to: "/onboarding/create-a-smart-account" }, + { from: "/modules/one-click-trading", to: "/smart-accounts/permissions/transaction-automation" }, + { from: "/modules/onramp", to: "/onramp/smart-routing-address" }, + { from: "/modules/signin", to: "/onboarding/auth-providers" }, + { from: "/modules/transaction-automation", to: "/smart-accounts/permissions/transaction-automation" }, + + // /wallet/* early Wallet UI paths (9) + { from: "/wallet", to: "/wallets" }, + { from: "/wallet/authentication/email-otp", to: "/wallets/auth/email-otp" }, + { from: "/wallet/authentication/google-oauth", to: "/wallets/auth/google-oauth" }, + { from: "/wallet/authentication/passkey-webauthn", to: "/wallets/auth/passkeys" }, + { from: "/wallet/getting-started/quickstart", to: "/wallets/quickstart" }, + { from: "/wallet/wallet-api/export-wallet-seed-phrase", to: "/wallets/export" }, + { from: "/wallet/wallet-api/send-transaction", to: "/wallets/wallet-api/send-transaction" }, + { from: "/wallet/wallet-api/sign-message", to: "/wallets/wallet-api/sign-message" }, + { from: "/wallet/wallet-api/sign-typed-message", to: "/wallets/wallet-api/sign-message" }, + + // /dapp/* early dapp-integration paths (6) + { from: "/dapp", to: "/get-started/quickstart" }, + { from: "/dapp/knowledge/7702", to: "/get-started/eip-7702/quickstart" }, + { from: "/dapp/knowledge/capabilities", to: "https://www.zerodev.app/blogs/sdks-are-dead-long-live-capabilities" }, + { from: "/dapp/onboarding/yi-connector-setup", to: "/onboarding/create-a-smart-account" }, + { from: "/dapp/onboarding/yi-connector-styling", to: "/onboarding/create-a-smart-account" }, + { from: "/dapp/using-your-app/chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, + + // remaining orphan /smart-wallet/* paths (6) + { from: "/smart-wallet/batching", to: "/smart-accounts/batch-transactions" }, + { from: "/smart-wallet/gas", to: "/smart-accounts/sponsor-gas/evm" }, + { from: "/smart-wallet/intro", to: "/onboarding/create-a-smart-account" }, + { from: "/smart-wallet/onramp", to: "/onramp/smart-routing-address" }, + { from: "/smart-wallet/setup", to: "/onboarding/create-a-smart-account" }, + { from: "/smart-wallet/signin", to: "/onboarding/auth-providers" }, + + // /kernel/* contract docs (1) + { from: "/kernel/intro", to: "/" }, + + // versioned SDK path (1) + { from: "/sdk/v5_3_x/faqs/why-chain-abstraction", to: "/sdk/v5_3_x/advanced/chain-abstraction" }, ]; diff --git a/resolve-redirect.d.ts b/resolve-redirect.d.ts new file mode 100644 index 00000000..9d4ad807 --- /dev/null +++ b/resolve-redirect.d.ts @@ -0,0 +1,3 @@ +export declare const PREFIX: [string, string][]; +export declare function stripTrailingSlash(pathname: string): string; +export declare function resolveRedirect(pathname: string): string | null; diff --git a/resolve-redirect.js b/resolve-redirect.js new file mode 100644 index 00000000..747c0b73 --- /dev/null +++ b/resolve-redirect.js @@ -0,0 +1,23 @@ +// Single source of truth for how a request path becomes a redirect target. +// server.mjs serves with it; scripts/check-redirects.mjs audits with it. + +import { redirects } from "./redirects.config.js"; + +const EXACT = new Map(redirects.map((r) => [r.from, r.to])); + +// Prefix rules are a last resort for whole trees whose subpaths were never +// enumerated. Anything we know about gets an exact rule in redirects.config.js. +export const PREFIX = [["/magic-account", "/smart-accounts/chain-abstraction/overview"]]; + +export const stripTrailingSlash = (pathname) => + pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname; + +export function resolveRedirect(pathname) { + const clean = stripTrailingSlash(pathname); + return ( + EXACT.get(pathname) ?? + EXACT.get(clean) ?? + PREFIX.find(([p]) => clean === p || clean.startsWith(p + "/"))?.[1] ?? + null + ); +} diff --git a/scripts/check-redirects.mjs b/scripts/check-redirects.mjs new file mode 100644 index 00000000..3efacff2 --- /dev/null +++ b/scripts/check-redirects.mjs @@ -0,0 +1,75 @@ +// Audits redirects.config.js. Run with `npm run check:redirects`. +// +// Catches the four ways a redirect table rots: +// 1. a rule whose target is a page that does not exist (readers land on a 404) +// 2. a rule whose target is itself redirected (two hops; Google discounts them) +// 3. two rules with the same source (one silently wins) +// 4. a rule whose source is a page that still exists (the redirect shadows it) + +import { existsSync, readdirSync, statSync } from "node:fs"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { redirects } from "../redirects.config.js"; +import { PREFIX, resolveRedirect, stripTrailingSlash } from "../resolve-redirect.js"; + +const ROOT = fileURLToPath(new URL("..", import.meta.url)); +const PAGES = join(ROOT, "docs", "pages"); + +// Every route Vocs will build, derived from the page sources so the check runs +// without a build step. +function routes(dir = PAGES, prefix = "") { + const out = new Set(); + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + if (statSync(full).isDirectory()) { + for (const r of routes(full, `${prefix}/${entry}`)) out.add(r); + } else if (/\.mdx?$/.test(entry)) { + const name = entry.replace(/\.mdx?$/, ""); + out.add(name === "index" ? prefix || "/" : `${prefix}/${name}`); + } + } + return out; +} + +if (!existsSync(PAGES)) { + console.error(`docs/pages not found at ${PAGES}`); + process.exit(1); +} + +const ROUTES = routes(); +const errors = []; +const seen = new Map(); + +for (const { from, to } of redirects) { + if (seen.has(from)) errors.push(`duplicate source ${from} (${seen.get(from)} and ${to})`); + seen.set(from, to); + + // Shadowing only counts against internal targets. The blog posts deliberately + // keep their source files while pointing at the marketing site; once the last + // two posts are published there (DES-19) those files can go. + if (!to.startsWith("http") && ROUTES.has(stripTrailingSlash(from))) { + errors.push(`${from} is redirected but the page still exists`); + } + + if (to.startsWith("http")) continue; + + const target = stripTrailingSlash(to.split(/[?#]/)[0]); + if (!ROUTES.has(target)) errors.push(`${from} -> ${to} (no such page)`); + if (resolveRedirect(target)) errors.push(`${from} -> ${to} -> ${resolveRedirect(target)} (chain)`); +} + +// The prefix rules in resolve-redirect.js must also land somewhere real. +for (const [prefix, target] of PREFIX) { + if (!ROUTES.has(stripTrailingSlash(target))) { + errors.push(`prefix ${prefix} -> ${target} (no such page)`); + } +} + +if (errors.length) { + console.error(`${errors.length} redirect problem(s):`); + for (const e of errors) console.error(` ${e}`); + process.exit(1); +} + +console.log(`${redirects.length} redirects ok, ${ROUTES.size} routes`); diff --git a/server.mjs b/server.mjs index 94687dc7..aa927516 100644 --- a/server.mjs +++ b/server.mjs @@ -8,17 +8,11 @@ import { fileURLToPath } from "node:url"; import sirv from "sirv"; -import { redirects } from "./redirects.config.js"; +import { resolveRedirect } from "./resolve-redirect.js"; const PORT = Number(process.env.PORT) || 10000; const DIR = join(fileURLToPath(new URL(".", import.meta.url)), "docs", "dist"); -const EXACT = new Map(redirects.map((r) => [r.from, r.to])); -const PREFIX = [ - ["/magic-account", "/smart-accounts/chain-abstraction/overview"], -]; -const SPA_FALLBACK = ["/sdk", "/meta-infra", "/recovery-flow", "/react", "/smart-wallet"]; - const assets = sirv(DIR, { etag: true, gzip: true, @@ -33,35 +27,18 @@ const assets = sirv(DIR, { } }, }); -const shell = sirv(DIR, { etag: true, single: true }); // serves index.html on miss const notFound = existsSync(join(DIR, "404.html")) ? readFileSync(join(DIR, "404.html")) : null; -function redirectTo(pathname) { - const clean = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname; - return ( - EXACT.get(pathname) ?? - EXACT.get(clean) ?? - PREFIX.find(([p]) => clean === p || clean.startsWith(p + "/"))?.[1] ?? - null - ); -} - -const isSpaFallback = (pathname) => { - const clean = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname; - return SPA_FALLBACK.some((p) => clean === p || clean.startsWith(p + "/")); -}; - createServer((req, res) => { const { pathname, search } = new URL(req.url, "http://localhost"); - const to = redirectTo(pathname); + const to = resolveRedirect(pathname); if (to) { res.writeHead(301, { location: to + search }); return res.end(); } assets(req, res, () => { - if (isSpaFallback(pathname)) return shell(req, res, () => res.end()); if (notFound) { res.writeHead(404, { "content-type": "text/html; charset=utf-8" }); return res.end(notFound); diff --git a/vocs.config.tsx b/vocs.config.tsx index cae54eea..7c0e6035 100644 --- a/vocs.config.tsx +++ b/vocs.config.tsx @@ -1,7 +1,7 @@ import { resolve } from "node:path"; import { defineConfig } from "vocs"; import dotenv from "dotenv"; -import { redirects } from "./redirects.config.js"; +import { resolveRedirect } from "./resolve-redirect.js"; // Absolute path to docs/components, used as the `@components` Vite alias below // so MDX pages can import shared components with a single stable specifier @@ -12,13 +12,6 @@ const componentsDir = resolve(process.cwd(), "docs/components"); dotenv.config(); -// Lookup table for the dev-server redirect middleware below. The redirect list -// lives in redirects.config.js (single source of truth); in production the same -// list is applied by server.mjs (the Render web service). -const REDIRECT_MAP: Record = Object.fromEntries( - redirects.map((r) => [r.from, r.to]), -); - export default defineConfig({ iconUrl: "/favicon.ico", logoUrl: { @@ -1053,11 +1046,14 @@ export default defineConfig({ plugins: [ { name: "ia-revamp-redirects", + // Uses the same resolver as server.mjs so dev and production agree on + // trailing slashes and on the /magic-account prefix rule. 307 not 301: + // a browser caches a 301 forever, which is painful while editing. configureServer(server) { server.middlewares.use((req, res, next) => { if (!req.url) return next(); const url = new URL(req.url, "http://localhost"); - const target = REDIRECT_MAP[url.pathname]; + const target = resolveRedirect(url.pathname); if (target) { res.statusCode = 307; res.setHeader("Location", target + url.search); From 0bd11603c0d4d1e7912e85891d324efa588a51ee Mon Sep 17 00:00:00 2001 From: Jayesh Bhole <54071350+jayeshbhole@users.noreply.github.com> Date: Tue, 1 Sep 2026 10:57:39 +0530 Subject: [PATCH 2/7] fix: drop 96 redirects for URLs that were never on main The source list came from git log --all, which swept in pages that only ever existed on unmerged branches (smart-recipes-docs, omar/react-kit-docs, chain-abstracted-smart-account, zerodev-wallet and others). Those URLs were never deployed and never indexed, and the rules would have 301'd the real pages away had any of those branches merged. Rebuilt from --first-parent --full-history origin/main. Coverage is unchanged: 311 of 313 dead URLs have a rule, the 2 without are the offramp stubs that 404 by design. --- redirects.config.js | 123 +++++--------------------------------------- 1 file changed, 14 insertions(+), 109 deletions(-) diff --git a/redirects.config.js b/redirects.config.js index 1d8d7ef7..e06b8c58 100644 --- a/redirects.config.js +++ b/redirects.config.js @@ -284,30 +284,32 @@ export const redirects = [ { from: "/blog/zerodev-litprotocol", to: "https://www.zerodev.app/blogs/blog-zerodev-litprotocol" }, // 2026-06 IA revamp fallout: every URL below was live in a past build and is - // dead today. Recovered by walking the Git history of docs/pages; each target - // was picked by comparing the deleted page against the live tree and each one - // resolves to a page that exists. /modules/offramp and /smart-wallet/offramp - // are deliberately absent: both were one-line stubs with no successor, so they - // 404 rather than send a reader somewhere unrelated. + // dead today. Each target was picked by comparing the deleted page against the + // live tree, and each one resolves to a page that exists. + // + // Sources come from `git log --first-parent --full-history origin/main`, NOT + // from `--all`. Only main ever deployed, so a page that lived solely on a + // feature branch was never reachable and never indexed. Adding a rule for one + // is worse than useless: if that branch later merges, the rule 301s the brand + // new pages away on their first day. An earlier pass of this list used --all + // and picked up 96 such paths from smart-recipes-docs, omar/react-kit-docs, + // chain-abstracted-smart-account, zerodev-wallet and others; they are gone. + // + // /modules/offramp and /smart-wallet/offramp are deliberately absent: both were + // one-line stubs with no successor, so they 404 rather than send a reader + // somewhere unrelated. // Legacy /sdk/* tree (deleted in the 2026-06 IA revamp) (61) - { from: "/sdk", to: "/get-started/sdks/overview" }, { from: "/sdk/advanced/magic-address", to: "/onramp/smart-routing-address" }, { from: "/sdk/core-api/intro", to: "/onboarding/create-a-smart-account" }, { from: "/sdk/core-api/pay-gas-in-erc20s", to: "/smart-accounts/pay-gas-with-erc20s" }, - { from: "/sdk/core-api/session-keys", to: "/smart-accounts/permissions/session-keys" }, { from: "/sdk/core-api/sign-and-validate-messages", to: "/smart-accounts/sign-and-verify" }, { from: "/sdk/core-api/wallet-connect", to: "/advanced/wallet-connect" }, { from: "/sdk/faqs/why-chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, { from: "/sdk/getting-started/wallet-connect", to: "/advanced/wallet-connect" }, { from: "/sdk/infra/stackup", to: "/api-and-toolings/infrastructure/choose-an-infra-provider" }, { from: "/sdk/intro", to: "/" }, - { from: "/sdk/knowledge-base/capabilities", to: "https://www.zerodev.app/blogs/sdks-are-dead-long-live-capabilities" }, - { from: "/sdk/knowledge-base/chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, - { from: "/sdk/knowledge-base/permissions", to: "/smart-accounts/permissions/intro" }, - { from: "/sdk/knowledge-base/smart-account", to: "/onboarding/create-a-smart-account" }, { from: "/sdk/permissions/getting-started", to: "/smart-accounts/permissions/1-click-trading" }, - { from: "/sdk/permissions/signers/intro", to: "/smart-accounts/permissions/intro" }, { from: "/sdk/plugins/ecdsa", to: "/smart-accounts/permissions/signers/ecdsa" }, { from: "/sdk/plugins/guardians", to: "/advanced/account-recovery/sdk-recovery" }, { from: "/sdk/plugins/intro", to: "/smart-accounts/use-plugins/overview" }, @@ -317,41 +319,6 @@ export const redirects = [ { from: "/sdk/plugins/recovery", to: "/advanced/account-recovery/sdk-recovery" }, { from: "/sdk/plugins/session-keys", to: "/smart-accounts/permissions/session-keys" }, { from: "/sdk/plugins/weighted-ecdsa", to: "/advanced/multisig" }, - { from: "/sdk/quickstart", to: "/get-started/quickstart" }, - { from: "/sdk/setting-up-yi", to: "/onboarding/create-a-smart-account" }, - { from: "/sdk/setting-up-yi/auth/eoa", to: "/onboarding/eoa" }, - { from: "/sdk/setting-up-yi/auth/passkeys", to: "/onboarding/passkeys/overview" }, - { from: "/sdk/setting-up-yi/auth/social", to: "/onboarding/social-login" }, - { from: "/sdk/setting-up-yi/auth/third-party-signers", to: "/onboarding/auth-providers" }, - { from: "/sdk/setting-up-yi/chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, - { from: "/sdk/setting-up-yi/eoa", to: "/onboarding/eoa" }, - { from: "/sdk/setting-up-yi/passkeys", to: "/onboarding/passkeys/overview" }, - { from: "/sdk/setting-up-yi/social", to: "/onboarding/social-login" }, - { from: "/sdk/setting-up-yi/third-party-signers", to: "/onboarding/auth-providers" }, - { from: "/sdk/setup", to: "/onboarding/create-a-smart-account" }, - { from: "/sdk/setup/eoa", to: "/onboarding/eoa" }, - { from: "/sdk/setup/passkeys", to: "/onboarding/passkeys/overview" }, - { from: "/sdk/setup/social", to: "/onboarding/social-login" }, - { from: "/sdk/setup/third-party-signers", to: "/onboarding/auth-providers" }, - { from: "/sdk/shared/mutation-result", to: "/advanced/react-hooks/getting-started" }, - { from: "/sdk/shared/query-result", to: "/advanced/react-hooks/getting-started" }, - { from: "/sdk/signers/comparison", to: "/onboarding/auth-providers" }, - { from: "/sdk/usage", to: "/smart-accounts/send-transactions" }, - { from: "/sdk/usage/automating-transactions", to: "/smart-accounts/permissions/transaction-automation" }, - { from: "/sdk/usage/batching-transactions", to: "/smart-accounts/batch-transactions" }, - { from: "/sdk/usage/chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, - { from: "/sdk/usage/one-click-trading", to: "/smart-accounts/permissions/1-click-trading" }, - { from: "/sdk/usage/paying-gas-with-erc20", to: "/smart-accounts/pay-gas-with-erc20s" }, - { from: "/sdk/usage/sponsoring-gas", to: "/smart-accounts/sponsor-gas/evm" }, - { from: "/sdk/using-yi/automating-transactions", to: "/smart-accounts/permissions/transaction-automation" }, - { from: "/sdk/using-yi/basic-usage", to: "/smart-accounts/send-transactions" }, - { from: "/sdk/using-yi/batching-transactions", to: "/smart-accounts/batch-transactions" }, - { from: "/sdk/using-yi/chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, - { from: "/sdk/using-yi/one-click-trading", to: "/smart-accounts/permissions/1-click-trading" }, - { from: "/sdk/using-yi/paying-gas-with-erc20", to: "/smart-accounts/pay-gas-with-erc20s" }, - { from: "/sdk/using-yi/sponsoring-gas", to: "/smart-accounts/sponsor-gas/evm" }, - { from: "/sdk/why-chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, - { from: "/sdk/yi-vs-others", to: "/smart-accounts/chain-abstraction/overview" }, // kerneljs.com docs merged into this site (34) { from: "/kerneljs/core-api/batch-transactions", to: "/smart-accounts/batch-transactions" }, @@ -390,13 +357,8 @@ export const redirects = [ { from: "/kerneljs/signers/web3auth", to: "/onboarding/web3auth" }, // /wallets/* pages moved or renamed (29) - { from: "/wallets/api-reference", to: "/wallets/wallet-api/send-transaction" }, - { from: "/wallets/auth", to: "/wallets/auth/email-otp" }, { from: "/wallets/auth/wallet-ui-kit", to: "/wallets/auth/wallet-ui-kit/getting-started" }, - { from: "/wallets/hooks/export-web-view", to: "/wallets/react-native/web" }, { from: "/wallets/hooks/use-authenticate-oauth", to: "/wallets/auth/google-oauth" }, - { from: "/wallets/hooks/use-authenticate-oauth-with-expo-web-browser", to: "/wallets/react-native/google-oauth" }, - { from: "/wallets/hooks/use-authenticators", to: "/wallets/quickstart" }, { from: "/wallets/hooks/use-export-private-key", to: "/wallets/export" }, { from: "/wallets/hooks/use-export-wallet", to: "/wallets/export" }, { from: "/wallets/hooks/use-get-user-email", to: "/wallets/quickstart" }, @@ -407,17 +369,8 @@ export const redirects = [ { from: "/wallets/hooks/use-send-otp", to: "/wallets/auth/email-otp" }, { from: "/wallets/hooks/use-verify-magic-link", to: "/wallets/auth/magic-link" }, { from: "/wallets/hooks/use-verify-otp", to: "/wallets/auth/email-otp" }, - { from: "/wallets/overview", to: "/wallets" }, - { from: "/wallets/react-kit/configuration", to: "/wallets/auth/wallet-ui-kit/installation" }, - { from: "/wallets/react-kit/features/authentication", to: "/wallets/auth/wallet-ui-kit/connect-wallet" }, - { from: "/wallets/react-kit/features/transaction-signing", to: "/wallets/wallet-api/send-transaction" }, - { from: "/wallets/react-kit/getting-started", to: "/wallets/auth/wallet-ui-kit/getting-started" }, - { from: "/wallets/react-kit/hooks/use-auth", to: "/wallets/auth/wallet-ui-kit/getting-started" }, - { from: "/wallets/react-kit/hooks/use-pending-request", to: "/wallets/auth/wallet-ui-kit/coming-soon" }, - { from: "/wallets/sdk-reference", to: "/wallets/quickstart" }, { from: "/wallets/shared/mutation-result", to: "/wallets/quickstart" }, { from: "/wallets/shared/query-result", to: "/wallets/quickstart" }, - { from: "/wallets/wallet-api", to: "/wallets/wallet-api/send-transaction" }, { from: "/wallets/wallet-api/sign-typed-message", to: "/wallets/wallet-api/sign-message" }, // /smart-accounts/embedded-wallet/* moved to /wallets/* (24) @@ -467,35 +420,6 @@ export const redirects = [ { from: "/magic-account/why-chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, { from: "/magic-account/yi-vs-others", to: "/smart-accounts/chain-abstraction/overview" }, - // /smart-recipes/* folded into the main tree (13) - { from: "/onramp/smart-recipes", to: "/onramp/smart-routing-address" }, - { from: "/onramp/smart-recipes/api-reference", to: "/onramp/smart-routing-address" }, - { from: "/onramp/smart-recipes/bridge-and-swap", to: "/onramp/smart-routing-address" }, - { from: "/onramp/smart-recipes/deposits", to: "/onramp/smart-routing-address" }, - { from: "/onramp/smart-recipes/errors", to: "/onramp/smart-routing-address" }, - { from: "/onramp/smart-recipes/how-it-works", to: "/onramp/smart-routing-address" }, - { from: "/onramp/smart-recipes/integration-guide", to: "/onramp/smart-routing-address/quickstart" }, - { from: "/onramp/smart-recipes/quickstart", to: "/onramp/smart-routing-address/quickstart" }, - { from: "/onramp/smart-recipes/quotes", to: "/onramp/smart-routing-address" }, - { from: "/onramp/smart-recipes/recipes", to: "/onramp/smart-routing-address" }, - { from: "/onramp/smart-recipes/reference", to: "/onramp/smart-routing-address" }, - { from: "/onramp/smart-recipes/tracking-status", to: "/onramp/smart-routing-address/fetching-status" }, - { from: "/onramp/smart-recipes/vault-discovery", to: "/onramp/smart-routing-address" }, - - // React WaaS SDK pages (12) - { from: "/sdk/waas/use-balance", to: "/advanced/react-hooks/use-balance" }, - { from: "/sdk/waas/use-create-basic-session", to: "/advanced/react-hooks/use-create-basic-session" }, - { from: "/sdk/waas/use-create-kernelclient-eoa", to: "/advanced/react-hooks/use-create-kernelclient-eoa" }, - { from: "/sdk/waas/use-create-kernelclient-passkey", to: "/advanced/react-hooks/use-create-kernelclient-passkey" }, - { from: "/sdk/waas/use-create-session", to: "/advanced/react-hooks/use-create-session" }, - { from: "/sdk/waas/use-disconnect-kernelclient", to: "/advanced/react-hooks/use-disconnect-kernelclient" }, - { from: "/sdk/waas/use-kernelclient", to: "/advanced/react-hooks/use-kernelclient" }, - { from: "/sdk/waas/use-send-useroperation", to: "/advanced/react-hooks/use-send-useroperation" }, - { from: "/sdk/waas/use-send-useroperation-with-session", to: "/advanced/react-hooks/use-send-useroperation-with-session" }, - { from: "/sdk/waas/use-session-kernelclient", to: "/advanced/react-hooks/use-session-kernelclient" }, - { from: "/sdk/waas/use-sessions", to: "/advanced/react-hooks/use-sessions" }, - { from: "/sdk/waas/use-set-kernelclient", to: "/advanced/react-hooks/use-set-kernelclient" }, - // /modules/* (7579 modules) folded into /smart-accounts/* (10) { from: "/modules", to: "/get-started/sdks/overview" }, { from: "/modules/account-recovery", to: "/advanced/account-recovery/sdk-recovery" }, @@ -508,25 +432,6 @@ export const redirects = [ { from: "/modules/signin", to: "/onboarding/auth-providers" }, { from: "/modules/transaction-automation", to: "/smart-accounts/permissions/transaction-automation" }, - // /wallet/* early Wallet UI paths (9) - { from: "/wallet", to: "/wallets" }, - { from: "/wallet/authentication/email-otp", to: "/wallets/auth/email-otp" }, - { from: "/wallet/authentication/google-oauth", to: "/wallets/auth/google-oauth" }, - { from: "/wallet/authentication/passkey-webauthn", to: "/wallets/auth/passkeys" }, - { from: "/wallet/getting-started/quickstart", to: "/wallets/quickstart" }, - { from: "/wallet/wallet-api/export-wallet-seed-phrase", to: "/wallets/export" }, - { from: "/wallet/wallet-api/send-transaction", to: "/wallets/wallet-api/send-transaction" }, - { from: "/wallet/wallet-api/sign-message", to: "/wallets/wallet-api/sign-message" }, - { from: "/wallet/wallet-api/sign-typed-message", to: "/wallets/wallet-api/sign-message" }, - - // /dapp/* early dapp-integration paths (6) - { from: "/dapp", to: "/get-started/quickstart" }, - { from: "/dapp/knowledge/7702", to: "/get-started/eip-7702/quickstart" }, - { from: "/dapp/knowledge/capabilities", to: "https://www.zerodev.app/blogs/sdks-are-dead-long-live-capabilities" }, - { from: "/dapp/onboarding/yi-connector-setup", to: "/onboarding/create-a-smart-account" }, - { from: "/dapp/onboarding/yi-connector-styling", to: "/onboarding/create-a-smart-account" }, - { from: "/dapp/using-your-app/chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, - // remaining orphan /smart-wallet/* paths (6) { from: "/smart-wallet/batching", to: "/smart-accounts/batch-transactions" }, { from: "/smart-wallet/gas", to: "/smart-accounts/sponsor-gas/evm" }, From ef117390e9df23328b6a7de3b1b51a1d7ae35f6d Mon Sep 17 00:00:00 2001 From: Jayesh Bhole <54071350+jayeshbhole@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:00:01 +0530 Subject: [PATCH 3/7] chore: trim comments in redirect files --- redirects.config.js | 38 +++++++++++++++---------------------- resolve-redirect.js | 6 ++---- scripts/check-redirects.mjs | 18 +++++------------- vocs.config.tsx | 4 +--- 4 files changed, 23 insertions(+), 43 deletions(-) diff --git a/redirects.config.js b/redirects.config.js index e06b8c58..6e345f78 100644 --- a/redirects.config.js +++ b/redirects.config.js @@ -283,23 +283,15 @@ export const redirects = [ { from: "/blog/zerodev-glider", to: "https://www.zerodev.app/blogs/blog-zerodev-glider" }, { from: "/blog/zerodev-litprotocol", to: "https://www.zerodev.app/blogs/blog-zerodev-litprotocol" }, - // 2026-06 IA revamp fallout: every URL below was live in a past build and is - // dead today. Each target was picked by comparing the deleted page against the - // live tree, and each one resolves to a page that exists. + // 2026-06 IA revamp fallout. Sources found with + // `git log --first-parent --full-history origin/main`, never `--all`: a page + // that only ever lived on a feature branch was never deployed, and a rule for + // one would 301 those pages away if the branch later merged. // - // Sources come from `git log --first-parent --full-history origin/main`, NOT - // from `--all`. Only main ever deployed, so a page that lived solely on a - // feature branch was never reachable and never indexed. Adding a rule for one - // is worse than useless: if that branch later merges, the rule 301s the brand - // new pages away on their first day. An earlier pass of this list used --all - // and picked up 96 such paths from smart-recipes-docs, omar/react-kit-docs, - // chain-abstracted-smart-account, zerodev-wallet and others; they are gone. - // - // /modules/offramp and /smart-wallet/offramp are deliberately absent: both were - // one-line stubs with no successor, so they 404 rather than send a reader - // somewhere unrelated. + // /modules/offramp and /smart-wallet/offramp are absent on purpose. Both were + // one-line stubs with no successor, so they 404. - // Legacy /sdk/* tree (deleted in the 2026-06 IA revamp) (61) + // Legacy /sdk/* { from: "/sdk/advanced/magic-address", to: "/onramp/smart-routing-address" }, { from: "/sdk/core-api/intro", to: "/onboarding/create-a-smart-account" }, { from: "/sdk/core-api/pay-gas-in-erc20s", to: "/smart-accounts/pay-gas-with-erc20s" }, @@ -320,7 +312,7 @@ export const redirects = [ { from: "/sdk/plugins/session-keys", to: "/smart-accounts/permissions/session-keys" }, { from: "/sdk/plugins/weighted-ecdsa", to: "/advanced/multisig" }, - // kerneljs.com docs merged into this site (34) + // kerneljs.com docs, merged into this site { from: "/kerneljs/core-api/batch-transactions", to: "/smart-accounts/batch-transactions" }, { from: "/kerneljs/core-api/create-account", to: "/onboarding/create-a-smart-account" }, { from: "/kerneljs/core-api/delegatecall", to: "/smart-accounts/delegatecall" }, @@ -356,7 +348,7 @@ export const redirects = [ { from: "/kerneljs/signers/turnkey", to: "/onboarding/turnkey" }, { from: "/kerneljs/signers/web3auth", to: "/onboarding/web3auth" }, - // /wallets/* pages moved or renamed (29) + // /wallets/* moved or renamed { from: "/wallets/auth/wallet-ui-kit", to: "/wallets/auth/wallet-ui-kit/getting-started" }, { from: "/wallets/hooks/use-authenticate-oauth", to: "/wallets/auth/google-oauth" }, { from: "/wallets/hooks/use-export-private-key", to: "/wallets/export" }, @@ -373,7 +365,7 @@ export const redirects = [ { from: "/wallets/shared/query-result", to: "/wallets/quickstart" }, { from: "/wallets/wallet-api/sign-typed-message", to: "/wallets/wallet-api/sign-message" }, - // /smart-accounts/embedded-wallet/* moved to /wallets/* (24) + // /smart-accounts/embedded-wallet/* -> /wallets/* { from: "/smart-accounts/embedded-wallet", to: "/wallets" }, { from: "/smart-accounts/embedded-wallet/auth/email-otp", to: "/wallets/auth/email-otp" }, { from: "/smart-accounts/embedded-wallet/auth/google-oauth", to: "/wallets/auth/google-oauth" }, @@ -399,7 +391,7 @@ export const redirects = [ { from: "/smart-accounts/embedded-wallet/wallet-api/sign-message", to: "/wallets/wallet-api/sign-message" }, { from: "/smart-accounts/embedded-wallet/wallet-api/sign-typed-message", to: "/wallets/wallet-api/sign-message" }, - // Magic Account subpaths with a closer home than the overview (19) + // Magic Account subpaths with a closer home than the overview { from: "/magic-account/knowledge-base/capabilities", to: "https://www.zerodev.app/blogs/sdks-are-dead-long-live-capabilities" }, { from: "/magic-account/knowledge-base/chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, { from: "/magic-account/knowledge-base/permissions", to: "/smart-accounts/permissions/intro" }, @@ -420,7 +412,7 @@ export const redirects = [ { from: "/magic-account/why-chain-abstraction", to: "/smart-accounts/chain-abstraction/overview" }, { from: "/magic-account/yi-vs-others", to: "/smart-accounts/chain-abstraction/overview" }, - // /modules/* (7579 modules) folded into /smart-accounts/* (10) + // /modules/* (7579 modules) -> /smart-accounts/* { from: "/modules", to: "/get-started/sdks/overview" }, { from: "/modules/account-recovery", to: "/advanced/account-recovery/sdk-recovery" }, { from: "/modules/batching", to: "/smart-accounts/batch-transactions" }, @@ -432,7 +424,7 @@ export const redirects = [ { from: "/modules/signin", to: "/onboarding/auth-providers" }, { from: "/modules/transaction-automation", to: "/smart-accounts/permissions/transaction-automation" }, - // remaining orphan /smart-wallet/* paths (6) + // orphan /smart-wallet/* { from: "/smart-wallet/batching", to: "/smart-accounts/batch-transactions" }, { from: "/smart-wallet/gas", to: "/smart-accounts/sponsor-gas/evm" }, { from: "/smart-wallet/intro", to: "/onboarding/create-a-smart-account" }, @@ -440,9 +432,9 @@ export const redirects = [ { from: "/smart-wallet/setup", to: "/onboarding/create-a-smart-account" }, { from: "/smart-wallet/signin", to: "/onboarding/auth-providers" }, - // /kernel/* contract docs (1) + // /kernel/* contract docs { from: "/kernel/intro", to: "/" }, - // versioned SDK path (1) + // versioned SDK path { from: "/sdk/v5_3_x/faqs/why-chain-abstraction", to: "/sdk/v5_3_x/advanced/chain-abstraction" }, ]; diff --git a/resolve-redirect.js b/resolve-redirect.js index 747c0b73..0ab20fc0 100644 --- a/resolve-redirect.js +++ b/resolve-redirect.js @@ -1,12 +1,10 @@ -// Single source of truth for how a request path becomes a redirect target. -// server.mjs serves with it; scripts/check-redirects.mjs audits with it. +// Shared by server.mjs, the vocs dev middleware, and scripts/check-redirects.mjs. import { redirects } from "./redirects.config.js"; const EXACT = new Map(redirects.map((r) => [r.from, r.to])); -// Prefix rules are a last resort for whole trees whose subpaths were never -// enumerated. Anything we know about gets an exact rule in redirects.config.js. +// Catch-all for subpaths with no exact rule. export const PREFIX = [["/magic-account", "/smart-accounts/chain-abstraction/overview"]]; export const stripTrailingSlash = (pathname) => diff --git a/scripts/check-redirects.mjs b/scripts/check-redirects.mjs index 3efacff2..3433aa74 100644 --- a/scripts/check-redirects.mjs +++ b/scripts/check-redirects.mjs @@ -1,10 +1,5 @@ -// Audits redirects.config.js. Run with `npm run check:redirects`. -// -// Catches the four ways a redirect table rots: -// 1. a rule whose target is a page that does not exist (readers land on a 404) -// 2. a rule whose target is itself redirected (two hops; Google discounts them) -// 3. two rules with the same source (one silently wins) -// 4. a rule whose source is a page that still exists (the redirect shadows it) +// npm run check:redirects — fails on a dead target, a chain, a duplicate +// source, or a rule that shadows a page that still exists. import { existsSync, readdirSync, statSync } from "node:fs"; import { join } from "node:path"; @@ -16,8 +11,7 @@ import { PREFIX, resolveRedirect, stripTrailingSlash } from "../resolve-redirect const ROOT = fileURLToPath(new URL("..", import.meta.url)); const PAGES = join(ROOT, "docs", "pages"); -// Every route Vocs will build, derived from the page sources so the check runs -// without a build step. +// Derived from page sources, so the check runs without a build. function routes(dir = PAGES, prefix = "") { const out = new Set(); for (const entry of readdirSync(dir)) { @@ -45,9 +39,8 @@ for (const { from, to } of redirects) { if (seen.has(from)) errors.push(`duplicate source ${from} (${seen.get(from)} and ${to})`); seen.set(from, to); - // Shadowing only counts against internal targets. The blog posts deliberately - // keep their source files while pointing at the marketing site; once the last - // two posts are published there (DES-19) those files can go. + // Internal targets only: blog posts keep their source files on purpose while + // pointing at the marketing site. if (!to.startsWith("http") && ROUTES.has(stripTrailingSlash(from))) { errors.push(`${from} is redirected but the page still exists`); } @@ -59,7 +52,6 @@ for (const { from, to } of redirects) { if (resolveRedirect(target)) errors.push(`${from} -> ${to} -> ${resolveRedirect(target)} (chain)`); } -// The prefix rules in resolve-redirect.js must also land somewhere real. for (const [prefix, target] of PREFIX) { if (!ROUTES.has(stripTrailingSlash(target))) { errors.push(`prefix ${prefix} -> ${target} (no such page)`); diff --git a/vocs.config.tsx b/vocs.config.tsx index 7c0e6035..7c0bc7e5 100644 --- a/vocs.config.tsx +++ b/vocs.config.tsx @@ -1046,9 +1046,7 @@ export default defineConfig({ plugins: [ { name: "ia-revamp-redirects", - // Uses the same resolver as server.mjs so dev and production agree on - // trailing slashes and on the /magic-account prefix rule. 307 not 301: - // a browser caches a 301 forever, which is painful while editing. + // 307 not 301: browsers cache a 301 forever, painful while editing. configureServer(server) { server.middlewares.use((req, res, next) => { if (!req.url) return next(); From cfa8e4eac6401e384c1c29289e86de3b081b092a Mon Sep 17 00:00:00 2001 From: Jayesh Bhole <54071350+jayeshbhole@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:54:26 +0530 Subject: [PATCH 4/7] fix: retarget 15 redirects to same-SDK, maintained pages Package-level comparison of each deleted page against its target found redirects that matched on topic word but sent readers to a different SDK. Corrects the worst: /modules/signin and /smart-wallet/signin pointed at /onboarding/auth-providers (third-party wallet services) when the old pages documented ZeroDev's own passkey, social and ECDSA validators. Pages built on @zerodev/waas now go to the current wallet SDK rather than the unmaintained hooks tree. The 20 /react/* rules still point there because those URLs were the waas docs and the content moved. --- redirects.config.js | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/redirects.config.js b/redirects.config.js index 6e345f78..be9b3bea 100644 --- a/redirects.config.js +++ b/redirects.config.js @@ -125,8 +125,8 @@ export const redirects = [ { from: "/recovery-flow/portal", to: "/advanced/account-recovery/portal" }, // Smart Routing Address (and global-address duplicate) - { from: "/smart-routing-address", to: "/onramp/smart-routing-address" }, - { from: "/global-address", to: "/onramp/smart-routing-address" }, + { from: "/smart-routing-address", to: "/onramp/smart-routing-address/quickstart" }, + { from: "/global-address", to: "/onramp/smart-routing-address/quickstart" }, // Magic Account (legacy) → Chain Abstraction. Was a manual Render redirect to // /sdk/advanced/chain-abstraction, itself now legacy; collapse the hop straight @@ -136,7 +136,9 @@ export const redirects = [ // Embedded Wallet docs live at /wallets/* (alpha — unlisted in sidebars). - // React WaaS hooks → Advanced › React Hooks (legacy) + // React WaaS hooks → Advanced › React Hooks. @zerodev/waas is legacy and + // unmaintained, so only these 1:1 page moves point here; anything else that + // merely used waas goes to the current wallet SDK instead. { from: "/react/getting-started", to: "/advanced/react-hooks/getting-started" }, { from: "/react/use-balance", to: "/advanced/react-hooks/use-balance" }, { from: "/react/use-chainid", to: "/advanced/react-hooks/use-chainid" }, @@ -159,13 +161,13 @@ export const redirects = [ { from: "/react/use-wallet-connect", to: "/advanced/react-hooks/use-wallet-connect" }, // Orphan /smart-wallet/* (deleted) → canonical homes in the new tree - { from: "/smart-wallet/which-sdk", to: "/onboarding/create-a-smart-account" }, + { from: "/smart-wallet/which-sdk", to: "/get-started/sdks/overview" }, { from: "/smart-wallet/quickstart-core", to: "/get-started/quickstart" }, - { from: "/smart-wallet/quickstart-react", to: "/get-started/quickstart" }, - { from: "/smart-wallet/quickstart-capabilities", to: "/get-started/quickstart" }, - { from: "/smart-wallet/creating-wallets", to: "/onboarding/create-a-smart-account" }, - { from: "/smart-wallet/setting-up-zerodev-projects", to: "/" }, - { from: "/smart-wallet/sending-transactions", to: "/smart-accounts/send-transactions" }, + { from: "/smart-wallet/quickstart-react", to: "/wallets/quickstart" }, + { from: "/smart-wallet/quickstart-capabilities", to: "/wallets/quickstart" }, + { from: "/smart-wallet/creating-wallets", to: "/wallets/quickstart" }, + { from: "/smart-wallet/setting-up-zerodev-projects", to: "/get-started/sdks/setup-project" }, + { from: "/smart-wallet/sending-transactions", to: "/wallets/wallet-api/send-transaction" }, { from: "/smart-wallet/batching-transactions", to: "/smart-accounts/batch-transactions" }, { from: "/smart-wallet/pay-gas-in-erc20s", to: "/smart-accounts/pay-gas-with-erc20s" }, { from: "/smart-wallet/sponsoring-gas", to: "/smart-accounts/sponsor-gas/evm" }, @@ -174,7 +176,7 @@ export const redirects = [ { from: "/smart-wallet/account-recovery", to: "/advanced/account-recovery/sdk-recovery" }, { from: "/smart-wallet/importing-assets", to: "/onboarding/create-a-smart-account" }, { from: "/smart-wallet/defi-integrations", to: "/smart-accounts/defi" }, - { from: "/smart-wallet/one-click-trading", to: "/smart-accounts/permissions/transaction-automation" }, + { from: "/smart-wallet/one-click-trading", to: "/smart-accounts/permissions/1-click-trading" }, { from: "/smart-wallet/parallel-transactions", to: "/smart-accounts/parallel-transactions" }, { from: "/smart-wallet/transaction-automation", to: "/smart-accounts/permissions/transaction-automation" }, { from: "/smart-wallet/wallet-connect", to: "/advanced/wallet-connect" }, @@ -224,7 +226,7 @@ export const redirects = [ { from: "/smart-accounts/account-recovery/flow-setup", to: "/advanced/account-recovery/flow-setup" }, { from: "/smart-accounts/account-recovery/portal", to: "/advanced/account-recovery/portal" }, { from: "/smart-accounts/eip-7702/quickstart", to: "/get-started/eip-7702/quickstart" }, - { from: "/cross-chain/smart-routing-address", to: "/onramp/smart-routing-address" }, + { from: "/cross-chain/smart-routing-address", to: "/onramp/smart-routing-address/quickstart" }, { from: "/cross-chain/chain-abstraction/overview", to: "/smart-accounts/chain-abstraction/overview" }, { from: "/cross-chain/chain-abstraction/supported-base-tokens", to: "/smart-accounts/chain-abstraction/supported-base-tokens" }, { from: "/cross-chain/chain-abstraction/supported-defi-tokens", to: "/smart-accounts/chain-abstraction/supported-defi-tokens" }, @@ -292,7 +294,7 @@ export const redirects = [ // one-line stubs with no successor, so they 404. // Legacy /sdk/* - { from: "/sdk/advanced/magic-address", to: "/onramp/smart-routing-address" }, + { from: "/sdk/advanced/magic-address", to: "/onramp/smart-routing-address/quickstart" }, { from: "/sdk/core-api/intro", to: "/onboarding/create-a-smart-account" }, { from: "/sdk/core-api/pay-gas-in-erc20s", to: "/smart-accounts/pay-gas-with-erc20s" }, { from: "/sdk/core-api/sign-and-validate-messages", to: "/smart-accounts/sign-and-verify" }, @@ -413,15 +415,15 @@ export const redirects = [ { from: "/magic-account/yi-vs-others", to: "/smart-accounts/chain-abstraction/overview" }, // /modules/* (7579 modules) -> /smart-accounts/* - { from: "/modules", to: "/get-started/sdks/overview" }, + { from: "/modules", to: "/" }, { from: "/modules/account-recovery", to: "/advanced/account-recovery/sdk-recovery" }, { from: "/modules/batching", to: "/smart-accounts/batch-transactions" }, { from: "/modules/defi-integrations", to: "/smart-accounts/defi" }, { from: "/modules/gas", to: "/smart-accounts/sponsor-gas/evm" }, - { from: "/modules/importing-assets", to: "/onboarding/create-a-smart-account" }, + { from: "/modules/importing-assets", to: "/smart-accounts/chain-abstraction/overview" }, { from: "/modules/one-click-trading", to: "/smart-accounts/permissions/transaction-automation" }, { from: "/modules/onramp", to: "/onramp/smart-routing-address" }, - { from: "/modules/signin", to: "/onboarding/auth-providers" }, + { from: "/modules/signin", to: "/onboarding/create-a-smart-account" }, { from: "/modules/transaction-automation", to: "/smart-accounts/permissions/transaction-automation" }, // orphan /smart-wallet/* @@ -430,7 +432,7 @@ export const redirects = [ { from: "/smart-wallet/intro", to: "/onboarding/create-a-smart-account" }, { from: "/smart-wallet/onramp", to: "/onramp/smart-routing-address" }, { from: "/smart-wallet/setup", to: "/onboarding/create-a-smart-account" }, - { from: "/smart-wallet/signin", to: "/onboarding/auth-providers" }, + { from: "/smart-wallet/signin", to: "/onboarding/create-a-smart-account" }, // /kernel/* contract docs { from: "/kernel/intro", to: "/" }, From 46e23511075791123d9bc9d36809ef80f7628ab3 Mon Sep 17 00:00:00 2001 From: Jayesh Bhole <54071350+jayeshbhole@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:08:37 +0530 Subject: [PATCH 5/7] fix: never redirect to a page the sidebar does not link /smart-wallet/one-click-trading had moved from transaction-automation (in the sidebar) to 1-click-trading (orphaned), which would have dropped readers on a page with no navigation. Reverted. /sdk/permissions/getting-started now goes to permissions/session-keys, which matches the old session-key tutorial and is in the sidebar. check:redirects now fails on any target no sidebar links to. The React hooks tree (DES-21) and the api-and-toolings gaps are allowlisted so the existing backlog does not block the build. --- redirects.config.js | 4 ++-- scripts/check-redirects.mjs | 30 ++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/redirects.config.js b/redirects.config.js index be9b3bea..09e2c486 100644 --- a/redirects.config.js +++ b/redirects.config.js @@ -176,7 +176,7 @@ export const redirects = [ { from: "/smart-wallet/account-recovery", to: "/advanced/account-recovery/sdk-recovery" }, { from: "/smart-wallet/importing-assets", to: "/onboarding/create-a-smart-account" }, { from: "/smart-wallet/defi-integrations", to: "/smart-accounts/defi" }, - { from: "/smart-wallet/one-click-trading", to: "/smart-accounts/permissions/1-click-trading" }, + { from: "/smart-wallet/one-click-trading", to: "/smart-accounts/permissions/transaction-automation" }, { from: "/smart-wallet/parallel-transactions", to: "/smart-accounts/parallel-transactions" }, { from: "/smart-wallet/transaction-automation", to: "/smart-accounts/permissions/transaction-automation" }, { from: "/smart-wallet/wallet-connect", to: "/advanced/wallet-connect" }, @@ -303,7 +303,7 @@ export const redirects = [ { from: "/sdk/getting-started/wallet-connect", to: "/advanced/wallet-connect" }, { from: "/sdk/infra/stackup", to: "/api-and-toolings/infrastructure/choose-an-infra-provider" }, { from: "/sdk/intro", to: "/" }, - { from: "/sdk/permissions/getting-started", to: "/smart-accounts/permissions/1-click-trading" }, + { from: "/sdk/permissions/getting-started", to: "/smart-accounts/permissions/session-keys" }, { from: "/sdk/plugins/ecdsa", to: "/smart-accounts/permissions/signers/ecdsa" }, { from: "/sdk/plugins/guardians", to: "/advanced/account-recovery/sdk-recovery" }, { from: "/sdk/plugins/intro", to: "/smart-accounts/use-plugins/overview" }, diff --git a/scripts/check-redirects.mjs b/scripts/check-redirects.mjs index 3433aa74..4d6bad0f 100644 --- a/scripts/check-redirects.mjs +++ b/scripts/check-redirects.mjs @@ -1,7 +1,8 @@ // npm run check:redirects — fails on a dead target, a chain, a duplicate -// source, or a rule that shadows a page that still exists. +// source, a rule that shadows a page that still exists, or a target no sidebar +// links to. -import { existsSync, readdirSync, statSync } from "node:fs"; +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; @@ -31,6 +32,27 @@ if (!existsSync(PAGES)) { process.exit(1); } +// Routes the sidebar links to. A redirect that lands anywhere else drops the +// reader on a page with no navigation around it. +function sidebarLinks() { + const cfg = readFileSync(join(ROOT, "vocs.config.tsx"), "utf8"); + const start = cfg.indexOf(" sidebar: {"); + if (start < 0) return null; + let depth = 0, end = -1; + for (let i = cfg.indexOf("{", start); i < cfg.length; i++) { + if (cfg[i] === "{") depth++; + else if (cfg[i] === "}" && --depth === 0) { end = i; break; } + } + if (end < 0) return null; + return new Set([...cfg.slice(start, end).matchAll(/link:\s*["'](\/[^"']*)["']/g)] + .map((m) => stripTrailingSlash(m[1]))); +} + +// Trees that are already orphaned and tracked elsewhere (DES-21 owns the React +// hooks tree). New orphan targets outside these must not be added. +const ORPHAN_OK = ["/advanced/react-hooks/", "/api-and-toolings/", "/smart-accounts/permissions/1-click-trading"]; + +const SIDEBAR = sidebarLinks(); const ROUTES = routes(); const errors = []; const seen = new Map(); @@ -50,6 +72,10 @@ for (const { from, to } of redirects) { const target = stripTrailingSlash(to.split(/[?#]/)[0]); if (!ROUTES.has(target)) errors.push(`${from} -> ${to} (no such page)`); if (resolveRedirect(target)) errors.push(`${from} -> ${to} -> ${resolveRedirect(target)} (chain)`); + + if (SIDEBAR && target !== "/" && !SIDEBAR.has(target) && !ORPHAN_OK.some((p) => target.startsWith(p))) { + errors.push(`${from} -> ${to} (no sidebar links to this page)`); + } } for (const [prefix, target] of PREFIX) { From 960443c147f63b1e06e923196012ce4f200c51a4 Mon Sep 17 00:00:00 2001 From: Jayesh Bhole <54071350+jayeshbhole@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:18:45 +0530 Subject: [PATCH 6/7] chore: make redirect check an audit script, not CI Drop the workflow. Fail closed when the sidebar block will not parse, and flag pages a prefix rule shadows. --- .github/workflows/check-redirects.yml | 21 --------------------- scripts/check-redirects.mjs | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 24 deletions(-) delete mode 100644 .github/workflows/check-redirects.yml diff --git a/.github/workflows/check-redirects.yml b/.github/workflows/check-redirects.yml deleted file mode 100644 index 07a87629..00000000 --- a/.github/workflows/check-redirects.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: check-redirects - -on: - pull_request: - paths: - - "redirects.config.js" - - "resolve-redirect.js" - - "scripts/check-redirects.mjs" - - "docs/pages/**" - push: - branches: [main] - -jobs: - check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - - run: node scripts/check-redirects.mjs diff --git a/scripts/check-redirects.mjs b/scripts/check-redirects.mjs index 4d6bad0f..086aaf11 100644 --- a/scripts/check-redirects.mjs +++ b/scripts/check-redirects.mjs @@ -1,6 +1,6 @@ -// npm run check:redirects — fails on a dead target, a chain, a duplicate -// source, a rule that shadows a page that still exists, or a target no sidebar -// links to. +// npm run check:redirects — an audit, run it by hand. Reports a dead target, a +// chain, a duplicate source, a rule (exact or prefix) that shadows a page that +// still exists, or a target no sidebar links to. import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; import { join } from "node:path"; @@ -57,6 +57,10 @@ const ROUTES = routes(); const errors = []; const seen = new Map(); +// Fail loudly rather than skipping the reachability check: if the sidebar is +// reformatted or renamed, silence here would read as a pass. +if (!SIDEBAR) errors.push("cannot parse the sidebar block in vocs.config.tsx, so target reachability went unchecked"); + for (const { from, to } of redirects) { if (seen.has(from)) errors.push(`duplicate source ${from} (${seen.get(from)} and ${to})`); seen.set(from, to); @@ -82,6 +86,12 @@ for (const [prefix, target] of PREFIX) { if (!ROUTES.has(stripTrailingSlash(target))) { errors.push(`prefix ${prefix} -> ${target} (no such page)`); } + // A page added under the prefix is unreachable: the redirect answers first. + for (const route of ROUTES) { + if (route === prefix || route.startsWith(prefix + "/")) { + if (!seen.has(route)) errors.push(`prefix ${prefix} shadows ${route}, which still exists`); + } + } } if (errors.length) { From 4dc4f0129ba464ff1378c14016836b6cf0d9e9e6 Mon Sep 17 00:00:00 2001 From: Jayesh Bhole <54071350+jayeshbhole@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:09:50 +0530 Subject: [PATCH 7/7] chore: explain why the react hooks tree stays out of the sidebar --- scripts/check-redirects.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/check-redirects.mjs b/scripts/check-redirects.mjs index 086aaf11..0334ceb9 100644 --- a/scripts/check-redirects.mjs +++ b/scripts/check-redirects.mjs @@ -48,8 +48,10 @@ function sidebarLinks() { .map((m) => stripTrailingSlash(m[1]))); } -// Trees that are already orphaned and tracked elsewhere (DES-21 owns the React -// hooks tree). New orphan targets outside these must not be added. +// Orphaned on purpose. The React hooks tree documents @zerodev/waas, which is +// unmaintained: DES-21 settled on keeping the pages for old links but hiding +// them from crawlers, so they stay out of the sidebar. New orphan targets +// outside these must not be added. const ORPHAN_OK = ["/advanced/react-hooks/", "/api-and-toolings/", "/smart-accounts/permissions/1-click-trading"]; const SIDEBAR = sidebarLinks();