From 33b0e2f557d9d6661f0e90dbc7603be6e2b26505 Mon Sep 17 00:00:00 2001 From: Nikolay Matrosov Date: Tue, 10 Oct 2023 13:22:17 +0200 Subject: [PATCH] test: add script to check that all services have corresponding endpoints --- .github/workflows/pr-checks.yml | 5 +++ package-lock.json | 14 +++--- package.json | 3 +- scripts/check-endpoints.ts | 79 +++++++++++++++++++++++++++++++++ src/service-endpoints.ts | 2 +- 5 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 scripts/check-endpoints.ts diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 418de734..ca11af2f 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -11,6 +11,11 @@ jobs: steps: - uses: yandex-cloud/nodejs-sdk/.github/actions/checkout-and-install-node@f69248b52b7991214847e889f28ba0883ed0ca2c - run: npm run test +# check-endpoints: +# runs-on: ubuntu-20.04 +# steps: +# - uses: yandex-cloud/nodejs-sdk/.github/actions/checkout-and-install-node@f69248b52b7991214847e889f28ba0883ed0ca2c +# - run: npm run check-endpoints lint: runs-on: ubuntu-20.04 steps: diff --git a/package-lock.json b/package-lock.json index 8d9a1dd5..fd7e7eba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "eslint-plugin-prefer-arrow-functions": "^3.1.4", "eslint-plugin-unicorn": "^39.0.0", "fast-glob": "^3.2.7", - "grpc-tools": "^1.11.2", + "grpc-tools": "^1.12.4", "husky": "^7.0.4", "jest": "^27.4.5", "semantic-release": "^21.0.1", @@ -5553,9 +5553,9 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "node_modules/grpc-tools": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/grpc-tools/-/grpc-tools-1.11.2.tgz", - "integrity": "sha512-4+EgpnnkJraamY++oyBCw5Hp9huRYfgakjNVKbiE3PgO9Tv5ydVlRo7ZyGJ0C0SEiA7HhbVc1sNNtIyK7FiEtg==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/grpc-tools/-/grpc-tools-1.12.4.tgz", + "integrity": "sha512-5+mLAJJma3BjnW/KQp6JBjUMgvu7Mu3dBvBPd1dcbNIb+qiR0817zDpgPjS7gRb+l/8EVNIa3cB02xI9JLToKg==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -18006,9 +18006,9 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "grpc-tools": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/grpc-tools/-/grpc-tools-1.11.2.tgz", - "integrity": "sha512-4+EgpnnkJraamY++oyBCw5Hp9huRYfgakjNVKbiE3PgO9Tv5ydVlRo7ZyGJ0C0SEiA7HhbVc1sNNtIyK7FiEtg==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/grpc-tools/-/grpc-tools-1.12.4.tgz", + "integrity": "sha512-5+mLAJJma3BjnW/KQp6JBjUMgvu7Mu3dBvBPd1dcbNIb+qiR0817zDpgPjS7gRb+l/8EVNIa3cB02xI9JLToKg==", "dev": true, "requires": { "@mapbox/node-pre-gyp": "^1.0.5" diff --git a/package.json b/package.json index 0a01db09..1e6b1bb1 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "eslint-plugin-prefer-arrow-functions": "^3.1.4", "eslint-plugin-unicorn": "^39.0.0", "fast-glob": "^3.2.7", - "grpc-tools": "^1.11.2", + "grpc-tools": "^1.12.4", "husky": "^7.0.4", "jest": "^27.4.5", "semantic-release": "^21.0.1", @@ -70,6 +70,7 @@ "lint": "eslint src config", "build": "cross-env NODE_OPTIONS=\"--max-old-space-size=4096\" tsc -p .", "generate-code": "ts-node scripts/generate-code.ts", + "check-endpoints": "ts-node scripts/check-endpoints.ts", "prepare": "husky install", "prepublishOnly": "npm run build" }, diff --git a/scripts/check-endpoints.ts b/scripts/check-endpoints.ts new file mode 100644 index 00000000..bfdbf786 --- /dev/null +++ b/scripts/check-endpoints.ts @@ -0,0 +1,79 @@ +import * as fg from 'fast-glob'; +import * as path from 'path'; +import { + Namespace, NamespaceBase, Root, Service, +} from 'protobufjs'; +import { SERVICE_ENDPOINTS_LIST } from '../src/service-endpoints'; + +const PROTO_DIR = path.resolve('./cloudapi'); + +const protoFiles = fg.sync('**/*.proto', { cwd: PROTO_DIR }); + +const pbRoot = new Root(); + +pbRoot.resolvePath = (origin, target) => { + const targets = target.split('/'); + + switch (targets[0]) { + case 'google': { + switch (targets[1]) { + case 'protobuf': { + return `./node_modules/protobufjs/${target}`; + } + default: { + return `./cloudapi/third_party/googleapis/${target}`; + } + } + } + case 'third_party': { + return `./cloudapi/${target}`; + } + case 'yandex': { + return `./cloudapi/${target}`; + } + default: { + return target; + } + } +}; + +const SERVICES: Service[] = []; +const findServices = (node: T) => { + for (const child of Object.values(node.nested ?? {}) + .sort((a, b) => (a.name < b.name ? -1 : (a.name === b.name ? 0 : 1)))) { + if (child instanceof Service) { + SERVICES.push(child); + } else if (child instanceof Namespace) { + findServices(child); + } + } +}; + +// eslint-disable-next-line unicorn/prefer-top-level-await +pbRoot.load(protoFiles, { alternateCommentMode: true }).then((loadedRoot) => { + const SERVICE_IDS = new Set(); + let hasMissing = false; + + for (const serviceEndpoint of SERVICE_ENDPOINTS_LIST) { + for (const service of serviceEndpoint.serviceIds) { + SERVICE_IDS.add(service); + } + } + + findServices(loadedRoot); + for (const s of SERVICES) { + // full name without leading dot + const fullName = s.fullName.slice(1); + + if (!SERVICE_IDS.has(fullName)) { + // eslint-disable-next-line no-console + console.log('Missing service', fullName); + hasMissing = true; + } + } + + if (hasMissing) { + // eslint-disable-next-line unicorn/no-process-exit + process.exit(1); + } +}); diff --git a/src/service-endpoints.ts b/src/service-endpoints.ts index 009a0e27..a85b05c8 100644 --- a/src/service-endpoints.ts +++ b/src/service-endpoints.ts @@ -12,7 +12,7 @@ interface ServiceEndpoint { type ServiceEndpointsList = ServiceEndpoint[]; // @see https://api.cloud.yandex.net/endpoints -const SERVICE_ENDPOINTS_LIST: ServiceEndpointsList = [ +export const SERVICE_ENDPOINTS_LIST: ServiceEndpointsList = [ { serviceIds: ['yandex.cloud.operation.OperationService'], endpoint: 'operation.api.cloud.yandex.net:443',