Bump all dependencies (#234)

* Bump all dependencies

* build and reformat

* lint

* format
This commit is contained in:
Nikola Jokic
2025-07-29 11:06:45 +02:00
committed by GitHub
parent dd4f7dae2c
commit 589414ea69
29 changed files with 23797 additions and 12824 deletions

View File

@@ -1,13 +1,26 @@
// eslint-disable-next-line import/no-commonjs
module.exports = {
clearMocks: true,
preset: 'ts-jest',
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*-test.ts'],
testRunner: 'jest-circus/runner',
verbose: true,
transform: {
'^.+\\.ts$': 'ts-jest'
'^.+\\.ts$': [
'ts-jest',
{
tsconfig: 'tsconfig.test.json'
}
],
// Transform ESM modules to CommonJS
'^.+\\.(js|mjs)$': ['babel-jest', {
presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
}]
},
setupFilesAfterEnv: ['./jest.setup.js'],
verbose: true
transformIgnorePatterns: [
// Transform these ESM packages
'node_modules/(?!(shlex|@kubernetes/client-node|openid-client|oauth4webapi|jose|uuid)/)'
],
setupFilesAfterEnv: ['./jest.setup.js']
}

View File

@@ -1 +1,2 @@
// eslint-disable-next-line filenames/match-regex, no-undef
jest.setTimeout(500000)

File diff suppressed because it is too large Load Diff

View File

@@ -13,20 +13,24 @@
"author": "",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.9.1",
"@actions/core": "^1.11.1",
"@actions/exec": "^1.1.1",
"@actions/io": "^1.1.2",
"@kubernetes/client-node": "^0.22.2",
"@actions/io": "^1.1.3",
"@kubernetes/client-node": "^1.3.0",
"hooklib": "file:../hooklib",
"js-yaml": "^4.1.0",
"shlex": "^2.1.2"
"shlex": "^3.0.0",
"uuid": "^11.1.0"
},
"devDependencies": {
"@types/jest": "^27.4.1",
"@types/node": "^17.0.23",
"@vercel/ncc": "^0.33.4",
"jest": "^27.5.1",
"ts-jest": "^27.1.4",
"typescript": "^4.6.3"
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.4",
"@types/jest": "^30.0.0",
"@types/node": "^24.0.14",
"@vercel/ncc": "^0.38.3",
"babel-jest": "^30.0.4",
"jest": "^30.0.4",
"ts-jest": "^29.4.0",
"typescript": "^5.8.3"
}
}

View File

