diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..f18cae1 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,33 @@ +name: Lint HttpsExclusions + +env: + NODE_VERSION: 20 + +on: + push: + branches: + - master + paths: + - "**/*.txt" + pull_request: + branches: + - master + paths: + - "**/*.txt" + +jobs: + bank: + name: Check that banks.txt is sorted + runs-on: ubuntu-latest + steps: + - name: Set up Node.js LTS + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + - name: Install dependencies + run: npm ci + - name: Check out to repository + uses: actions/checkout@v4 + - name: Run lint + run: npm run lint \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4582bd3..392d8e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ dist -.DS_Store \ No newline at end of file +.DS_Store +node_modules \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3e00c5a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,140 @@ +{ + "name": "https-exclusions", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "https-exclusions", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@types/node": "^20.14.2", + "tsx": "^4.15.4", + "typescript": "^5.4.5" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/node": { + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.15.4.tgz", + "integrity": "sha512-d++FLCwJLrXaBFtRcqdPBzu6FiVOJ2j+UsvUZPtoTrnYtCGU5CEW7iHXtNZfA2fcRTvJFWPqA6SWBuB0GSva9w==", + "dev": true, + "dependencies": { + "esbuild": "~0.21.4", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 7795ae9..9f5c5ca 100644 --- a/package.json +++ b/package.json @@ -5,5 +5,13 @@ "main": "index.js", "repository": "git@github.com:AdguardTeam/HttpsExclusions.git", "author": "AdGuard", - "license": "MIT" + "license": "MIT", + "scripts": { + "lint": "tsx tools/check.ts exclusions/banks.txt" + }, + "devDependencies": { + "@types/node": "^20.14.2", + "tsx": "^4.15.4", + "typescript": "^5.4.5" + } } diff --git a/tools/check.ts b/tools/check.ts new file mode 100644 index 0000000..6af7909 --- /dev/null +++ b/tools/check.ts @@ -0,0 +1,65 @@ +const { readFile, stat } = require('fs').promises; + +const RE_NL = /\r?\n/; +const COMMENT_MARKER = '//'; + +const isFileExists = async (filePath: string): Promise => { + return stat(filePath) + .then((stats) => stats.isFile()) + .catch(() => false); +}; + +const readRules = async (filePath: string): Promise => { + const fileContent = await readFile(filePath, { encoding: 'utf-8' }); + + return fileContent + .split(RE_NL) + .map((line) => line.trim()) + .filter((line) => line.length > 0 && !line.startsWith(COMMENT_MARKER)); +}; + +const isSortedAlphabetically = (arr: string[]): boolean => { + for (let i = 0; i < arr.length - 1; i += 1) { + if (arr[i] > arr[i + 1]) { + return false; + } + } + return true; +}; + +const getErrorMessage = (err: unknown): string => { + if (err instanceof Error) { + return err.message; + } + + return 'Unknown error'; +}; + +const isNonEmptyString = (value: unknown): value is string => { + return typeof value === 'string' && value.length > 0; +}; + +const main = async () => { + const filePath = process.argv[2]; + + try { + if (!isNonEmptyString(filePath)) { + throw new Error('File path is not provided'); + } + + if (!(await isFileExists(filePath))) { + throw new Error(`File ${filePath} does not exist`); + } + + const rules = await readRules(filePath); + + if (!isSortedAlphabetically(rules)) { + throw new Error(`Rules in ${filePath} is not sorted alphabetically`); + } + } catch (err) { + console.error(getErrorMessage(err)); + process.exit(1); + } +}; + +main(); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fe90ec4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + }, + "include": [ + "tools/**/*.ts" + ] +} \ No newline at end of file