diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..e3fe983 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,101 @@ +version: 2.1 +orbs: + snyk: snyk/snyk@0.0.8 + +jobs: + build-test-monitor: + docker: + # specify the version + - image: circleci/node:10.16.3 + + steps: + - checkout + - run: + name: "Install deps" + command: | + npm install + - run: + name: "Run Tests" + command: | + npm test + - snyk/scan: + fail-on-issues: true + monitor-on-build: true + token-variable: SNYK_TOKEN + - run: ./generate-binaries.sh + - persist_to_workspace: + root: . + paths: + - dist/* + + build-test: + docker: + # specify the version + - image: circleci/node:10.16.3 + + steps: + - checkout + - run: + name: "Install deps" + command: | + npm install + - run: + name: "Run Tests" + command: | + npm test + - snyk/scan: + fail-on-issues: true + monitor-on-build: false + token-variable: SNYK_TOKEN + + publish-github-release: + docker: + - image: gcr.io/snyk-technical-services/cicd-github + auth: + username: _json_key + password: $GCLOUD_GCR_SNYK_TS_READER + steps: + - checkout + - attach_workspace: + at: . + - run: + name: "Publish Release on GitHub" + command: | + VERSIONJUMP=$(git log --oneline -1 --pretty=%B | cat | grep -E 'minor|major|patch' | awk -F ':' '{print $1}') + VERSION=$(/workdir/nextver.sh "$VERSIONJUMP") + ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} ${VERSION} dist/ + +workflows: + version: 2.1 + nightly: + triggers: + - schedule: + cron: "0 0 * * *" + filters: + branches: + only: + - master + jobs: + - build-test-monitor + build-test-monitor-publish: + jobs: + - build-test-monitor: + filters: + branches: + only: + - master + - publish-github-release: + requires: + - build-test-monitor + filters: + branches: + only: + - master + build-test: + jobs: + - build-test: + filters: + branches: + ignore: + - master + diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..2ccbe46 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +/node_modules/ diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..ef6f63b --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,30 @@ +{ + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "parserOptions": { + "ecmaVersion": 6 + }, + "env": { + "node": true, + "es6": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "prettier", + "prettier/@typescript-eslint" + ], + "rules": { + "@typescript-eslint/explicit-function-return-type": [ + "error", + { + "allowExpressions": true, + "allowTypedFunctionExpressions": true + } + ], + "no-var": "error", + "prefer-arrow-callback": "error", + "prefer-const": "error" + } +} diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..8edbe37 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,20 @@ +- [ ] Tests written and linted [ℹ︎](https://github.com/snyk/general/wiki/Tests) +- [ ] Documentation written [ℹ︎](https://github.com/snyk/general/wiki/Documentation) +- [ ] Commit history is tidy [ℹ︎](https://github.com/snyk/general/wiki/Git) + +### What this does + +_Explain why this PR exists_ + +### Notes for the reviewer + +_Instructions on how to run this locally, background context, what to review, questions…_ + +### More information + +- [Jira ticket SC-0000](https://snyksec.atlassian.net/browse/SC-0000) +- [Link to documentation](https://github.com/snyk/snyk-request-manager/wiki/) + +### Screenshots + +_Visuals that may help the reviewer_ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02392b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +npm-debug.log +.npmrc +# output +dist +.DS_Store +*.log +package-lock.json +yarn.lock +.eslintcache diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..2ccbe46 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +/node_modules/ diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..ced318d --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "arrowParens": "always", + "trailingComma": "all", + "singleQuote": true, + "htmlWhitespaceSensitivity": "ignore" +} diff --git a/.snyk b/.snyk new file mode 100644 index 0000000..b4e98b4 --- /dev/null +++ b/.snyk @@ -0,0 +1,8 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.13.5 +ignore: {} +# patches apply the minimum changes required to fix a vulnerability +patch: + SNYK-JS-LODASH-450202: + - lodash: + patched: '2019-07-05T10:44:37.780Z' diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..698e2bf --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2018 Snyk Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..88f44d0 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +![Snyk logo](https://snyk.io/style/asset/logo/snyk-print.svg) + +*** + +[![Known Vulnerabilities](https://snyk.io/test/github/snyk/snyk-request-manager/badge.svg)](https://snyk.io/test/github/snyk/snyk-request-manager) + +Snyk helps you find, fix and monitor for known vulnerabilities in your dependencies, both on an ad hoc basis and as part of your CI (Build) system. + +## Snyk snyk-request-manager +Rate controlled and retry enabled request manager to interact with Snyk APIs diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..039da88 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverageFrom: ['lib/**/*.ts'], + coverageReporters: ['text-summary', 'html'], +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..39d02ef --- /dev/null +++ b/package.json @@ -0,0 +1,75 @@ +{ + "name": "snyk-request-manager", + "description": "Rate controlled and retry enabled request manager to interact with Snyk APIs", + "main": "dist/index.js", + "scripts": { + "format:check": "prettier --check '{''{lib,test}/!(fixtures)/**/*,*}.{js,ts,json,yml}'", + "format": "prettier --write '{''{lib,test}/!(fixtures)/**/*,*}.{js,ts,json,yml}'", + "lint": "npm run format:check && npm run lint:eslint", + "lint:eslint": "eslint --cache '{lib,test}/**/*.ts'", + "test": "npm run lint && npm run test:unit", + "test:unit": "jest", + "test:coverage": "npm run test:unit -- --coverage", + "test:watch": "tsc-watch --onSuccess 'npm run test:unit'", + "build": "tsc", + "build-watch": "tsc -w", + "prepare": "npm run build", + "snyk-test": "snyk test", + "pkg-binaries": "npx pkg . -t node12-linux-x64,node12-macos-x64,node12-win-x64 --out-path ./dist/binaries" + }, + "types": "./dist/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/snyk-tech-services/snyk-request-manager" + }, + "author": "Snyk Tech Services", + "license": "Apache-2.0", + "engines": { + "node": ">=12" + }, + "files": [ + "bin", + "dist" + ], + "homepage": "https://github.com/snyk-tech-services/snyk-request-manager#readme", + "dependencies": { + "@snyk/configstore": "^3.2.0-rc1", + "@types/uuid": "^7.0.3", + "axios": "^0.19.2", + "chalk": "^4.0.0", + "debug": "^4.1.1", + "leaky-bucket-queue": "0.0.2", + "lodash": "^4.17.15", + "snyk": "^1.316.2", + "snyk-config": "^3.0.0", + "source-map-support": "^0.5.16", + "tslib": "^1.10.0", + "uuid": "^8.0.0" + }, + "devDependencies": { + "@types/jest": "^25.1.1", + "@types/lodash": "^4.14.149", + "@types/node": "^12.12.26", + "@typescript-eslint/eslint-plugin": "^2.18.0", + "@typescript-eslint/parser": "^2.18.0", + "eslint": "^6.8.0", + "eslint-config-prettier": "^6.10.0", + "jest": "^25.1.0", + "nock": "^12.0.3", + "prettier": "^1.19.1", + "ts-jest": "^25.1.0", + "ts-node": "8.6.2", + "tsc-watch": "^4.1.0", + "typescript": "^3.7.5" + }, + "pkg": { + "scripts": [ + "dist/**/*.js" + ] + }, + "release": { + "branches": [ + "master" + ] + } +} diff --git a/src/lib/customErrors/apiError.ts b/src/lib/customErrors/apiError.ts new file mode 100644 index 0000000..98342e8 --- /dev/null +++ b/src/lib/customErrors/apiError.ts @@ -0,0 +1,40 @@ + + +class ApiError extends Error { + constructor(message: any){ + super(message) + this.name = "ApiError" + this.message = (message || "") + } +} + +class ApiAuthenticationError extends Error { + constructor(message: any){ + super(message) + this.name = "ApiAuthenticationError" + this.message = (message || "") + } +} + +class NotFoundError extends Error { + constructor(message: any){ + super(message) + this.name = "NotFoundError" + this.message = (message || "") + } +} + +class GenericError extends Error { + constructor(message: any){ + super(message) + this.name = "Unknown" + this.message = (message || "") + } +} + +export { + ApiError, + ApiAuthenticationError, + NotFoundError, + GenericError +} \ No newline at end of file diff --git a/src/lib/customErrors/inputError.ts b/src/lib/customErrors/inputError.ts new file mode 100644 index 0000000..52b7a2c --- /dev/null +++ b/src/lib/customErrors/inputError.ts @@ -0,0 +1,13 @@ + + +class BadInputError extends Error { + constructor(message: any){ + super(message) + this.name = "BadInputError" + this.message = (message || "") + } +} + +export { + BadInputError +} \ No newline at end of file diff --git a/src/lib/customErrors/requestManagerErrors.ts b/src/lib/customErrors/requestManagerErrors.ts new file mode 100644 index 0000000..cd029b7 --- /dev/null +++ b/src/lib/customErrors/requestManagerErrors.ts @@ -0,0 +1,72 @@ +import {ApiError} from './apiError' + +const requestsManagerErrorOverload = (err: Error, channel: string, requestId: string): Error => { + switch(err?.name){ + case 'ApiError': + return new RequestsManagerApiError(err.message, channel, requestId) + case 'ApiAuthenticationError': + return new RequestsManagerApiAuthenticationError(err.message, channel, requestId) + case 'NotFoundError': + return new RequestsManagerNotFoundError(err.message, channel, requestId) + case 'Unknown': + return new RequestsManagerGenericError(err.message, channel, requestId) + break; + default: + } return new RequestsManagerGenericError("Unclassified", channel, requestId) +} + +class RequestsManagerApiError extends ApiError { + channel: string + requestId: string + constructor(message: any, channel: string, requestId: string){ + super(message) + this.name = "ApiError" + this.channel = channel + this.requestId = requestId + this.message = (message || "") + } +} + +class RequestsManagerApiAuthenticationError extends ApiError { + channel: string + requestId: string + constructor(message: any, channel: string, requestId: string){ + super(message) + this.name = "ApiAuthenticationError" + this.channel = channel + this.requestId = requestId + this.message = (message || "") + } +} + +class RequestsManagerNotFoundError extends ApiError { + channel: string + requestId: string + constructor(message: any, channel: string, requestId: string){ + super(message) + this.name = "NotFoundError" + this.channel = channel + this.requestId = requestId + this.message = (message || "") + } +} + +class RequestsManagerGenericError extends ApiError { + channel: string + requestId: string + constructor(message: any, channel: string, requestId: string){ + super(message) + this.name = "Unknown" + this.channel = channel + this.requestId = requestId + this.message = (message || "") + } +} + +export { + RequestsManagerApiError, + RequestsManagerApiAuthenticationError, + RequestsManagerNotFoundError, + RequestsManagerGenericError, + requestsManagerErrorOverload +} \ No newline at end of file diff --git a/src/lib/error.ts b/src/lib/error.ts new file mode 100644 index 0000000..51331e4 --- /dev/null +++ b/src/lib/error.ts @@ -0,0 +1,33 @@ +import * as chalk from 'chalk' +const debugModule = require('debug'); + + +const handleError = (error: Error) => { + const debug = debugModule('snyk') + if(!process.env.DEBUG) { + console.log(chalk.hex("#316fcc")("hint: Check debug mode -d")) + } + switch(error.name){ + case 'ApiError': + console.log("Uh oh, seems like we messed something up?") + debug(error) + break; + case 'ApiAuthenticationError': + console.log("Hum, looks like we have a wrong token?") + debug(error) + break; + case 'NotFoundError': + console.log("Couldn't find find this resource") + debug(error) + break; + case 'BadInputError': + console.log("Bad input. Please check the --help") + debug(error) + break; + default: + //console.log("Unknown error") + debug(error) + } +} + +export default handleError \ No newline at end of file diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..07f6a9c --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1,82 @@ +import 'source-map-support/register'; +import { requestsManager } from './requests/requestsManager' + +const run = async () => { + const manager = new requestsManager() + manager.on('data', { + callback:(requestId, data) => { + console.log("response for request ", requestId) + console.log(data) + } + }) + + manager.on('error', { + callback:(requestId, data) => { + console.log("response for request ", requestId) + console.log(data) + } + }) + + try{ + let requestSync = await manager.request({verb: "GET", url: '/', body: ''}) + console.log(requestSync) + console.log('done with synced request') + } catch (err) { + console.log('error') + console.log(err) + } + + + manager.on('data', { + callback:(requestId, data) => { + console.log("response for request on test-channel ", requestId) + console.log(data) + }, + channel: 'test-channel' + }) + + try { + console.log('1',manager.requestStream({verb: "GET", url: '/', body: ''})) + console.log('1-channel',manager.requestStream({verb: "GET", url: '/', body: ''}, 'test-channel')) + console.log('2',manager.requestStream({verb: "GET", url: '/', body: ''})) + console.log('2-channel',manager.requestStream({verb: "GET", url: '/', body: ''}, 'test-channel')) + console.log('3',manager.requestStream({verb: "GET", url: '/', body: ''})) + console.log('3-channel',manager.requestStream({verb: "GET", url: '/', body: ''}, 'test-channel')) + } catch (err) { + console.log(err) + } + + + + + const filters = `{ + "filters": { + "severities": [ + "high", + "medium", + "low" + ], + "exploitMaturity": [ + "mature", + "proof-of-concept", + "no-known-exploit", + "no-data" + ], + "types": [ + "vuln", + "license" + ], + "ignored": false + } + } +` + try { + const results = await manager.requestBulk([{verb: "GET", url: '/', body: ''}, {verb: "POST", url: '/org/334e0c45-5d3d-40f6-b882-ae82a164b317/project/0bbbfee1-2138-4322-80d4-4166d1259ae5/issues', body: filters}, {verb: "GET", url: '/', body: ''}]) + console.log(results) + } catch(resultsWithError) { + console.log(resultsWithError) + } + +} + +run() \ No newline at end of file diff --git a/src/lib/request/request.ts b/src/lib/request/request.ts new file mode 100644 index 0000000..938c748 --- /dev/null +++ b/src/lib/request/request.ts @@ -0,0 +1,72 @@ +const Configstore = require('@snyk/configstore'); +import axios from 'axios' +import * as Error from '../customErrors/apiError' + +interface snykRequest { + verb: string, + url: string, + body?: string, + headers?: Object +} + +const makeSnykRequest = async (request: snykRequest) => { + const userConfig = getConfig() + const requestHeaders: Object = { + 'Content-Type': 'application/json', + 'Authorization': 'token '+userConfig.token, + 'User-Agent': 'tech-services/snyk-prevent/1.0' + } + + const apiClient = axios.create({ + baseURL: userConfig.endpoint, + responseType: 'json', + headers: {...requestHeaders, ...request.headers } + }); + + try { + let res; + switch(request.verb){ + case "GET": + res = await apiClient.get(request.url) + break; + case "POST": + res = await apiClient.post(request.url,request.body) + break; + case "PUT": + res = await apiClient.put(request.url,request.body) + break; + case "DELETE": + res = await apiClient.delete(request.url) + break; + default: + throw new Error.GenericError('Unexpected http command') + } + return res?.data + + } catch (err) { + switch(err.response.status){ + case 401: + throw new Error.ApiAuthenticationError(err) + case 404: + throw new Error.NotFoundError("Snyk API - Could not find this resource") + case 500: + throw new Error.ApiError(err) + default: + throw new Error.GenericError(err) + } + } + + +} + +const getConfig = () => { + const snykApiEndpoint: string = process.env.SNYK_API || new Configstore('snyk').get('endpoint') || 'https://snyk.io/api/v1' + const snykToken = process.env.SNYK_TOKEN || new Configstore('snyk').get('api') + return {endpoint: snykApiEndpoint, token: snykToken} +} + +export { + makeSnykRequest, + getConfig, + snykRequest +} \ No newline at end of file diff --git a/src/lib/request/requestManager.ts b/src/lib/request/requestManager.ts new file mode 100644 index 0000000..8d8466a --- /dev/null +++ b/src/lib/request/requestManager.ts @@ -0,0 +1,204 @@ +import { LeakyBucketQueue } from 'leaky-bucket-queue'; +import { snykRequest, makeSnykRequest } from './request' +import { v4 as uuidv4 } from 'uuid'; +import * as requestsManagerError from '../customErrors/requestManagerErrors' + +interface queuedRequest { + id: string, + channel: string, + snykRequest: snykRequest +} +interface queueCallbackListenerBundle { + callback(requestId: string, data: any): void, + channel?: string +} +enum eventType { + data = "data", + error = "error", +} +interface responseEvent { + eventType: eventType, + channel: string, + requestId: string, + data: any +} + + +class requestsManager { + _requestsQueue: LeakyBucketQueue + // TODO: Type _events rather than plain obscure object structure + _events: any + + constructor() { + this._requestsQueue = new LeakyBucketQueue({ burstSize: 10, period: 500 }); + this._setupQueueExecutors(this._requestsQueue) + this._events = {} + } + + _setupQueueExecutors = (queue: LeakyBucketQueue) => { + queue.consume().subscribe({ + next: this._makeRequest, + error: this._queueErrorHandler, + complete: () => { + console.log("Stopped queue") + } + }) + } + + _makeRequest = async (request: queuedRequest) => { + let requestId = request.id + try { + let response = await makeSnykRequest(request.snykRequest) + this._emit({eventType: eventType.data, channel: request.channel, requestId: requestId, data: response }) + } catch (err) { + let overloadedError = requestsManagerError.requestsManagerErrorOverload(err, request.channel, requestId) + this._emit({eventType: eventType.error, channel: request.channel, requestId: requestId, data: overloadedError }) + } + + } + + _queueErrorHandler = (err: Error) => { + //debug(err) + // TODO: Add retry logic + // Track request ID count and throw it back into the queue + // Throw error when count > MAX_RETRIES_LIMIT + throw new Error(err.stack) + } + + + _emit = (response: responseEvent) => { + if (!this._events[response.eventType]) { + throw new Error(`Can't emit an event. Event "${eventType}" doesn't exits.`); + } + + const fireCallbacks = (listenerBundle: queueCallbackListenerBundle) => { + if(response.channel == listenerBundle.channel){ + + listenerBundle.callback(response.requestId, response.data); + } + + }; + + this._events[response.eventType].forEach(fireCallbacks); + } + + _removeAllListenersForChannel = (channel: string) => { + Object.keys(eventType).forEach(typeOfEvent => { + if (!this._events[typeOfEvent]) { + throw new Error(`Can't remove a listener. Event "${typeOfEvent}" doesn't exits.`); + } + const filterListeners = (callbackListener: queueCallbackListenerBundle) => callbackListener.channel !== channel; + + this._events[typeOfEvent] = this._events[typeOfEvent].filter(filterListeners); + }) + } + + _doesChannelHaveListeners = (channel: string) => { + let dataEventListeners = this._events['data'] as Array + return dataEventListeners.some(listener => listener.channel == channel) + } + + request = (request: snykRequest): Promise => { + return new Promise((resolve,reject) => { + let syncRequestChannel = uuidv4() + + const callbackBundle = { + callback: (originalRequestId: string, data: any) => { + if(requestId == originalRequestId){ + this._removeAllListenersForChannel(syncRequestChannel) + resolve(data) + } + }, + channel: syncRequestChannel + } + const errorCallbackBundle = { + callback:(originalRequestId: string, data: any) => { + if(requestId == originalRequestId){ + this._removeAllListenersForChannel(syncRequestChannel) + reject(data) + } + }, + channel: syncRequestChannel + } + + this.on('data', callbackBundle) + this.on('error', errorCallbackBundle) + let requestId = this.requestStream(request, syncRequestChannel) + }) + + } + + + requestBulk = (snykRequestsArray: Array): Promise> => { + return new Promise((resolve,reject) => { + // Fire off all requests in Array and return only when responses are all returned + // Must return array of responses in the same order. + let requestsMap: Map = new Map() + let bulkRequestChannel = uuidv4() + let isErrorInAtLeastOneRequest = false + let requestRemainingCount = snykRequestsArray.length + const callbackBundle = { + callback: (originalRequestId: string, data: any) => { + requestsMap.set(originalRequestId, data) + requestRemainingCount-- + if(requestRemainingCount <= 0){ + let responsesArray: Array = [] + requestsMap.forEach((value) => { + responsesArray.push(value) + }) + isErrorInAtLeastOneRequest? reject(responsesArray) : resolve(responsesArray) + } + }, + channel: bulkRequestChannel + } + const errorCallbackBundle = { + callback:(originalRequestId: string, data: any) => { + isErrorInAtLeastOneRequest = true + callbackBundle.callback(originalRequestId,data) + }, + channel: bulkRequestChannel + } + + this.on('data', callbackBundle) + this.on('error', errorCallbackBundle) + + snykRequestsArray.forEach(snykRequest => { + requestsMap.set(this.requestStream(snykRequest, bulkRequestChannel), {}) + }) + }) + } + + requestStream = (request: snykRequest, channel: string = 'stream'): string => { + let requestId = uuidv4() + let requestForQueue: queuedRequest = {id: requestId, channel: channel, snykRequest: request} + this._requestsQueue.enqueue(requestForQueue) + if(!this._doesChannelHaveListeners(channel)){ + throw new Error(`Not listener(s) setup for channel ${channel}`) + } + return requestId + } + + + + on = (eventType: string, listenerBundle: queueCallbackListenerBundle) => { + if (!this._events[eventType]) { + this._events[eventType] = []; + } + if(!listenerBundle.channel) { + listenerBundle.channel = 'stream' + } + this._events[eventType].push(listenerBundle); + } + +} + + +export { + requestsManager +} + + + + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d304d32 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "pretty": true, + "target": "ES2019", + "module": "commonjs", + "sourceMap": true, + "declaration": true, + "importHelpers": true, + "strict": true + }, + "include": ["./src/lib/**/*"] +}