mirror of https://github.com/aminya/setup-cpp
Merge pull request #294 from aminya/cmakelang [skip ci]
This commit is contained in:
commit
fb2a9a2418
|
@ -17,7 +17,7 @@ Setting up a **cross-platform** environment for building and testing C++/C proje
|
|||
| category | tools |
|
||||
| --------------------- | --------------------------------------------------------------------------- |
|
||||
| compiler and analyzer | llvm, gcc, msvc, apple-clang, vcvarsall, cppcheck, clang-tidy, clang-format |
|
||||
| build system | cmake, ninja, meson, make, task, bazel |
|
||||
| build system | cmake, ninja, meson, make, task, bazel, cmakelang, cmake-format, cmake-lint |
|
||||
| package manager | vcpkg, conan, choco, brew, nala |
|
||||
| cache | ccache, sccache |
|
||||
| documentation | doxygen, graphviz |
|
||||
|
|
15
action.yml
15
action.yml
|
@ -70,6 +70,21 @@ inputs:
|
|||
cmake:
|
||||
description: "Wether to install cmake (true/false) or the specific version to install."
|
||||
required: false
|
||||
cmakelang:
|
||||
description: "Wether to install cmakelang (true/false) or the specific version to install."
|
||||
required: false
|
||||
cmake-lint:
|
||||
description: "Wether to install cmake-lint (true/false) or the specific version to install."
|
||||
required: false
|
||||
cmakelint:
|
||||
description: "Wether to install cmake-lint (true/false) or the specific version to install."
|
||||
required: false
|
||||
cmake-format:
|
||||
description: "Wether to install cmake-format (true/false) or the specific version to install."
|
||||
required: false
|
||||
cmakeformat:
|
||||
description: "Wether to install cmake-format (true/false) or the specific version to install."
|
||||
required: false
|
||||
ninja:
|
||||
description: "Wether to install ninja (true/false) or the specific version to install."
|
||||
required: false
|
||||
|
|
|
@ -28,6 +28,7 @@ words:
|
|||
- choco
|
||||
- clangd
|
||||
- cmake
|
||||
- cmakeformat
|
||||
- cobertura
|
||||
- copr
|
||||
- CPATH
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -68,55 +68,60 @@ const retryErrors = [
|
|||
* ```
|
||||
*/
|
||||
export async function installAptPack(packages: AptPackage[], update = false): Promise<InstallationInfo> {
|
||||
const apt: string = getApt()
|
||||
|
||||
for (const { name, version } of packages) {
|
||||
info(`Installing ${name} ${version ?? ""} via ${apt}`)
|
||||
}
|
||||
|
||||
// Update the repos if needed
|
||||
if (update) {
|
||||
updateAptReposMemoized(apt)
|
||||
}
|
||||
|
||||
// Add the repos if needed
|
||||
await addRepositories(apt, packages)
|
||||
|
||||
const needToInstall = await filterAndQualifyAptPackages(packages, apt)
|
||||
|
||||
if (needToInstall.length === 0) {
|
||||
info("All packages are already installed")
|
||||
return { binDir: "/usr/bin/" }
|
||||
}
|
||||
|
||||
// Initialize apt if needed
|
||||
await initAptMemoized(apt)
|
||||
|
||||
try {
|
||||
// Add the keys if needed
|
||||
await addAptKeys(packages)
|
||||
const apt: string = getApt()
|
||||
|
||||
// Install
|
||||
execRootSync(apt, ["install", "--fix-broken", "-y", ...needToInstall], {
|
||||
...defaultExecOptions,
|
||||
env: getAptEnv(apt),
|
||||
})
|
||||
} catch (err) {
|
||||
if (isExecaError(err)) {
|
||||
if (retryErrors.some((error) => err.stderr.includes(error))) {
|
||||
warning(`Failed to install packages ${needToInstall}. Retrying...`)
|
||||
execRootSync(
|
||||
apt,
|
||||
["install", "--fix-broken", "-y", "-o", aptTimeout, ...needToInstall],
|
||||
{ ...defaultExecOptions, env: getAptEnv(apt) },
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw err
|
||||
for (const { name, version } of packages) {
|
||||
info(`Installing ${name} ${version ?? ""} via ${apt}`)
|
||||
}
|
||||
}
|
||||
|
||||
return { binDir: "/usr/bin/" }
|
||||
// Update the repos if needed
|
||||
if (update) {
|
||||
updateAptReposMemoized(apt)
|
||||
}
|
||||
|
||||
// Add the repos if needed
|
||||
await addRepositories(apt, packages)
|
||||
|
||||
const needToInstall = await filterAndQualifyAptPackages(packages, apt)
|
||||
|
||||
if (needToInstall.length === 0) {
|
||||
info("All packages are already installed")
|
||||
return { binDir: "/usr/bin/" }
|
||||
}
|
||||
|
||||
// Initialize apt if needed
|
||||
await initAptMemoized(apt)
|
||||
|
||||
try {
|
||||
// Add the keys if needed
|
||||
await addAptKeys(packages)
|
||||
|
||||
// Install
|
||||
execRootSync(apt, ["install", "--fix-broken", "-y", ...needToInstall], {
|
||||
...defaultExecOptions,
|
||||
env: getAptEnv(apt),
|
||||
})
|
||||
} catch (err) {
|
||||
if (isExecaError(err)) {
|
||||
if (retryErrors.some((error) => err.stderr.includes(error))) {
|
||||
warning(`Failed to install packages ${needToInstall}. Retrying...`)
|
||||
execRootSync(
|
||||
apt,
|
||||
["install", "--fix-broken", "-y", "-o", aptTimeout, ...needToInstall],
|
||||
{ ...defaultExecOptions, env: getAptEnv(apt) },
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
return { binDir: "/usr/bin/" }
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? `${err.message}\n${err.stack}` : String(err)
|
||||
throw new Error(`Failed to install apt packages: ${msg}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function addRepositories(apt: string, packages: AptPackage[]) {
|
||||
|
|
|
@ -35,7 +35,9 @@ All the available tools:
|
|||
"compiler and analyzer": {
|
||||
tools: "--llvm, --gcc, --msvc, --apple-clang, --vcvarsall, --cppcheck, --clang-tidy, --clang-format",
|
||||
},
|
||||
"build system": { tools: "--cmake, --ninja, --meson, --make, --task, --bazel" },
|
||||
"build system": {
|
||||
tools: "--cmake, --ninja, --meson, --make, --task, --bazel, --cmakelang, --cmake-lint, --cmake-format",
|
||||
},
|
||||
"package manager": { tools: "--vcpkg, --conan, --choco, --brew, --nala" },
|
||||
cache: { tools: "--ccache, --sccache" },
|
||||
documentation: { tools: "--doxygen, --graphviz" },
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { ubuntuVersion } from "../../utils/env/ubuntu_version.js"
|
||||
import { testBin } from "../../utils/tests/test-helpers.js"
|
||||
import { getVersion } from "../../versions/versions.js"
|
||||
import { setupCmakelang } from "../cmakelang.js"
|
||||
|
||||
jest.setTimeout(300000)
|
||||
describe("setup-cmakelang", () => {
|
||||
it("should setup cmakelang", async () => {
|
||||
const installInfo = await setupCmakelang(getVersion("cmakelang", "true", await ubuntuVersion()), "", process.arch)
|
||||
await testBin("cmake-lint", ["--version"], installInfo.binDir)
|
||||
await testBin("cmake-format", ["--version"], installInfo.binDir)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,6 @@
|
|||
import { setupPipPack } from "../utils/setup/setupPipPack.js"
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export function setupCmakelang(version: string | undefined, _setupDir: string, _arch: string) {
|
||||
return setupPipPack("cmakelang[YAML]", version)
|
||||
}
|
|
@ -26,7 +26,11 @@ import { isBinUptoDate } from "../utils/setup/version.js"
|
|||
import { unique } from "../utils/std/index.js"
|
||||
import { MinVersions } from "../versions/default_versions.js"
|
||||
|
||||
export async function setupPython(version: string, setupDir: string, arch: string): Promise<InstallationInfo> {
|
||||
export async function setupPython(
|
||||
version: string,
|
||||
setupDir: string,
|
||||
arch: string,
|
||||
): Promise<InstallationInfo & { bin: string }> {
|
||||
const installInfo = await findOrSetupPython(version, setupDir, arch)
|
||||
assert(installInfo.bin !== undefined)
|
||||
const foundPython = installInfo.bin
|
||||
|
@ -41,7 +45,7 @@ export async function setupPython(version: string, setupDir: string, arch: strin
|
|||
|
||||
await setupWheel(foundPython)
|
||||
|
||||
return installInfo
|
||||
return installInfo as InstallationInfo & { bin: string }
|
||||
}
|
||||
|
||||
async function setupPipx(foundPython: string) {
|
||||
|
@ -56,12 +60,20 @@ async function setupPipx(foundPython: string) {
|
|||
}
|
||||
}
|
||||
await execa(foundPython, ["-m", "pipx", "ensurepath"], { stdio: "inherit" })
|
||||
await setupPipPackWithPython(foundPython, "venv", undefined, { upgrade: false, usePipx: false })
|
||||
await setupVenv(foundPython)
|
||||
} catch (err) {
|
||||
warning(`Failed to install pipx: ${(err as Error).toString()}. Ignoring...`)
|
||||
}
|
||||
}
|
||||
|
||||
async function setupVenv(foundPython: string) {
|
||||
try {
|
||||
await setupPipPackWithPython(foundPython, "venv", undefined, { upgrade: false, usePipx: false })
|
||||
} catch (err) {
|
||||
warning(`Failed to install venv: ${(err as Error).toString()}. Ignoring...`)
|
||||
}
|
||||
}
|
||||
|
||||
/** Setup wheel and setuptools */
|
||||
async function setupWheel(foundPython: string) {
|
||||
try {
|
||||
|
@ -70,9 +82,9 @@ async function setupWheel(foundPython: string) {
|
|||
isLibrary: true,
|
||||
usePipx: false,
|
||||
})
|
||||
await setupPipPackWithPython(foundPython, "wheel", undefined, { upgrade: true, isLibrary: true, usePipx: false })
|
||||
await setupPipPackWithPython(foundPython, "wheel", undefined, { upgrade: false, isLibrary: true, usePipx: false })
|
||||
} catch (err) {
|
||||
warning(`Failed to install setuptools or wheel: ${(err as Error).toString()}. Ignoring...`)
|
||||
warning(`Failed to install setuptools/wheel: ${(err as Error).toString()}. Ignoring...`)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,7 +183,7 @@ async function setupPythonSystem(setupDir: string, version: string) {
|
|||
}
|
||||
|
||||
async function findPython(binDir?: string) {
|
||||
for (const pythonBin of ["python3", "python"]) {
|
||||
for (const pythonBin of ["python", "python3"]) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const foundPython = await isPythonUpToDate(pythonBin, binDir)
|
||||
if (foundPython !== undefined) {
|
||||
|
@ -203,10 +215,8 @@ async function isPythonUpToDate(candidate: string, binDir?: string) {
|
|||
try {
|
||||
if (binDir !== undefined) {
|
||||
const pythonBinPath = join(binDir, addExeExt(candidate))
|
||||
if (await pathExists(pythonBinPath)) {
|
||||
if (await isBinUptoDate(pythonBinPath, MinVersions.python!)) {
|
||||
return pythonBinPath
|
||||
}
|
||||
if (await pathExists(pythonBinPath) && await isBinUptoDate(pythonBinPath, MinVersions.python!)) {
|
||||
return pythonBinPath
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
|
|
22
src/tool.ts
22
src/tool.ts
|
@ -3,6 +3,7 @@ import { setupBazel } from "./bazel/bazel.js"
|
|||
import { setupCcache } from "./ccache/ccache.js"
|
||||
import { setupChocolatey } from "./chocolatey/chocolatey.js"
|
||||
import { setupCmake } from "./cmake/cmake.js"
|
||||
import { setupCmakelang } from "./cmakelang/cmakelang.js"
|
||||
import { setupConan } from "./conan/conan.js"
|
||||
import { setupCppcheck } from "./cppcheck/cppcheck.js"
|
||||
import { setupDoxygen } from "./doxygen/doxygen.js"
|
||||
|
@ -42,19 +43,32 @@ export const appleClangSetups = {
|
|||
"apple-llvm": setupAppleClang,
|
||||
} as const
|
||||
|
||||
const cmakeLangSetups = {
|
||||
cmakelang: setupCmakelang,
|
||||
"cmake-lint": setupCmakelang,
|
||||
"cmake-format": setupCmakelang,
|
||||
cmakelint: setupCmakelang,
|
||||
cmakeformat: setupCmakelang,
|
||||
} as const
|
||||
|
||||
export const llvmTools = ["llvm", "clang", "clang++", "clang-tidy", "clang-format", "clangtidy", "clangformat"]
|
||||
|
||||
/** The setup functions */
|
||||
export const setups = {
|
||||
nala: setupNala,
|
||||
brew: setupBrew,
|
||||
choco: setupChocolatey,
|
||||
python: setupPython,
|
||||
powershell: setupPowershell,
|
||||
pwsh: setupPowershell,
|
||||
...llvmSetups,
|
||||
...gccSetups,
|
||||
...mingwSetups,
|
||||
...msvcSetups,
|
||||
...appleClangSetups,
|
||||
nala: setupNala,
|
||||
...cmakeLangSetups,
|
||||
cmake: setupCmake,
|
||||
ninja: setupNinja,
|
||||
python: setupPython,
|
||||
vcpkg: setupVcpkg,
|
||||
bazel: setupBazel,
|
||||
conan: setupConan,
|
||||
|
@ -62,10 +76,6 @@ export const setups = {
|
|||
gcovr: setupGcovr,
|
||||
opencppcoverage: setupOpencppcoverage,
|
||||
OpenCppCoverage: setupOpencppcoverage,
|
||||
choco: setupChocolatey,
|
||||
brew: setupBrew,
|
||||
powershell: setupPowershell,
|
||||
pwsh: setupPowershell,
|
||||
ccache: setupCcache,
|
||||
sccache: setupSccache,
|
||||
doxygen: setupDoxygen,
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
// the installed @types/node package is version 12 so that backwards compatibility is maintained
|
||||
// node: prefix is removed by Babel, so define the types of those packages here so that TypeScript can find them
|
||||
|
||||
declare module "node:fs" {
|
||||
import fs from "fs"
|
||||
export = fs
|
||||
}
|
||||
|
||||
declare module "node:path" {
|
||||
import path from "path"
|
||||
export = path
|
||||
}
|
||||
|
||||
declare module "node:child_process" {
|
||||
import child_process from "child_process"
|
||||
export = child_process
|
||||
}
|
||||
|
||||
declare module "node:os" {
|
||||
import os from "os"
|
||||
export = os
|
||||
}
|
||||
|
||||
declare module "node:util" {
|
||||
import util from "util"
|
||||
export = util
|
||||
}
|
||||
|
||||
declare module "node:stream" {
|
||||
import stream from "stream"
|
||||
export = stream
|
||||
}
|
||||
|
||||
declare module "node:zlib" {
|
||||
import zlib from "zlib"
|
||||
export = zlib
|
||||
}
|
||||
|
||||
declare module "node:crypto" {
|
||||
import crypto from "crypto"
|
||||
export = crypto
|
||||
}
|
||||
declare module "node:http" {
|
||||
import http from "http"
|
||||
export = http
|
||||
}
|
||||
|
||||
declare module "node:https" {
|
||||
import https from "https"
|
||||
export = https
|
||||
}
|
||||
|
||||
declare module "node:events" {
|
||||
import events from "events"
|
||||
export = events
|
||||
}
|
||||
|
||||
declare module "node:assert" {
|
||||
import assert from "assert"
|
||||
export = assert
|
||||
}
|
||||
|
||||
declare module "node:constants" {
|
||||
import constants from "constants"
|
||||
export = constants
|
||||
}
|
||||
|
||||
declare module "node:querystring" {
|
||||
import querystring from "querystring"
|
||||
export = querystring
|
||||
}
|
||||
|
||||
declare module "node:url" {
|
||||
import url from "url"
|
||||
export = url
|
||||
}
|
||||
|
||||
declare module "node:fs/promises" {
|
||||
import fsPromises from "fs/promises"
|
||||
export = fsPromises
|
||||
}
|
||||
|
||||
declare module "node:path/posix" {
|
||||
import pathPosix from "path/posix"
|
||||
export = pathPosix
|
||||
}
|
||||
|
||||
declare module "node:path/win32" {
|
||||
import pathWin32 from "path/win32"
|
||||
export = pathWin32
|
||||
}
|
|
@ -50,9 +50,26 @@ export async function setupPipPackWithPython(
|
|||
const { usePipx = true, user = true, upgrade = false, isLibrary = false } = options
|
||||
|
||||
const isPipx = usePipx && !isLibrary && (await hasPipx(givenPython))
|
||||
|
||||
const pip = isPipx ? "pipx" : "pip"
|
||||
|
||||
const hasPackage = await pipHasPackage(givenPython, name)
|
||||
// remove `[]` extensions
|
||||
const nameOnly = getPackageName(name)
|
||||
|
||||
// if upgrade is not requested, check if the package is already installed, and return if it is
|
||||
if (!upgrade) {
|
||||
const installed = isPipx
|
||||
? await pipxPackageInstalled(givenPython, nameOnly)
|
||||
: await pipPackageIsInstalled(givenPython, nameOnly)
|
||||
if (installed) {
|
||||
const binDir = isPipx
|
||||
? await finishPipxPackageInstall()
|
||||
: await finishPipPackageInstall(givenPython, nameOnly)
|
||||
return { binDir }
|
||||
}
|
||||
}
|
||||
|
||||
const hasPackage = await pipHasPackage(givenPython, nameOnly)
|
||||
if (hasPackage) {
|
||||
try {
|
||||
info(`Installing ${name} ${version ?? ""} via ${pip}`)
|
||||
|
@ -74,27 +91,36 @@ export async function setupPipPackWithPython(
|
|||
env,
|
||||
})
|
||||
} catch (err) {
|
||||
info(`Failed to install ${name} via ${pip}: ${err}.`)
|
||||
const msg = err instanceof Error ? `${err.message}\n${err.stack}` : String(err)
|
||||
info(`Failed to install ${name} via ${pip}: ${msg}`)
|
||||
if ((await setupPipPackSystem(name)) === null) {
|
||||
throw new Error(`Failed to install ${name} via ${pip}: ${err}.`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((await setupPipPackSystem(name)) === null) {
|
||||
throw new Error(`Failed to install ${name} as it was not found via ${pip} or the system package manager`)
|
||||
}
|
||||
} else if ((await setupPipPackSystem(name)) === null) {
|
||||
throw new Error(`Failed to install ${name} as it was not found via ${pip} or the system package manager`)
|
||||
}
|
||||
|
||||
const execPaths = await addPythonBaseExecPrefix(givenPython)
|
||||
const binDir = await findBinDir(execPaths, name)
|
||||
|
||||
await addPath(binDir, rcOptions)
|
||||
|
||||
const binDir = isPipx
|
||||
? await finishPipxPackageInstall()
|
||||
: await finishPipPackageInstall(givenPython, nameOnly)
|
||||
return { binDir }
|
||||
}
|
||||
|
||||
function finishPipxPackageInstall() {
|
||||
return getPipxBinDir()
|
||||
}
|
||||
|
||||
async function finishPipPackageInstall(givenPython: string, name: string) {
|
||||
const pythonBaseExecPrefix = await addPythonBaseExecPrefix(givenPython)
|
||||
const binDir = await findBinDir(pythonBaseExecPrefix, name)
|
||||
await addPath(binDir, rcOptions)
|
||||
return binDir
|
||||
}
|
||||
|
||||
export async function hasPipx(givenPython: string) {
|
||||
return (await execa(givenPython, ["-m", "pipx", "--help"], { stdio: "ignore", reject: false })).exitCode === 0
|
||||
const res = await execa(givenPython, ["-m", "pipx", "--help"], { stdio: "ignore", reject: false })
|
||||
return res.exitCode === 0
|
||||
}
|
||||
|
||||
async function getPipxHome_() {
|
||||
|
@ -144,14 +170,77 @@ async function getPipxBinDir_() {
|
|||
}
|
||||
const getPipxBinDir = memoize(getPipxBinDir_, { promise: true })
|
||||
|
||||
async function getPython_(): Promise<string> {
|
||||
const pythonBin = (await setupPython(getVersion("python", undefined, await ubuntuVersion()), "", process.arch)).bin
|
||||
if (pythonBin === undefined) {
|
||||
throw new Error("Python binary was not found")
|
||||
/* eslint-disable require-atomic-updates */
|
||||
let pythonBin: string | undefined
|
||||
|
||||
async function getPython(): Promise<string> {
|
||||
if (pythonBin !== undefined) {
|
||||
return pythonBin
|
||||
}
|
||||
|
||||
pythonBin = (await setupPython(getVersion("python", undefined, await ubuntuVersion()), "", process.arch)).bin
|
||||
return pythonBin
|
||||
}
|
||||
const getPython = memoize(getPython_, { promise: true })
|
||||
|
||||
/**
|
||||
* Get the actual name of a pip package from the given string
|
||||
* @param pkg the given name that might contain extensions in `[]`.
|
||||
* @returns stirped down name of the package
|
||||
*/
|
||||
function getPackageName(pkg: string) {
|
||||
return pkg.replace(/\[.*]/g, "").trim()
|
||||
}
|
||||
|
||||
async function pipPackageIsInstalled(python: string, name: string) {
|
||||
try {
|
||||
const result = await execa(python, ["-m", "pip", "-qq", "show", name], {
|
||||
stdio: "ignore",
|
||||
reject: false,
|
||||
})
|
||||
return result.exitCode === 0
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type PipxShowType = {
|
||||
venvs: Record<string, {
|
||||
metadata: {
|
||||
main_package: {
|
||||
package: string
|
||||
package_or_url: string
|
||||
apps: string[]
|
||||
}
|
||||
}
|
||||
}>
|
||||
}
|
||||
|
||||
async function pipxPackageInstalled(python: string, name: string) {
|
||||
try {
|
||||
const result = await execa(python, ["-m", "pipx", "list", "--json"], {
|
||||
stdio: "ignore",
|
||||
reject: false,
|
||||
})
|
||||
if (result.exitCode !== 0 || typeof result.stdout !== "string") {
|
||||
return false
|
||||
}
|
||||
|
||||
const pipxOut = JSON.parse(result.stdout) as PipxShowType
|
||||
// search among the venvs
|
||||
if (name in pipxOut.venvs) {
|
||||
return true
|
||||
}
|
||||
// search among the urls
|
||||
for (const venv of Object.values(pipxOut.venvs)) {
|
||||
if (venv.metadata.main_package.package_or_url === name || venv.metadata.main_package.package === name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
async function pipHasPackage(python: string, name: string) {
|
||||
const result = await execa(python, ["-m", "pip", "-qq", "index", "versions", name], {
|
||||
|
|
|
@ -20,9 +20,11 @@
|
|||
"skipLibCheck": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true,
|
||||
// target Node.js 12 https://node.green/#ES2019
|
||||
"lib": [
|
||||
"ES2019"
|
||||
// target Node.js 12 https://node.green/#ES2019
|
||||
"ES2019",
|
||||
// https://node.green/#ES2020-features-String-prototype-matchAll
|
||||
"ES2020.String"
|
||||
],
|
||||
"target": "ESNext",
|
||||
"allowJs": true,
|
||||
|
|
Loading…
Reference in New Issue