diff --git a/lychee.toml b/lychee.toml
index 60e8be5a2..73b34c1e3 100644
--- a/lychee.toml
+++ b/lychee.toml
@@ -41,4 +41,4 @@ exclude = [
]
# Accept these status codes
-accept = ["100..=103", "200..=299", "403..=403", "502..=502"]
+accept = ["100..=103", "200..=299", "403..=403", "502..=502"]
\ No newline at end of file
diff --git a/notes/fix-redirects.md b/notes/fix-redirects.md
new file mode 100644
index 000000000..fdb47fdb1
--- /dev/null
+++ b/notes/fix-redirects.md
@@ -0,0 +1,83 @@
+# Redirect links management guide
+
+## Scripts overview
+Two scripts help maintain internal links when pages are redirected:
+
+* `check-redirects`: Identifies links that need updating based on the `_redirects` file.
+* `fix-redirects`: Automatically updates links to match `_redirects` entries.
+
+## Checking for broken links
+
+Run the check script:
+
+```bash
+pnpm lint //OR
+pnpm check-redirects
+```
+## What it does
+
+* Scans all `.mdx` files in the docs
+* Compares internal links against `_redirects` file
+* Reports any outdated links that need updating
+* Provides a summary of total, broken, and valid links
+
+## Example output
+
+```bash
+File "builders/overview.mdx" contains outdated link "/chain/overview" - should be updated to "/stack/overview"
+
+Summary:
+Total pages 🔍 - 50
+Pages broken 🚫 - 2
+Pages OK ✅ - 48
+
+```
+
+## Fixing broken links
+
+Fix links automatically:
+
+```bash
+pnpm fix //OR
+pnpm fix-redirects
+```
+
+## What it does
+
+* Updates all internal links to match `_redirects` entries
+* Preserves other content and formatting
+* Shows which files and links were updated
+* Provides a summary of changes made
+
+## Example output
+
+```bash
+Fixed in "builders/overview.mdx": /chain/overview → /stack/overview
+
+Summary:
+Total files 🔍 - 50
+Files fixed ✅ - 2
+Files skipped ⏭️ - 48
+```
+
+## Best practices
+
+1. Before running
+
+ * Commit current changes
+ * Review `_redirects` file is up-to-date
+ * Run `check-redirects` first to preview changes
+
+
+2. After running
+
+ * Review git diff of updated files
+ * Test updated links locally
+ * Commit changes with descriptive message
+
+
+
+## Common issues
+
+* Script fails: Ensure `_redirects` file exists in public folder, it should always be there!
+* No broken links found: Verify `_redirects` entries are correct.
\ No newline at end of file
diff --git a/package.json b/package.json
index b1690e4f0..733b523eb 100644
--- a/package.json
+++ b/package.json
@@ -3,12 +3,14 @@
"version": "0.0.1",
"description": "Optimism Docs",
"scripts": {
- "lint": "eslint . --ext mdx --max-warnings 0 && pnpm spellcheck:lint && pnpm check-breadcrumbs",
- "fix": "eslint . --ext mdx --fix && pnpm spellcheck:fix && pnpm check-breadcrumbs",
+ "lint": "eslint . --ext mdx --max-warnings 0 && pnpm spellcheck:lint && pnpm check-breadcrumbs && pnpm check-redirects",
+ "fix": "eslint . --ext mdx --fix && pnpm spellcheck:fix && pnpm check-breadcrumbs && pnpm fix-redirects",
"spellcheck:lint": "cspell lint \"**/*.mdx\"",
"spellcheck:fix": "cspell --words-only --unique \"**/*.mdx\" | sort --ignore-case | uniq > words.txt",
"linkcheck": "lychee --config ./lychee.toml --quiet \"./pages\"",
"breadcrumbs":"npx ts-node --skip-project utils/create-breadcrumbs.ts",
+ "check-redirects": "npx ts-node --skip-project utils/redirects.ts",
+ "fix-redirects": "npx ts-node --skip-project utils/fix-redirects.ts",
"check-breadcrumbs":"npx ts-node --skip-project utils/breadcrumbs.ts",
"index:docs": "npx ts-node --skip-project utils/algolia-indexer.ts",
"dev": "next dev",
diff --git a/pages/builders.mdx b/pages/builders.mdx
index ab69e59b6..98f4178cf 100644
--- a/pages/builders.mdx
+++ b/pages/builders.mdx
@@ -15,6 +15,6 @@ Welcome to the Builders section. Here you'll find resources and guides for devel
-
+
diff --git a/pages/builders/app-developers/overview.mdx b/pages/builders/app-developers/overview.mdx
index 21bed4d47..e38817e46 100644
--- a/pages/builders/app-developers/overview.mdx
+++ b/pages/builders/app-developers/overview.mdx
@@ -16,14 +16,14 @@ In this area of the Optimism Docs you'll find everything you need to know about
If you're brand new to OP Mainnet, try starting with the guide on [deploying a basic contract](/chain/getting-started).
It'll get you familiar with the basic steps required to get a contract deployed to the network.
OP Mainnet is [EVM equivalent](https://web.archive.org/web/20231127160757/https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306) so you can feel confident that your existing Ethereum smart contract skills will carry over to OP Mainnet.
-Just make sure to be aware of the few small [differences between Ethereum and OP Mainnet](/chain/differences).
+Just make sure to be aware of the few small [differences between Ethereum and OP Mainnet](/stack/differences).
You might also want to check out the [testing on OP Networks guide](/chain/testing/testing-apps) and the tutorial on [running a local development environment](/chain/testing/dev-node) to help you feel totally confident in your OP Mainnet deployment.
- } />
+ } />
- } />
+ } />
} />
diff --git a/pages/builders/app-developers/tutorials/cross-dom-bridge-erc20.mdx b/pages/builders/app-developers/tutorials/cross-dom-bridge-erc20.mdx
index c2de64336..883e4908c 100644
--- a/pages/builders/app-developers/tutorials/cross-dom-bridge-erc20.mdx
+++ b/pages/builders/app-developers/tutorials/cross-dom-bridge-erc20.mdx
@@ -31,7 +31,7 @@ Make sure to check out the [Standard Bridge guide](/builders/app-developers/brid
The Optimism SDK supports any of the [Superchain networks](/chain/networks).
[Some Superchain networks](https://sdk.optimism.io/enums/l2chainid) are already included in the SDK by default.
-If you want to use a network that isn't included by default, you can simply [instantiate the SDK with the appropriate contract addresses](/builders/chain-operators/tutorials/sdk).
+If you want to use a network that isn't included by default, you can simply [instantiate the SDK with the appropriate contract addresses](/builders/app-developers/overview).
## Dependencies
diff --git a/pages/builders/chain-operators/tutorials.mdx b/pages/builders/chain-operators/tutorials.mdx
index e78b0bd6b..6b2387f7a 100644
--- a/pages/builders/chain-operators/tutorials.mdx
+++ b/pages/builders/chain-operators/tutorials.mdx
@@ -23,5 +23,5 @@ This section provides information on adding attributes to the derivation functio
-
+
diff --git a/pages/chain/getting-started.mdx b/pages/chain/getting-started.mdx
index cf9703d44..d4af9faea 100644
--- a/pages/chain/getting-started.mdx
+++ b/pages/chain/getting-started.mdx
@@ -11,7 +11,7 @@ import { Steps } from 'nextra/components'
This guide explains the basics of OP Mainnet development.
OP Mainnet is [EVM equivalent](https://web.archive.org/web/20231127160757/https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306), meaning we run a slightly modified version of the same `geth` you run on mainnet.
Therefore, the differences between OP Mainnet development and Ethereum development are minor.
-But a few differences [do exist](/chain/differences).
+But a few differences [do exist](/stack/differences).
## OP Mainnet and OP Sepolia endpoint URLs
diff --git a/pages/chain/testing/dev-node.mdx b/pages/chain/testing/dev-node.mdx
index 477176e7d..6471f9876 100644
--- a/pages/chain/testing/dev-node.mdx
+++ b/pages/chain/testing/dev-node.mdx
@@ -24,7 +24,7 @@ We generally recommend using the local development environment if your applicati
1. **You're building contracts on both OP Mainnet and Ethereum that need to interact with one another.** The local development environment is a great way to quickly test interactions between L1 and L2. The OP Mainnet and test networks have a communication delay between L1 and L2 that can make testing slow during the early stages of development.
-2. **You're building an application that might be subject to one of the few [differences between Ethereum and OP Mainnet](/chain/differences).** Although OP Mainnet is [EVM equivalent](https://web.archive.org/web/20231127160757/https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306), it's not exactly the same as Ethereum. If you're building an application that might be subject to one of these differences, you should use the local development environment to double check that everything is running as expected. You might otherwise have unexpected issues when you move to testnet. We strongly recommend reviewing these differences carefully to see if you might fall into this category.
+2. **You're building an application that might be subject to one of the few [differences between Ethereum and OP Mainnet](/stack/differences).** Although OP Mainnet is [EVM equivalent](https://web.archive.org/web/20231127160757/https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306), it's not exactly the same as Ethereum. If you're building an application that might be subject to one of these differences, you should use the local development environment to double check that everything is running as expected. You might otherwise have unexpected issues when you move to testnet. We strongly recommend reviewing these differences carefully to see if you might fall into this category.
However, not everyone will need to use the local development environment.
OP Mainnet is [EVM equivalent](https://web.archive.org/web/20231127160757/https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306), which means that OP Mainnet looks almost exactly like Ethereum under the hood.
diff --git a/pages/index.mdx b/pages/index.mdx
index 15d4aebdd..fa4175ccc 100644
--- a/pages/index.mdx
+++ b/pages/index.mdx
@@ -22,7 +22,7 @@ Whether you're a developer building a app on OP Mainnet, a node operator running
} />
- } />
+ } />
} />
diff --git a/pages/stack/differences.mdx b/pages/stack/differences.mdx
index 9d6e6250c..5f370f4b0 100644
--- a/pages/stack/differences.mdx
+++ b/pages/stack/differences.mdx
@@ -16,11 +16,11 @@ However, there are some minor differences between the behavior of Ethereum and O
### Bridging - Deposit Transactions
-Deposit transactions don't exist on L1's, and are how transactions on an L2 can be initiated from the L1. Importantly, this is how bridge applications can get L1 ETH or tokens into an L2 OP Stack chain. You can read more on deposit transactions [here](/stack/protocol/rollup/deposit-flow).
+Deposit transactions don't exist on L1's, and are how transactions on an L2 can be initiated from the L1. Importantly, this is how bridge applications can get L1 ETH or tokens into an L2 OP Stack chain. You can read more on deposit transactions [here](/stack/transactions/deposit-flow).
### Bridging - Withdrawal Transactions and Fault Proofs
-Withdrawal transactions are how the state of the L2 rollup can be proven to the L1. Often this involves users withdrawing tokens or ETH to the L1. Fault proofs are the mechanism by which withdrawal transactions are currently proven to the L1. You can read more about fault proofs [here](/stack/protocol/fault-proofs/explainer).
+Withdrawal transactions are how the state of the L2 rollup can be proven to the L1. Often this involves users withdrawing tokens or ETH to the L1. Fault proofs are the mechanism by which withdrawal transactions are currently proven to the L1. You can read more about fault proofs [here](/stack/fault-proofs/explainer).
## Opcodes
diff --git a/pages/stack/features/send-raw-transaction-conditional.mdx b/pages/stack/features/send-raw-transaction-conditional.mdx
index 5717ae5e8..75cb7e8c5 100644
--- a/pages/stack/features/send-raw-transaction-conditional.mdx
+++ b/pages/stack/features/send-raw-transaction-conditional.mdx
@@ -56,8 +56,8 @@ Successful submission does **NOT** guarantee inclusion! The caller must observe
This feature can be enabled with the addition of a flag to op-geth.
* `--rollup.sequencertxconditionalenabled` (default: false) a boolean flag which enables the rpc.
-* `--rollup.sequencertxconditionalcostratelimit` (default: 5000) an integer flag that sets the rate limit for cost observable per second.
+* `--rollup.sequencertxconditionalcostratelimit` (default: 5000) an integer flag that sets the rate limit for cost observable per second.
- It is not advised to publicly expose this sequencer endpoint due to DoS concerns. This supplemental proxy, [op-txproxy](/stack/operators/features/op-txproxy), should be used to apply additional constraints on this endpoint prior to passing through to the sequencer.
+ It is not advised to publicly expose this sequencer endpoint due to DoS concerns. This supplemental proxy, [op-txproxy](/builders/chain-operators/tools/op-txproxy), should be used to apply additional constraints on this endpoint prior to passing through to the sequencer.
diff --git a/pages/stack/getting-started.mdx b/pages/stack/getting-started.mdx
index 67096b0dc..a3f5a13a2 100644
--- a/pages/stack/getting-started.mdx
+++ b/pages/stack/getting-started.mdx
@@ -16,7 +16,7 @@ import { Callout } from 'nextra/components'
The OP Stack consists of the many different software components managed and maintained by the Optimism Collective that, together, form the backbone of Optimism.
The OP Stack is built as a public good for the Ethereum and Optimism ecosystems.
-To understand how to operate an OP Stack chain, including roll-up and chain deployment basics, visit [Chain Operator guide](/builders/chain-operators/self-hosted). Check out these guides to get an overview of everything you need to know to properly support OP mainnet within your [exchange](/builders/cex-wallet-developers/cex-support) and [wallet](/builders/cex-wallet-developers/wallet-support).
+To understand how to operate an OP Stack chain, including roll-up and chain deployment basics, visit [Chain Operator guide](/builders/chain-operators/self-hosted). Check out these guides to get an overview of everything you need to know to properly support OP mainnet within your [exchange](/builders/app-developers/overview) and [wallet](/builders/app-developers/overview).
## The OP Stack powers Optimism
diff --git a/pages/stack/interop/assets/deploy-superchain-erc20.mdx b/pages/stack/interop/assets/deploy-superchain-erc20.mdx
index af506e238..19b1917aa 100644
--- a/pages/stack/interop/assets/deploy-superchain-erc20.mdx
+++ b/pages/stack/interop/assets/deploy-superchain-erc20.mdx
@@ -13,7 +13,7 @@ import { Steps } from 'nextra/components'
Interop is currently in active development and not yet ready for production use. The information provided here may change. Check back regularly for the most up-to-date information.
-This guide explains how to issue new assets with the `SuperchainERC20` and bridge them effectively using the `SuperchainERC20Bridge`. If you want more information about the `SuperchainERC20 standard`, see our [`SuperchainERC20` standard explainer](/stack/interop/superchain-erc20)
+This guide explains how to issue new assets with the `SuperchainERC20` and bridge them effectively using the `SuperchainERC20Bridge`. If you want more information about the `SuperchainERC20 standard`, see our [`SuperchainERC20` standard explainer](/stack/interop/assets/superchain-erc20)
Note that bridging assets through the Superchain using `SuperchainERC20` never affects the total supply of your asset. The supply remains fixed, and bridging only changes the chain on which your asset is located. This keeps the token's total amount the same across all networks, ensuring its value stays stable during the move and that the `SuperchainERC20` retains a unified, global supply count.
diff --git a/pages/stack/interop/explainer.mdx b/pages/stack/interop/explainer.mdx
index b52b28800..3f84e1b0f 100644
--- a/pages/stack/interop/explainer.mdx
+++ b/pages/stack/interop/explainer.mdx
@@ -24,7 +24,7 @@ Native OP Stack interoperability provides the ability to read messages and trans
Superchain interop includes both the protocol layer message passing and the Superchain ERC20 token specification.
* **Message passing protocol:** the initial + finalizing/executing [message](cross-chain-message) that fire events to be consumed by the chains in the [dependency set](https://specs.optimism.io/interop/dependency-set.html)
-* **SuperchainERC20 token specification**: the [SuperchainERC20](superchain-erc20) turns message passing into asset transfer between chains in the interop set. Learn more about how the SuperchainERC20 token standard enables asset interoperability in the Superchain [here](/stack/interop/superchain-erc20)
+* **SuperchainERC20 token specification**: the [SuperchainERC20](superchain-erc20) turns message passing into asset transfer between chains in the interop set. Learn more about how the SuperchainERC20 token standard enables asset interoperability in the Superchain [here](/stack/interop/assets/superchain-erc20)
This means ETH and ERC-20s can seamlessly and securely move across L2s, and intent-based protocols (i.e., bridges) can build better experiences on top of the message passing protocol.
diff --git a/utils/fix-redirects.ts b/utils/fix-redirects.ts
new file mode 100644
index 000000000..5d609c20b
--- /dev/null
+++ b/utils/fix-redirects.ts
@@ -0,0 +1,126 @@
+import * as fs from 'fs/promises';
+import * as path from 'path';
+
+const rootDir = path.join(process.cwd(), 'pages');
+const redirectsPath = path.join(process.cwd(), 'public', '_redirects');
+const updates: string[] = [];
+
+// ANSI color codes
+const WHITE = '\x1b[37m';
+const GREEN = '\x1b[32m';
+const YELLOW = '\x1b[33m';
+const RESET = '\x1b[0m';
+const BOLD = '\x1b[1m';
+
+interface Redirect {
+ from: string;
+ to: string;
+}
+
+interface Summary {
+ total: number;
+ fixed: number;
+ skipped: number;
+}
+
+async function getRedirects(): Promise {
+ const content = await fs.readFile(redirectsPath, 'utf-8');
+ return content.split('\n')
+ .filter(line => line.trim() && !line.startsWith('#'))
+ .map(line => {
+ const [from, to] = line.split(/\s+/);
+ return { from, to };
+ });
+}
+
+async function findMdxFiles(dir: string): Promise {
+ const files: string[] = [];
+ const entries = await fs.readdir(dir, { withFileTypes: true });
+
+ for (const entry of entries) {
+ const fullPath = path.join(dir, entry.name);
+ if (entry.isDirectory() && !entry.name.startsWith('_')) {
+ files.push(...await findMdxFiles(fullPath));
+ } else if (entry.isFile() && /\.(md|mdx)$/.test(entry.name)) {
+ files.push(fullPath);
+ }
+ }
+ return files;
+}
+
+async function fixFile(filePath: string, redirects: Redirect[]): Promise {
+ let content = await fs.readFile(filePath, 'utf-8');
+ let hasChanges = false;
+ const relativeFilePath = path.relative(rootDir, filePath);
+
+ redirects.forEach(redirect => {
+ const markdownRegex = new RegExp(`\\[([^\\]]+)\\]\\(${redirect.from}\\)`, 'g');
+ const hrefRegex = new RegExp(`href="${redirect.from}"`, 'g');
+
+ if (content.match(markdownRegex) || content.match(hrefRegex)) {
+ content = content
+ .replace(markdownRegex, `[$1](${redirect.to})`)
+ .replace(hrefRegex, `href="${redirect.to}"`);
+
+ updates.push(`${WHITE}Fixed in "${relativeFilePath}": ${YELLOW}${redirect.from}${WHITE} → ${GREEN}${redirect.to}${RESET}`);
+ hasChanges = true;
+ }
+ });
+
+ if (hasChanges) {
+ await fs.writeFile(filePath, content);
+ }
+
+ return hasChanges;
+}
+
+function printSummary(summary: Summary) {
+ console.log('\nSummary:');
+ console.log(`${WHITE}Total files 🔍 - ${summary.total}`);
+ console.log(`${GREEN}Files fixed ✅ - ${summary.fixed}`);
+ console.log(`${WHITE}Files skipped ⏭️ - ${summary.skipped}${RESET}`);
+}
+
+async function main() {
+ const summary: Summary = {
+ total: 0,
+ fixed: 0,
+ skipped: 0
+ };
+
+ console.log('Starting to fix redirect links...');
+ console.log('Root directory:', rootDir);
+
+ try {
+ const redirects = await getRedirects();
+ const files = await findMdxFiles(rootDir);
+
+ summary.total = files.length;
+
+ for (const file of files) {
+ const wasFixed = await fixFile(file, redirects);
+ if (wasFixed) {
+ summary.fixed++;
+ } else {
+ summary.skipped++;
+ }
+ }
+
+ if (updates.length > 0) {
+ console.log(`${GREEN}${BOLD}Fixed links:${RESET}`);
+ updates.forEach(update => console.log(update));
+ printSummary(summary);
+ } else {
+ console.log(`${GREEN}No broken links found. Everything is up to date.${RESET}`);
+ printSummary(summary);
+ }
+ } catch (error) {
+ console.error(`${YELLOW}${BOLD}Error fixing redirects:${RESET}`, error);
+ process.exit(1);
+ }
+}
+
+main().catch(error => {
+ console.error(`${YELLOW}${BOLD}Error in main process:${RESET}`, error);
+ process.exit(1);
+});
\ No newline at end of file
diff --git a/utils/redirects.ts b/utils/redirects.ts
new file mode 100644
index 000000000..bd2cb66b1
--- /dev/null
+++ b/utils/redirects.ts
@@ -0,0 +1,135 @@
+import * as fs from 'fs/promises';
+import * as path from 'path';
+
+const rootDir = path.join(process.cwd(), 'pages');
+const redirectsPath = path.join(process.cwd(), 'public', '_redirects');
+const warnings: string[] = [];
+
+// ANSI color codes
+const WHITE = '\x1b[37m';
+const GREEN = '\x1b[32m';
+const YELLOW = '\x1b[33m';
+const RESET = '\x1b[0m';
+const BOLD = '\x1b[1m';
+
+interface Redirect {
+ from: string;
+ to: string;
+}
+
+interface Summary {
+ total: number;
+ ok: number;
+ errors: number;
+}
+
+function formatWarning(filePath: string, fromLink: string, toLink: string): string {
+ return `${WHITE}File "${filePath}" contains outdated link ${YELLOW}"${fromLink}"${WHITE} - should be updated to ${GREEN}"${toLink}"${RESET}`;
+}
+
+async function getRedirects(): Promise {
+ const content = await fs.readFile(redirectsPath, 'utf-8');
+ return content.split('\n')
+ .filter(line => line.trim() && !line.startsWith('#'))
+ .map(line => {
+ const [from, to] = line.split(/\s+/);
+ return { from, to };
+ });
+}
+
+async function findMdxFiles(dir: string): Promise {
+ const files: string[] = [];
+ const entries = await fs.readdir(dir, { withFileTypes: true });
+
+ for (const entry of entries) {
+ const fullPath = path.join(dir, entry.name);
+ if (entry.isDirectory() && !entry.name.startsWith('_')) {
+ files.push(...await findMdxFiles(fullPath));
+ } else if (entry.isFile() && /\.(md|mdx)$/.test(entry.name)) {
+ files.push(fullPath);
+ }
+ }
+ return files;
+}
+
+function extractLinks(content: string): string[] {
+ const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
+ const hrefRegex = /href="([^"]+)"/g;
+ const links: string[] = [];
+
+ let match;
+ while ((match = markdownLinkRegex.exec(content)) !== null) {
+ if (!match[2].startsWith('http')) {
+ links.push(match[2]);
+ }
+ }
+ while ((match = hrefRegex.exec(content)) !== null) {
+ if (!match[1].startsWith('http')) {
+ links.push(match[1]);
+ }
+ }
+ return links;
+}
+
+async function checkFile(filePath: string, redirects: Redirect[]): Promise {
+ const content = await fs.readFile(filePath, 'utf-8');
+ const links = extractLinks(content);
+ const relativeFilePath = path.relative(rootDir, filePath);
+
+ links.forEach(link => {
+ const redirect = redirects.find(r => r.from === link);
+ if (redirect) {
+ warnings.push(formatWarning(relativeFilePath, link, redirect.to));
+ }
+ });
+}
+
+function printSummary(summary: Summary) {
+ console.log('\nSummary:');
+ console.log(`${WHITE}Total pages 🔍 - ${summary.total}`);
+ console.log(`${YELLOW}Pages broken 🚫 - ${summary.errors}`);
+ console.log(`${GREEN}Pages OK ✅ - ${summary.ok}${RESET}`);
+}
+
+async function main() {
+ const summary: Summary = {
+ total: 0,
+ ok: 0,
+ errors: 0
+ };
+
+ console.log('Starting redirect link check...');
+ console.log('Root directory:', rootDir);
+
+ try {
+ const redirects = await getRedirects();
+ const files = await findMdxFiles(rootDir);
+
+ summary.total = files.length;
+
+ for (const file of files) {
+ await checkFile(file, redirects);
+ }
+
+ summary.errors = warnings.length;
+ summary.ok = summary.total - summary.errors;
+
+ if (warnings.length > 0) {
+ console.log(`${YELLOW}${BOLD}Links that need updating:${RESET}`);
+ warnings.forEach(warning => console.log(warning));
+ printSummary(summary);
+ process.exit(1);
+ } else {
+ console.log(`${GREEN}All internal links are up to date.${RESET}`);
+ printSummary(summary);
+ }
+ } catch (error) {
+ console.error(`${YELLOW}${BOLD}Error checking redirects:${RESET}`, error);
+ process.exit(1);
+ }
+}
+
+main().catch(error => {
+ console.error(`${YELLOW}${BOLD}Error in main process:${RESET}`, error);
+ process.exit(1);
+});
\ No newline at end of file