From bf114b89c1acc0b3bde998d4c3ee5c6317866f29 Mon Sep 17 00:00:00 2001 From: Cang Truong Date: Wed, 14 Jul 2021 15:16:43 -0400 Subject: [PATCH 01/14] disable type check for test, start, build in CI --- .../react-scripts/config/webpack.config.js | 5 +++-- .../modular-scripts/react-scripts/scripts/start.js | 8 ++++++-- packages/modular-scripts/src/cli.ts | 8 ++++++++ packages/modular-scripts/src/config/jest/index.ts | 12 ++++++++++++ packages/modular-scripts/src/typecheck.ts | 13 +++++++++++++ 5 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 packages/modular-scripts/src/typecheck.ts diff --git a/packages/modular-scripts/react-scripts/config/webpack.config.js b/packages/modular-scripts/react-scripts/config/webpack.config.js index 704a36067..97a44586f 100644 --- a/packages/modular-scripts/react-scripts/config/webpack.config.js +++ b/packages/modular-scripts/react-scripts/config/webpack.config.js @@ -25,8 +25,8 @@ const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin'); const typescriptFormatter = require('react-dev-utils/typescriptFormatter'); const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); - const postcssNormalize = require('postcss-normalize'); +const isCI = require('is-ci'); const appPackageJson = require(paths.appPackageJson); @@ -634,8 +634,9 @@ module.exports = function (webpackEnv) { // See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270 maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, }), - // TypeScript type checking + // TypeScript type checking for non CI envs useTypeScript && + !isCI && new ForkTsCheckerWebpackPlugin({ typescript: resolve.sync('typescript', { basedir: paths.appNodeModules, diff --git a/packages/modular-scripts/react-scripts/scripts/start.js b/packages/modular-scripts/react-scripts/scripts/start.js index 1cbd452c3..58a236e65 100644 --- a/packages/modular-scripts/react-scripts/scripts/start.js +++ b/packages/modular-scripts/react-scripts/scripts/start.js @@ -28,6 +28,8 @@ const { } = require('react-dev-utils/WebpackDevServerUtils'); const openBrowser = require('react-dev-utils/openBrowser'); const semver = require('semver'); +const isCI = require('is-ci'); + const paths = require('../config/paths'); const configFactory = require('../config/webpack.config'); const createDevServerConfig = require('../config/webpackDevServer.config'); @@ -83,8 +85,9 @@ checkBrowsers(paths.appPath, isInteractive) const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; const appName = require(paths.appPackageJson).name; - const useTypeScript = fs.existsSync(paths.appTsConfig); - const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; + const useTypeScript = !isCI && fs.existsSync(paths.appTsConfig); + const tscCompileOnError = + isCI || process.env.TSC_COMPILE_ON_ERROR === 'true'; const urls = prepareUrls( protocol, HOST, @@ -98,6 +101,7 @@ checkBrowsers(paths.appPath, isInteractive) devServer.sockWrite(devServer.sockets, 'errors', errors), }; // Create a webpack compiler that is configured with custom messages. + // Only run typecheck if not in CI env const compiler = createCompiler({ appName, config, diff --git a/packages/modular-scripts/src/cli.ts b/packages/modular-scripts/src/cli.ts index 954cc82bf..e647d0485 100755 --- a/packages/modular-scripts/src/cli.ts +++ b/packages/modular-scripts/src/cli.ts @@ -219,6 +219,14 @@ program await port(relativePath); }); +program + .command('typecheck') + .description('Typechecks the entire project') + .action(async () => { + const { typecheck } = await import('./typecheck'); + await typecheck(); + }); + void startupCheck() .then(() => { return program.parseAsync(process.argv); diff --git a/packages/modular-scripts/src/config/jest/index.ts b/packages/modular-scripts/src/config/jest/index.ts index ba9237d42..d761fd12b 100644 --- a/packages/modular-scripts/src/config/jest/index.ts +++ b/packages/modular-scripts/src/config/jest/index.ts @@ -2,6 +2,7 @@ import * as fs from 'fs-extra'; import path from 'path'; import chalk from 'chalk'; import glob from 'glob'; +import isCi from 'is-ci'; import type { Config } from '@jest/types'; import { defaults } from 'jest-config'; import getModularRoot from '../../utils/getModularRoot'; @@ -195,5 +196,16 @@ export function createJestConfig( moduleNameMapper: mergedMapper, }); } + + // don't typecheck tests in CI + if (isCi) { + jestConfig.globals = { + 'ts-jest': { + diagnostics: false, + isolatedModules: true, + }, + }; + } + return jestConfig; } diff --git a/packages/modular-scripts/src/typecheck.ts b/packages/modular-scripts/src/typecheck.ts new file mode 100644 index 000000000..4d8fbe1a7 --- /dev/null +++ b/packages/modular-scripts/src/typecheck.ts @@ -0,0 +1,13 @@ +import isCI from 'is-ci'; +import execa from 'execa'; +import getModularRoot from './utils/getModularRoot'; + +export async function typecheck(): Promise { + const options = ['--pretty']; + if (isCI) { + options.push('--noEmit'); + } + await execa('tsc', options, { + cwd: getModularRoot(), + }); +} From a7313f77c27968bf43e1d888c6292a7c80160569 Mon Sep 17 00:00:00 2001 From: Cang Truong Date: Wed, 14 Jul 2021 15:27:43 -0400 Subject: [PATCH 02/14] remove check on tscCompileOnError --- packages/modular-scripts/react-scripts/scripts/start.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/modular-scripts/react-scripts/scripts/start.js b/packages/modular-scripts/react-scripts/scripts/start.js index 58a236e65..a93347b7c 100644 --- a/packages/modular-scripts/react-scripts/scripts/start.js +++ b/packages/modular-scripts/react-scripts/scripts/start.js @@ -86,8 +86,7 @@ checkBrowsers(paths.appPath, isInteractive) const appName = require(paths.appPackageJson).name; const useTypeScript = !isCI && fs.existsSync(paths.appTsConfig); - const tscCompileOnError = - isCI || process.env.TSC_COMPILE_ON_ERROR === 'true'; + const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; const urls = prepareUrls( protocol, HOST, From 7f57346bb135d925896d5a1f2cb79e3bc270d5b3 Mon Sep 17 00:00:00 2001 From: Cang Truong Date: Wed, 14 Jul 2021 15:30:47 -0400 Subject: [PATCH 03/14] add changeset --- .changeset/cyan-moose-judge.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cyan-moose-judge.md diff --git a/.changeset/cyan-moose-judge.md b/.changeset/cyan-moose-judge.md new file mode 100644 index 000000000..a856e5589 --- /dev/null +++ b/.changeset/cyan-moose-judge.md @@ -0,0 +1,5 @@ +--- +'modular-scripts': minor +--- + +Disable typechecking for modular start, test, build commands in CI environments From 10b0a27b621bfe205769e7dd3c32e5194142c48b Mon Sep 17 00:00:00 2001 From: Cang Truong Date: Thu, 15 Jul 2021 14:54:17 -0400 Subject: [PATCH 04/14] tsc api to print flat for non CI or pretty for CI --- packages/modular-scripts/src/typecheck.ts | 59 ++++++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/packages/modular-scripts/src/typecheck.ts b/packages/modular-scripts/src/typecheck.ts index 4d8fbe1a7..bae796afe 100644 --- a/packages/modular-scripts/src/typecheck.ts +++ b/packages/modular-scripts/src/typecheck.ts @@ -1,13 +1,56 @@ import isCI from 'is-ci'; -import execa from 'execa'; -import getModularRoot from './utils/getModularRoot'; +import path from 'path'; +import ts from 'typescript'; +import getPackageMetadata from './buildPackage/getPackageMetadata'; +import { reportTSDiagnostics } from './buildPackage/reportTSDiagnostics'; export async function typecheck(): Promise { - const options = ['--pretty']; - if (isCI) { - options.push('--noEmit'); + const { typescriptConfig } = await getPackageMetadata(); + + const { _compilerOptions, ...tsConfig } = typescriptConfig; + + // Parse all config except for compilerOptions + const configParseResult = ts.parseJsonConfigFileContent( + tsConfig, + ts.sys, + path.dirname('tsconfig.json'), + ); + + if (configParseResult.errors.length > 0) { + reportTSDiagnostics(':root', configParseResult.errors); + throw new Error('Could not parse Typescript configuration'); + } + + const program = ts.createProgram( + configParseResult.fileNames, + configParseResult.options, + ); + + // Pulled from typescript's getCanonicalFileName logic + // eslint-disable-next-line no-useless-escape + const fileNameLowerCaseRegExp = /[^\u0130\u0131\u00DFa-z0-9\\/:\-_\. ]+/g; + + function toFileNameLowerCase(x: string) { + return fileNameLowerCaseRegExp.test(x) + ? x.replace(fileNameLowerCaseRegExp, x.toLowerCase()) + : x; + } + + const diagnostics = ts.getPreEmitDiagnostics(program) as ts.Diagnostic[]; + + const diagnosticHost = { + getCurrentDirectory: (): string => ts.sys.getCurrentDirectory(), + getNewLine: (): string => ts.sys.newLine, + getCanonicalFileName: (file: string): string => + ts.sys.useCaseSensitiveFileNames ? file : toFileNameLowerCase(file), + }; + + if (isCI && diagnostics.length) { + throw new Error(ts.formatDiagnostics(diagnostics, diagnosticHost)); + } + if (diagnostics.length) { + throw new Error( + ts.formatDiagnosticsWithColorAndContext(diagnostics, diagnosticHost), + ); } - await execa('tsc', options, { - cwd: getModularRoot(), - }); } From da3d26e9024cb4fb0d107009399da6a978ffc179 Mon Sep 17 00:00:00 2001 From: Cang Truong Date: Thu, 15 Jul 2021 14:58:19 -0400 Subject: [PATCH 05/14] add additional changeset --- .changeset/polite-lizards-thank.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/polite-lizards-thank.md diff --git a/.changeset/polite-lizards-thank.md b/.changeset/polite-lizards-thank.md new file mode 100644 index 000000000..e49a20434 --- /dev/null +++ b/.changeset/polite-lizards-thank.md @@ -0,0 +1,5 @@ +--- +'modular-scripts': patch +--- + +Added `modular typecheck` command to programatically type check the project From d4415c97037df59c79dcc1496d4071af06b0cc8f Mon Sep 17 00:00:00 2001 From: Cang Truong Date: Thu, 15 Jul 2021 15:24:49 -0400 Subject: [PATCH 06/14] add silent option --- packages/modular-scripts/src/cli.ts | 12 ++++++++++-- packages/modular-scripts/src/typecheck.ts | 17 +++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/modular-scripts/src/cli.ts b/packages/modular-scripts/src/cli.ts index e647d0485..a883822f1 100755 --- a/packages/modular-scripts/src/cli.ts +++ b/packages/modular-scripts/src/cli.ts @@ -219,12 +219,20 @@ program await port(relativePath); }); +interface TypecheckOptions { + silent: boolean; +} + program .command('typecheck') + .option( + '--silent', + 'silences the error messages gathered during the type check', + ) .description('Typechecks the entire project') - .action(async () => { + .action(async (options: TypecheckOptions) => { const { typecheck } = await import('./typecheck'); - await typecheck(); + await typecheck(options.silent); }); void startupCheck() diff --git a/packages/modular-scripts/src/typecheck.ts b/packages/modular-scripts/src/typecheck.ts index bae796afe..1675cbc45 100644 --- a/packages/modular-scripts/src/typecheck.ts +++ b/packages/modular-scripts/src/typecheck.ts @@ -1,10 +1,12 @@ import isCI from 'is-ci'; import path from 'path'; import ts from 'typescript'; +import chalk from 'chalk'; import getPackageMetadata from './buildPackage/getPackageMetadata'; import { reportTSDiagnostics } from './buildPackage/reportTSDiagnostics'; +import * as logger from './utils/logger'; -export async function typecheck(): Promise { +export async function typecheck(silent: boolean): Promise { const { typescriptConfig } = await getPackageMetadata(); const { _compilerOptions, ...tsConfig } = typescriptConfig; @@ -45,12 +47,19 @@ export async function typecheck(): Promise { ts.sys.useCaseSensitiveFileNames ? file : toFileNameLowerCase(file), }; - if (isCI && diagnostics.length) { - throw new Error(ts.formatDiagnostics(diagnostics, diagnosticHost)); - } if (diagnostics.length) { + if (silent) { + throw new Error('\u0078 Typecheck did not pass'); + } + + if (isCI) { + throw new Error(ts.formatDiagnostics(diagnostics, diagnosticHost)); + } + throw new Error( ts.formatDiagnosticsWithColorAndContext(diagnostics, diagnosticHost), ); } + + logger.log(chalk.green('\u2713 Typecheck passed')); } From db576f8495d8d33ef1a32595c8df146c735c582b Mon Sep 17 00:00:00 2001 From: Cang Truong Date: Fri, 16 Jul 2021 08:57:42 -0400 Subject: [PATCH 07/14] added comments and moved getPackageMetadata --- .../react-scripts/config/webpack.config.js | 3 +- .../src/buildPackage/getPackageEntryPoints.ts | 2 +- .../modular-scripts/src/buildPackage/index.ts | 2 +- .../src/buildPackage/makeBundle.ts | 2 +- .../src/buildPackage/makeTypings.ts | 2 +- packages/modular-scripts/src/typecheck.ts | 29 ++++++++++++------- .../getPackageMetadata.ts | 8 ++--- 7 files changed, 28 insertions(+), 20 deletions(-) rename packages/modular-scripts/src/{buildPackage => utils}/getPackageMetadata.ts (94%) diff --git a/packages/modular-scripts/react-scripts/config/webpack.config.js b/packages/modular-scripts/react-scripts/config/webpack.config.js index 7af8972f8..6e6b5780d 100644 --- a/packages/modular-scripts/react-scripts/config/webpack.config.js +++ b/packages/modular-scripts/react-scripts/config/webpack.config.js @@ -634,7 +634,8 @@ module.exports = function (webpackEnv) { // See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270 maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, }), - // TypeScript type checking for non CI envs + // TypeScript type checking turned off for CI envs + // https://github.com/jpmorganchase/modular/issues/605 useTypeScript && !isCI && new ForkTsCheckerWebpackPlugin({ diff --git a/packages/modular-scripts/src/buildPackage/getPackageEntryPoints.ts b/packages/modular-scripts/src/buildPackage/getPackageEntryPoints.ts index 120a4d851..c361ace60 100644 --- a/packages/modular-scripts/src/buildPackage/getPackageEntryPoints.ts +++ b/packages/modular-scripts/src/buildPackage/getPackageEntryPoints.ts @@ -1,4 +1,4 @@ -import getPackageMetadata from './getPackageMetadata'; +import getPackageMetadata from '../utils/getPackageMetadata'; export async function getPackageEntryPoints(packagePath: string): Promise<{ main: string; diff --git a/packages/modular-scripts/src/buildPackage/index.ts b/packages/modular-scripts/src/buildPackage/index.ts index 71018cdfa..bd461f2b7 100644 --- a/packages/modular-scripts/src/buildPackage/index.ts +++ b/packages/modular-scripts/src/buildPackage/index.ts @@ -16,7 +16,7 @@ import * as fse from 'fs-extra'; import { getLogger } from './getLogger'; import { getPackageEntryPoints } from './getPackageEntryPoints'; -import getPackageMetadata from './getPackageMetadata'; +import getPackageMetadata from '../utils/getPackageMetadata'; import getModularRoot from '../utils/getModularRoot'; import { makeBundle } from './makeBundle'; import { makeTypings } from './makeTypings'; diff --git a/packages/modular-scripts/src/buildPackage/makeBundle.ts b/packages/modular-scripts/src/buildPackage/makeBundle.ts index d41e16419..cf6fba6fe 100644 --- a/packages/modular-scripts/src/buildPackage/makeBundle.ts +++ b/packages/modular-scripts/src/buildPackage/makeBundle.ts @@ -15,7 +15,7 @@ import resolve from '@rollup/plugin-node-resolve'; import { getLogger } from './getLogger'; import { getPackageEntryPoints } from './getPackageEntryPoints'; -import getPackageMetadata from './getPackageMetadata'; +import getPackageMetadata from '../utils/getPackageMetadata'; import getModularRoot from '../utils/getModularRoot'; const outputDirectory = 'dist'; diff --git a/packages/modular-scripts/src/buildPackage/makeTypings.ts b/packages/modular-scripts/src/buildPackage/makeTypings.ts index 90610e3ff..0ba91a0f1 100644 --- a/packages/modular-scripts/src/buildPackage/makeTypings.ts +++ b/packages/modular-scripts/src/buildPackage/makeTypings.ts @@ -4,7 +4,7 @@ import * as ts from 'typescript'; import * as fse from 'fs-extra'; import { getLogger } from './getLogger'; import { reportTSDiagnostics } from './reportTSDiagnostics'; -import getPackageMetadata from './getPackageMetadata'; +import getPackageMetadata from '../utils/getPackageMetadata'; const outputDirectory = 'dist'; const typescriptConfigFilename = 'tsconfig.json'; diff --git a/packages/modular-scripts/src/typecheck.ts b/packages/modular-scripts/src/typecheck.ts index 1675cbc45..58434f9de 100644 --- a/packages/modular-scripts/src/typecheck.ts +++ b/packages/modular-scripts/src/typecheck.ts @@ -2,15 +2,22 @@ import isCI from 'is-ci'; import path from 'path'; import ts from 'typescript'; import chalk from 'chalk'; -import getPackageMetadata from './buildPackage/getPackageMetadata'; -import { reportTSDiagnostics } from './buildPackage/reportTSDiagnostics'; +import getPackageMetadata from './utils/getPackageMetadata'; import * as logger from './utils/logger'; +import getModularRoot from './utils/getModularRoot'; export async function typecheck(silent: boolean): Promise { const { typescriptConfig } = await getPackageMetadata(); const { _compilerOptions, ...tsConfig } = typescriptConfig; + const diagnosticHost = { + getCurrentDirectory: (): string => getModularRoot(), + getNewLine: (): string => ts.sys.newLine, + getCanonicalFileName: (file: string): string => + ts.sys.useCaseSensitiveFileNames ? file : toFileNameLowerCase(file), + }; + // Parse all config except for compilerOptions const configParseResult = ts.parseJsonConfigFileContent( tsConfig, @@ -19,8 +26,10 @@ export async function typecheck(silent: boolean): Promise { ); if (configParseResult.errors.length > 0) { - reportTSDiagnostics(':root', configParseResult.errors); - throw new Error('Could not parse Typescript configuration'); + logger.error('Failed to parse your tsconfig.json'); + throw new Error( + ts.formatDiagnostics(configParseResult.errors, diagnosticHost), + ); } const program = ts.createProgram( @@ -40,26 +49,24 @@ export async function typecheck(silent: boolean): Promise { const diagnostics = ts.getPreEmitDiagnostics(program) as ts.Diagnostic[]; - const diagnosticHost = { - getCurrentDirectory: (): string => ts.sys.getCurrentDirectory(), - getNewLine: (): string => ts.sys.newLine, - getCanonicalFileName: (file: string): string => - ts.sys.useCaseSensitiveFileNames ? file : toFileNameLowerCase(file), - }; - if (diagnostics.length) { if (silent) { + // "x Typecheck did not pass" throw new Error('\u0078 Typecheck did not pass'); } if (isCI) { + // formatDiagnostics will return a readable list of error messages, each with its own line throw new Error(ts.formatDiagnostics(diagnostics, diagnosticHost)); } throw new Error( + // formatDiagnosticsWithColorAndContext will return a list of errors, each with its own line + // and provide an expanded snapshot of the line with the error ts.formatDiagnosticsWithColorAndContext(diagnostics, diagnosticHost), ); } + // "✓ Typecheck passed" logger.log(chalk.green('\u2713 Typecheck passed')); } diff --git a/packages/modular-scripts/src/buildPackage/getPackageMetadata.ts b/packages/modular-scripts/src/utils/getPackageMetadata.ts similarity index 94% rename from packages/modular-scripts/src/buildPackage/getPackageMetadata.ts rename to packages/modular-scripts/src/utils/getPackageMetadata.ts index 2aa425a26..e2be6fe91 100644 --- a/packages/modular-scripts/src/buildPackage/getPackageMetadata.ts +++ b/packages/modular-scripts/src/utils/getPackageMetadata.ts @@ -4,11 +4,11 @@ import * as path from 'path'; import * as ts from 'typescript'; import * as fse from 'fs-extra'; -import getModularRoot from '../utils/getModularRoot'; -import { getAllWorkspaces } from '../utils/getAllWorkspaces'; -import { reportTSDiagnostics } from './reportTSDiagnostics'; +import getModularRoot from './getModularRoot'; +import { getAllWorkspaces } from './getAllWorkspaces'; +import { reportTSDiagnostics } from '../buildPackage/reportTSDiagnostics'; -import memoize from '../utils/memoize'; +import memoize from './memoize'; const typescriptConfigFilename = 'tsconfig.json'; From c9511e3d2232a2e197a30476028c35f3fabcb252 Mon Sep 17 00:00:00 2001 From: Cang Truong Date: Fri, 16 Jul 2021 09:02:19 -0400 Subject: [PATCH 08/14] moved reporttsdiagnostics into utils --- packages/modular-scripts/src/buildPackage/makeTypings.ts | 2 +- packages/modular-scripts/src/utils/getPackageMetadata.ts | 2 +- .../src/{buildPackage => utils}/reportTSDiagnostics.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename packages/modular-scripts/src/{buildPackage => utils}/reportTSDiagnostics.ts (93%) diff --git a/packages/modular-scripts/src/buildPackage/makeTypings.ts b/packages/modular-scripts/src/buildPackage/makeTypings.ts index 8fd607f25..cd37d40c9 100644 --- a/packages/modular-scripts/src/buildPackage/makeTypings.ts +++ b/packages/modular-scripts/src/buildPackage/makeTypings.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import * as ts from 'typescript'; import * as fse from 'fs-extra'; import { getLogger } from './getLogger'; -import { reportTSDiagnostics } from './reportTSDiagnostics'; +import { reportTSDiagnostics } from '../utils/reportTSDiagnostics'; import getPackageMetadata from '../utils/getPackageMetadata'; const outputDirectory = 'dist'; diff --git a/packages/modular-scripts/src/utils/getPackageMetadata.ts b/packages/modular-scripts/src/utils/getPackageMetadata.ts index e2be6fe91..f8f4a92ee 100644 --- a/packages/modular-scripts/src/utils/getPackageMetadata.ts +++ b/packages/modular-scripts/src/utils/getPackageMetadata.ts @@ -6,7 +6,7 @@ import * as fse from 'fs-extra'; import getModularRoot from './getModularRoot'; import { getAllWorkspaces } from './getAllWorkspaces'; -import { reportTSDiagnostics } from '../buildPackage/reportTSDiagnostics'; +import { reportTSDiagnostics } from './reportTSDiagnostics'; import memoize from './memoize'; diff --git a/packages/modular-scripts/src/buildPackage/reportTSDiagnostics.ts b/packages/modular-scripts/src/utils/reportTSDiagnostics.ts similarity index 93% rename from packages/modular-scripts/src/buildPackage/reportTSDiagnostics.ts rename to packages/modular-scripts/src/utils/reportTSDiagnostics.ts index b93883a50..537f67f43 100644 --- a/packages/modular-scripts/src/buildPackage/reportTSDiagnostics.ts +++ b/packages/modular-scripts/src/utils/reportTSDiagnostics.ts @@ -1,5 +1,5 @@ import * as ts from 'typescript'; -import { getLogger } from './getLogger'; +import { getLogger } from '../buildPackage/getLogger'; // from https://github.com/Microsoft/TypeScript/issues/6387 // a helper to output a readable message from a ts diagnostics object From c18abdc72203605c2577a16c2382f2853f9c23d1 Mon Sep 17 00:00:00 2001 From: Cang Truong Date: Fri, 16 Jul 2021 09:03:31 -0400 Subject: [PATCH 09/14] update changeset to minor --- .changeset/polite-lizards-thank.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/polite-lizards-thank.md b/.changeset/polite-lizards-thank.md index e49a20434..1f4cef2e5 100644 --- a/.changeset/polite-lizards-thank.md +++ b/.changeset/polite-lizards-thank.md @@ -1,5 +1,5 @@ --- -'modular-scripts': patch +'modular-scripts': minor --- Added `modular typecheck` command to programatically type check the project From 1b243ccd4fa88ffc2eb15acd401c4f8bcacbb081 Mon Sep 17 00:00:00 2001 From: Cang Truong Date: Fri, 16 Jul 2021 09:08:08 -0400 Subject: [PATCH 10/14] add typecheck to workflow --- .github/workflows/node.js.yml | 3 ++- package.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index e13d921cf..a5eb79c89 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - command: ['prettier --check .', tsc, lint] + command: ['prettier --check .', typecheck, lint] steps: - uses: actions/checkout@v2 - name: Use Node.js @@ -41,4 +41,5 @@ jobs: node-version: ${{ matrix.node-version }} cache: 'yarn' - run: yarn --frozen-lockfile + - run: yarn typecheck - run: yarn test diff --git a/package.json b/package.json index b972d673c..66bf59c1f 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "build": "yarn workspace create-modular-react-app build && yarn workspace modular-scripts build && yarn workspace modular-views.macro build", "release": "yarn build && changeset publish", "start": "yarn modular start modular-site", - "postinstall": "is-ci || husky install" + "postinstall": "is-ci || husky install", + "typecheck": "yarn modular typecheck" }, "dependencies": { "@babel/cli": "^7.14.5", From b6f0b3d236d3be91a13bd79e25beb67b715dc5b2 Mon Sep 17 00:00:00 2001 From: Cang Truong Date: Fri, 16 Jul 2021 09:09:34 -0400 Subject: [PATCH 11/14] remove unnecessary typecheck step --- .github/workflows/node.js.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index a5eb79c89..1029fbe8f 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -41,5 +41,4 @@ jobs: node-version: ${{ matrix.node-version }} cache: 'yarn' - run: yarn --frozen-lockfile - - run: yarn typecheck - run: yarn test From d424f09848949b788f7949442b99980f2ed3f489 Mon Sep 17 00:00:00 2001 From: Cang Truong Date: Fri, 16 Jul 2021 16:43:07 -0400 Subject: [PATCH 12/14] add unit tests and fixture and update logger --- .../__fixtures__/typecheck/InvalidTyping.ts | 12 ++ .../src/__tests__/typecheck.test.ts | 119 ++++++++++++++++++ packages/modular-scripts/src/typecheck.ts | 32 ++++- .../src/utils/getPackageMetadata.ts | 6 +- packages/modular-scripts/src/utils/logger.ts | 17 ++- 5 files changed, 174 insertions(+), 12 deletions(-) create mode 100644 packages/modular-scripts/src/__tests__/__fixtures__/typecheck/InvalidTyping.ts create mode 100644 packages/modular-scripts/src/__tests__/typecheck.test.ts diff --git a/packages/modular-scripts/src/__tests__/__fixtures__/typecheck/InvalidTyping.ts b/packages/modular-scripts/src/__tests__/__fixtures__/typecheck/InvalidTyping.ts new file mode 100644 index 000000000..dc670d82d --- /dev/null +++ b/packages/modular-scripts/src/__tests__/__fixtures__/typecheck/InvalidTyping.ts @@ -0,0 +1,12 @@ +/* eslint-disable */ +//@ts-nocheck + +import foo from 'foo'; + +function convertToCelcius(temp: number): number { + const result = (temp - 32) * (5 / 9); +} + +window.__invalid__ = foo.bar; + +convertToCelcius('75'); diff --git a/packages/modular-scripts/src/__tests__/typecheck.test.ts b/packages/modular-scripts/src/__tests__/typecheck.test.ts new file mode 100644 index 000000000..1a6ad622f --- /dev/null +++ b/packages/modular-scripts/src/__tests__/typecheck.test.ts @@ -0,0 +1,119 @@ +import * as path from 'path'; +import * as fs from 'fs-extra'; +import execa from 'execa'; + +jest.setTimeout(10 * 60 * 1000); + +const fixturesFolder = path.join(__dirname, '__fixtures__'); + +describe('Modular typecheck', () => { + describe('when there are type errors', () => { + beforeEach(() => { + fs.writeFileSync( + path.join(fixturesFolder, 'typecheck', 'InvalidTyping.ts'), + fs + .readFileSync( + path.join(fixturesFolder, 'typecheck', 'InvalidTyping.ts'), + 'utf-8', + ) + .replace('//@ts-nocheck', '//'), + ); + }); + + afterEach(() => { + fs.writeFileSync( + path.join(fixturesFolder, 'typecheck', 'InvalidTyping.ts'), + fs + .readFileSync( + path.join(fixturesFolder, 'typecheck', 'InvalidTyping.ts'), + 'utf-8', + ) + .replace('//', '//@ts-nocheck'), + ); + }); + + describe('when in CI', () => { + beforeEach(() => { + process.env.CI = 'true'; + }); + afterEach(() => { + process.env.CI = undefined; + }); + it('should display truncated errors', async () => { + let tsc = ''; + try { + await execa('tsc', ['--noEmit', '--pretty', 'false'], { + all: true, + cleanup: true, + }); + } catch ({ stdout }) { + tsc = stdout as string; + } + let modularStdErr = ''; + try { + await execa('yarnpkg', ['modular', 'typecheck'], { + all: true, + cleanup: true, + }); + } catch ({ stderr }) { + modularStdErr = stderr as string; + } + const tscErrors = tsc.split('\n'); + const modularErrors = modularStdErr.split('\n'); + tscErrors.forEach((errorMessage: string, i: number) => { + expect(modularErrors[i]).toMatch(errorMessage); + }); + }); + }); + describe('when not in CI', () => { + it('should match display full error logs', async () => { + let tsc = ''; + try { + await execa('tsc', ['--noEmit'], { + all: true, + cleanup: true, + }); + } catch ({ stdout }) { + tsc = stdout as string; + } + let modularStdErr = ''; + try { + await execa('yarnpkg', ['modular', 'typecheck'], { + all: true, + cleanup: true, + }); + } catch ({ stderr }) { + modularStdErr = stderr as string; + } + const tscErrors = tsc.split('\n'); + const modularErrors = modularStdErr.split('\n'); + tscErrors.forEach((errorMessage: string, i: number) => { + expect(modularErrors[i]).toMatch(errorMessage); + }); + }); + }); + describe('when run silently', () => { + it('should print a one line error message', async () => { + let output = ''; + try { + await execa('yarnpkg', ['modular', 'typecheck', '--silent'], { + all: true, + cleanup: true, + }); + } catch ({ stderr }) { + output = stderr as string; + } + expect(output).toMatch('\u0078 Typecheck did not pass'); + }); + }); + }); + describe('when there are no type errors', () => { + it('should print a one line success message', async () => { + const result = await execa('yarnpkg', ['modular', 'typecheck'], { + all: true, + cleanup: true, + }); + expect(result.stdout).toMatch('\u2713 Typecheck passed'); + }); + }); +}); diff --git a/packages/modular-scripts/src/typecheck.ts b/packages/modular-scripts/src/typecheck.ts index 58434f9de..9acdee074 100644 --- a/packages/modular-scripts/src/typecheck.ts +++ b/packages/modular-scripts/src/typecheck.ts @@ -6,10 +6,27 @@ import getPackageMetadata from './utils/getPackageMetadata'; import * as logger from './utils/logger'; import getModularRoot from './utils/getModularRoot'; -export async function typecheck(silent: boolean): Promise { +export async function typecheck(silent = false): Promise { const { typescriptConfig } = await getPackageMetadata(); - const { _compilerOptions, ...tsConfig } = typescriptConfig; + const { _compilerOptions, ...rest } = typescriptConfig; + + const tsConfig = { + ...rest, + exclude: [ + 'node_modules', + 'bower_components', + 'jspm_packages', + 'tmp', + '**/dist-types', + '**/dist-cjs', + '**/dist-es', + 'dist', + ], + compilerOptions: { + noEmit: true, + }, + }; const diagnosticHost = { getCurrentDirectory: (): string => getModularRoot(), @@ -47,7 +64,12 @@ export async function typecheck(silent: boolean): Promise { : x; } - const diagnostics = ts.getPreEmitDiagnostics(program) as ts.Diagnostic[]; + const emitResult = program.emit(); + + // Report errors + const diagnostics = ts + .getPreEmitDiagnostics(program) + .concat(emitResult.diagnostics); if (diagnostics.length) { if (silent) { @@ -60,9 +82,9 @@ export async function typecheck(silent: boolean): Promise { throw new Error(ts.formatDiagnostics(diagnostics, diagnosticHost)); } + // formatDiagnosticsWithColorAndContext will return a list of errors, each with its own line + // and provide an expanded snapshot of the line with the error throw new Error( - // formatDiagnosticsWithColorAndContext will return a list of errors, each with its own line - // and provide an expanded snapshot of the line with the error ts.formatDiagnosticsWithColorAndContext(diagnostics, diagnosticHost), ); } diff --git a/packages/modular-scripts/src/utils/getPackageMetadata.ts b/packages/modular-scripts/src/utils/getPackageMetadata.ts index f8f4a92ee..2aa425a26 100644 --- a/packages/modular-scripts/src/utils/getPackageMetadata.ts +++ b/packages/modular-scripts/src/utils/getPackageMetadata.ts @@ -4,11 +4,11 @@ import * as path from 'path'; import * as ts from 'typescript'; import * as fse from 'fs-extra'; -import getModularRoot from './getModularRoot'; -import { getAllWorkspaces } from './getAllWorkspaces'; +import getModularRoot from '../utils/getModularRoot'; +import { getAllWorkspaces } from '../utils/getAllWorkspaces'; import { reportTSDiagnostics } from './reportTSDiagnostics'; -import memoize from './memoize'; +import memoize from '../utils/memoize'; const typescriptConfigFilename = 'tsconfig.json'; diff --git a/packages/modular-scripts/src/utils/logger.ts b/packages/modular-scripts/src/utils/logger.ts index a5a112092..a00804c8f 100644 --- a/packages/modular-scripts/src/utils/logger.ts +++ b/packages/modular-scripts/src/utils/logger.ts @@ -5,7 +5,7 @@ const prefix = '[modular] '; const DEBUG = process.env.MODULAR_LOGGER_DEBUG; const SILENT = process.env.MODULAR_LOGGER_MUTE; -function print(x: string) { +function printStdErr(x: string) { if (SILENT) { return; } @@ -13,14 +13,23 @@ function print(x: string) { process.stderr.write(chalk.dim(prefix) + l + '\n'); }); } + +function printStdOut(x: string) { + if (SILENT) { + return; + } + x.split('\n').forEach((l) => { + process.stdout.write(chalk.dim(prefix) + l + '\n'); + }); +} export function log(...x: string[]): void { - print(x.join(' ')); + printStdOut(x.join(' ')); } export function warn(...x: string[]): void { - print(chalk.yellow(x.join(' '))); + printStdErr(chalk.yellow(x.join(' '))); } export function error(...x: string[]): void { - print(chalk.red(x.join(' '))); + printStdErr(chalk.red(x.join(' '))); } export function debug(...x: string[]): void { From 1255d6f340ca796beabea585105cea18d85c5b8e Mon Sep 17 00:00:00 2001 From: Cang Truong Date: Fri, 16 Jul 2021 16:54:51 -0400 Subject: [PATCH 13/14] Add more comments for emitDiagnostics --- packages/modular-scripts/src/typecheck.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/modular-scripts/src/typecheck.ts b/packages/modular-scripts/src/typecheck.ts index 9acdee074..3b4bdc730 100644 --- a/packages/modular-scripts/src/typecheck.ts +++ b/packages/modular-scripts/src/typecheck.ts @@ -64,9 +64,10 @@ export async function typecheck(silent = false): Promise { : x; } + // Does not emit files or typings but will add declaration diagnostics to our errors + // This will ensure that makeTypings will be successful in CI before actually attempting to build const emitResult = program.emit(); - // Report errors const diagnostics = ts .getPreEmitDiagnostics(program) .concat(emitResult.diagnostics); From 812156d54b83158af035ede43a37723258924b7e Mon Sep 17 00:00:00 2001 From: Cang Truong Date: Tue, 20 Jul 2021 08:03:19 -0400 Subject: [PATCH 14/14] remove silent flag --- .../src/__tests__/typecheck.test.ts | 14 -------------- packages/modular-scripts/src/cli.ts | 12 ++---------- packages/modular-scripts/src/typecheck.ts | 7 +------ 3 files changed, 3 insertions(+), 30 deletions(-) diff --git a/packages/modular-scripts/src/__tests__/typecheck.test.ts b/packages/modular-scripts/src/__tests__/typecheck.test.ts index 1a6ad622f..7dc113c9c 100644 --- a/packages/modular-scripts/src/__tests__/typecheck.test.ts +++ b/packages/modular-scripts/src/__tests__/typecheck.test.ts @@ -92,20 +92,6 @@ describe('Modular typecheck', () => { }); }); }); - describe('when run silently', () => { - it('should print a one line error message', async () => { - let output = ''; - try { - await execa('yarnpkg', ['modular', 'typecheck', '--silent'], { - all: true, - cleanup: true, - }); - } catch ({ stderr }) { - output = stderr as string; - } - expect(output).toMatch('\u0078 Typecheck did not pass'); - }); - }); }); describe('when there are no type errors', () => { it('should print a one line success message', async () => { diff --git a/packages/modular-scripts/src/cli.ts b/packages/modular-scripts/src/cli.ts index 19ecfeb3a..6538bfb54 100755 --- a/packages/modular-scripts/src/cli.ts +++ b/packages/modular-scripts/src/cli.ts @@ -226,20 +226,12 @@ program await port(relativePath); }); -interface TypecheckOptions { - silent: boolean; -} - program .command('typecheck') - .option( - '--silent', - 'silences the error messages gathered during the type check', - ) .description('Typechecks the entire project') - .action(async (options: TypecheckOptions) => { + .action(async () => { const { typecheck } = await import('./typecheck'); - await typecheck(options.silent); + await typecheck(); }); void startupCheck() diff --git a/packages/modular-scripts/src/typecheck.ts b/packages/modular-scripts/src/typecheck.ts index 3b4bdc730..584401eec 100644 --- a/packages/modular-scripts/src/typecheck.ts +++ b/packages/modular-scripts/src/typecheck.ts @@ -6,7 +6,7 @@ import getPackageMetadata from './utils/getPackageMetadata'; import * as logger from './utils/logger'; import getModularRoot from './utils/getModularRoot'; -export async function typecheck(silent = false): Promise { +export async function typecheck(): Promise { const { typescriptConfig } = await getPackageMetadata(); const { _compilerOptions, ...rest } = typescriptConfig; @@ -73,11 +73,6 @@ export async function typecheck(silent = false): Promise { .concat(emitResult.diagnostics); if (diagnostics.length) { - if (silent) { - // "x Typecheck did not pass" - throw new Error('\u0078 Typecheck did not pass'); - } - if (isCI) { // formatDiagnostics will return a readable list of error messages, each with its own line throw new Error(ts.formatDiagnostics(diagnostics, diagnosticHost));