From 4fe552628c50ce414d0079d8a6a93e540f2f646e Mon Sep 17 00:00:00 2001 From: Manh Cao Date: Mon, 16 Sep 2024 16:52:53 +0700 Subject: [PATCH] init verify schema github action --- .github/workflows/combine-config.yml | 2 +- .github/workflows/validate-config.yml | 41 +++++++++++ protocols/protocol-name-1/config.json | 2 +- protocols/protocol-name-2/config.json | 2 +- validate-config.js | 99 +++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/validate-config.yml create mode 100644 validate-config.js diff --git a/.github/workflows/combine-config.yml b/.github/workflows/combine-config.yml index af489c5..626275c 100644 --- a/.github/workflows/combine-config.yml +++ b/.github/workflows/combine-config.yml @@ -26,7 +26,7 @@ jobs: git config --global user.name "$GIT_USERNAME" git config --global user.email "$GIT_EMAIL" git add config.json - git commit -m "Merge JSON files" + git diff-index --quiet HEAD || git commit -m "Merge JSON files" git push env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/validate-config.yml b/.github/workflows/validate-config.yml new file mode 100644 index 0000000..7abb58e --- /dev/null +++ b/.github/workflows/validate-config.yml @@ -0,0 +1,41 @@ +name: Validate configuration files + +on: + push: + branches: + - '**' + +jobs: + merge_json_files: + runs-on: ubuntu-latest + + if: github.ref != 'refs/heads/main' + + steps: + - name: Checkout HEAD (current branch) + uses: actions/checkout@v4 + with: + fetch-depth: 1 # Fetch only the latest commit of the current branch + + - name: Save HEAD commit SHA + id: head + run: echo "HEAD_SHA=$(git rev-parse HEAD)" >> $GITHUB_ENV + + - name: Checkout main branch + run: | + git fetch origin main --depth=1 # Fetch only the latest commit of main + git checkout FETCH_HEAD + echo "MAIN_SHA=$(git rev-parse HEAD)" >> $GITHUB_ENV + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' # Or your preferred version + + - name: Calculate changed folders + run: | + echo "CHANGED_FOLDERS=$$(git diff --name-only $MAIN_SHA $HEAD_SHA -- 'protocols/*' | grep '^protocols/' | xargs -L1 dirname | sed 's|protocols/||' | sort -u)" >> $GITHUB_ENV + + - name: Validate + run: | + node validate-config.js \ No newline at end of file diff --git a/protocols/protocol-name-1/config.json b/protocols/protocol-name-1/config.json index 733f360..854b596 100644 --- a/protocols/protocol-name-1/config.json +++ b/protocols/protocol-name-1/config.json @@ -1,5 +1,5 @@ { - "name": "Protocol Name 1", + "name": "Protocol Name 1 abc", "icon": "logo.svg", "metadata": { "pt": [ diff --git a/protocols/protocol-name-2/config.json b/protocols/protocol-name-2/config.json index dafeae2..4982198 100644 --- a/protocols/protocol-name-2/config.json +++ b/protocols/protocol-name-2/config.json @@ -1,5 +1,5 @@ { - "name": "Protocol Name 2", + "name": "Protocol Name 2 abc", "icon": "logo.jpeg", "metadata": { "pt": [ diff --git a/validate-config.js b/validate-config.js new file mode 100644 index 0000000..3334c3a --- /dev/null +++ b/validate-config.js @@ -0,0 +1,99 @@ +const fs = require('fs'); +const path = require('path'); + +const CHANGED_PROTOCOLS = process.env.CHANGED_PROTOCOLS; + +function isValidEthereumAddress(address) { + const ethereumAddressPattern = /^0x[a-fA-F0-9]{40}$/; + return ethereumAddressPattern.test(address); +} + +async function main() { + if (!CHANGED_PROTOCOLS) { + console.log('No changed protocols'); + return; + } + + const protocols = CHANGED_PROTOCOLS.split('\n'); + + console.log('Currently validating protocols:', protocols); + + protocols.forEach((protocol) => validateConfig(protocol)); +} + +function validateConfig(protocol) { + const protocolsPath = path.join(__dirname, 'protocols'); + const configPath = path.join(protocolsPath, protocol, 'config.json'); + + if (!fs.existsSync(configPath)) { + throw new Error(`protocol ${protocol}: config.json not found`); + } + + const protocolConfigStr = fs.readFileSync(configPath, 'utf8'); + const protocolConfig = JSON.parse(protocolConfigStr); + + if (typeof protocolConfig !== 'object'){ + throw new Error(`protocol ${protocol}: config is not an object`); + } + + const {name, icon, metadata} = protocolConfig; + + if (!mustBeNonEmptyString(name)) { + throw new Error(`protocol ${protocol}: invalid field 'name'`); + } + + if (!mustBeNonEmptyString(icon)) { + throw new Error(`protocol ${protocol}: invalid field 'icon'`); + } + + if (typeof metadata !== 'object') { + throw new Error(`protocol ${protocol}: invalid field 'metadata'`); + } + + const iconPath = path.join(protocolsPath, protocol, icon); + if (!fs.existsSync(iconPath)) { + throw new Error(`protocol ${protocol}: icon path not found for protocol ${icon}`); + } + + const {pt, yt, lp} = metadata; + checkMetadataField(pt, protocol, 'pt'); + checkMetadataField(yt, protocol, 'yt'); + checkMetadataField(lp, protocol, 'lp'); +} + +function mustBeNonEmptyString(str) { + return typeof str === 'string' && str.trim() !== ''; +} + +function checkMetadataField(data, protocol, field) { + if (data === null || data === undefined) { + return; + } + + if (!Array.isArray(data)) { + throw new Error(`protocol ${protocol}: metadata ${field} must be an array`) + } + + for (let index = 0; index < data.length; index ++) { + const item = data[index]; + const {chainId, address, description, integrationUrl} = item; + + if (typeof chainId !== 'number') { + throw new Error(`protocol ${protocol}: metadata ${field} invalid 'chainId' field at index ${index}`); + } + + if (!mustBeNonEmptyString(address) || !isValidEthereumAddress(address)) { + throw new Error(`protocol ${protocol}: metadata ${field} invalid 'address' field at index ${index}`); + } + + if (!mustBeNonEmptyString(description)) { + throw new Error(`protocol ${protocol}: metadata ${field} invalid 'description' field at index ${index}`); + } + + if (!mustBeNonEmptyString(integrationUrl)) { + throw new Error(`protocol ${protocol}: metadata ${field} invalid 'integrationUrl' field at index ${index}`); + } + } +} + +void main() \ No newline at end of file