@@ -5,7 +5,8 @@ import {
JobContainerInfo,
ContextPorts,
PrepareJobArgs,
writeToResponseFile
writeToResponseFile,
ServiceContainerInfo
} from 'hooklib'
import path from 'path'
import {
@@ -167,7 +168,9 @@ function generateResponseFile(
const ctxPorts: ContextPorts = {}
if (c.ports?.length) {
for (const port of c.ports) {
ctxPorts[port.containerPort] = port.hostPort
if (port.containerPort && port.hostPort) {
ctxPorts[port.containerPort.toString()] = port.hostPort.toString()
}
}
}
@@ -193,7 +196,7 @@ async function copyExternalsToRoot(): Promise<void> {
}
export function createContainerSpec(
container: JobContainerInfo,
container: JobContainerInfo | ServiceContainerInfo,
name: string,
jobContainer = false,
extension?: k8s.V1PodTemplateSpec
@@ -208,24 +211,24 @@ export function createContainerSpec(
image: container.image,
ports: containerPorts(container)
} as k8s.V1Container
if (container.workingDirectory) {
podContainer.workingDir = container.workingDirectory
if (container['workingDirectory']) {
podContainer.workingDir = container['workingDirectory']
}
if (container.entryPoint) {
podContainer.command = [container.entryPoint]
}
if (container.entryPointArgs?.length > 0) {
if (container.entryPointArgs && container.entryPointArgs.length > 0) {
podContainer.args = fixArgs(container.entryPointArgs)
}
podContainer.env = []
for (const [key, value] of Object.entries(
container['environmentVariables']
container['environmentVariables'] || {}
)) {
if (value && key !== 'HOME') {
podContainer.env.push({ name: key, value: value as string })
podContainer.env.push({ name: key, value })
}
}
@@ -234,7 +237,7 @@ export function createContainerSpec(
value: 'true'
})
if (!('CI' in container['environmentVariables'])) {
if (!('CI' in (container['environmentVariables'] || {}))) {
podContainer.env.push({
name: 'CI',
value: 'true'

View File

@@ -1,5 +1,11 @@
import * as core from '@actions/core'
import { Command, getInputFromStdin, prepareJobArgs } from 'hooklib'
import {
Command,
getInputFromStdin,
PrepareJobArgs,
RunContainerStepArgs,
RunScriptStepArgs
} from 'hooklib'
import {
cleanupJob,
prepareJob,
@@ -27,16 +33,16 @@ async function run(): Promise<void> {
let exitCode = 0
switch (command) {
case Command.PrepareJob:
await prepareJob(args as prepareJobArgs, responseFile)
await prepareJob(args as PrepareJobArgs, responseFile)
return process.exit(0)
case Command.CleanupJob:
await cleanupJob()
return process.exit(0)
case Command.RunScriptStep:
await runScriptStep(args, state, null)
await runScriptStep(args as RunScriptStepArgs, state, null)
return process.exit(0)
case Command.RunContainerStep:
exitCode = await runContainerStep(args)
exitCode = await runContainerStep(args as RunContainerStepArgs)
return process.exit(exitCode)
default:
throw new Error(`Command not recognized: ${command}`)

View File

@@ -1,7 +1,7 @@
import * as core from '@actions/core'
import * as k8s from '@kubernetes/client-node'
import { ContainerInfo, Registry } from 'hooklib'
import * as stream from 'stream'
import type { ContainerInfo, Registry } from 'hooklib'
import {
getJobPodName,
getRunnerPodName,
@@ -127,8 +127,10 @@ export async function createPod(
mergePodSpecWithOptions(appPod.spec, extension.spec)
}
const { body } = await k8sApi.createNamespacedPod(namespace(), appPod)
return body
return await k8sApi.createNamespacedPod({
namespace: namespace(),
body: appPod
})
}
export async function createJob(
@@ -183,46 +185,42 @@ export async function createJob(
}
}
const { body } = await k8sBatchV1Api.createNamespacedJob(namespace(), job)
return body
return await k8sBatchV1Api.createNamespacedJob({
namespace: namespace(),
body: job
})
}
export async function getContainerJobPodName(jobName: string): Promise<string> {
const selector = `job-name=${jobName}`
const backOffManager = new BackOffManager(60)
while (true) {
const podList = await k8sApi.listNamespacedPod(
namespace(),
undefined,
undefined,
undefined,
undefined,
selector,
1
)
const podList = await k8sApi.listNamespacedPod({
namespace: namespace(),
labelSelector: selector,
limit: 1
})
if (!podList.body.items?.length) {
if (!podList.items?.length) {
await backOffManager.backOff()
continue
}
if (!podList.body.items[0].metadata?.name) {
if (!podList.items[0].metadata?.name) {
throw new Error(
`Failed to determine the name of the pod for job ${jobName}`
)
}
return podList.body.items[0].metadata.name
return podList.items[0].metadata.name
}
}
export async function deletePod(podName: string): Promise<void> {
await k8sApi.deleteNamespacedPod(
podName,
namespace(),
undefined,
undefined,
0
)
export async function deletePod(name: string): Promise<void> {
await k8sApi.deleteNamespacedPod({
name,
namespace: namespace(),
gracePeriodSeconds: 0
})
}
export async function execPodStep(
@@ -261,7 +259,6 @@ export async function execPodStep(
}
)
// If exec.exec fails, explicitly reject the outer promise
// eslint-disable-next-line github/no-then
.catch(e => reject(e))
})
}
@@ -274,7 +271,7 @@ export async function waitForJobToComplete(jobName: string): Promise<void> {
return
}
} catch (error) {
throw new Error(`job ${jobName} has failed`)
throw new Error(`job ${jobName} has failed: ${JSON.stringify(error)}`)
}
await backOffManager.backOff()
}
@@ -315,8 +312,10 @@ export async function createDockerSecret(
)
}
const { body } = await k8sApi.createNamespacedSecret(namespace(), secret)
return body
return await k8sApi.createNamespacedSecret({
namespace: namespace(),
body: secret
})
}
export async function createSecretForEnvs(envs: {
@@ -340,30 +339,33 @@ export async function createSecretForEnvs(envs: {
secret.data[key] = Buffer.from(value).toString('base64')
}
await k8sApi.createNamespacedSecret(namespace(), secret)
await k8sApi.createNamespacedSecret({
namespace: namespace(),
body: secret
})
return secretName
}
export async function deleteSecret(secretName: string): Promise<void> {
await k8sApi.deleteNamespacedSecret(secretName, namespace())
export async function deleteSecret(name: string): Promise<void> {
await k8sApi.deleteNamespacedSecret({
name,
namespace: namespace()
})
}
export async function pruneSecrets(): Promise<void> {
const secretList = await k8sApi.listNamespacedSecret(
namespace(),
undefined,
undefined,
undefined,
undefined,
new RunnerInstanceLabel().toString()
)
if (!secretList.body.items.length) {
const secretList = await k8sApi.listNamespacedSecret({
namespace: namespace(),
labelSelector: new RunnerInstanceLabel().toString()
})
if (!secretList.items.length) {
return
}
await Promise.all(
secretList.body.items.map(
secret => secret.metadata?.name && deleteSecret(secret.metadata.name)
secretList.items.map(
async secret =>
secret.metadata?.name && (await deleteSecret(secret.metadata.name))
)
)
}
@@ -391,7 +393,9 @@ export async function waitForPodPhases(
await backOffManager.backOff()
}
} catch (error) {
throw new Error(`Pod ${podName} is unhealthy with phase status ${phase}`)
throw new Error(
`Pod ${podName} is unhealthy with phase status ${phase}: ${JSON.stringify(error)}`
)
}
}
@@ -414,7 +418,7 @@ export function getPrepareJobTimeoutSeconds(): number {
return timeoutSeconds
}
async function getPodPhase(podName: string): Promise<PodPhase> {
async function getPodPhase(name: string): Promise<PodPhase> {
const podPhaseLookup = new Set<string>([
PodPhase.PENDING,
PodPhase.RUNNING,
@@ -422,8 +426,10 @@ async function getPodPhase(podName: string): Promise<PodPhase> {
PodPhase.FAILED,
PodPhase.UNKNOWN
])
const { body } = await k8sApi.readNamespacedPod(podName, namespace())
const pod = body
const pod = await k8sApi.readNamespacedPod({
name,
namespace: namespace()
})
if (!pod.status?.phase || !podPhaseLookup.has(pod.status.phase)) {
return PodPhase.UNKNOWN
@@ -431,11 +437,13 @@ async function getPodPhase(podName: string): Promise<PodPhase> {
return pod.status?.phase as PodPhase
}
async function isJobSucceeded(jobName: string): Promise<boolean> {
const { body } = await k8sBatchV1Api.readNamespacedJob(jobName, namespace())
const job = body
async function isJobSucceeded(name: string): Promise<boolean> {
const job = await k8sBatchV1Api.readNamespacedJob({
name,
namespace: namespace()
})
if (job.status?.failed) {
throw new Error(`job ${jobName} has failed`)
throw new Error(`job ${name} has failed`)
}
return !!job.status?.succeeded
}
@@ -455,30 +463,26 @@ export async function getPodLogs(
process.stderr.write(err.message)
})
const r = await log.log(namespace(), podName, containerName, logStream, {
await log.log(namespace(), podName, containerName, logStream, {
follow: true,
pretty: false,
timestamps: false
})
await new Promise(resolve => r.on('close', () => resolve(null)))
await new Promise(resolve => logStream.on('end', () => resolve(null)))
}
export async function prunePods(): Promise<void> {
const podList = await k8sApi.listNamespacedPod(
namespace(),
undefined,
undefined,
undefined,
undefined,
new RunnerInstanceLabel().toString()
)
if (!podList.body.items.length) {
const podList = await k8sApi.listNamespacedPod({
namespace: namespace(),
labelSelector: new RunnerInstanceLabel().toString()
})
if (!podList.items.length) {
return
}
await Promise.all(
podList.body.items.map(
pod => pod.metadata?.name && deletePod(pod.metadata.name)
podList.items.map(
async pod => pod.metadata?.name && (await deletePod(pod.metadata.name))
)
)
}
@@ -486,16 +490,16 @@ export async function prunePods(): Promise<void> {
export async function getPodStatus(
name: string
): Promise<k8s.V1PodStatus | undefined> {
const { body } = await k8sApi.readNamespacedPod(name, namespace())
return body.status
const pod = await k8sApi.readNamespacedPod({
name,
namespace: namespace()
})
return pod.status
}
export async function isAuthPermissionsOK(): Promise<boolean> {
const sar = new k8s.V1SelfSubjectAccessReview()
const asyncs: Promise<{
response: unknown
body: k8s.V1SelfSubjectAccessReview
}>[] = []
const asyncs: Promise<k8s.V1SelfSubjectAccessReview>[] = []
for (const resource of requiredPermissions) {
for (const verb of resource.verbs) {
sar.spec = new k8s.V1SelfSubjectAccessReviewSpec()
@@ -505,11 +509,13 @@ export async function isAuthPermissionsOK(): Promise<boolean> {
sar.spec.resourceAttributes.group = resource.group
sar.spec.resourceAttributes.resource = resource.resource
sar.spec.resourceAttributes.subresource = resource.subresource
asyncs.push(k8sAuthorizationV1Api.createSelfSubjectAccessReview(sar))
asyncs.push(
k8sAuthorizationV1Api.createSelfSubjectAccessReview({ body: sar })
)
}
}
const responses = await Promise.all(asyncs)
return responses.every(resp => resp.body.status?.allowed)
return responses.every(resp => resp.status?.allowed)
}
export async function isPodContainerAlpine(
@@ -527,7 +533,7 @@ export async function isPodContainerAlpine(
podName,
containerName
)
} catch (err) {
} catch {
isAlpine = false
}
@@ -535,9 +541,12 @@ export async function isPodContainerAlpine(
}
async function getCurrentNodeName(): Promise<string> {
const resp = await k8sApi.readNamespacedPod(getRunnerPodName(), namespace())
const resp = await k8sApi.readNamespacedPod({
name: getRunnerPodName(),
namespace: namespace()
})
const nodeName = resp.body.spec?.nodeName
const nodeName = resp.spec?.nodeName
if (!nodeName) {
throw new Error('Failed to determine node name')
}
@@ -647,6 +656,8 @@ export function containerPorts(
}
export async function getPodByName(name): Promise<k8s.V1Pod> {
const { body } = await k8sApi.readNamespacedPod(name, namespace())
return body
return await k8sApi.readNamespacedPod({
name,
namespace: namespace()
})
}

View File

@@ -167,7 +167,7 @@ exec ${environmentPrefix} ${entryPoint} ${
export function generateContainerName(image: string): string {
const nameWithTag = image.split('/').pop()
const name = nameWithTag?.split(':').at(0)
const name = nameWithTag?.split(':')[0]
if (!name) {
throw new Error(`Image definition '${image}' is invalid`)

View File

@@ -3,6 +3,7 @@ import { cleanupJob, prepareJob } from '../src/hooks'
import { RunnerInstanceLabel } from '../src/hooks/constants'
import { namespace } from '../src/k8s'
import { TestHelper } from './test-setup'
import { PrepareJobArgs } from 'hooklib'
let testHelper: TestHelper
@@ -14,7 +15,10 @@ describe('Cleanup Job', () => {
const prepareJobOutputFilePath = testHelper.createFile(
'prepare-job-output.json'
)
await prepareJob(prepareJobData.args, prepareJobOutputFilePath)
await prepareJob(
prepareJobData.args as PrepareJobArgs,
prepareJobOutputFilePath
)
})
afterEach(async () => {
@@ -32,16 +36,12 @@ describe('Cleanup Job', () => {
kc.loadFromDefault()
const k8sApi = kc.makeApiClient(k8s.CoreV1Api)
const podList = await k8sApi.listNamespacedPod(
namespace(),
undefined,
undefined,
undefined,
undefined,
new RunnerInstanceLabel().toString()
)
const podList = await k8sApi.listNamespacedPod({
namespace: namespace(),
labelSelector: new RunnerInstanceLabel().toString()
})
expect(podList.body.items.length).toBe(0)
expect(podList.items.length).toBe(0)
})
it('should have no runner linked secrets', async () => {
@@ -51,15 +51,11 @@ describe('Cleanup Job', () => {
kc.loadFromDefault()
const k8sApi = kc.makeApiClient(k8s.CoreV1Api)
const secretList = await k8sApi.listNamespacedSecret(
namespace(),
undefined,
undefined,
undefined,
undefined,
new RunnerInstanceLabel().toString()
)
const secretList = await k8sApi.listNamespacedSecret({
namespace: namespace(),
labelSelector: new RunnerInstanceLabel().toString()
})
expect(secretList.body.items.length).toBe(0)
expect(secretList.items.length).toBe(0)
})
})

View File

@@ -6,6 +6,7 @@ import {
runScriptStep
} from '../src/hooks'
import { TestHelper } from './test-setup'
import { RunContainerStepArgs, RunScriptStepArgs } from 'hooklib'
jest.useRealTimers()
@@ -36,13 +37,17 @@ describe('e2e', () => {
const prepareJobOutputData = JSON.parse(prepareJobOutputJson.toString())
await expect(
runScriptStep(scriptStepData.args, prepareJobOutputData.state, null)
runScriptStep(
scriptStepData.args as RunScriptStepArgs,
prepareJobOutputData.state,
null
)
).resolves.not.toThrow()
const runContainerStepData = testHelper.getRunContainerStepDefinition()
await expect(
runContainerStep(runContainerStepData.args)
runContainerStep(runContainerStepData.args as RunContainerStepArgs)
).resolves.not.toThrow()
await expect(cleanupJob()).resolves.not.toThrow()

View File

@@ -230,7 +230,9 @@ describe('k8s utils', () => {
containerVolumes(
[
{
sourceVolumePath: '/outside/of/workdir'
sourceVolumePath: '/outside/of/workdir',
targetVolumePath: '/some/target/path',
readOnly: false
}
],
true,

View File

@@ -6,12 +6,10 @@ import { TestHelper } from './test-setup'
import {
ENV_HOOK_TEMPLATE_PATH,
ENV_USE_KUBE_SCHEDULER,
generateContainerName,
readExtensionFromFile
generateContainerName
} from '../src/k8s/utils'
import { getPodByName } from '../src/k8s'
import { V1Container } from '@kubernetes/client-node'
import * as yaml from 'js-yaml'
import { JOB_CONTAINER_NAME } from '../src/hooks/constants'
jest.useRealTimers()

View File

@@ -1,6 +1,7 @@
import * as fs from 'fs'
import { cleanupJob, prepareJob, runScriptStep } from '../src/hooks'
import { TestHelper } from './test-setup'
import { PrepareJobArgs } from 'hooklib'
jest.useRealTimers()
@@ -21,7 +22,10 @@ describe('Run script step', () => {
const prepareJobData = testHelper.getPrepareJobDefinition()
runScriptStepDefinition = testHelper.getRunScriptStepDefinition()
await prepareJob(prepareJobData.args, prepareJobOutputFilePath)
await prepareJob(
prepareJobData.args as PrepareJobArgs,
prepareJobOutputFilePath
)
const outputContent = fs.readFileSync(prepareJobOutputFilePath)
prepareJobOutputData = JSON.parse(outputContent.toString())
})

View File

@@ -19,7 +19,7 @@ export class TestHelper {
this.podName = uuidv4().replace(/-/g, '')
}
public async initialize(): Promise<void> {
async initialize(): Promise<void> {
process.env['ACTIONS_RUNNER_POD_NAME'] = `${this.podName}`
process.env['RUNNER_WORKSPACE'] = `${this.tempDirPath}/_work/repo`
process.env['RUNNER_TEMP'] = `${this.tempDirPath}/_work/_temp`
@@ -44,49 +44,66 @@ export class TestHelper {
}
}
public async cleanup(): Promise<void> {
async cleanup(): Promise<void> {
try {
await this.cleanupK8sResources()
fs.rmSync(this.tempDirPath, { recursive: true })
} catch {}
} catch {
// Ignore errors during cleanup
}
}
public async cleanupK8sResources() {
async cleanupK8sResources(): Promise<void> {
await k8sApi
.deleteNamespacedPersistentVolumeClaim(
`${this.podName}-work`,
'default',
undefined,
undefined,
0
)
.catch(e => {})
await k8sApi.deletePersistentVolume(`${this.podName}-pv`).catch(e => {})
await k8sStorageApi.deleteStorageClass('local-storage').catch(e => {})
.deleteNamespacedPersistentVolumeClaim({
name: `${this.podName}-work`,
namespace: 'default',
gracePeriodSeconds: 0
})
.catch(e => {
console.error(e)
})
await k8sApi
.deleteNamespacedPod(this.podName, 'default', undefined, undefined, 0)
.catch(e => {})
.deletePersistentVolume({ name: `${this.podName}-pv` })
.catch(e => {
console.error(e)
})
await k8sStorageApi
.deleteStorageClass({ name: 'local-storage' })
.catch(e => {
console.error(e)
})
await k8sApi
.deleteNamespacedPod(
`${this.podName}-workflow`,
'default',
undefined,
undefined,
0
)
.catch(e => {})
.deleteNamespacedPod({
name: this.podName,
namespace: 'default',
gracePeriodSeconds: 0
})
.catch(e => {
console.error(e)
})
await k8sApi
.deleteNamespacedPod({
name: `${this.podName}-workflow`,
namespace: 'default',
gracePeriodSeconds: 0
})
.catch(e => {
console.error(e)
})
}
public createFile(fileName?: string): string {
createFile(fileName?: string): string {
const filePath = `${this.tempDirPath}/${fileName || uuidv4()}`
fs.writeFileSync(filePath, '')
return filePath
}
public removeFile(fileName: string): void {
removeFile(fileName: string): void {
const filePath = `${this.tempDirPath}/${fileName}`
fs.rmSync(filePath)
}
public async createTestJobPod() {
async createTestJobPod(): Promise<void> {
const container = {
name: 'nginx',
image: 'nginx:latest',
@@ -102,10 +119,10 @@ export class TestHelper {
containers: [container]
}
} as k8s.V1Pod
await k8sApi.createNamespacedPod('default', pod)
await k8sApi.createNamespacedPod({ namespace: 'default', body: pod })
}
public async createTestVolume() {
async createTestVolume(): Promise<void> {
var sc: k8s.V1StorageClass = {
metadata: {
name: 'local-storage'
@@ -113,7 +130,7 @@ export class TestHelper {
provisioner: 'kubernetes.io/no-provisioner',
volumeBindingMode: 'Immediate'
}
await k8sStorageApi.createStorageClass(sc)
await k8sStorageApi.createStorageClass({ body: sc })
var volume: k8s.V1PersistentVolume = {
metadata: {
@@ -131,7 +148,7 @@ export class TestHelper {
}
}
}
await k8sApi.createPersistentVolume(volume)
await k8sApi.createPersistentVolume({ body: volume })
var volumeClaim: k8s.V1PersistentVolumeClaim = {
metadata: {
name: `${this.podName}-work`
@@ -148,10 +165,13 @@ export class TestHelper {
}
}
}
await k8sApi.createNamespacedPersistentVolumeClaim('default', volumeClaim)
await k8sApi.createNamespacedPersistentVolumeClaim({
namespace: 'default',
body: volumeClaim
})
}
public getPrepareJobDefinition(): HookData {
getPrepareJobDefinition(): HookData {
const prepareJob = JSON.parse(
fs.readFileSync(
path.resolve(__dirname + '/../../../examples/prepare-job.json'),
@@ -168,7 +188,7 @@ export class TestHelper {
return prepareJob
}
public getRunScriptStepDefinition(): HookData {
getRunScriptStepDefinition(): HookData {
const runScriptStep = JSON.parse(
fs.readFileSync(
path.resolve(__dirname + '/../../../examples/run-script-step.json'),
@@ -180,7 +200,7 @@ export class TestHelper {
return runScriptStep
}
public getRunContainerStepDefinition(): HookData {
getRunContainerStepDefinition(): HookData {
const runContainerStep = JSON.parse(
fs.readFileSync(
path.resolve(__dirname + '/../../../examples/run-container-step.json'),

View File

@@ -5,7 +5,8 @@
"outDir": "./lib",
"rootDir": "./src"
},
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"include": [
"./src"
"src/**/*",
]
}
}

View File

@@ -0,0 +1,6 @@
{
"compilerOptions": {
"allowJs": true
},
"extends": "./tsconfig.json"
}