Skip to content

Commit

Permalink
refactor: setup src and util directories, update build script
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewgallo committed Nov 18, 2024
1 parent 03149ca commit 52e0a14
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 97 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}'"
Expand Down
112 changes: 23 additions & 89 deletions index.ts → src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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({
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 (
Expand All @@ -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);
}
});
Expand All @@ -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,
Expand Down Expand Up @@ -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;
};
File renamed without changes.
File renamed without changes.
19 changes: 19 additions & 0 deletions src/utils/findFileUpParent.ts
Original file line number Diff line number Diff line change
@@ -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;
};
28 changes: 28 additions & 0 deletions src/utils/getImports.ts
Original file line number Diff line number Diff line change
@@ -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);
}
};
32 changes: 32 additions & 0 deletions src/utils/isExternalImport.ts
Original file line number Diff line number Diff line change
@@ -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;
};
13 changes: 6 additions & 7 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -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"]
}

0 comments on commit 52e0a14

Please sign in to comment.