diff --git a/.gitignore b/.gitignore
index 130dcf2b..7d0b7e0b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,7 @@
.vscode
+.idea
+
+node_modules
+venv
+
forta-docs
-venv
\ No newline at end of file
diff --git a/docs/2022-7-11 Forta Litepaper.pdf b/docs/2022-7-11_Forta_Litepaper.pdf
similarity index 100%
rename from docs/2022-7-11 Forta Litepaper.pdf
rename to docs/2022-7-11_Forta_Litepaper.pdf
diff --git a/docs/FAQs.md b/docs/FAQs.md
index 483bddbd..bd447354 100644
--- a/docs/FAQs.md
+++ b/docs/FAQs.md
@@ -29,7 +29,7 @@ You can use Forta’s GraphQL API to query the data, or subscribe to notificatio
[Here](https://docs.forta.network/en/latest/api-reference/) you can learn how to use the API.
-[Here](https://docs.forta.network/en/latest/subcribing-to-bot/) you can learn how to subscribe to notifications using the Forta App.
+[Here](subscribing-to-bot.md) you can learn how to subscribe to notifications using the Forta App.
### **How can I find valuable detection bots in the network?**
@@ -167,4 +167,4 @@ The community of data scientists and developers in the Forta Network have create
### **Is Forta a blockchain?**
-Short answer: no. Forta is not a blockchain in the sense that it does not maintain a shared state between nodes, but it has similar characteristics to a blockchain in that it is a decentralized network of nodes. Forta is more similar to The Graph protocol in that it runs on top of a blockchain. Forta uses a blockchain (i.e. Polygon) to coordinate scan nodes and store IPFS references to alerts generated by bots.
\ No newline at end of file
+Short answer: no. Forta is not a blockchain in the sense that it does not maintain a shared state between nodes, but it has similar characteristics to a blockchain in that it is a decentralized network of nodes. Forta is more similar to The Graph protocol in that it runs on top of a blockchain. Forta uses a blockchain (i.e. Polygon) to coordinate scan nodes and store IPFS references to alerts generated by bots.
diff --git a/docs/Forta Network Airdrop Audit Report.pdf b/docs/Forta_Network_Airdrop_Audit_Report.pdf
similarity index 100%
rename from docs/Forta Network Airdrop Audit Report.pdf
rename to docs/Forta_Network_Airdrop_Audit_Report.pdf
diff --git a/docs/OZ Forta Protocol Audit.pdf b/docs/OZ_Forta_Protocol_Audit.pdf
similarity index 100%
rename from docs/OZ Forta Protocol Audit.pdf
rename to docs/OZ_Forta_Protocol_Audit.pdf
diff --git a/docs/scam-detector-bot.md b/docs/scam-detector-bot.md
index 601def0d..b2b16aec 100644
--- a/docs/scam-detector-bot.md
+++ b/docs/scam-detector-bot.md
@@ -54,7 +54,7 @@ The Scam Detector *labels* are each available via Forta's GraphQL API. For acces
### *Labels*
Labels allow a contributor to tag an entity (like an address) with a label. Labels are available via our [GraphQL API](https://docs.forta.network/en/latest/forta-api-reference/#query-labels). This API allows one to search by date range and page over results.
-A recent one month sample of the labels can be downloaded [here (prod, V1 label format)](../ScamDetector_0x1d646c4045189991fdfd24a66b192a294158b839a6ec121d740474bdacb3ab23_labels_20230601-20230630.csv) and [here (beta, V2 label format)](../ScamDetector_0x47c45816807d2eac30ba88745bf2778b61bc106bc76411b520a5289495c76db8_labels_20230622-20230630.csv).
+A recent one month sample of the labels can be downloaded [here (prod, V1 label format)](ScamDetector_0x1d646c4045189991fdfd24a66b192a294158b839a6ec121d740474bdacb3ab23_labels_20230601-20230630.csv) and [here (beta, V2 label format)](ScamDetector_0x47c45816807d2eac30ba88745bf2778b61bc106bc76411b520a5289495c76db8_labels_20230622-20230630.csv).
**Note:**
diff --git a/docs/scan-node/monitor.md b/docs/scan-node/monitor.md
index 004052d3..6e686dd8 100644
--- a/docs/scan-node/monitor.md
+++ b/docs/scan-node/monitor.md
@@ -14,7 +14,7 @@ into your browser's search bar.
![View scan node SLA example](../scan-node-view.png)
-You can use the [SLA API](sla-api.md) for more details insights.
+You can use the [SLA API](../sla-api.md) for more details insights.
Please note that the number in Forta App can differ from what SLA API returns because of time range differences (see Q3 in [FAQ](faq.md)).
diff --git a/docs/security-design.md b/docs/security-design.md
index 0e7ef198..e2a70e4d 100644
--- a/docs/security-design.md
+++ b/docs/security-design.md
@@ -1,6 +1,6 @@
## Security - Design
-Security starts in the design phase by incorporating strategies to disincentivize and mitigate malicious behaviors. As described in the [Forta Litepaper](../2022-7-11 Forta Litepaper.pdf), the primary guiding principles around the Forta protocol were:
+Security starts in the design phase by incorporating strategies to disincentivize and mitigate malicious behaviors. As described in the [Forta Litepaper](2022-7-11_Forta_Litepaper.pdf), the primary guiding principles around the Forta protocol were:
- **Decentralization** - this not only applies to the network of independent scan nodes, but also to the detection bots that the community develops and the governance over the Network. This creates redundancies and increases the reliability of the network in case isolated failures occur.
- **Cryptoeconomic Incentives** - these incentives, like node and bot staking, encourage net positive behavior and disincentivize malicious behaviors.
@@ -20,4 +20,4 @@ As an example, the [Forta Litepaper](../2022-7-11 Forta Litepaper.pdf) detailed
The architecture of the Forta smart contracts can be seen in the below diagram.
-![Forta Network Smart Contract architecture](Forta-Contract-Architecture.jpg)
\ No newline at end of file
+![Forta Network Smart Contract architecture](Forta-Contract-Architecture.jpg)
diff --git a/docs/security-testing.md b/docs/security-testing.md
index 75cdca5e..45ea6d23 100644
--- a/docs/security-testing.md
+++ b/docs/security-testing.md
@@ -31,10 +31,10 @@ Testing of the code must happen through GitHub Actions on each pull request and
Adopting an attacker mindset, the Foundation went beyond employing secure design, development, deployment and testing and enlisted external security experts to assess the Forta Network after it was built. This helped to surface erroneous assumptions and uncover security gaps that may have remained hidden. Forta primarily engaged OpenZeppelin's smart contract auditing expertise as well as Dedalo's web2 and broad threat assessment expertise for other critical components of the Network. All such reports/findings are linked below:
-- [Dedalo's Airdrop Assessment, June 17th 2022](../2022Q2-FortaAirdrop-AuditReport.pdf)
-- OpenZeppelin's Airdrop Smart Contract Audit, June 9th 2022
-- [Dedalo's Forta Scan Node Assessment, April 7th 2022](../2022Q1-V2-FortaNode-AuditReport.pdf)
-- OpenZeppelin's Protocol Audit, February 7th 2022
-- [Dedalo's Web Security Assessment, January 5th 2022 ](../Forta-Report-DDL-05-01-2022.pdf)
+- [Dedalo's Airdrop Assessment, June 17th 2022](2022Q2-FortaAirdrop-AuditReport.pdf)
+- [OpenZeppelin's Airdrop Smart Contract Audit, June 9th 2022](Forta_Network_Airdrop_Audit_Report.pdf)
+- [Dedalo's Forta Scan Node Assessment, April 7th 2022](2022Q1-V2-FortaNode-AuditReport.pdf)
+- [OpenZeppelin's Protocol Audit, February 7th 2022](OZ_Forta_Protocol_Audit.pdf)
+- [Dedalo's Web Security Assessment, January 5th 2022 ](Forta-Report-DDL-05-01-2022.pdf)
- [MixBytes' Security Assessment of slash proposal changes, September 2nd 2022](https://github.com/forta-network/forta-contracts/commit/c940dc39b94bc8be6c298deab92a3dd55527f321)
-- [Consensys' Security Assessment of Delegated Staking, November 2022](https://consensys.net/diligence/audits/2022/11/forta-delegated-staking/)
\ No newline at end of file
+- [Consensys' Security Assessment of Delegated Staking, November 2022](https://consensys.net/diligence/audits/2022/11/forta-delegated-staking/)
diff --git a/docs/slashing-creating-proposal.md b/docs/slashing-creating-proposal.md
index 355fda43..1144435c 100644
--- a/docs/slashing-creating-proposal.md
+++ b/docs/slashing-creating-proposal.md
@@ -4,7 +4,7 @@ Anyone who detects a slashable offence perpetrated by a Scan Node or Bot may rep
## Requirements.
-- Have 1000 FORT bridged to Polygon, per proposal. ** This deposit can be slashed if the proposal does not follow appropriate formatting, it is malicious, false or spam**. Check the [slashing process](./slashing-process.md) for more info.
+- Have 1000 FORT bridged to Polygon, per proposal. ** This deposit can be slashed if the proposal does not follow appropriate formatting, it is malicious, false or spam**. Check the [slashing policy](slashing-policy.md) for more info.
- Present verifiable evidence, in the form of screen captures, log files or any file that proves the accusations.
- Have knowledge of uploading files to IPFS.
- Is able to present evidence following Forta's [Evidence Format](#evidence-format)
diff --git a/docs/useful-libraries.md b/docs/useful-libraries.md
index d375c2ca..5af889c8 100644
--- a/docs/useful-libraries.md
+++ b/docs/useful-libraries.md
@@ -1,6 +1,6 @@
# Useful libraries
-Here is a collection of libraries and APIs that bot developers may find useful when building their bots. Want to add your library here? Check out [this section](/contributing/#improve-the-documentation) to suggest an edit.
+Here is a collection of libraries and APIs that bot developers may find useful when building their bots. Want to add your library here? Check out [this section](contributing#improve-the-documentation) to suggest an edit.
## forta-helpers
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..c676b776
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,656 @@
+{
+ "name": "forta-docs",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "forta-docs",
+ "version": "1.0.0",
+ "license": "MIT",
+ "devDependencies": {
+ "fs-extra": "^11.2.0",
+ "glob": "^10.3.12",
+ "yamljs": "^0.3.0"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true
+ },
+ "node_modules/foreground-child": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+ "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
+ "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/glob": {
+ "version": "10.3.12",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
+ "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
+ "dev": true,
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^2.3.6",
+ "minimatch": "^9.0.1",
+ "minipass": "^7.0.4",
+ "path-scurry": "^1.10.2"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/jackspeak": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
+ "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
+ "dev": true,
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
+ "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
+ "dev": true,
+ "engines": {
+ "node": "14 || >=16.14"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
+ "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+ "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-scurry": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
+ "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/yamljs": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz",
+ "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "glob": "^7.0.5"
+ },
+ "bin": {
+ "json2yaml": "bin/json2yaml",
+ "yaml2json": "bin/yaml2json"
+ }
+ },
+ "node_modules/yamljs/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/yamljs/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/yamljs/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..8448c42b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "forta-docs",
+ "version": "1.0.0",
+ "description": "Forta Documentation",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [
+ "forta",
+ "documentation"
+ ],
+ "author": "",
+ "license": "MIT",
+ "devDependencies": {
+ "fs-extra": "^11.2.0",
+ "glob": "^10.3.12",
+ "yamljs": "^0.3.0"
+ }
+}
diff --git a/scripts/remove-unused-files.js b/scripts/remove-unused-files.js
new file mode 100644
index 00000000..ed0d9835
--- /dev/null
+++ b/scripts/remove-unused-files.js
@@ -0,0 +1,211 @@
+const fs = require('fs')
+const path = require('path')
+const yaml = require('yamljs')
+const { globSync } = require('glob')
+
+function exploreMarkdownFilesFromConfig (nav, markdownList, mediaList, visited) {
+ if (Array.isArray(nav)) {
+ nav.forEach(item => exploreMarkdownFilesFromConfig(item, markdownList, mediaList, visited))
+ } else if (typeof nav === 'object') {
+ Object.values(nav).forEach(value => {
+ if (typeof value === 'string') {
+ processFile(path.join(docsDir, value), markdownList, mediaList, visited)
+ } else {
+ exploreMarkdownFilesFromConfig(value, markdownList, mediaList, visited)
+ }
+ })
+ }
+}
+
+function exploreFilesFromFile (filePath, markdownLinks, mediaLinks, visited) {
+ let absolutePath = path.resolve(filePath)
+
+ if (visited.has(absolutePath)) {
+ return
+ }
+
+ visited.add(absolutePath)
+
+ if (absolutePath.includes('https:') || absolutePath.includes('http:')) {
+ return
+ }
+
+ let content
+ try {
+ content = fs.readFileSync(absolutePath, 'utf8')
+ } catch (error) {
+ console.error(`Error reading file ${absolutePath}: ${error}`)
+ return
+ }
+
+ const markdownLinkRegex = /\[.*?\]\((\S+\.[a-zA-Z-]+)(?:#[a-zA-Z-\d]+)?(?:\s+".+")?\s*\)/gi
+ const linkRegex = /docs\.forta\.network\/en\/latest\/([a-zA-Z-/\d\.]+)(?:#[a-zA-Z-\d]+)?/gi
+ const imgTagRegex = / folder/page-name/
+ const pagePath = getPagePath(absolutePath)
+
+ // Process HTML img tags
+ while ((match = imgTagRegex.exec(content)) !== null) {
+ processMediaLink(match[1], pagePath, markdownLinks, mediaLinks, visited)
+ }
+
+ // Process HTML video tags
+ while ((match = videoTagRegex.exec(content)) !== null) {
+ processMediaLink(match[1], pagePath, markdownLinks, mediaLinks, visited)
+ }
+}
+
+// path/docs/readme-file.md => path/docs/readme-file/
+function getPagePath (absolutePath) {
+ const parsedPath = path.parse(absolutePath)
+
+ const folderPath = parsedPath.dir
+ const fileName = parsedPath.name // no ext
+ const pagePath = folderPath + '/' + fileName + '/'
+
+ return pagePath
+}
+
+function processFile (fullPath, markdownLinks, mediaLinks, visited) {
+ const fullPathLowerCase = fullPath.toLowerCase()
+
+ if (fullPathLowerCase.endsWith('.md')) {
+ markdownLinks.add(fullPath)
+ exploreFilesFromFile(fullPath, markdownLinks, mediaLinks, visited)
+ } else if (/\.(png|jpg|jpeg|gif|bmp|svg|mp4|avi|mov|webm|pdf|csv)$/i.test(fullPathLowerCase)) {
+ mediaLinks.add(fullPath)
+ }
+}
+
+function processSiteLink (linkPath, markdownLinks, mediaLinks, visited) {
+ if(!(/\S+\.\S+/.test(linkPath))) {
+ linkPath = linkPath.replace(/\/$/, "") + '.md';
+ }
+
+ let absolutePath = path.resolve(docsDir, linkPath)
+
+ processFile(absolutePath, markdownLinks, mediaLinks, visited)
+}
+
+function processMarkdownLink (link, currentPath, markdownLinks, mediaLinks, visited) {
+ const fullPath = path.resolve(path.dirname(currentPath), link)
+ processFile(fullPath, markdownLinks, mediaLinks, visited)
+}
+
+function processMediaLink (link, currentPath, markdownLinks, mediaLinks, visited) {
+ const fullPath = path.resolve(currentPath, link)
+ processFile(fullPath, markdownLinks, mediaLinks, visited)
+}
+
+// Here we start execution
+// ------------
+
+const SHOULD_REMOVE = false;
+const SHOULD_LOG = true;
+
+const EXCLUDE_FOLDERS = ['forta-api-reference', 'stylesheets', 'contracts'];
+const INCLUDE_DOC_FILES = ['scam-detector-bot.md', 'attack-detector-bot.md'];
+
+// Load and parse the mkdocs config
+const docsDir = path.resolve(__dirname, '../docs')
+
+const mkdocsConfig = yaml.load('mkdocs.yml')
+
+const usedMarkdownFileSet = new Set()
+const usedMediaFileSet = new Set()
+const visitedFileSet = new Set()
+
+for(const docFile of INCLUDE_DOC_FILES) {
+ processFile(path.resolve(docsDir, docFile), usedMarkdownFileSet, usedMediaFileSet, visitedFileSet)
+}
+
+// Parse mkdocs config
+exploreMarkdownFilesFromConfig(mkdocsConfig.nav, usedMarkdownFileSet, usedMediaFileSet, visitedFileSet)
+exploreMarkdownFilesFromConfig(mkdocsConfig.theme, usedMarkdownFileSet, usedMediaFileSet, visitedFileSet)
+// Parse meta tags
+exploreFilesFromFile(path.resolve(docsDir, '../overrides', 'main.html'), usedMarkdownFileSet, usedMediaFileSet, visitedFileSet)
+
+// Recursively find all related files referenced within each file
+for (const filePath of [...usedMarkdownFileSet]) {
+ exploreFilesFromFile(filePath, usedMarkdownFileSet, usedMediaFileSet, visitedFileSet)
+}
+
+const allMarkdownFiles = globSync(`${docsDir}/**/*.md`, { ignore: 'node_modules/**', absolute: true })
+const allMediaFiles = globSync(`${docsDir}/**/*.{png,jpg,jpeg,gif,bmp,svg,mp4,avi,mov,webm,pdf,csv}`, {
+ ignore: 'node_modules/**',
+ absolute: true
+})
+
+const canBeRemovedMarkdownFiles = new Set(allMarkdownFiles.filter(file => !usedMarkdownFileSet.has(file) && !file.includes('index.md')))
+const canBeRemovedMediaFiles = new Set(allMediaFiles.filter(file => !usedMediaFileSet.has(file)))
+
+for(const folder of EXCLUDE_FOLDERS) {
+ const folderPath = path.resolve(docsDir, folder)
+
+ for(const filePath of canBeRemovedMarkdownFiles) {
+ if(filePath.includes(folderPath)) {
+ canBeRemovedMarkdownFiles.delete(filePath)
+ }
+ }
+
+ for(const filePath of canBeRemovedMediaFiles) {
+ if(filePath.includes(folderPath)) {
+ canBeRemovedMediaFiles.delete(filePath)
+ }
+ }
+}
+
+console.log()
+console.log('-----------------------------')
+console.log(`Used markdown files: ${usedMarkdownFileSet.size}`)
+console.log(`Unused markdown files: ${canBeRemovedMarkdownFiles.size}`)
+console.log(`Used media files: ${usedMediaFileSet.size}`)
+console.log(`Unused media files: ${canBeRemovedMediaFiles.size}`)
+console.log('-----------------------------')
+console.log()
+
+if(SHOULD_LOG) {
+ for (const file of usedMarkdownFileSet) {
+ console.log(`[IN USE]: ${file}`)
+ }
+
+ console.log('--------------------')
+
+ for (const file of canBeRemovedMarkdownFiles) {
+ console.log(`[REMOVE]: ${file}`)
+ }
+
+ console.log('--------------------')
+
+ for (const file of usedMediaFileSet) {
+ console.log(`[IN USE]: ${file}`)
+ }
+
+ console.log('--------------------')
+
+ for (const file of canBeRemovedMediaFiles) {
+ console.log(`[REMOVE]: ${file}`)
+ }
+}
+
+if(SHOULD_REMOVE) {
+ for (const file of canBeRemovedMarkdownFiles) {
+ fs.unlinkSync(file);
+ }
+ for (const file of canBeRemovedMediaFiles) {
+ fs.unlinkSync(file);
+ }
+}