diff --git a/.changeset/fresh-turkeys-grow.md b/.changeset/fresh-turkeys-grow.md new file mode 100644 index 000000000..5fd014073 --- /dev/null +++ b/.changeset/fresh-turkeys-grow.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/registry': patch +--- + +Improve CI workflow by separating concerns diff --git a/.github/workflows/optimize-svg.yml b/.github/workflows/optimize-svg.yml index 5b70e7491..e9d20831e 100644 --- a/.github/workflows/optimize-svg.yml +++ b/.github/workflows/optimize-svg.yml @@ -32,7 +32,7 @@ jobs: fi - name: setup-node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml new file mode 100644 index 000000000..e241aca64 --- /dev/null +++ b/.github/workflows/validate.yaml @@ -0,0 +1,45 @@ +name: validate-files + +on: + push: + branches: ["main"] + pull_request: + # Allows you to run this workflow manually + workflow_dispatch: + +jobs: + validate-files: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + with: + path: | + **/node_modules + .yarn/cache + key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} + + - name: yarn-install + run: | + yarn install + CHANGES=$(git status -s) + if [[ ! -z $CHANGES ]]; then + echo "Changes found: $CHANGES" + git diff + exit 1 + fi + + - name: setup-node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: validate-file-path + run: | + node ./scripts/validate-file-path.js + + - name: validate-svg + run: | + node ./scripts/validate-svg.js diff --git a/scripts/optimize-svg.js b/scripts/optimize-svg.js index c6226e808..80fce693e 100644 --- a/scripts/optimize-svg.js +++ b/scripts/optimize-svg.js @@ -1,95 +1,10 @@ import fs from 'fs'; -import path from 'path'; import { optimize } from 'svgo'; +import { getFilePaths } from './utils.js'; const directories = ['./chains', './deployments']; -const MAX_FILE_SIZE = 100 * 1024; // 100KBs -const RASTER_IMAGE_REGEX = /]*>/i; -const invalidNameSVGs = []; -const invalidSizeSVGs = []; -const rasterImgSVGs = []; - -function isValidSvg(filePath) { - const fileName = path.basename(filePath); - const stats = fs.statSync(filePath); - const fileSize = (stats.size / 1024).toFixed(2); - - if (!fileName.endsWith('logo.svg')) { - invalidNameSVGs.push(filePath); - } - - if (stats.size > MAX_FILE_SIZE) { - invalidSizeSVGs.push({ filePath, fileSize: `${fileSize}KBs` }); - } - - const fileContent = fs.readFileSync(filePath, 'utf8'); - if (RASTER_IMAGE_REGEX.test(fileContent)) { - rasterImgSVGs.push(filePath); - } -} - -// Finds all svgs, validates and return all paths found that has svgs -function findAndValidateSVGs(directory) { - const files = fs.readdirSync(directory); - - return files.flatMap((file) => { - const fullPath = path.join(directory, file); - const stats = fs.statSync(fullPath); - - if (stats.isDirectory()) { - return findAndValidateSVGs(fullPath); // Recurse into subdirectories - } else if (path.extname(fullPath) === '.svg') { - isValidSvg(fullPath); - return fullPath; - } - - return []; - }); -} - -// Get all svg paths that are validated -function getSVGPaths() { - return directories - .filter((directory) => { - if (fs.existsSync(directory)) { - console.log(`Checking directory: ${directory}`); - return true; - } else { - console.log(`Directory does not exist: ${directory}`); - return false; - } - }) - .flatMap((directory) => findAndValidateSVGs(directory)); -} - -function validateErrors() { - const errorCount = invalidNameSVGs.length + invalidSizeSVGs.length + rasterImgSVGs.length; - if (errorCount === 0) return; - - console.error(`Number of errors found: ${errorCount}`); - - if (invalidNameSVGs.length > 0) { - console.error( - "Error: Files do not end with 'logo.svg' in the following paths:", - invalidNameSVGs, - ); - } - - if (invalidSizeSVGs.length > 0) { - console.error('Error: Files size exceed 100KBs in:', invalidSizeSVGs); - } - - if (rasterImgSVGs.length > 0) { - console.error( - 'Error: Files contain an tag, likely embedding a raster image in the following paths:', - rasterImgSVGs, - ); - } - - process.exit(1); -} -// Optimize svg in given path +// Optimize svg in given paths function optimizeSVGs(svgPaths) { svgPaths.forEach((filePath) => { try { @@ -122,8 +37,7 @@ function optimizeSVGs(svgPaths) { } function main() { - const svgPaths = getSVGPaths(); - validateErrors(); + const svgPaths = getFilePaths(directories, ['.svg']); optimizeSVGs(svgPaths); } diff --git a/scripts/utils.js b/scripts/utils.js new file mode 100644 index 000000000..8c04291a6 --- /dev/null +++ b/scripts/utils.js @@ -0,0 +1,34 @@ +import fs from 'fs'; +import path from 'path'; + +function findFiles(directory, fileTypes = [], isRecursive = true) { + const files = fs.readdirSync(directory); + + return files.flatMap((file) => { + const fullPath = path.join(directory, file); + const stats = fs.statSync(fullPath); + + if (stats.isDirectory() && isRecursive) { + return findFiles(fullPath, fileTypes); // Recurse into subdirectories + } else if (fileTypes.includes(path.extname(fullPath))) { + return fullPath; + } + + return []; + }); +} + +// Get all fille paths for given directories and given file types +export function getFilePaths(directories = [], fileTypes = [], isRecursive = true) { + return directories + .filter((directory) => { + if (fs.existsSync(directory)) { + console.log(`Checking directory: ${directory}`); + return true; + } else { + console.log(`Directory does not exist: ${directory}`); + return false; + } + }) + .flatMap((directory) => findFiles(directory, fileTypes, isRecursive)); +} diff --git a/scripts/validate-file-path.js b/scripts/validate-file-path.js new file mode 100644 index 000000000..fbd97010e --- /dev/null +++ b/scripts/validate-file-path.js @@ -0,0 +1,24 @@ +import { getFilePaths } from './utils.js'; + +const directories = [ + { paths: ['./src'], recursive: true }, + { paths: ['./'], recursive: false }, +]; + +const fileExtensions = ['.svg', '.yaml']; + +function main() { + const invalidFilesPaths = directories.flatMap((directory) => + getFilePaths(directory.paths, fileExtensions, directory.recursive), + ); + + if (invalidFilesPaths.length === 0) return; + + console.error( + 'Error: invalid file paths found, make sure they are in the proper directories (chains or deployments):', + invalidFilesPaths, + ); + process.exit(1); +} + +main(); diff --git a/scripts/validate-svg.js b/scripts/validate-svg.js new file mode 100644 index 000000000..aaaaba354 --- /dev/null +++ b/scripts/validate-svg.js @@ -0,0 +1,69 @@ +import fs from 'fs'; +import path from 'path'; +import { getFilePaths } from './utils.js'; + +const directories = ['./chains', './deployments']; +const MAX_FILE_SIZE = 100 * 1024; // 100KBs +const RASTER_IMAGE_REGEX = /]*>/i; + +const invalidNameSVGs = []; +const invalidSizeSVGs = []; +const rasterImgSVGs = []; + +function isValidSvg(filePath) { + const fileName = path.basename(filePath); + const stats = fs.statSync(filePath); + const fileSize = (stats.size / 1024).toFixed(2); + + if (!fileName.endsWith('logo.svg')) { + invalidNameSVGs.push(filePath); + } + + if (stats.size > MAX_FILE_SIZE) { + invalidSizeSVGs.push({ filePath, fileSize: `${fileSize}KBs` }); + } + + const fileContent = fs.readFileSync(filePath, 'utf8'); + if (RASTER_IMAGE_REGEX.test(fileContent)) { + rasterImgSVGs.push(filePath); + } +} + +function validateErrors() { + const errorCount = invalidNameSVGs.length + invalidSizeSVGs.length + rasterImgSVGs.length; + if (errorCount === 0) return; + + console.error(`Number of errors found: ${errorCount}`); + + if (invalidNameSVGs.length > 0) { + console.error( + "Error: Files do not end with 'logo.svg' in the following paths:", + invalidNameSVGs, + ); + } + + if (invalidSizeSVGs.length > 0) { + console.error('Error: Files size exceed 100KBs in:', invalidSizeSVGs); + } + + if (rasterImgSVGs.length > 0) { + console.error( + 'Error: Files contain an tag, likely embedding a raster image in the following paths:', + rasterImgSVGs, + ); + } + + process.exit(1); +} + +function validateSvgs(paths = []) { + paths.forEach((path) => isValidSvg(path)); + validateErrors(); +} + +function main() { + const svgPaths = getFilePaths(directories, ['.svg']); + validateSvgs(svgPaths); +} + +main();