diff --git a/.changeset/angry-timers-shave.md b/.changeset/angry-timers-shave.md new file mode 100644 index 0000000..1c148d1 --- /dev/null +++ b/.changeset/angry-timers-shave.md @@ -0,0 +1,5 @@ +--- +"@arethetypeswrong/cli": patch +--- + +Use exit code 3 for errors, 1 for failures diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 1bada8a..6a1df69 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -46,13 +46,13 @@ program .name("attw") .description( `${chalk.bold.blue( - "Are the Types Wrong?" + "Are the Types Wrong?", )} attempts to analyze npm package contents for issues with their TypeScript types, -particularly ESM-related module resolution issues.` +particularly ESM-related module resolution issues.`, ) .argument( "[file-directory-or-package-spec]", - "the packed .tgz, or directory containing package.json with --pack, or package spec with --from-npm" + "the packed .tgz, or directory containing package.json with --pack, or package spec with --from-npm", ) .option("-P, --pack", "Run `npm pack` in the specified directory and delete the resulting .tgz file afterwards") .option("-p, --from-npm", "Read from the npm registry instead of a local file") @@ -64,15 +64,15 @@ particularly ESM-related module resolution issues.` "--entrypoints ", "Specify an exhaustive list of entrypoints to check. " + 'The package root is `"." Specifying this option disables automatic entrypoint discovery, ' + - "and overrides the `--include-entrypoints` and `--exclude-entrypoints` options." + "and overrides the `--include-entrypoints` and `--exclude-entrypoints` options.", ) .option( "--include-entrypoints ", - "Specify entrypoints to check in addition to automatically discovered ones." + "Specify entrypoints to check in addition to automatically discovered ones.", ) .option("--exclude-entrypoints ", "Specify entrypoints to exclude from checking.") .addOption( - new Option("--ignore-rules ", "Specify rules to ignore").choices(Object.values(problemFlags)).default([]) + new Option("--ignore-rules ", "Specify rules to ignore").choices(Object.values(problemFlags)).default([]), ) .option("--summary, --no-summary", "Whether to print summary information about the different errors") .option("--emoji, --no-emoji", "Whether to use any emojis") @@ -82,7 +82,7 @@ particularly ESM-related module resolution issues.` const opts = program.opts(); await readConfig(program, opts.configPath); opts.ignoreRules = opts.ignoreRules?.map( - (value) => Object.keys(problemFlags).find((key) => problemFlags[key as core.ProblemKind] === value) as string + (value) => Object.keys(problemFlags).find((key) => problemFlags[key as core.ProblemKind] === value) as string, ); if (opts.quiet) { @@ -114,7 +114,7 @@ particularly ESM-related module resolution issues.` let pkg; if (dtIsPath) { const dtPackage = core.createPackageFromTarballData( - new Uint8Array(await readFile(opts.definitelyTyped as string)) + new Uint8Array(await readFile(opts.definitelyTyped as string)), ); const pkgVersion = result.data.versionKind === "none" @@ -150,15 +150,15 @@ particularly ESM-related module resolution issues.` if (!(await stat(path.join(fileOrDirectory, "package.json")).catch(() => false))) { program.error( `Specified directory must contain a package.json. No package.json found in ${path.resolve( - fileOrDirectory - )}.` + fileOrDirectory, + )}.`, ); } if (!opts.pack) { if (!process.stdout.isTTY) { program.error( - "Specifying a directory requires the --pack option to confirm that running `npm pack` is ok." + "Specifying a directory requires the --pack option to confirm that running `npm pack` is ok.", ); } const rl = readline.createInterface(process.stdin, process.stdout); @@ -173,7 +173,7 @@ particularly ESM-related module resolution issues.` fileName = deleteTgz = path.resolve( fileOrDirectory, - execSync("npm pack", { cwd: fileOrDirectory, encoding: "utf8", stdio: "pipe" }).trim() + execSync("npm pack", { cwd: fileOrDirectory, encoding: "utf8", stdio: "pipe" }).trim(), ); } const file = await readFile(fileName); @@ -182,7 +182,7 @@ particularly ESM-related module resolution issues.` ? core .createPackageFromTarballData(data) .mergedWithTypes( - core.createPackageFromTarballData(new Uint8Array(await readFile(opts.definitelyTyped as string))) + core.createPackageFromTarballData(new Uint8Array(await readFile(opts.definitelyTyped as string))), ) : core.createPackageFromTarballData(data); analysis = await core.checkPackage(pkg, { @@ -234,9 +234,14 @@ program.parse(process.argv); function handleError(error: unknown, title: string): never { if (error && typeof error === "object" && "message" in error) { program.error(`error while ${title}:\n${error.message}`, { + exitCode: 3, code: "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN", }); } - program.error(`unknown error while ${title}`, { code: "UNKNOWN" }); + program.error(`unknown error while ${title}`, { code: "UNKNOWN", exitCode: 3 }); } + +process.on("unhandledRejection", (error) => { + handleError(error, "checking package"); +}); diff --git a/packages/cli/test/snapshots.test.ts b/packages/cli/test/snapshots.test.ts index d0119c6..6c0860a 100644 --- a/packages/cli/test/snapshots.test.ts +++ b/packages/cli/test/snapshots.test.ts @@ -23,6 +23,8 @@ const tests = [ ["react-chartjs-2@5.2.0.tgz"], ["rfdc@1.3.0.tgz"], ["vue@3.3.4.tgz"], + ["moment@2.29.1.tgz"], + ["Babel@0.0.1.tgz"], ["vue@3.3.4.tgz", "--entrypoints vue"], ["vue@3.3.4.tgz", "--entrypoints . jsx-runtime"], @@ -61,6 +63,7 @@ describe("snapshots", async () => { test(fixture, async () => { const tarballPath = new URL(`../../../core/test/fixtures/${tarball}`, import.meta.url).pathname; let stdout; + let stderr = ""; let exitCode = 0; try { stdout = execSync(`${attw} ${tarballPath} ${options ?? defaultOpts}`, { @@ -69,6 +72,7 @@ describe("snapshots", async () => { }); } catch (error) { stdout = (error as SpawnSyncReturns).stdout; + stderr = (error as SpawnSyncReturns).stderr; exitCode = (error as SpawnSyncReturns).status ?? 1; } const snapshotURL = new URL(`../snapshots/${fixture.replace(/\//g, "")}.md`, import.meta.url); @@ -79,7 +83,7 @@ describe("snapshots", async () => { "```", `$ attw ${tarball} ${options ?? defaultOpts}`, "", - stdout, + [stdout, stderr].filter(Boolean).join("\n"), "", "```", "", diff --git a/packages/cli/test/snapshots/Babel@0.0.1.tgz.md b/packages/cli/test/snapshots/Babel@0.0.1.tgz.md new file mode 100644 index 0000000..fe18525 --- /dev/null +++ b/packages/cli/test/snapshots/Babel@0.0.1.tgz.md @@ -0,0 +1,12 @@ +# Babel@0.0.1.tgz + +``` +$ attw Babel@0.0.1.tgz -f table-flipped + +error while checking file: +Expected double-quoted property name in JSON at position 450 + + +``` + +Exit code: 3 \ No newline at end of file diff --git a/packages/cli/test/snapshots/moment@2.29.1.tgz.md b/packages/cli/test/snapshots/moment@2.29.1.tgz.md new file mode 100644 index 0000000..f91c880 --- /dev/null +++ b/packages/cli/test/snapshots/moment@2.29.1.tgz.md @@ -0,0 +1,25 @@ +# moment@2.29.1.tgz + +``` +$ attw moment@2.29.1.tgz -f table-flipped + + +moment v2.29.1 + +Build tools: +- typescript@^1.8.10 +- rollup@2.17.1 + + No problems found 🌟 + + +┌──────────┬────────┬───────────────────┬───────────────────┬─────────┐ +│ │ node10 │ node16 (from CJS) │ node16 (from ESM) │ bundler │ +├──────────┼────────┼───────────────────┼───────────────────┼─────────┤ +│ "moment" │ 🟢 │ 🟢 (CJS) │ 🟢 (CJS) │ 🟢 │ +└──────────┴────────┴───────────────────┴───────────────────┴─────────┘ + + +``` + +Exit code: 0 \ No newline at end of file diff --git a/packages/core/test/fixtures/Babel@0.0.1.tgz b/packages/core/test/fixtures/Babel@0.0.1.tgz new file mode 100644 index 0000000..1a8ea30 Binary files /dev/null and b/packages/core/test/fixtures/Babel@0.0.1.tgz differ diff --git a/packages/core/test/fixtures/moment@2.29.1.tgz b/packages/core/test/fixtures/moment@2.29.1.tgz new file mode 100644 index 0000000..b71468a Binary files /dev/null and b/packages/core/test/fixtures/moment@2.29.1.tgz differ diff --git a/packages/core/test/snapshots.test.ts b/packages/core/test/snapshots.test.ts index 79477c3..01d207f 100644 --- a/packages/core/test/snapshots.test.ts +++ b/packages/core/test/snapshots.test.ts @@ -26,6 +26,8 @@ describe("snapshots", async () => { "react@18.2.0.tgz": "@types__react@18.2.21.tgz", }; + const errorPackages = ["Babel@0.0.1.tgz"]; + for (const fixture of fs.readdirSync(new URL("../fixtures", import.meta.url))) { if (fixture === ".DS_Store" || fixture.startsWith("@types__")) { continue; @@ -35,10 +37,19 @@ describe("snapshots", async () => { const typesTarball = typesPackages[fixture] ? await readFile(new URL(`../fixtures/${typesPackages[fixture]}`, import.meta.url)) : undefined; - const pkg = createPackageFromTarballData(tarball); - const analysis = await checkPackage( - typesTarball ? pkg.mergedWithTypes(createPackageFromTarballData(typesTarball)) : pkg - ); + let analysis; + try { + const pkg = createPackageFromTarballData(tarball); + analysis = await checkPackage( + typesTarball ? pkg.mergedWithTypes(createPackageFromTarballData(typesTarball)) : pkg, + ); + } catch (error) { + if (errorPackages.includes(fixture)) { + return; + } + throw error; + } + const snapshotURL = new URL(`../snapshots/${fixture}.json`, import.meta.url); const expectedSnapshot = JSON.stringify(analysis, null, 2) + "\n"; diff --git a/packages/core/test/snapshots/moment@2.29.1.tgz.json b/packages/core/test/snapshots/moment@2.29.1.tgz.json new file mode 100644 index 0000000..b08e3d1 --- /dev/null +++ b/packages/core/test/snapshots/moment@2.29.1.tgz.json @@ -0,0 +1,165 @@ +{ + "packageName": "moment", + "packageVersion": "2.29.1", + "types": { + "kind": "included" + }, + "buildTools": { + "typescript": "^1.8.10", + "rollup": "2.17.1" + }, + "entrypoints": { + ".": { + "subpath": ".", + "resolutions": { + "node10": { + "name": ".", + "resolutionKind": "node10", + "resolution": { + "fileName": "/node_modules/moment/ts3.1-typings/moment.d.ts", + "isJson": false, + "isTypeScript": true, + "trace": [ + "======== Resolving module 'moment' from '/index.ts'. ========", + "Explicitly specified module resolution kind: 'Node10'.", + "Loading module 'moment' from 'node_modules' folder, target file types: TypeScript, Declaration.", + "Searching all ancestor node_modules directories for preferred extensions: TypeScript, Declaration.", + "Found 'package.json' at '/node_modules/moment/package.json'.", + "File '/node_modules/moment.ts' does not exist.", + "File '/node_modules/moment.tsx' does not exist.", + "File '/node_modules/moment.d.ts' does not exist.", + "'package.json' has a 'typesVersions' field with version-specific path mappings.", + "'package.json' has 'typings' field './moment.d.ts' that references '/node_modules/moment/moment.d.ts'.", + "'package.json' has a 'typesVersions' entry '>=3.1' that matches compiler version '5.2.2', looking for a pattern to match module name 'moment.d.ts'.", + "Module name 'moment.d.ts', matched pattern '*'.", + "Trying substitution 'ts3.1-typings/*', candidate module location: 'ts3.1-typings/moment.d.ts'.", + "File '/node_modules/moment/ts3.1-typings/moment.d.ts' exists - use it as a name resolution result.", + "======== Module name 'moment' was successfully resolved to '/node_modules/moment/ts3.1-typings/moment.d.ts' with Package ID 'moment/ts3.1-typings/moment.d.ts@2.29.1'. ========" + ] + }, + "files": [ + "/node_modules/typescript/lib/lib.d.ts", + "/node_modules/moment/ts3.1-typings/moment.d.ts" + ], + "visibleProblems": [] + }, + "node16-cjs": { + "name": ".", + "resolutionKind": "node16-cjs", + "resolution": { + "fileName": "/node_modules/moment/ts3.1-typings/moment.d.ts", + "isJson": false, + "isTypeScript": true, + "trace": [ + "======== Resolving module 'moment' from '/index.ts'. ========", + "Explicitly specified module resolution kind: 'Node16'.", + "Resolving in CJS mode with conditions 'require', 'types', 'node'.", + "File '/package.json' does not exist.", + "Loading module 'moment' from 'node_modules' folder, target file types: TypeScript, JavaScript, Declaration, JSON.", + "Searching all ancestor node_modules directories for preferred extensions: TypeScript, Declaration.", + "Found 'package.json' at '/node_modules/moment/package.json'.", + "File '/node_modules/moment.ts' does not exist.", + "File '/node_modules/moment.tsx' does not exist.", + "File '/node_modules/moment.d.ts' does not exist.", + "'package.json' has a 'typesVersions' field with version-specific path mappings.", + "'package.json' has 'typings' field './moment.d.ts' that references '/node_modules/moment/moment.d.ts'.", + "'package.json' has a 'typesVersions' entry '>=3.1' that matches compiler version '5.2.2', looking for a pattern to match module name 'moment.d.ts'.", + "Module name 'moment.d.ts', matched pattern '*'.", + "Trying substitution 'ts3.1-typings/*', candidate module location: 'ts3.1-typings/moment.d.ts'.", + "File '/node_modules/moment/ts3.1-typings/moment.d.ts' exists - use it as a name resolution result.", + "======== Module name 'moment' was successfully resolved to '/node_modules/moment/ts3.1-typings/moment.d.ts' with Package ID 'moment/ts3.1-typings/moment.d.ts@2.29.1'. ========" + ] + }, + "files": [ + "/node_modules/typescript/lib/lib.d.ts", + "/node_modules/moment/ts3.1-typings/moment.d.ts" + ], + "visibleProblems": [] + }, + "node16-esm": { + "name": ".", + "resolutionKind": "node16-esm", + "resolution": { + "fileName": "/node_modules/moment/ts3.1-typings/moment.d.ts", + "isJson": false, + "isTypeScript": true, + "trace": [ + "======== Resolving module 'moment' from '/index.mts'. ========", + "Explicitly specified module resolution kind: 'Node16'.", + "Resolving in ESM mode with conditions 'import', 'types', 'node'.", + "File '/package.json' does not exist according to earlier cached lookups.", + "Loading module 'moment' from 'node_modules' folder, target file types: TypeScript, JavaScript, Declaration, JSON.", + "Searching all ancestor node_modules directories for preferred extensions: TypeScript, Declaration.", + "File '/node_modules/moment/package.json' exists according to earlier cached lookups.", + "'package.json' has 'typings' field './moment.d.ts' that references '/node_modules/moment/moment.d.ts'.", + "'package.json' has a 'typesVersions' entry '>=3.1' that matches compiler version '5.2.2', looking for a pattern to match module name 'moment.d.ts'.", + "Module name 'moment.d.ts', matched pattern '*'.", + "Trying substitution 'ts3.1-typings/*', candidate module location: 'ts3.1-typings/moment.d.ts'.", + "File '/node_modules/moment/ts3.1-typings/moment.d.ts' exists - use it as a name resolution result.", + "======== Module name 'moment' was successfully resolved to '/node_modules/moment/ts3.1-typings/moment.d.ts' with Package ID 'moment/ts3.1-typings/moment.d.ts@2.29.1'. ========" + ] + }, + "files": [ + "/node_modules/typescript/lib/lib.d.ts", + "/node_modules/moment/ts3.1-typings/moment.d.ts" + ], + "visibleProblems": [] + }, + "bundler": { + "name": ".", + "resolutionKind": "bundler", + "resolution": { + "fileName": "/node_modules/moment/ts3.1-typings/moment.d.ts", + "isJson": false, + "isTypeScript": true, + "trace": [ + "======== Resolving module 'moment' from '/index.ts'. ========", + "Explicitly specified module resolution kind: 'Bundler'.", + "Resolving in CJS mode with conditions 'import', 'types'.", + "File '/package.json' does not exist.", + "Loading module 'moment' from 'node_modules' folder, target file types: TypeScript, JavaScript, Declaration, JSON.", + "Searching all ancestor node_modules directories for preferred extensions: TypeScript, Declaration.", + "Found 'package.json' at '/node_modules/moment/package.json'.", + "File '/node_modules/moment.ts' does not exist.", + "File '/node_modules/moment.tsx' does not exist.", + "File '/node_modules/moment.d.ts' does not exist.", + "'package.json' has a 'typesVersions' field with version-specific path mappings.", + "'package.json' has 'typings' field './moment.d.ts' that references '/node_modules/moment/moment.d.ts'.", + "'package.json' has a 'typesVersions' entry '>=3.1' that matches compiler version '5.2.2', looking for a pattern to match module name 'moment.d.ts'.", + "Module name 'moment.d.ts', matched pattern '*'.", + "Trying substitution 'ts3.1-typings/*', candidate module location: 'ts3.1-typings/moment.d.ts'.", + "File '/node_modules/moment/ts3.1-typings/moment.d.ts' exists - use it as a name resolution result.", + "======== Module name 'moment' was successfully resolved to '/node_modules/moment/ts3.1-typings/moment.d.ts' with Package ID 'moment/ts3.1-typings/moment.d.ts@2.29.1'. ========" + ] + }, + "files": [ + "/node_modules/typescript/lib/lib.d.ts", + "/node_modules/moment/ts3.1-typings/moment.d.ts" + ], + "visibleProblems": [] + } + }, + "hasTypes": true, + "isWildcard": false + } + }, + "programInfo": { + "node10": {}, + "node16": { + "moduleKinds": { + "/node_modules/typescript/lib/lib.d.ts": { + "detectedKind": 1, + "detectedReason": "no:type", + "reasonFileName": "/node_modules/typescript/lib/lib.d.ts" + }, + "/node_modules/moment/ts3.1-typings/moment.d.ts": { + "detectedKind": 1, + "detectedReason": "no:type", + "reasonFileName": "/node_modules/moment/package.json" + } + } + }, + "bundler": {} + }, + "problems": [] +} diff --git a/packages/history/scripts/generateFull.ts b/packages/history/scripts/generateFull.ts index dbf8568..8e9229a 100644 --- a/packages/history/scripts/generateFull.ts +++ b/packages/history/scripts/generateFull.ts @@ -29,8 +29,6 @@ const excludedSpecs = [ "next@13.4.4", // File not found: /node_modules/next/types/misc.d.ts "next@13.4.7", // File not found: /node_modules/next/types/misc.d.ts "next@13.4.12", // File not found: /node_modules/next/types/misc.d.ts - "moment@2.29.1", // Invalid gzip data - "moment@2.29.3", // Invalid gzip data ]; // Array of month starts from 2022-01-01 until the first of this month