diff --git a/.circleci/config.yml b/.circleci/config.yml index 7d187778889f..5e7cc4a22d24 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -195,6 +195,11 @@ jobs: command: | cd scripts yarn get-template --check + - run: + name: Type check + command: | + cd scripts + yarn check - run: name: Run tests command: | diff --git a/scripts/event-log-collector.ts b/scripts/event-log-collector.ts index 160120428553..f3ad10700887 100644 --- a/scripts/event-log-collector.ts +++ b/scripts/event-log-collector.ts @@ -12,7 +12,7 @@ server.post('/event-log', (req, res) => { res.end('OK'); }); -server.get('/event-log', (req, res) => { +server.get('/event-log', (_req, res) => { console.log(`Sending ${events.length} events`); res.json(events); }); diff --git a/scripts/package.json b/scripts/package.json index 6b7d18750111..eff165cd7f76 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -3,6 +3,7 @@ "version": "7.0.0-alpha.16", "private": true, "scripts": { + "check": "./prepare/check-scripts.ts", "docs:prettier:check": "cd ../docs && prettier --check ./snippets", "docs:prettier:write": "cd ../docs && prettier --write ./snippets", "get-report-message": "ts-node --swc ./get-report-message.ts", @@ -175,6 +176,7 @@ "slash": "^3.0.0", "sort-package-json": "^2.0.0", "tempy": "^1.0.0", + "tiny-invariant": "^1.3.1", "trash": "^7.0.0", "ts-dedent": "^2.0.0", "ts-node": "^10.9.1", diff --git a/scripts/prepare/check-scripts.ts b/scripts/prepare/check-scripts.ts new file mode 100755 index 000000000000..86418acd93ba --- /dev/null +++ b/scripts/prepare/check-scripts.ts @@ -0,0 +1,75 @@ +#!/usr/bin/env ./node_modules/.bin/ts-node-script + +import { join } from 'path'; +import * as ts from 'typescript'; + +const run = async ({ cwd }: { cwd: string }) => { + const { options, fileNames } = getTSFilesAndConfig('tsconfig.json'); + const { program, host } = getTSProgramAndHost(fileNames, options); + + const tsDiagnostics = getTSDiagnostics(program, cwd, host); + if (tsDiagnostics.length > 0) { + console.log(tsDiagnostics); + process.exit(1); + } else { + console.log('no type errors'); + } + + // TODO, add more package checks here, like: + // - check for missing dependencies/peerDependencies + // - check for unused exports + + console.log('done'); +}; + +run({ cwd: process.cwd() }).catch((err: unknown) => { + // We can't let the stack try to print, it crashes in a way that sets the exit code to 0. + // Seems to have something to do with running JSON.parse() on binary / base64 encoded sourcemaps + // in @cspotcode/source-map-support + if (err instanceof Error) { + console.error(err.message); + } + process.exit(1); +}); + +function getTSDiagnostics(program: ts.Program, cwd: string, host: ts.CompilerHost): any { + return ts.formatDiagnosticsWithColorAndContext( + ts.getPreEmitDiagnostics(program).filter((d) => d.file.fileName.startsWith(cwd)), + host + ); +} + +function getTSProgramAndHost(fileNames: string[], options: ts.CompilerOptions) { + const program = ts.createProgram({ + rootNames: fileNames, + options: { + module: ts.ModuleKind.CommonJS, + ...options, + declaration: false, + noEmit: true, + }, + }); + + const host = ts.createCompilerHost(program.getCompilerOptions()); + return { program, host }; +} + +function getTSFilesAndConfig(tsconfigPath: string) { + const content = ts.readJsonConfigFile(tsconfigPath, ts.sys.readFile); + return ts.parseJsonSourceFileConfigFileContent( + content, + { + useCaseSensitiveFileNames: true, + readDirectory: ts.sys.readDirectory, + fileExists: ts.sys.fileExists, + readFile: ts.sys.readFile, + }, + process.cwd(), + { + noEmit: true, + outDir: join(process.cwd(), 'types'), + target: ts.ScriptTarget.ES2022, + declaration: false, + } + ); +} diff --git a/scripts/prepare/esm-bundle.ts b/scripts/prepare/esm-bundle.ts index 33a7c8314bae..08e039ad6dd2 100755 --- a/scripts/prepare/esm-bundle.ts +++ b/scripts/prepare/esm-bundle.ts @@ -13,7 +13,8 @@ import { exec } from '../utils/exec'; /* TYPES */ type BundlerConfig = { - entries: string[]; + browserEntries: string[]; + nodeEntries: string[]; externals: string[]; pre: string; post: string; diff --git a/scripts/release/pick-patches.ts b/scripts/release/pick-patches.ts index bbd90fb415e5..82f1fe2b4a1a 100644 --- a/scripts/release/pick-patches.ts +++ b/scripts/release/pick-patches.ts @@ -6,6 +6,7 @@ import ora from 'ora'; import { setOutput } from '@actions/core'; import { git } from './utils/git-client'; import { getUnpickedPRs } from './utils/github-client'; +import invariant from 'tiny-invariant'; program.name('pick-patches').description('Cherry pick patch PRs back to main'); @@ -57,6 +58,7 @@ export const run = async (_: unknown) => { await git.raw(['cherry-pick', '-m', '1', '--keep-redundant-commits', '-x', pr.mergeCommit]); prSpinner.succeed(`Picked: ${formatPR(pr)}`); } catch (pickError) { + invariant(pickError instanceof Error); prSpinner.fail(`Failed to automatically pick: ${formatPR(pr)}`); logger.error(pickError.message); const abort = ora(`Aborting cherry pick for merge commit: ${pr.mergeCommit}`).start(); @@ -64,8 +66,9 @@ export const run = async (_: unknown) => { await git.raw(['cherry-pick', '--abort']); abort.stop(); } catch (abortError) { + invariant(abortError instanceof Error); abort.warn(`Failed to abort cherry pick (${pr.mergeCommit})`); - logger.error(pickError.message); + logger.error(abortError.message); } failedCherryPicks.push(pr.mergeCommit); prSpinner.info( diff --git a/scripts/release/utils/get-github-info.ts b/scripts/release/utils/get-github-info.ts index 65508bcc05ae..6bd7126aec04 100644 --- a/scripts/release/utils/get-github-info.ts +++ b/scripts/release/utils/get-github-info.ts @@ -97,7 +97,7 @@ function makeQuery(repos: ReposWithCommitsAndPRsToFetch) { // getReleaseLine will be called a large number of times but it'll be called at the same time // so instead of doing a bunch of network requests, we can do a single one. const GHDataLoader = new DataLoader( - async (requests: RequestData[]) => { + async (requests: readonly RequestData[]) => { if (!process.env.GH_TOKEN) { throw new Error( 'Please create a GitHub personal access token at https://github.com/settings/tokens/new with `read:user` and `repo:status` permissions and add it as the GH_TOKEN environment variable' diff --git a/scripts/release/version.ts b/scripts/release/version.ts index 1636b248b454..3b34d288a8c7 100644 --- a/scripts/release/version.ts +++ b/scripts/release/version.ts @@ -141,12 +141,10 @@ const bumpVersionSources = async (currentVersion: string, nextVersion: string) = const bumpAllPackageJsons = async ({ packages, - currentVersion, nextVersion, verbose, }: { packages: Workspace[]; - currentVersion: string; nextVersion: string; verbose?: boolean; }) => { @@ -279,7 +277,7 @@ export const run = async (options: unknown) => { await bumpCodeVersion(nextVersion); await bumpVersionSources(currentVersion, nextVersion); - await bumpAllPackageJsons({ packages, currentVersion, nextVersion, verbose }); + await bumpAllPackageJsons({ packages, nextVersion, verbose }); console.log(`⬆️ Updating lock file with ${chalk.blue('yarn install --mode=update-lockfile')}`); await execaCommand(`yarn install --mode=update-lockfile`, { diff --git a/scripts/sandbox/utils/git.ts b/scripts/sandbox/utils/git.ts index e4261083c9a5..84fe02f25cdb 100644 --- a/scripts/sandbox/utils/git.ts +++ b/scripts/sandbox/utils/git.ts @@ -1,4 +1,6 @@ import fetch from 'node-fetch'; +import invariant from 'tiny-invariant'; + import { execaCommand } from '../../utils/exec'; // eslint-disable-next-line import/no-cycle import { logger } from '../publish'; @@ -27,9 +29,9 @@ const getTheLastCommitHashThatUpdatedTheSandboxRepo = async (branch: string) => `Could not find the last commit hash in the following commit message: "${latestCommitMessage}".\nDid someone manually push to the sandboxes repo?` ); } - return lastCommitHash; } catch (error) { + invariant(error instanceof Error); if (!error.message.includes('Did someone manually push to the sandboxes repo')) { logger.error( `⚠️ Error getting latest commit message of ${owner}/${repo} on branch ${branch}: ${error.message}` @@ -84,6 +86,7 @@ export async function commitAllToGit({ cwd, branch }: { cwd: string; branch: str ].join('\n'); gitCommitCommand = `git commit -m "${commitTitle}" -m "${commitBody}"`; } catch (err) { + invariant(err instanceof Error); logger.log( `⚠️ Falling back to a simpler commit message because of an error while trying to get the previous commit hash: ${err.message}` ); @@ -95,6 +98,7 @@ export async function commitAllToGit({ cwd, branch }: { cwd: string; branch: str cwd, }); } catch (e) { + invariant(e instanceof Error); if (e.message.includes('nothing to commit')) { logger.log( `🤷 Git found no changes between previous versions so there is nothing to commit. Skipping publish!` diff --git a/scripts/sandbox/utils/template.ts b/scripts/sandbox/utils/template.ts index 4cc3723827f0..10fdef474eb1 100644 --- a/scripts/sandbox/utils/template.ts +++ b/scripts/sandbox/utils/template.ts @@ -33,20 +33,17 @@ export async function getTemplatesData(branch: string) { > >; - const templatesData = Object.keys(sandboxTemplates).reduce( - (acc, curr: keyof typeof sandboxTemplates) => { - const [dirName, templateName] = curr.split('/'); - const groupName = - dirName === 'cra' ? 'CRA' : dirName.slice(0, 1).toUpperCase() + dirName.slice(1); - const generatorData = sandboxTemplates[curr]; - acc[groupName] = acc[groupName] || {}; - acc[groupName][templateName] = { - ...generatorData, - stackblitzUrl: getStackblitzUrl(curr, branch), - }; - return acc; - }, - {} - ); + const templatesData = Object.keys(sandboxTemplates).reduce((acc, curr) => { + const [dirName, templateName] = curr.split('/'); + const groupName = + dirName === 'cra' ? 'CRA' : dirName.slice(0, 1).toUpperCase() + dirName.slice(1); + const generatorData = sandboxTemplates[curr as keyof typeof sandboxTemplates]; + acc[groupName] = acc[groupName] || {}; + acc[groupName][templateName] = { + ...generatorData, + stackblitzUrl: getStackblitzUrl(curr, branch), + }; + return acc; + }, {}); return templatesData; } diff --git a/scripts/task.ts b/scripts/task.ts index 67a70ae30a86..9e18d1f618c2 100644 --- a/scripts/task.ts +++ b/scripts/task.ts @@ -35,6 +35,7 @@ import { } from '../code/lib/cli/src/sandbox-templates'; import { version } from '../code/package.json'; +import invariant from 'tiny-invariant'; const sandboxDir = process.env.SANDBOX_ROOT || SANDBOX_DIRECTORY; @@ -73,7 +74,7 @@ export type Task = { /** * Is this task already "ready", and potentially not required? */ - ready: (details: TemplateDetails, options: PassedOptionValues) => MaybePromise; + ready: (details: TemplateDetails, options?: PassedOptionValues) => MaybePromise; /** * Run the task */ @@ -320,6 +321,7 @@ async function runTask(task: Task, details: TemplateDetails, optionValues: Passe return controller; } catch (err) { + invariant(err instanceof Error); const hasJunitFile = await pathExists(junitFilename); // If there's a non-test related error (junit report has not been reported already), we report the general failure in a junit report if (junitFilename && !hasJunitFile) { @@ -466,6 +468,7 @@ async function run() { }); if (controller) controllers.push(controller); } catch (err) { + invariant(err instanceof Error); logger.error(`Error running task ${getTaskKey(task)}:`); // If it is the last task, we don't need to log the full trace if (task === finalTask) { diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index fadbdf41c901..f0aaa39d825d 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -390,8 +390,7 @@ export const addStories: Task['run'] = async ( // Ensure that we match the right stories in the stories directory updateStoriesField( mainConfig, - (await detectLanguage(packageManager)) === SupportedLanguage.JAVASCRIPT, - disableDocs + (await detectLanguage(packageManager)) === SupportedLanguage.JAVASCRIPT ); const isCoreRenderer = diff --git a/scripts/ts-to-ts49.ts b/scripts/ts-to-ts49.ts index a73eaabbd2be..869d71bfb9a2 100644 --- a/scripts/ts-to-ts49.ts +++ b/scripts/ts-to-ts49.ts @@ -7,6 +7,7 @@ import * as recast from 'recast'; import type Babel from '@babel/core'; import type { File } from '@babel/types'; import * as t from '@babel/types'; +import invariant from 'tiny-invariant'; const files = glob.sync('**/*.ts.mdx', { absolute: true, @@ -74,6 +75,7 @@ for (const [, file] of files.entries()) { console.log('changed', file); } } catch (e) { + invariant(e instanceof Error); console.error(e.message); } } diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index 591055d2ab92..c82e14a95108 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -8,14 +8,14 @@ "moduleResolution": "Node", "target": "ES2020", "module": "CommonJS", - "skipLibCheck": false, + "skipLibCheck": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, "isolatedModules": true, "strictBindCallApply": true, "lib": ["dom", "esnext"], "types": ["node", "jest"], - "strict": false, + "strict": true, "strictNullChecks": false, "forceConsistentCasingInFileNames": true, "noUnusedLocals": true, diff --git a/scripts/utils/exec.ts b/scripts/utils/exec.ts index 6280f65ff9cf..74a886189420 100644 --- a/scripts/utils/exec.ts +++ b/scripts/utils/exec.ts @@ -67,7 +67,7 @@ export const exec = async ( } } } catch (err) { - if (!err.killed) { + if (!(typeof err === 'object' && 'killed' in err && err.killed)) { logger.error(chalk.red(`An error occurred while executing: \`${command}\``)); logger.log(`${errorMessage}\n`); } diff --git a/scripts/utils/options.test.ts b/scripts/utils/options.test.ts index ccbc6a058ec3..c183db207f2c 100644 --- a/scripts/utils/options.test.ts +++ b/scripts/utils/options.test.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from '@jest/globals'; import { createCommand } from 'commander'; -import type { MaybeOptionValues, OptionValues } from './options'; import { areOptionsSatisfied, createOptions, getCommand, getOptions } from './options'; const allOptions = createOptions({ @@ -35,17 +34,6 @@ const allOptions = createOptions({ }, }); -// TS "tests" -// deepscan-disable-next-line -function test(mv: MaybeOptionValues, v: OptionValues) { - console.log(mv.first, mv.second, mv.third, mv.fourth, mv.fifth, mv.sixth); - // @ts-expect-error as it's not allowed - console.log(mv.seventh); - console.log(v.first, v.second, v.third, v.fourth, v.fifth, v.sixth); - // @ts-expect-error as it's not allowed - console.log(v.seventh); -} - describe('getOptions', () => { it('deals with boolean options', () => { expect(getOptions(createCommand(), allOptions, ['command', 'name', '--first'])).toMatchObject({ @@ -71,7 +59,6 @@ describe('getOptions', () => { }); it('deals with string options', () => { - const r = getOptions(createCommand(), allOptions, ['command', 'name', '--third', 'one']); expect( getOptions(createCommand(), allOptions, ['command', 'name', '--third', 'one']) ).toMatchObject({ diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 6fe301173b5d..be6fe9587929 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -3033,6 +3033,7 @@ __metadata: slash: ^3.0.0 sort-package-json: ^2.0.0 tempy: ^1.0.0 + tiny-invariant: ^1.3.1 trash: ^7.0.0 ts-dedent: ^2.0.0 ts-loader: ^9.4.2 @@ -15618,6 +15619,13 @@ __metadata: languageName: node linkType: hard +"tiny-invariant@npm:^1.3.1": + version: 1.3.1 + resolution: "tiny-invariant@npm:1.3.1" + checksum: 5b87c1d52847d9452b60d0dcb77011b459044e0361ca8253bfe7b43d6288106e12af926adb709a6fc28900e3864349b91dad9a4ac93c39aa15f360b26c2ff4db + languageName: node + linkType: hard + "tmp@npm:~0.2.1": version: 0.2.1 resolution: "tmp@npm:0.2.1"