diff --git a/.gitignore b/.gitignore index 32a6447..3a8eb31 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ foundry-docs/ # Node modules node_modules/ +docs-gen/bun.lock .DS_Store src/.DS_Store diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 59f861d..0000000 --- a/.prettierrc +++ /dev/null @@ -1,30 +0,0 @@ -{ - "bracketSpacing": false, - "singleQuote": true, - "arrowParens": "avoid", - "plugins": ["prettier-plugin-solidity"], - "overrides": [ - { - "files": "*.sol", - "options": { - "parser": "solidity-parse", - "printWidth": 100, - "tabWidth": 4, - "useTabs": false, - "singleQuote": false, - "bracketSpacing": false, - "compiler": "0.8.28" - } - }, - { - "files": "*.md", - "options": { - "printWidth": 100, - "tabWidth": 2, - "useTabs": false, - "singleQuote": true, - "bracketSpacing": false - } - } - ] -} \ No newline at end of file diff --git a/.solhint.json b/.solhint.json index da7a64b..303ec4d 100644 --- a/.solhint.json +++ b/.solhint.json @@ -1,6 +1,5 @@ { "extends": "solhint:recommended", - "plugins": ["prettier"], "rules": { "code-complexity": ["error", 9], "compiler-version": ["error", ">=0.8.4"], @@ -8,12 +7,6 @@ "max-line-length": ["error", 120], "named-parameters-mapping": "off", "no-console": "off", - "not-rely-on-time": "off", - "prettier/prettier": [ - "error", - { - "endOfLine": "auto" - } - ] + "not-rely-on-time": "off" } } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c84ad63 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "prettier.documentSelectors": ["**/*.sol"], + "solidity.formatter": "forge", + "editor.tabSize": 4, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[solidity]": { + "editor.defaultFormatter": "JuanBlanco.solidity" + } +} diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..57f1170 --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,13 @@ +{ + "languages": { + "Solidity": { + "format_on_save": "on", + "formatter": { + "external": { + "command": "forge", + "arguments": ["fmt", "-", "--raw"] + } + } + } + } +} diff --git a/docs-gen/bunfig.toml b/docs-gen/bunfig.toml new file mode 100644 index 0000000..a1e564d --- /dev/null +++ b/docs-gen/bunfig.toml @@ -0,0 +1,2 @@ +[install] +lifecycle-scripts = false diff --git a/docs-gen/package.json b/docs-gen/package.json new file mode 100644 index 0000000..754bb79 --- /dev/null +++ b/docs-gen/package.json @@ -0,0 +1,16 @@ +{ + "private": true, + "version": "1.1", + "repository": { + "type": "git", + "url": "https://github.com/aragon/staged-proposal-processor-plugin" + }, + "devDependencies": { + "@aragon/osx-commons-configs": "^1.4.6", + "fs-extra": "^11.0.0", + "glob": "^8.0.0", + "lodash.startcase": "^4.4.0", + "solc": "^0.8.28", + "solidity-docgen": "^0.6.0-beta.36" + } +} diff --git a/docs-gen/prepare-docs.js b/docs-gen/prepare-docs.js new file mode 100644 index 0000000..025e62c --- /dev/null +++ b/docs-gen/prepare-docs.js @@ -0,0 +1,121 @@ +const docgen = require("solidity-docgen/dist/main"); + +const { readdir } = require("node:fs/promises"); +const { join } = require("node:path"); +const path = require("path"); +const fs = require("fs-extra"); +const solc = require("solc"); +const glob = require("glob"); +const startCase = require("lodash.startcase"); +const { version, repository } = require("./package.json"); + +const ROOT_DIR = path.resolve(__dirname, ".."); + +const walk = async (dirPath) => + Promise.all( + await readdir(dirPath, { withFileTypes: true }).then((entries) => + entries.map((entry) => { + const childPath = join(dirPath, entry.name); + return entry.isDirectory() ? walk(childPath) : childPath; + }), + ), + ); + +let includePaths = [path.join(__dirname, "node_modules")]; + +function resolveImports(filePath) { + for (const includePath of includePaths) { + const fullPath = path.resolve(includePath, filePath); + if (fs.existsSync(fullPath)) { + return { contents: fs.readFileSync(fullPath, "utf8") }; + } + } + return { error: `File not found: ${filePath}` }; +} + +const compile = async (filePaths) => { + const compilerInput = { + language: "Solidity", + sources: filePaths.reduce((input, fileName) => { + const source = fs.readFileSync(fileName, "utf8"); + return { ...input, [fileName]: { content: source } }; + }, {}), + settings: { + outputSelection: { + "*": { + "*": ["*"], + "": ["ast"], + }, + }, + }, + }; + + return { + output: JSON.parse( + solc.compile(JSON.stringify(compilerInput), { + import: resolveImports, + }), + ), + input: compilerInput, + }; +}; + +function generateNav(pagesDir) { + const files = glob + .sync(pagesDir + "/**/*.adoc") + .map((f) => path.relative(pagesDir, f)); + + function getPageTitle(name) { + switch (name) { + case "metatx": return "Meta Transactions"; + case "common": return "Common (Tokens)"; + default: return startCase(name); + } + } + + const links = files + .map((file) => ({ xref: `* xref:${file}[${getPageTitle(path.parse(file).name)}]`, title: path.parse(file).name })) + .sort((a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase(), undefined, { numeric: true })); + + return [".API", ...links.map((l) => l.xref)].join("\n") + "\n"; +} + +async function main() { + const contractPath = path.resolve(ROOT_DIR, "src"); + const allFiles = await walk(contractPath); + + const solFiles = allFiles.flat(Number.POSITIVE_INFINITY).filter((item) => { + return path.extname(item).toLowerCase() == ".sol"; + }); + + const { input, output } = await compile(solFiles); + + const templatesPath = path.resolve(ROOT_DIR, "docs/templates"); + const apiPath = path.resolve(ROOT_DIR, "docs/modules/api"); + + const helpers = require(path.join(templatesPath, "helpers")); + + // overwrite the functions. + helpers.version = () => `${version}`; + helpers.githubURI = () => repository.url; + + const config = { + outputDir: `${apiPath}/pages`, + sourcesDir: contractPath, + templates: templatesPath, + exclude: ["mocks", "test"], + pageExtension: ".adoc", + collapseNewlines: true, + pages: (_, file, config) => { + return "StagedProposalProcessor" + config.pageExtension; + }, + }; + + await docgen.main([{ input: input, output: await output }], config); + + fs.writeFileSync(`${apiPath}/nav.adoc`, generateNav(`${apiPath}/pages`), "utf8"); + + fs.rm(templatesPath, { recursive: true, force: true }, () => {}); +} + +main(); diff --git a/docs-gen/prepare-docs.sh b/docs-gen/prepare-docs.sh new file mode 100644 index 0000000..5909506 --- /dev/null +++ b/docs-gen/prepare-docs.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +rm -rf "$ROOT/docs/templates" + +PACKAGE_PATH=$(bun -p "require.resolve('@aragon/osx-commons-configs')") +TEMPLATES_PATH=$(dirname "$PACKAGE_PATH")/docs/templates + +[ -d "$TEMPLATES_PATH" ] || { echo "Error: templates not found at $TEMPLATES_PATH" >&2; exit 1; } + +mkdir -p "$ROOT/docs/templates" +cp -r "$TEMPLATES_PATH" "$ROOT/docs" diff --git a/justfile b/justfile index 8008a7f..654fdbb 100644 --- a/justfile +++ b/justfile @@ -1,6 +1,12 @@ default: help + import 'lib/just-foundry/justfile' +# Generate Solidity documentation (requires bun) +[group('docs')] +docs: + cd docs-gen && bun install && bash prepare-docs.sh && bun prepare-docs.js + DEPLOY_SCRIPT := "script/Deploy.s.sol:Deploy" # Publish a new SPP plugin version (deploys setup, prints DAO proposal calldata) @@ -8,10 +14,10 @@ DEPLOY_SCRIPT := "script/Deploy.s.sol:Deploy" new-version *args: #!/usr/bin/env bash set -euo pipefail - source {{ENV_RESOLVE_LIB}} && env_load_network + source {{ ENV_RESOLVE_LIB }} && env_load_network mkdir -p logs LOG_FILE="logs/new-version-$NETWORK_NAME-$(date +"%y-%m-%d-%H-%M").log" just test 2>&1 | tee -a "$LOG_FILE" - just run script/NewVersion.s.sol:NewVersion {{args}} 2>&1 | tee -a "$LOG_FILE" + just run script/NewVersion.s.sol:NewVersion {{ args }} 2>&1 | tee -a "$LOG_FILE" echo "Logs saved in $LOG_FILE" diff --git a/npm-artifacts/prepare-abi.sh b/npm-artifacts/prepare-abi.sh index c8aa571..07dcd10 100644 --- a/npm-artifacts/prepare-abi.sh +++ b/npm-artifacts/prepare-abi.sh @@ -21,7 +21,7 @@ do CONTRACT_NAME=${SRC_FILE_NAME%".sol"} SRC_FILE_PATH=$BUILD_OUT_FOLDER/$SRC_FILE_NAME/${SRC_FILE_NAME%".sol"}.json - ABI=$(node -p "JSON.stringify(JSON.parse(fs.readFileSync(\"$SRC_FILE_PATH\").toString()).abi)") + ABI=$(bun -p "JSON.stringify(JSON.parse(fs.readFileSync(\"$SRC_FILE_PATH\").toString()).abi)") echo "const ${CONTRACT_NAME}ABI = $ABI as const;" >> $TARGET_ABI_FILE echo "export {${CONTRACT_NAME}ABI};" >> $TARGET_ABI_FILE diff --git a/package.json b/package.json deleted file mode 100644 index db6ea51..0000000 --- a/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "private": true, - "version": "1.1", - "author": "Aragon X", - "repository": { - "type": "git", - "url": "https://github.com/aragon/staged-proposal-processor-plugin" - }, - "homepage": "https://github.com/aragon/staged-proposal-processor-plugin#readme", - "license": "AGPL-3.0-or-later", - "keywords": [ - "blockchain", - "ethereum", - "forge", - "foundry", - "smart-contracts", - "solidity", - "aragon", - "osx", - "dao", - "daobox" - ], - "scripts": { - "lint": "yarn lint:sol && yarn prettier:check", - "lint:sol": "solhint --max-warnings 0 \"{script,src,test}/**/*.sol\"", - "prettier:check": "prettier --check --plugin=prettier-plugin-solidity \"**/*.{js,json,md,sol,ts,yml}\"", - "prettier:write": "prettier --write --plugin=prettier-plugin-solidity \"**/*.{js,json,md,sol,ts,yml}\"", - "docs": "script/prepare-docs.sh && node ./script/prepare-docs.js" - }, - "devDependencies": { - "lodash.startcase": "^4.4.0", - "prettier": "^2.8.8", - "prettier-plugin-solidity": "^1.1.3", - "solc": "^0.8.28", - "solhint": "^3.6.2", - "solhint-plugin-prettier": "^0.0.5", - "solidity-docgen": "^0.6.0-beta.36" - } -} diff --git a/script/gen-nav.js b/script/gen-nav.js deleted file mode 100755 index f3f2963..0000000 --- a/script/gen-nav.js +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env node - -const path = require('path'); -const glob = require('glob'); -const startCase = require('lodash.startcase'); - -const baseDir = process.argv[2]; - -const files = glob - .sync(baseDir + '/**/*.adoc') - .map(f => path.relative(baseDir, f)); - -console.log('.API'); - -function getPageTitle(directory) { - switch (directory) { - case 'metatx': - return 'Meta Transactions'; - case 'common': - return 'Common (Tokens)'; - default: - return startCase(directory); - } -} - -const links = files.map(file => { - const doc = file.replace(baseDir, ''); - const title = path.parse(file).name; - - return { - xref: `* xref:${doc}[${getPageTitle(title)}]`, - title, - }; -}); - -// Case-insensitive sort based on titles (so 'token/ERC20' gets sorted as 'erc20') -const sortedLinks = links.sort(function (a, b) { - return a.title - .toLowerCase() - .localeCompare(b.title.toLowerCase(), undefined, {numeric: true}); -}); - -for (const link of sortedLinks) { - console.log(link.xref); -} diff --git a/script/prepare-docs.js b/script/prepare-docs.js deleted file mode 100644 index 5b47f5a..0000000 --- a/script/prepare-docs.js +++ /dev/null @@ -1,101 +0,0 @@ -const docgen = require('solidity-docgen/dist/main'); - -const { readdir } = require('node:fs/promises') -const { join } = require('node:path') -const path = require('path'); -const fs = require("fs-extra"); -const solc = require("solc"); -const { execSync } = require('child_process'); -const { version, repository } = require('../package.json'); - -const ROOT_DIR = path.resolve(__dirname, '..') - -const walk = async (dirPath) => Promise.all( - await readdir(dirPath, { withFileTypes: true }).then((entries) => entries.map((entry) => { - const childPath = join(dirPath, entry.name) - return entry.isDirectory() ? walk(childPath) : childPath - })), -) - -let includePaths = ['node_modules'] - -function resolveImports(filePath) { - for (const includePath of includePaths) { - const fullPath = path.resolve(ROOT_DIR, path.join(includePath, filePath)); - if (fs.existsSync(fullPath)) { - return { contents: fs.readFileSync(fullPath, 'utf8') }; - } - } - return { error: `File not found: ${filePath}` }; -} - -const compile = async (filePaths) => { - const compilerInput = { - language: "Solidity", - sources: filePaths.reduce((input, fileName) => { - const source = fs.readFileSync(fileName, "utf8"); - return { ...input, [fileName]: { content: source } }; - }, {}), - settings: { - outputSelection: { - "*": { - '*': ['*'], - "": [ - "ast" - ] - }, - }, - }, - }; - - return { - output: JSON.parse(solc.compile(JSON.stringify(compilerInput), { - import: resolveImports, - })), - input: compilerInput - } -} - -async function main() { - const contractPath = path.resolve(ROOT_DIR, "src"); - const allFiles = await walk(contractPath); - - const solFiles = allFiles.flat(Number.POSITIVE_INFINITY).filter(item => { - return path.extname(item).toLowerCase() == '.sol' - }) - - const { input, output } = await compile(solFiles) - - const templatesPath = 'docs/templates' - const apiPath = 'docs/modules/api' - - const helpers = require(path.resolve(ROOT_DIR, 'docs/templates/helpers')) - - // overwrite the functions. - helpers.version = () => `${version}`; - helpers.githubURI = () => repository.url; - - const config = { - outputDir: `${apiPath}/pages`, - sourcesDir: path.resolve(ROOT_DIR, "src"), - templates: templatesPath, - exclude: ['mocks', 'test'], - pageExtension: '.adoc', - collapseNewlines: true, - pages: (_, file, config) => { - return 'StagedProposalProcessor' + config.pageExtension; - }, - }; - - await docgen.main([{ input: input, output: await output }], config); - - const navOutput = execSync(`node script/gen-nav.js ${apiPath}/pages`, { encoding: 'utf8' }); - - // Write the output to the target file - const targetFilePath = `${apiPath}/nav.adoc`; - fs.writeFileSync(targetFilePath, navOutput, 'utf8'); - - fs.rm(templatesPath, { recursive: true, force: true }, () => { }) -} - -main() \ No newline at end of file diff --git a/script/prepare-docs.sh b/script/prepare-docs.sh deleted file mode 100755 index 80a0cd8..0000000 --- a/script/prepare-docs.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -rm -rf ./docs/templates - -PACKAGE_NAME="@aragon/osx-commons-configs" -PACKAGE_PATH=$(node -p "require.resolve('$PACKAGE_NAME')") -TEMPLATES_PATH=$(dirname "$PACKAGE_PATH")/docs/templates - -mkdir -p ./docs/templates - -cp -r "$TEMPLATES_PATH" "./docs" - -if [ ! -d node_modules ]; then - npm ci -fi