diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index f3044353f76..b231aeb81ef 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -41,8 +41,8 @@ jobs: - name: pre-commit run: | set +e # Don't exit immediately on failure - # Skip eslint as it requires `npm ci` and is executed in another job - export SKIP=eslint + # Skip eslint and type-checking-frontend as they requires `npm ci`. Both eslint and type-check are handled in other jobs. + export SKIP=eslint,type-checking-frontend pre-commit run --all-files if [ $? -ne 0 ] || ! git diff --quiet --exit-code; then echo "❌ Pre-commit check failed." diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a40c26f218a..eb88910febb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -68,6 +68,13 @@ repos: language: system pass_filenames: true files: \.(js|jsx|ts|tsx)$ + - id: type-checking-frontend + name: Type-Checking (Frontend) + entry: ./scripts/check-type.js + args: [package=superset-frontend, excludeDeclarationDir=cypress-base] + language: node + files: ^superset-frontend\/.*\.(js|jsx|ts|tsx)$ + exclude: ^superset-frontend/cypress-base\/ # blacklist unsafe functions like make_url (see #19526) - repo: https://github.com/skorokithakis/blacklist-pre-commit-hook rev: e2f070289d8eddcaec0b580d3bde29437e7c8221 @@ -83,5 +90,5 @@ repos: rev: v0.8.0 hooks: - id: ruff - args: [ --fix ] + args: [--fix] - id: ruff-format diff --git a/scripts/check-type.js b/scripts/check-type.js new file mode 100755 index 00000000000..dcd93993c43 --- /dev/null +++ b/scripts/check-type.js @@ -0,0 +1,260 @@ +#!/usr/bin/env node + +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +// @ts-check +const { exit } = require("node:process"); +const { join, dirname, normalize, sep } = require("node:path"); +const { readdir, stat } = require("node:fs/promises"); +const { existsSync } = require("node:fs"); +const { chdir, cwd } = require("node:process"); +const { createRequire } = require("node:module"); + +const SUPERSET_ROOT = dirname(__dirname); +const PACKAGE_ARG_REGEX = /^package=/; +const EXCLUDE_DECLARATION_DIR_REGEX = /^excludeDeclarationDir=/; +const DECLARATION_FILE_REGEX = /\.d\.ts$/; + +void (async () => { + const args = process.argv.slice(2); + const { + matchedArgs: [packageArg, excludeDeclarationDirArg], + remainingArgs, + } = extractArgs(args, [PACKAGE_ARG_REGEX, EXCLUDE_DECLARATION_DIR_REGEX]); + + if (!packageArg) { + console.error("package is not specified"); + exit(1); + } + + const packageRootDir = await getPackage(packageArg); + const updatedArgs = removePackageSegment(remainingArgs, packageRootDir); + const argsStr = updatedArgs.join(" "); + + const excludedDeclarationDirs = getExcludedDeclarationDirs( + excludeDeclarationDirArg + ); + let declarationFiles = await getFilesRecursively( + packageRootDir, + DECLARATION_FILE_REGEX, + excludedDeclarationDirs + ); + declarationFiles = removePackageSegment(declarationFiles, packageRootDir); + const declarationFilesStr = declarationFiles.join(" "); + + const packageRootDirAbsolute = join(SUPERSET_ROOT, packageRootDir); + const tsConfig = getTsConfig(packageRootDirAbsolute); + const command = `--noEmit --allowJs --composite false --project ${tsConfig} ${argsStr} ${declarationFilesStr}`; + + try { + chdir(packageRootDirAbsolute); + // Please ensure that tscw-config is installed in the package being type-checked. + const tscw = packageRequire("tscw-config"); + const child = await tscw`${command}`; + + if (child.stdout) { + console.log(child.stdout); + } + + if (child.stderr) { + console.error(child.stderr); + } + + exit(child.exitCode); + } catch (e) { + console.error("Failed to execute type checking:", e); + console.error("Package:", packageRootDir); + console.error("Command:", `tscw ${command}`); + exit(1); + } +})(); + +/** + * + * @param {string} fullPath + * @param {string[]} excludedDirs + */ +function shouldExcludeDir(fullPath, excludedDirs) { + return excludedDirs.some((excludedDir) => { + const normalizedExcludedDir = normalize(excludedDir); + const normalizedPath = normalize(fullPath); + return ( + normalizedExcludedDir === normalizedPath || + normalizedPath + .split(sep) + .filter((segment) => segment) + .includes(normalizedExcludedDir) + ); + }); +} + +/** + * @param {string} dir + * @param {RegExp} regex + * @param {string[]} excludedDirs + * + * @returns {Promise} + */ + +async function getFilesRecursively(dir, regex, excludedDirs) { + try { + const files = await readdir(dir, { withFileTypes: true }); + const recursivePromises = []; + const result = []; + + for (const file of files) { + const fullPath = join(dir, file.name); + + if (file.isDirectory() && !shouldExcludeDir(fullPath, excludedDirs)) { + recursivePromises.push( + getFilesRecursively(fullPath, regex, excludedDirs) + ); + } else if (regex.test(file.name)) { + result.push(fullPath); + } + } + + const recursiveResults = await Promise.all(recursivePromises); + return result.concat(...recursiveResults); + } catch (e) { + console.error(`Error reading directory: ${dir}`); + console.error(e); + exit(1); + } +} + +/** + * + * @param {string} packageArg + * @returns {Promise} + */ +async function getPackage(packageArg) { + const packageDir = packageArg.split("=")[1].replace(/\/$/, ""); + try { + const stats = await stat(packageDir); + if (!stats.isDirectory()) { + console.error( + `Please specify a valid package, ${packageDir} is not a directory.` + ); + exit(1); + } + } catch (e) { + console.error(`Error reading package: ${packageDir}`); + console.error(e); + exit(1); + } + return packageDir; +} + +/** + * + * @param {string | undefined} excludeDeclarationDirArg + * @returns {string[]} + */ +function getExcludedDeclarationDirs(excludeDeclarationDirArg) { + const excludedDirs = ["node_modules"]; + + return !excludeDeclarationDirArg + ? excludedDirs + : excludeDeclarationDirArg + .split("=")[1] + .split(",") + .map((dir) => dir.replace(/\/$/, "").trim()) + .concat(excludedDirs); +} + +/** + * + * @param {string[]} args + * @param {RegExp[]} regexes + * @returns {{ matchedArgs: (string | undefined)[], remainingArgs: string[] }} + */ + +function extractArgs(args, regexes) { + /** + * @type {(string | undefined)[]} + */ + const matchedArgs = []; + const remainingArgs = [...args]; + + regexes.forEach((regex) => { + const index = remainingArgs.findIndex((arg) => regex.test(arg)); + if (index !== -1) { + const [arg] = remainingArgs.splice(index, 1); + matchedArgs.push(arg); + } else { + matchedArgs.push(undefined); + } + }); + + return { matchedArgs, remainingArgs }; +} + +/** + * Remove the package segment from path. + * + * For example: `superset-frontend/foo/bar.ts` -> `foo/bar.ts` + * + * @param {string[]} args + * @param {string} package + * @returns {string[]} + */ +function removePackageSegment(args, package) { + const packageSegment = package.concat(sep); + return args.map((arg) => { + const normalizedPath = normalize(arg); + + if (normalizedPath.startsWith(packageSegment)) { + return normalizedPath.slice(packageSegment.length); + } + return arg; + }); +} + +/** + * + * @param {string} dir + */ +function getTsConfig(dir) { + const defaultTsConfig = "tsconfig.json"; + const tsConfig = join(dir, defaultTsConfig); + + if (!existsSync(tsConfig)) { + console.error(`Error: ${defaultTsConfig} not found in ${dir}`); + exit(1); + } + return tsConfig; +} + +/** + * + * @param {string} module + */ +function packageRequire(module) { + try { + const localRequire = createRequire(join(cwd(), "node_modules")); + return localRequire(module); + } catch (e) { + console.error( + `Error: ${module} is not installed in ${cwd()}. Please install it first.` + ); + exit(1); + } +} diff --git a/superset-embedded-sdk/package-lock.json b/superset-embedded-sdk/package-lock.json index 9d2b809ac29..a8c3f84b979 100644 --- a/superset-embedded-sdk/package-lock.json +++ b/superset-embedded-sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "@superset-ui/embedded-sdk", - "version": "0.1.2", + "version": "0.1.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@superset-ui/embedded-sdk", - "version": "0.1.2", + "version": "0.1.3", "license": "Apache-2.0", "dependencies": { "@superset-ui/switchboard": "^0.20.3", @@ -22,6 +22,7 @@ "axios": "^1.7.7", "babel-loader": "^9.1.3", "jest": "^29.7.0", + "tscw-config": "^1.1.2", "typescript": "^5.6.2", "webpack": "^5.94.0", "webpack-cli": "^5.1.4" @@ -7800,6 +7801,35 @@ "node": ">=8.0" } }, + "node_modules/tscw-config": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tscw-config/-/tscw-config-1.1.2.tgz", + "integrity": "sha512-mrrMxCqC6kjqjuhGc7mTOB3P7JuBebZ0ZnFQTi4e+K0K+2kT1OvTXzFygWCPBor/F8WJ1IWVRrnBLKctFhFwOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-json-comments": "^5.0.1" + }, + "bin": { + "tscw": "dist/cli.js" + }, + "peerDependencies": { + "typescript": ">=2.0.0" + } + }, + "node_modules/tscw-config/node_modules/strip-json-comments": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", + "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -13826,6 +13856,23 @@ "is-number": "^7.0.0" } }, + "tscw-config": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tscw-config/-/tscw-config-1.1.2.tgz", + "integrity": "sha512-mrrMxCqC6kjqjuhGc7mTOB3P7JuBebZ0ZnFQTi4e+K0K+2kT1OvTXzFygWCPBor/F8WJ1IWVRrnBLKctFhFwOQ==", + "dev": true, + "requires": { + "strip-json-comments": "^5.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", + "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", + "dev": true + } + } + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", diff --git a/superset-embedded-sdk/package.json b/superset-embedded-sdk/package.json index 64bbae9d440..c18fab803a8 100644 --- a/superset-embedded-sdk/package.json +++ b/superset-embedded-sdk/package.json @@ -46,6 +46,7 @@ "axios": "^1.7.7", "babel-loader": "^9.1.3", "jest": "^29.7.0", + "tscw-config": "^1.1.2", "typescript": "^5.6.2", "webpack": "^5.94.0", "webpack-cli": "^5.1.4" diff --git a/superset-frontend/cypress-base/package-lock.json b/superset-frontend/cypress-base/package-lock.json index eae3221dcb2..67aeb28525b 100644 --- a/superset-frontend/cypress-base/package-lock.json +++ b/superset-frontend/cypress-base/package-lock.json @@ -24,7 +24,8 @@ "devDependencies": { "@types/querystringify": "^2.0.0", "cypress": "^11.2.0", - "eslint-plugin-cypress": "^3.5.0" + "eslint-plugin-cypress": "^3.5.0", + "tscw-config": "^1.1.2" } }, "node_modules/@ampproject/remapping": { @@ -10297,6 +10298,35 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/tscw-config": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tscw-config/-/tscw-config-1.1.2.tgz", + "integrity": "sha512-mrrMxCqC6kjqjuhGc7mTOB3P7JuBebZ0ZnFQTi4e+K0K+2kT1OvTXzFygWCPBor/F8WJ1IWVRrnBLKctFhFwOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-json-comments": "^5.0.1" + }, + "bin": { + "tscw": "dist/cli.js" + }, + "peerDependencies": { + "typescript": ">=2.0.0" + } + }, + "node_modules/tscw-config/node_modules/strip-json-comments": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", + "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -10355,6 +10385,21 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -18604,6 +18649,23 @@ "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==" }, + "tscw-config": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tscw-config/-/tscw-config-1.1.2.tgz", + "integrity": "sha512-mrrMxCqC6kjqjuhGc7mTOB3P7JuBebZ0ZnFQTi4e+K0K+2kT1OvTXzFygWCPBor/F8WJ1IWVRrnBLKctFhFwOQ==", + "dev": true, + "requires": { + "strip-json-comments": "^5.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", + "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", + "dev": true + } + } + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -18650,6 +18712,13 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "devOptional": true, + "peer": true + }, "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/superset-frontend/cypress-base/package.json b/superset-frontend/cypress-base/package.json index 9842d103ba2..c5defbfdfb4 100644 --- a/superset-frontend/cypress-base/package.json +++ b/superset-frontend/cypress-base/package.json @@ -31,6 +31,7 @@ "devDependencies": { "@types/querystringify": "^2.0.0", "cypress": "^11.2.0", - "eslint-plugin-cypress": "^3.5.0" + "eslint-plugin-cypress": "^3.5.0", + "tscw-config": "^1.1.2" } } diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 75872a40477..9a96d3971db 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -289,6 +289,7 @@ "thread-loader": "^4.0.4", "ts-jest": "^29.2.5", "ts-loader": "^9.5.1", + "tscw-config": "^1.1.2", "typescript": "5.1.6", "vm-browserify": "^1.1.2", "webpack": "^5.97.1", @@ -45498,6 +45499,35 @@ "node": ">=4" } }, + "node_modules/tscw-config": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tscw-config/-/tscw-config-1.1.2.tgz", + "integrity": "sha512-mrrMxCqC6kjqjuhGc7mTOB3P7JuBebZ0ZnFQTi4e+K0K+2kT1OvTXzFygWCPBor/F8WJ1IWVRrnBLKctFhFwOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-json-comments": "^5.0.1" + }, + "bin": { + "tscw": "dist/cli.js" + }, + "peerDependencies": { + "typescript": ">=2.0.0" + } + }, + "node_modules/tscw-config/node_modules/strip-json-comments": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", + "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 59a66227462..ce27290de5d 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -356,6 +356,7 @@ "thread-loader": "^4.0.4", "ts-jest": "^29.2.5", "ts-loader": "^9.5.1", + "tscw-config": "^1.1.2", "typescript": "5.1.6", "vm-browserify": "^1.1.2", "webpack": "^5.97.1", diff --git a/superset-frontend/src/types/ace-builds.ts b/superset-frontend/src/types/ace-builds.d.ts similarity index 100% rename from superset-frontend/src/types/ace-builds.ts rename to superset-frontend/src/types/ace-builds.d.ts diff --git a/superset-websocket/package-lock.json b/superset-websocket/package-lock.json index 00ed81f90b5..fc1bb31e252 100644 --- a/superset-websocket/package-lock.json +++ b/superset-websocket/package-lock.json @@ -39,6 +39,7 @@ "prettier": "^3.4.2", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", + "tscw-config": "^1.1.2", "typescript": "^5.6.2", "typescript-eslint": "^8.19.0" }, @@ -6117,6 +6118,35 @@ "node": ">=0.4.0" } }, + "node_modules/tscw-config": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tscw-config/-/tscw-config-1.1.2.tgz", + "integrity": "sha512-mrrMxCqC6kjqjuhGc7mTOB3P7JuBebZ0ZnFQTi4e+K0K+2kT1OvTXzFygWCPBor/F8WJ1IWVRrnBLKctFhFwOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-json-comments": "^5.0.1" + }, + "bin": { + "tscw": "dist/cli.js" + }, + "peerDependencies": { + "typescript": ">=2.0.0" + } + }, + "node_modules/tscw-config/node_modules/strip-json-comments": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", + "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -11038,6 +11068,23 @@ } } }, + "tscw-config": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tscw-config/-/tscw-config-1.1.2.tgz", + "integrity": "sha512-mrrMxCqC6kjqjuhGc7mTOB3P7JuBebZ0ZnFQTi4e+K0K+2kT1OvTXzFygWCPBor/F8WJ1IWVRrnBLKctFhFwOQ==", + "dev": true, + "requires": { + "strip-json-comments": "^5.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", + "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", + "dev": true + } + } + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/superset-websocket/package.json b/superset-websocket/package.json index 5bffd394877..57e3020d27c 100644 --- a/superset-websocket/package.json +++ b/superset-websocket/package.json @@ -47,6 +47,7 @@ "prettier": "^3.4.2", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", + "tscw-config": "^1.1.2", "typescript": "^5.6.2", "typescript-eslint": "^8.19.0" }, diff --git a/superset-websocket/tsconfig.json b/superset-websocket/tsconfig.json index 095e822d45e..346f8a0ee7d 100644 --- a/superset-websocket/tsconfig.json +++ b/superset-websocket/tsconfig.json @@ -8,5 +8,5 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, - "include": ["src/**/*"], + "include": ["src/**/*"] }