feat: use cache
All checks were successful
Check Transpiled JavaScript / Check dist/ (push) Successful in 31s
Continuous Integration / GitHub Actions Test (push) Successful in 9s
Continuous Integration / TypeScript Tests (push) Successful in 47s

This commit is contained in:
Peter 2024-05-03 16:17:32 +02:00
parent 27c977abb6
commit 034dd15f69
Signed by: prskr
GPG key ID: F56BED6903BC5E37
14 changed files with 212 additions and 60 deletions

View file

@ -41,6 +41,8 @@ rules:
'eslint-comments/no-unused-disable': 'off', 'eslint-comments/no-unused-disable': 'off',
'i18n-text/no-en': 'off', 'i18n-text/no-en': 'off',
'import/no-namespace': 'off', 'import/no-namespace': 'off',
'import/no-unresolved': 'warn',
'no-shadow': 'warn',
'no-console': 'off', 'no-console': 'off',
'no-unused-vars': 'off', 'no-unused-vars': 'off',
'prettier/prettier': 'error', 'prettier/prettier': 'error',
@ -79,5 +81,6 @@ rules:
'@typescript-eslint/semi': ['error', 'never'], '@typescript-eslint/semi': ['error', 'never'],
'@typescript-eslint/space-before-function-paren': 'off', '@typescript-eslint/space-before-function-paren': 'off',
'@typescript-eslint/type-annotation-spacing': 'error', '@typescript-eslint/type-annotation-spacing': 'error',
'@typescript-eslint/unbound-method': 'error' '@typescript-eslint/unbound-method': 'error',
'@typescript-eslint/no-duplicate-enum-values': 'off'
} }

View file

