From 52e0a1426d37bede30265d8e1d4a790385d45615 Mon Sep 17 00:00:00 2001 From: Matt Gallo Date: Mon, 18 Nov 2024 16:09:19 -0500 Subject: [PATCH] refactor: setup src and util directories, update build script --- package.json | 2 +- index.ts => src/index.ts | 112 ++++-------------- .../tanstack-react-list.ts | 0 typings.d.ts => src/typings.d.ts | 0 src/utils/findFileUpParent.ts | 19 +++ src/utils/getImports.ts | 28 +++++ src/utils/isExternalImport.ts | 32 +++++ tsconfig.json | 13 +- 8 files changed, 109 insertions(+), 97 deletions(-) rename index.ts => src/index.ts (74%) rename tanstack-react-list.ts => src/tanstack-react-list.ts (100%) rename typings.d.ts => src/typings.d.ts (100%) create mode 100644 src/utils/findFileUpParent.ts create mode 100644 src/utils/getImports.ts create mode 100644 src/utils/isExternalImport.ts diff --git a/package.json b/package.json index b08a6ca..0e1dc59 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "tsc", + "build": "yarn clean && tsc", "clean": "rimraf ./dist", "prettier:check": "prettier --check '**/*.{js,ts}'", "prettier:write": "prettier --write '**/*.{js,ts}'" diff --git a/index.ts b/src/index.ts similarity index 74% rename from index.ts rename to src/index.ts index 686c295..b6a69b4 100755 --- a/index.ts +++ b/src/index.ts @@ -7,8 +7,9 @@ import { execSync } from 'child_process'; import Table from 'cli-table'; import path from 'path'; import { readdir } from 'node:fs/promises'; -import { existsSync } from 'fs'; -import { builtinModules } from 'module'; +import { isExternalImport } from './utils/isExternalImport.js'; +import { getImports } from './utils/getImports.js'; +import { findFileUpParent } from './utils/findFileUpParent.js'; import ts from 'typescript'; @@ -32,6 +33,20 @@ table.push( ] ); +const tsHost = ts.createCompilerHost( + { + allowJs: true, + noEmit: true, + isolatedModules: true, + resolveJsonModule: false, + moduleResolution: ts.ModuleResolutionKind.Classic, // we don't want node_modules + incremental: true, + noLib: true, + noResolve: true, + }, + true +); + const runPrompt = async () => { const answers = { pattern: await select({ @@ -63,7 +78,7 @@ const runPrompt = async () => { const { pattern, customPath, installDeps, type } = answers; - const { url } = reactExamples.find((e) => e.value === pattern); + const { url } = reactExamples.find((e) => e.value === pattern)!; const finalDestination = typeof customPath === 'string' && customPath.length > 0 @@ -119,8 +134,8 @@ const runPrompt = async () => { // and gets only the external packages const readExampleImports = async () => { const fileList = await readdir(finalDestination, { recursive: true }); - const allJSAndTSFiles = []; - const styleFiles = []; + const allJSAndTSFiles = [] as string[]; + const styleFiles = [] as string[]; for (const file of fileList) { const name = `${finalDestination}/${file}`; if ( @@ -137,14 +152,14 @@ const runPrompt = async () => { } if (allJSAndTSFiles.length > 0) { - const foundExternalPackages = []; + const foundExternalPackages = [] as string[]; // Gets imports for each js/ts file allJSAndTSFiles.forEach((filePath) => { - const fileImports = getImports(filePath); + const fileImports = getImports(filePath, tsHost); if (fileImports.length > 0) { fileImports.map((i) => { // Checks if import is from an external package - if (isExternalImport(filePath, i)) { + if (isExternalImport(filePath, i, tsHost)) { foundExternalPackages.push(i); } }); @@ -157,20 +172,6 @@ const runPrompt = async () => { } }; - // Recursively looks for a given file, going up directories until it is found or not - // Used here to find a package.json to confirm where to install dependencies - const findFileUpParent = (filename: string, startDir: string) => { - let currentDir = startDir || process.cwd(); - while (currentDir !== path.parse(currentDir).root) { - const filePath = path.join(currentDir, filename); - if (existsSync(filePath)) { - return filePath; - } - currentDir = path.dirname(currentDir); - } - return null; - }; - const runPackageManagerInstall = ( foundPackageLock: string, appDir: string, @@ -237,70 +238,3 @@ const runPrompt = async () => { }; runPrompt(); - -const tsHost = ts.createCompilerHost( - { - allowJs: true, - noEmit: true, - isolatedModules: true, - resolveJsonModule: false, - moduleResolution: ts.ModuleResolutionKind.Classic, // we don't want node_modules - incremental: true, - noLib: true, - noResolve: true, - }, - true -); - -// Returns array of imports for a given file path -const getImports = (fileName: string): string[] => { - const sourceFile = tsHost.getSourceFile( - fileName, - ts.ScriptTarget.Latest, - (msg) => { - throw new Error(`Failed to parse ${fileName}: ${msg}`); - } - ); - if (!sourceFile) throw ReferenceError(`Failed to find file ${fileName}`); - const importing: string[] = []; - delintNode(sourceFile); - return importing; - - function delintNode(node: ts.Node) { - if (ts.isImportDeclaration(node)) { - const moduleName = node.moduleSpecifier.getText().replace(/['"]/g, ''); - if ( - !moduleName.startsWith('node:') && - !builtinModules.includes(moduleName) - ) - importing.push(moduleName); - } else ts.forEachChild(node, delintNode); - } -}; - -// Returns true if a given import is external -const isExternalImport = (fileName: string, importPath: string): boolean => { - const program = ts.createProgram([fileName], {}, tsHost); - const sourceFile = program.getSourceFile(fileName); - if (sourceFile) { - const importDeclaration = sourceFile.statements.find((node) => { - return ( - ts.isImportDeclaration(node) && - node.moduleSpecifier.getText() === `'${importPath}'` - ); - }); - - if (importDeclaration) { - const resolvedModule = ts.resolveModuleName( - importPath, - fileName, - program.getCompilerOptions(), - ts.sys - ); - - return resolvedModule.resolvedModule?.isExternalLibraryImport ?? false; - } - } - - return false; -}; diff --git a/tanstack-react-list.ts b/src/tanstack-react-list.ts similarity index 100% rename from tanstack-react-list.ts rename to src/tanstack-react-list.ts diff --git a/typings.d.ts b/src/typings.d.ts similarity index 100% rename from typings.d.ts rename to src/typings.d.ts diff --git a/src/utils/findFileUpParent.ts b/src/utils/findFileUpParent.ts new file mode 100644 index 0000000..2fc56c7 --- /dev/null +++ b/src/utils/findFileUpParent.ts @@ -0,0 +1,19 @@ +import path from 'path'; +import { existsSync } from 'fs'; + +// Recursively looks for a given file, going up directories until it is found or not +// Used here to find a package.json to confirm where to install dependencies +export const findFileUpParent = ( + filename: string, + startDir: string +): string | null => { + let currentDir = startDir || process.cwd(); + while (currentDir !== path.parse(currentDir).root) { + const filePath = path.join(currentDir, filename); + if (existsSync(filePath)) { + return filePath; + } + currentDir = path.dirname(currentDir); + } + return null; +}; diff --git a/src/utils/getImports.ts b/src/utils/getImports.ts new file mode 100644 index 0000000..41a4973 --- /dev/null +++ b/src/utils/getImports.ts @@ -0,0 +1,28 @@ +import { builtinModules } from 'module'; +import ts from 'typescript'; + +// Returns array of imports for a given file path +export const getImports = (fileName: string, tsHost): string[] => { + const sourceFile = tsHost.getSourceFile( + fileName, + ts.ScriptTarget.Latest, + (msg) => { + throw new Error(`Failed to parse ${fileName}: ${msg}`); + } + ); + if (!sourceFile) throw ReferenceError(`Failed to find file ${fileName}`); + const importing: string[] = []; + delintNode(sourceFile); + return importing; + + function delintNode(node: ts.Node) { + if (ts.isImportDeclaration(node)) { + const moduleName = node.moduleSpecifier.getText().replace(/['"]/g, ''); + if ( + !moduleName.startsWith('node:') && + !builtinModules.includes(moduleName) + ) + importing.push(moduleName); + } else ts.forEachChild(node, delintNode); + } +}; diff --git a/src/utils/isExternalImport.ts b/src/utils/isExternalImport.ts new file mode 100644 index 0000000..6d7bca2 --- /dev/null +++ b/src/utils/isExternalImport.ts @@ -0,0 +1,32 @@ +import ts from 'typescript'; + +// Returns true if a given import is external +export const isExternalImport = ( + fileName: string, + importPath: string, + tsHost +): boolean => { + const program = ts.createProgram([fileName], {}, tsHost); + const sourceFile = program.getSourceFile(fileName); + if (sourceFile) { + const importDeclaration = sourceFile.statements.find((node) => { + return ( + ts.isImportDeclaration(node) && + node.moduleSpecifier.getText() === `'${importPath}'` + ); + }); + + if (importDeclaration) { + const resolvedModule = ts.resolveModuleName( + importPath, + fileName, + program.getCompilerOptions(), + ts.sys + ); + + return resolvedModule.resolvedModule?.isExternalLibraryImport ?? false; + } + } + + return false; +}; diff --git a/tsconfig.json b/tsconfig.json index 4f3d032..375a6c6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,14 @@ { "compilerOptions": { - "module": "esnext", + "module": "ESNext", "esModuleInterop": true, "target": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "sourceMap": true, - "outDir": "dist" + "outDir": "dist", + "rootDir": "src" }, "lib": ["es2015"], - "files": [ - "index.ts" - ], - "include": ["typings.d.ts"], + "include": ["src/**/*.ts"], + "exclude": ["node_modules", ".vscode"] }