11#!/usr/bin/env node
22
33import * as core from "@arethetypeswrong/core" ;
4+ import { groupProblemsByKind , parsePackageSpec } from "@arethetypeswrong/core/utils" ;
45import { versions } from "@arethetypeswrong/core/versions" ;
5- import { Option , program } from "commander" ;
66import chalk from "chalk" ;
7- import { readFile } from "fs/promises" ;
8- import { FetchError } from "node-fetch" ;
7+ import { execSync } from "child_process" ;
8+ import { Option , program } from "commander" ;
9+ import { readFile , stat , unlink } from "fs/promises" ;
910import { createRequire } from "module" ;
10-
11- import * as render from "./render/index.js " ;
12- import { readConfig } from "./readConfig.js " ;
11+ import { FetchError } from "node-fetch" ;
12+ import path from "path " ;
13+ import readline from "readline/promises " ;
1314import { problemFlags } from "./problemUtils.js" ;
14- import { groupProblemsByKind , parsePackageSpec } from "@arethetypeswrong/core/utils" ;
15+ import { readConfig } from "./readConfig.js" ;
16+ import * as render from "./render/index.js" ;
1517
1618const packageJson = createRequire ( import . meta. url ) ( "../package.json" ) ;
1719const version = packageJson . version ;
@@ -21,6 +23,7 @@ const formats = ["table", "table-flipped", "ascii", "json"] as const;
2123type Format = ( typeof formats ) [ number ] ;
2224
2325export interface Opts {
26+ pack ?: boolean ;
2427 fromNpm ? : boolean ;
2528 summary ? : boolean ;
2629 emoji ? : boolean ;
@@ -42,7 +45,11 @@ program
4245 ) } attempts to analyze npm package contents for issues with their TypeScript types,
4346particularly ESM-related module resolution issues.`
4447 )
45- . argument ( "<file-name>" , "the file to check; by default a path to a .tar.gz file, unless --from-npm is set" )
48+ . argument (
49+ "[file-directory-or-package-spec]" ,
50+ "the packed .tgz, or directory containing package.json with --pack, or package spec with --from-npm"
51+ )
52+ . option ( "-P, --pack" , "run `npm pack` in the specified directory and delete the resulting .tgz file afterwards" )
4653 . option ( "-p, --from-npm" , "read from the npm registry instead of a local file" )
4754 . addOption ( new Option ( "-f, --format <format>" , "specify the print format" ) . choices ( formats ) . default ( "table" ) )
4855 . option ( "-q, --quiet" , "don't print anything to STDOUT (overrides all other options)" )
@@ -53,7 +60,7 @@ particularly ESM-related module resolution issues.`
5360 . option ( "--emoji, --no-emoji" , "whether to use any emojis" )
5461 . option ( "--color, --no-color" , "whether to use any colors (the FORCE_COLOR env variable is also available)" )
5562 . option ( "--config-path <path>" , "path to config file (default: ./.attw.json)" )
56- . action ( async ( fileName : string ) => {
63+ . action ( async ( fileOrDirectory = "." ) => {
5764 const opts = program . opts < Opts > ( ) ;
5865 await readConfig ( program , opts . configPath ) ;
5966 opts . ignoreRules = opts . ignoreRules ?. map (
@@ -69,9 +76,13 @@ particularly ESM-related module resolution issues.`
6976 }
7077
7178 let analysis : core . CheckResult ;
79+ let deleteTgz ;
7280 if ( opts . fromNpm ) {
81+ if ( opts . pack ) {
82+ program . error ( "--pack and --from-npm cannot be used together" ) ;
83+ }
7384 try {
74- const result = parsePackageSpec ( fileName ) ;
85+ const result = parsePackageSpec ( fileOrDirectory ) ;
7586 if ( result . status === "error" ) {
7687 program . error ( result . error ) ;
7788 } else {
@@ -86,6 +97,39 @@ particularly ESM-related module resolution issues.`
8697 }
8798 } else {
8899 try {
100+ let fileName = fileOrDirectory ;
101+ if (
102+ await stat ( fileOrDirectory )
103+ . then ( ( stat ) => ! stat . isFile ( ) )
104+ . catch ( ( ) => false )
105+ ) {
106+ if ( ! ( await stat ( path . join ( fileOrDirectory , "package.json" ) ) . catch ( ( ) => false ) ) ) {
107+ program . error (
108+ `Specified directory must contain a package.json. No package.json found in ${ path . resolve (
109+ fileOrDirectory
110+ ) } .`
111+ ) ;
112+ }
113+
114+ if ( ! opts . pack ) {
115+ if ( ! process . stdout . isTTY ) {
116+ program . error (
117+ "Specifying a directory requires the --pack option to confirm that running `npm pack` is ok."
118+ ) ;
119+ }
120+ const rl = readline . createInterface ( process . stdin , process . stdout ) ;
121+ const answer = await rl . question ( `Run \`npm pack\`? (Pass -P/--pack to skip) (Y/n) ` ) ;
122+ rl . close ( ) ;
123+ if ( answer . trim ( ) && ! answer . trim ( ) . toLowerCase ( ) . startsWith ( "y" ) ) {
124+ process . exit ( 1 ) ;
125+ }
126+ }
127+
128+ fileName = deleteTgz = path . resolve (
129+ fileOrDirectory ,
130+ execSync ( "npm pack" , { cwd : fileOrDirectory , encoding : "utf8" , stdio : "pipe" } ) . trim ( )
131+ ) ;
132+ }
89133 const file = await readFile ( fileName ) ;
90134 const data = new Uint8Array ( file ) ;
91135 analysis = await core . checkTgz ( data ) ;
@@ -122,6 +166,10 @@ particularly ESM-related module resolution issues.`
122166 } else {
123167 render . untyped ( analysis as core . UntypedResult ) ;
124168 }
169+
170+ if ( deleteTgz ) {
171+ await unlink ( deleteTgz ) ;
172+ }
125173 } ) ;
126174
127175program . parse ( process . argv ) ;
0 commit comments