@ -10,9 +10,9 @@ beforeEach(() => {
describe('Asset lookup', () => { describe('Asset lookup', () => {
test('Hugo: should return valid version', async () => { test('Hugo: should return valid version', async () => {
const octoVersionDetermination = new OctokitReleaseLookup() const releaseLookup = new OctokitReleaseLookup()
const release = await octoVersionDetermination.getRelease( const release = await releaseLookup.getRelease(
Hugo.Org, Hugo.Org,
Hugo.Repo, Hugo.Repo,
'latest', 'latest',

32
__tests__/hugo.test.ts Normal file
View file

@ -0,0 +1,32 @@
import { HugoInstaller } from '../src/hugo'
import { OctokitReleaseLookup } from '../src/asset-lookup'
import * as fs from 'node:fs'
import path from 'path'
import * as os from 'node:os'
import { Platform } from '../src/os'
let tmpDir = ''
beforeEach(() => {
jest.resetModules()
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-hugo-'))
})
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true })
})
describe('Install Hugo', () => {
test('Download latest Hugo', async () => {
const releaseLookup = new OctokitReleaseLookup()
const platformMock = new Platform('linux', undefined, { HOME: tmpDir })
const hugo = new HugoInstaller(releaseLookup, platformMock)
expect(
async () =>
await hugo.install({
version: 'latest'
})
).not.toThrow()
}, 30_000)
})

View file

@ -10,7 +10,7 @@ const runMock = jest.spyOn(main, 'run').mockImplementation()
describe('index', () => { describe('index', () => {
it('calls run when imported', async () => { it('calls run when imported', async () => {
// eslint-disable-next-line @typescript-eslint/no-require-imports // eslint-disable-next-line @typescript-eslint/no-require-imports
require('../src/index.ts') require('../src/index')
expect(runMock).toHaveBeenCalled() expect(runMock).toHaveBeenCalled()
}) })

95
dist/index.js vendored
View file

@ -49333,12 +49333,10 @@ var Hugo;
Hugo["Org"] = "gohugoio"; Hugo["Org"] = "gohugoio";
Hugo["Repo"] = "hugo"; Hugo["Repo"] = "hugo";
Hugo["CmdName"] = "hugo"; Hugo["CmdName"] = "hugo";
Hugo["CmdOptVersion"] = "version";
Hugo["TestVersionLatest"] = "0.83.1";
Hugo["TestVersionSpec"] = "0.82.1";
})(Hugo || (Hugo = {})); })(Hugo || (Hugo = {}));
var DartSass; var DartSass;
(function (DartSass) { (function (DartSass) {
DartSass["Name"] = "dart-sass";
DartSass["Org"] = "sass"; DartSass["Org"] = "sass";
DartSass["Repo"] = "dart-sass"; DartSass["Repo"] = "dart-sass";
})(DartSass || (DartSass = {})); })(DartSass || (DartSass = {}));
@ -49411,6 +49409,18 @@ class Platform {
var tool_cache = __nccwpck_require__(7784); var tool_cache = __nccwpck_require__(7784);
;// CONCATENATED MODULE: external "node:os" ;// CONCATENATED MODULE: external "node:os"
const external_node_os_namespaceObject = require("node:os"); const external_node_os_namespaceObject = require("node:os");
// EXTERNAL MODULE: external "crypto"
var external_crypto_ = __nccwpck_require__(6113);
;// CONCATENATED MODULE: ./src/utils/error.ts
function errorMsg(e) {
if (typeof e === 'string') {
return e;
}
else if (e instanceof Error) {
return e.message;
}
}
;// CONCATENATED MODULE: ./src/hugo.ts ;// CONCATENATED MODULE: ./src/hugo.ts
@ -49419,11 +49429,13 @@ const external_node_os_namespaceObject = require("node:os");
class HugoInstaller { class HugoInstaller {
releaseLookup; releaseLookup;
platform; platform;
constructor(releaseLookup) { constructor(releaseLookup, platform) {
this.platform = new Platform(); this.platform = platform ?? new Platform();
this.releaseLookup = releaseLookup; this.releaseLookup = releaseLookup;
} }
async install(cmd) { async install(cmd) {
@ -49431,14 +49443,28 @@ class HugoInstaller {
core.debug(`Hugo extended: ${cmd.extended}`); core.debug(`Hugo extended: ${cmd.extended}`);
core.debug(`Operating System: ${this.platform.os}`); core.debug(`Operating System: ${this.platform.os}`);
core.debug(`Processor Architecture: ${this.platform.arch}`); core.debug(`Processor Architecture: ${this.platform.arch}`);
const hugoBinName = this.platform.binaryName(Hugo.CmdName);
const workDir = await this.platform.createWorkDir(); const workDir = await this.platform.createWorkDir();
const binDir = await this.platform.ensureBinDir(workDir); const binDir = await this.platform.ensureBinDir(workDir);
const tmpDir = external_node_os_namespaceObject.tmpdir(); const tmpDir = external_node_os_namespaceObject.tmpdir();
try {
const cachedTool = tool_cache.find(Hugo.Name, release.tag_name, this.platform.arch);
if (cachedTool) {
await (0,io.cp)(cachedTool, external_path_default().join(binDir, hugoBinName));
return;
}
else {
core.info('Tool not present in cache - downloading it...');
}
}
catch (e) {
core.warning(`Failed to lookup tool in cache: ${errorMsg(e)}`);
}
const toolUrl = release.assetUrl(this.platform, cmd.extended); const toolUrl = release.assetUrl(this.platform, cmd.extended);
if (!toolUrl) { if (!toolUrl) {
throw new Error('No matching URL detected for given platform'); throw new Error('No matching URL detected for given platform');
} }
const destPath = external_path_default().join(tmpDir, `hugo${this.platform.archiveExtension()}`); const destPath = external_path_default().join(tmpDir, `hugo_${(0,external_crypto_.randomUUID)()}${this.platform.archiveExtension()}`);
await tool_cache.downloadTool(toolUrl, destPath); await tool_cache.downloadTool(toolUrl, destPath);
core.debug(`Extract archive: ${destPath}`); core.debug(`Extract archive: ${destPath}`);
if (this.platform.isWindows()) { if (this.platform.isWindows()) {
@ -49447,8 +49473,15 @@ class HugoInstaller {
else { else {
await tool_cache.extractTar(destPath, tmpDir); await tool_cache.extractTar(destPath, tmpDir);
} }
await (0,io.rmRF)(destPath);
core.debug(`move binaries to binDir: ${binDir}`); core.debug(`move binaries to binDir: ${binDir}`);
await (0,io.mv)(external_path_default().join(tmpDir, this.platform.binaryName(Hugo.CmdName)), binDir); await (0,io.mv)(external_path_default().join(tmpDir, hugoBinName), binDir);
try {
await tool_cache.cacheFile(external_path_default().join(binDir, hugoBinName), hugoBinName, Hugo.Name, release.tag_name, this.platform.arch);
}
catch (e) {
core.warning(`Failed to cache Hugo install: ${errorMsg(e)}`);
}
} }
} }
const HugoReleaseTransformer = { const HugoReleaseTransformer = {
@ -49465,14 +49498,14 @@ class HugoRelease {
this.tag_name = tag_name; this.tag_name = tag_name;
this.defaultAssets = new Map(); this.defaultAssets = new Map();
this.extendedAssets = new Map(); this.extendedAssets = new Map();
assets.forEach(asset => { for (const asset of assets) {
if (asset.name.includes('extended')) { if (asset.name.includes('extended')) {
this.extendedAssets.set(asset.name.replace(HugoRelease.keyReplacementRegex, ''), asset.url); this.extendedAssets.set(asset.name.replace(HugoRelease.keyReplacementRegex, ''), asset.browser_download_url);
} }
else { else {
this.defaultAssets.set(asset.name.replace(HugoRelease.keyReplacementRegex, ''), asset.url); this.defaultAssets.set(asset.name.replace(HugoRelease.keyReplacementRegex, ''), asset.browser_download_url);
} }
}); }
} }
assetUrl(platform, extended) { assetUrl(platform, extended) {
const src = extended ? this.extendedAssets : this.defaultAssets; const src = extended ? this.extendedAssets : this.defaultAssets;
@ -49497,13 +49530,13 @@ class OctokitReleaseLookup {
async getRelease(owner, repo, version, transformer) { async getRelease(owner, repo, version, transformer) {
const latestRelease = version && version !== 'latest' const latestRelease = version && version !== 'latest'
? await this.octokit.rest.repos.getReleaseByTag({ ? await this.octokit.rest.repos.getReleaseByTag({
owner: owner, owner,
repo: repo, repo,
tag: version tag: version
}) })
: await this.octokit.rest.repos.getLatestRelease({ : await this.octokit.rest.repos.getLatestRelease({
owner: owner, owner,
repo: repo repo
}); });
return transformer.map(latestRelease.data); return transformer.map(latestRelease.data);
} }
@ -49517,6 +49550,8 @@ class OctokitReleaseLookup {
class DartSassInstaller { class DartSassInstaller {
releaseLookup; releaseLookup;
platform; platform;
@ -49531,11 +49566,18 @@ class DartSassInstaller {
const workDir = await this.platform.createWorkDir(); const workDir = await this.platform.createWorkDir();
const binDir = await this.platform.ensureBinDir(workDir); const binDir = await this.platform.ensureBinDir(workDir);
const tmpDir = external_node_os_namespaceObject.tmpdir(); const tmpDir = external_node_os_namespaceObject.tmpdir();
try {
core.addPath(tool_cache.find(DartSass.Name, release.tag_name, this.platform.arch));
return;
}
catch (e) {
core.warning(`Failed to lookup cached version: ${errorMsg(e)}`);
}
const toolUrl = release.assetUrl(this.platform); const toolUrl = release.assetUrl(this.platform);
if (!toolUrl) { if (!toolUrl) {
throw new Error('No matching URL detected for given platform'); throw new Error('No matching URL detected for given platform');
} }
const destPath = external_path_default().join(tmpDir, `dart-sass${this.platform.archiveExtension()}`); const destPath = external_path_default().join(tmpDir, `dart-sass-${(0,external_crypto_.randomUUID)()}${this.platform.archiveExtension()}`);
await tool_cache.downloadTool(toolUrl, destPath); await tool_cache.downloadTool(toolUrl, destPath);
core.debug(`Extract archive: ${destPath}`); core.debug(`Extract archive: ${destPath}`);
if (this.platform.isWindows()) { if (this.platform.isWindows()) {
@ -49544,8 +49586,16 @@ class DartSassInstaller {
else { else {
await tool_cache.extractTar(destPath, tmpDir); await tool_cache.extractTar(destPath, tmpDir);
} }
core.debug(`move binaries to binDir: ${binDir}`); await (0,io.rmRF)(destPath);
await (0,io.mv)(external_path_default().join(tmpDir, 'dart-sass', '*'), binDir); core.debug(`Move binaries to binDir: ${binDir}`);
await (0,io.mv)(external_path_default().join(tmpDir, 'dart-sass'), binDir);
core.debug(`Add 'dart-sass' directory to cache`);
try {
core.addPath(await tool_cache.cacheDir(external_path_default().join(binDir, 'dart-sass'), DartSass.Name, release.tag_name, this.platform.arch));
}
catch (e) {
core.warning(`Failed to cache dart-sass directory: ${errorMsg(e)}`);
}
} }
} }
const DartSassReleaseTransformer = { const DartSassReleaseTransformer = {
@ -49565,9 +49615,9 @@ class DartSassRelease {
constructor(tag_name, assets) { constructor(tag_name, assets) {
this.tag_name = tag_name; this.tag_name = tag_name;
this.assets = new Map(); this.assets = new Map();
assets.forEach(asset => { for (const asset of assets) {
this.assets.set(asset.name.replace(DartSassRelease.keyReplacementRegex, ''), asset.url); this.assets.set(asset.name.replace(DartSassRelease.keyReplacementRegex, ''), asset.browser_download_url);
}); }
} }
assetUrl(platform) { assetUrl(platform) {
const mappedOS = DartSassRelease.platformMapping[platform.os]; const mappedOS = DartSassRelease.platformMapping[platform.os];
@ -49598,12 +49648,13 @@ async function run() {
;// CONCATENATED MODULE: ./src/index.ts ;// CONCATENATED MODULE: ./src/index.ts
(async () => { (async () => {
try { try {
await run(); await run();
} }
catch (e) { catch (e) {
core.setFailed(`Action failed with error ${e.message}`); core.setFailed(`Action failed with error ${errorMsg(e)}`);
} }
})(); })();

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View file

@ -27,8 +27,8 @@
}, },
"lint-staged": { "lint-staged": {
"{src,__tests__}/**/*.ts": [ "{src,__tests__}/**/*.ts": [
"prettier --check", "npx prettier --check",
"eslint" "npx eslint . -c ./.forgejo/linters/.eslintrc.yml"
], ],
"README.md": [ "README.md": [
"npx doctoc@2.1.0 --github" "npx doctoc@2.1.0 --github"

View file

@ -36,13 +36,13 @@ export class OctokitReleaseLookup implements IReleaseLookup {
const latestRelease = const latestRelease =
version && version !== 'latest' version && version !== 'latest'
? await this.octokit.rest.repos.getReleaseByTag({ ? await this.octokit.rest.repos.getReleaseByTag({
owner: owner, owner,
repo: repo, repo,
tag: version tag: version
}) })
: await this.octokit.rest.repos.getLatestRelease({ : await this.octokit.rest.repos.getLatestRelease({
owner: owner, owner,
repo: repo repo
}) })
return transformer.map(latestRelease.data) return transformer.map(latestRelease.data)
} }

View file

@ -2,13 +2,11 @@ export enum Hugo {
Name = 'Hugo', Name = 'Hugo',
Org = 'gohugoio', Org = 'gohugoio',
Repo = 'hugo', Repo = 'hugo',
CmdName = 'hugo', CmdName = 'hugo'
CmdOptVersion = 'version',
TestVersionLatest = '0.83.1',
TestVersionSpec = '0.82.1'
} }
export enum DartSass { export enum DartSass {
Name = 'dart-sass',
Org = 'sass', Org = 'sass',
Repo = 'dart-sass' Repo = 'dart-sass'
} }

View file

@ -1,12 +1,14 @@
import { IGithubRelease, IReleaseLookup } from './asset-lookup' import { IGithubRelease, IReleaseLookup } from './asset-lookup'
import { DartSass, Hugo } from './constants' import { DartSass } from './constants'
import { components } from '@octokit/openapi-types' import { components } from '@octokit/openapi-types'
import * as tc from '@actions/tool-cache' import * as tc from '@actions/tool-cache'
import * as core from '@actions/core' import * as core from '@actions/core'
import { Platform } from './os' import { Platform } from './os'
import * as os from 'node:os' import * as os from 'node:os'
import path from 'path' import path from 'path'
import { mv } from '@actions/io' import { mv, rmRF } from '@actions/io'
import { randomUUID } from 'crypto'
import { errorMsg } from './utils/error'
export interface IDartSassInstallCommand { export interface IDartSassInstallCommand {
version: string version: string
@ -36,6 +38,13 @@ export class DartSassInstaller {
const binDir = await this.platform.ensureBinDir(workDir) const binDir = await this.platform.ensureBinDir(workDir)
const tmpDir = os.tmpdir() const tmpDir = os.tmpdir()
try {
core.addPath(tc.find(DartSass.Name, release.tag_name, this.platform.arch))
return
} catch (e) {
core.warning(`Failed to lookup cached version: ${errorMsg(e)}`)
}
const toolUrl = release.assetUrl(this.platform) const toolUrl = release.assetUrl(this.platform)
if (!toolUrl) { if (!toolUrl) {
@ -44,7 +53,7 @@ export class DartSassInstaller {
const destPath = path.join( const destPath = path.join(
tmpDir, tmpDir,
`dart-sass${this.platform.archiveExtension()}` `dart-sass-${randomUUID()}${this.platform.archiveExtension()}`
) )
await tc.downloadTool(toolUrl, destPath) await tc.downloadTool(toolUrl, destPath)
@ -55,8 +64,25 @@ export class DartSassInstaller {
await tc.extractTar(destPath, tmpDir) await tc.extractTar(destPath, tmpDir)
} }
core.debug(`move binaries to binDir: ${binDir}`) await rmRF(destPath)
await mv(path.join(tmpDir, 'dart-sass', '*'), binDir)
core.debug(`Move binaries to binDir: ${binDir}`)
await mv(path.join(tmpDir, 'dart-sass'), binDir)
core.debug(`Add 'dart-sass' directory to cache`)
try {
core.addPath(
await tc.cacheDir(
path.join(binDir, 'dart-sass'),
DartSass.Name,
release.tag_name,
this.platform.arch
)
)
} catch (e) {
core.warning(`Failed to cache dart-sass directory: ${errorMsg(e)}`)
}
} }
} }
@ -90,12 +116,12 @@ export class DartSassRelease implements IGithubRelease {
this.tag_name = tag_name this.tag_name = tag_name
this.assets = new Map<string, string>() this.assets = new Map<string, string>()
assets.forEach(asset => { for (const asset of assets) {
this.assets.set( this.assets.set(
asset.name.replace(DartSassRelease.keyReplacementRegex, ''), asset.name.replace(DartSassRelease.keyReplacementRegex, ''),
asset.url asset.browser_download_url
) )
}) }
} }
assetUrl(platform: Platform): string | undefined { assetUrl(platform: Platform): string | undefined {

View file

@ -6,19 +6,21 @@ import { components } from '@octokit/openapi-types'
import * as tc from '@actions/tool-cache' import * as tc from '@actions/tool-cache'
import path from 'path' import path from 'path'
import * as os from 'node:os' import * as os from 'node:os'
import { mv } from '@actions/io' import { mv, rmRF, cp } from '@actions/io'
import { randomUUID } from 'crypto'
import { errorMsg } from './utils/error'
export interface IHugoInstallCommand { export interface IHugoInstallCommand {
version: string version?: string
extended: boolean extended?: boolean
} }
export class HugoInstaller { export class HugoInstaller {
private readonly releaseLookup: IReleaseLookup private readonly releaseLookup: IReleaseLookup
private readonly platform: Platform private readonly platform: Platform
constructor(releaseLookup: IReleaseLookup) { constructor(releaseLookup: IReleaseLookup, platform?: Platform) {
this.platform = new Platform() this.platform = platform ?? new Platform()
this.releaseLookup = releaseLookup this.releaseLookup = releaseLookup
} }
@ -34,10 +36,27 @@ export class HugoInstaller {
core.debug(`Operating System: ${this.platform.os}`) core.debug(`Operating System: ${this.platform.os}`)
core.debug(`Processor Architecture: ${this.platform.arch}`) core.debug(`Processor Architecture: ${this.platform.arch}`)
const hugoBinName = this.platform.binaryName(Hugo.CmdName)
const workDir = await this.platform.createWorkDir() const workDir = await this.platform.createWorkDir()
const binDir = await this.platform.ensureBinDir(workDir) const binDir = await this.platform.ensureBinDir(workDir)
const tmpDir = os.tmpdir() const tmpDir = os.tmpdir()
try {
const cachedTool = tc.find(
Hugo.Name,
release.tag_name,
this.platform.arch
)
if (cachedTool) {
await cp(cachedTool, path.join(binDir, hugoBinName))
return
} else {
core.info('Tool not present in cache - downloading it...')
}
} catch (e) {
core.warning(`Failed to lookup tool in cache: ${errorMsg(e)}`)
}
const toolUrl = release.assetUrl(this.platform, cmd.extended) const toolUrl = release.assetUrl(this.platform, cmd.extended)
if (!toolUrl) { if (!toolUrl) {
@ -46,7 +65,7 @@ export class HugoInstaller {
const destPath = path.join( const destPath = path.join(
tmpDir, tmpDir,
`hugo${this.platform.archiveExtension()}` `hugo_${randomUUID()}${this.platform.archiveExtension()}`
) )
await tc.downloadTool(toolUrl, destPath) await tc.downloadTool(toolUrl, destPath)
@ -57,8 +76,22 @@ export class HugoInstaller {
await tc.extractTar(destPath, tmpDir) await tc.extractTar(destPath, tmpDir)
} }
await rmRF(destPath)
core.debug(`move binaries to binDir: ${binDir}`) core.debug(`move binaries to binDir: ${binDir}`)
await mv(path.join(tmpDir, this.platform.binaryName(Hugo.CmdName)), binDir) await mv(path.join(tmpDir, hugoBinName), binDir)
try {
await tc.cacheFile(
path.join(binDir, hugoBinName),
hugoBinName,
Hugo.Name,
release.tag_name,
this.platform.arch
)
} catch (e) {
core.warning(`Failed to cache Hugo install: ${errorMsg(e)}`)
}
} }
} }
@ -86,22 +119,22 @@ export class HugoRelease implements IGithubRelease {
this.defaultAssets = new Map<string, string>() this.defaultAssets = new Map<string, string>()
this.extendedAssets = new Map<string, string>() this.extendedAssets = new Map<string, string>()
assets.forEach(asset => { for (const asset of assets) {
if (asset.name.includes('extended')) { if (asset.name.includes('extended')) {
this.extendedAssets.set( this.extendedAssets.set(
asset.name.replace(HugoRelease.keyReplacementRegex, ''), asset.name.replace(HugoRelease.keyReplacementRegex, ''),
asset.url asset.browser_download_url
) )
} else { } else {
this.defaultAssets.set( this.defaultAssets.set(
asset.name.replace(HugoRelease.keyReplacementRegex, ''), asset.name.replace(HugoRelease.keyReplacementRegex, ''),
asset.url asset.browser_download_url
) )
} }
}) }
} }
assetUrl(platform: Platform, extended: boolean): string | undefined { assetUrl(platform: Platform, extended?: boolean): string | undefined {
const src = extended ? this.extendedAssets : this.defaultAssets const src = extended ? this.extendedAssets : this.defaultAssets
const arch = platform.os === 'darwin' ? 'universal' : platform.arch const arch = platform.os === 'darwin' ? 'universal' : platform.arch
const key = `${platform.os}-${arch}${platform.archiveExtension()}` const key = `${platform.os}-${arch}${platform.archiveExtension()}`

View file

@ -1,9 +1,10 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as main from './main' import * as main from './main'
import { errorMsg } from './utils/error'
;(async (): Promise<void> => { ;(async (): Promise<void> => {
try { try {
await main.run() await main.run()
} catch (e: any) { } catch (e) {
core.setFailed(`Action failed with error ${e.message}`) core.setFailed(`Action failed with error ${errorMsg(e)}`)
} }
})() })()

7
src/utils/error.ts Normal file
View file

@ -0,0 +1,7 @@
export function errorMsg(e: unknown): string | undefined {
if (typeof e === 'string') {
return e
} else if (e instanceof Error) {
return e.message
}
}

View file

@ -12,7 +12,8 @@
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"strict": true, "strict": true,
"skipLibCheck": true, "skipLibCheck": true,
"newLine": "lf" "newLine": "lf",
"useUnknownInCatchVariables": true
}, },
"exclude": ["./dist", "./node_modules", "./__tests__", "./coverage"] "exclude": ["./dist", "./node_modules", "./__tests__", "./coverage"]
} }