This repository has been archived by the owner on Dec 31, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(project): init with 1.0.0-beta.1
- Loading branch information
Michael Mullins
committed
Sep 18, 2021
1 parent
f7b160b
commit 7e59e6c
Showing
12 changed files
with
441 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
import { existsSync } from 'fs'; | ||
import { resolve } from 'path'; | ||
|
||
import { checkLocal } from './utils/check-deps'; | ||
import { checkImports } from './utils/check-imports'; | ||
import { checkPackageVersion } from './utils/check-package-version'; | ||
import { countImportHits } from './utils/count-import-hits'; | ||
import { getImports } from './utils/get-imports'; | ||
import { getPackageJson } from './utils/get-package-json'; | ||
import { | ||
IAnalyzeInput, | ||
IObject, | ||
IObjectTypes, | ||
IPackage, | ||
IPackageInput, | ||
IPackageJsonData, | ||
packageImportsDefault, | ||
} from './types'; | ||
|
||
export function analyze(params: IAnalyzeInput): IPackage[] { | ||
const timeStart = Date.now(); | ||
analysisPreChecks(params); | ||
|
||
const rootJson: IPackageJsonData = | ||
params.packageJson && (params.checkDeps === 'full' || params.checkPackageVersion) | ||
? getPackageJson(params.packageJson) | ||
: {}; | ||
|
||
const packages: IPackage[] = []; | ||
params.packages.forEach((pkgIn: IPackageInput) => { | ||
const start = Date.now(); | ||
const packageJson = packagePreChecks(pkgIn); | ||
packages.push({ | ||
name: pkgIn.name, | ||
path: pkgIn.path, | ||
packageJson, | ||
jsonData: {}, | ||
imports: packageImportsDefault, | ||
importsReport: new Map<string, IObject>([]), | ||
versionReport: '', | ||
time: Date.now() - start, | ||
}); | ||
}); | ||
|
||
packages.forEach((pkg: IPackage) => { | ||
const start = Date.now(); | ||
|
||
if (params.checkDeps === 'full' || params.checkPackageVersion) { | ||
pkg.jsonData = getPackageJson(pkg.packageJson); | ||
} | ||
if (params.countHits || params.checkImports || params.checkDeps) { | ||
pkg.imports = getImports(pkg, params.matchExt || [], params.ignoreImports || []); | ||
pkg.imports.importsUnique.forEach((item: string) => { | ||
pkg.importsReport.set(item, {}); | ||
}); | ||
} | ||
|
||
if (params.countHits) { | ||
const data = countImportHits(pkg.imports); | ||
Array.from(data.entries()).forEach(([key, report]: [string, IObjectTypes]) => { | ||
const item = pkg.importsReport.get(key) || {}; | ||
item.Hits = report; | ||
pkg.importsReport.set(key, item); | ||
}); | ||
} | ||
|
||
if (params.checkImports) { | ||
const { | ||
report: data, | ||
hasErrors, | ||
hasWarnings, | ||
} = checkImports(pkg, packages, params.treatImports || null); | ||
Array.from(data.entries()).forEach(([key, report]: [string, IObjectTypes]) => { | ||
const item = pkg.importsReport.get(key) || {}; | ||
item.Imports = report; | ||
pkg.importsReport.set(key, item); | ||
}); | ||
if (hasErrors) { | ||
pkg.hasErrors = true; | ||
} | ||
if (hasWarnings) { | ||
pkg.hasWarnings = true; | ||
} | ||
} | ||
|
||
if (params.checkDeps) { | ||
const { report: data, hasErrors, hasWarnings } = checkLocal(pkg, params.treatDeps || null); | ||
Array.from(data.entries()).forEach(([key, report]: [string, IObjectTypes]) => { | ||
const item = pkg.importsReport.get(key) || {}; | ||
item.Dependencies = report; | ||
pkg.importsReport.set(key, item); | ||
}); | ||
if (hasErrors) { | ||
pkg.hasErrors = true; | ||
} | ||
if (hasWarnings) { | ||
pkg.hasWarnings = true; | ||
} | ||
} | ||
|
||
if (params.checkPackageVersion) { | ||
const { | ||
report: data, | ||
hasErrors, | ||
hasWarnings, | ||
} = checkPackageVersion(pkg, rootJson, params.treatPackageVersion || null); | ||
pkg.versionReport = data; | ||
if (hasErrors) { | ||
pkg.hasErrors = true; | ||
} | ||
if (hasWarnings) { | ||
pkg.hasWarnings = true; | ||
} | ||
} | ||
|
||
if (params.logToConsole) { | ||
packageReportLog(pkg, params, start); | ||
} | ||
}); | ||
|
||
if (params.logToConsole) { | ||
const totalReport: IObject = { | ||
'Packages analyzed': packages.length, | ||
'Total time spent (s)': (Date.now() - timeStart) / 1000, | ||
}; | ||
console.table(totalReport); | ||
} | ||
|
||
if (packages.some((pkg: IPackage) => pkg.hasErrors)) { | ||
if (params.throwError && params.logToConsole) { | ||
throw new Error('Errors found. See the report above.'); | ||
} else if (params.logToConsole) { | ||
console.error('Errors found. See the report above.'); | ||
} else if (params.throwError) { | ||
throw new Error('Errors found. To see the report pass \'logToConsole\' to parameters.'); | ||
} else { | ||
console.error('Errors found. To see the report pass \'logToConsole\' to parameters.'); | ||
} | ||
} else if (packages.some((pkg: IPackage) => pkg.hasWarnings)) { | ||
if (params.logToConsole) { | ||
console.error('Warnings found. See the report above.'); | ||
} else { | ||
console.error('Warnings found. To see the report pass \'logToConsole\' to parameters.'); | ||
} | ||
} | ||
|
||
return packages; | ||
} | ||
|
||
function analysisPreChecks(params: IAnalyzeInput): void { | ||
if (params.packageJson && !existsSync(params.packageJson)) { | ||
throw new Error(`The provided package.json path (${params.packageJson}) does not exist`); | ||
} | ||
if (params.checkDeps === 'full' && !params.packageJson) { | ||
throw new Error('Can not fully check dependencies without package.json'); | ||
} | ||
if (params.checkPackageVersion && !params.packageJson) { | ||
throw new Error('Can not check versions without package.json'); | ||
} | ||
} | ||
|
||
function packagePreChecks(pkgIn: IPackageInput): string { | ||
if (!existsSync(pkgIn.path)) { | ||
throw new Error('The provided package path does not exist: ' + pkgIn.path); | ||
} | ||
const packageJson = pkgIn?.packageJson || resolve(pkgIn.path + '/package.json'); | ||
if (!existsSync(packageJson)) { | ||
throw new Error('The provided package.json path does not exist: ' + packageJson); | ||
} | ||
return packageJson; | ||
} | ||
|
||
function packageReportLog(pkg: IPackage, params: IAnalyzeInput, timeStart: number): void { | ||
const packageReport: IObject = { | ||
Package: pkg.name, | ||
Path: pkg.path, | ||
'Version info': pkg.versionReport, | ||
}; | ||
if (params.countHits || params.checkImports || params.checkDeps) { | ||
packageReport['Total files'] = pkg.imports.filesTotal; | ||
packageReport['Matched files'] = pkg.imports.filesMatched; | ||
packageReport['Total imports'] = pkg.imports.importsTotal; | ||
packageReport['Matched imports'] = pkg.imports.importsMatched.length; | ||
packageReport['Unique imports'] = pkg.imports.importsUnique.length; | ||
} | ||
const importsReport: IObject<IObjectTypes | IObject> = {}; | ||
if (pkg.importsReport.size > 0) { | ||
Array.from(pkg.importsReport.entries()) | ||
.filter(([, reports]: [string, IObject]) => Object.keys(reports).length > 0) | ||
.forEach(([key, reports]: [string, IObject]) => { | ||
importsReport[key] = reports; | ||
}); | ||
} | ||
|
||
pkg.time += Date.now() - timeStart; | ||
packageReport['Time spent (s)'] = pkg.time / 1000; | ||
|
||
console.group(); | ||
console.table(packageReport); | ||
if (pkg.importsReport.size > 0) { | ||
console.table(importsReport); | ||
} | ||
console.groupEnd(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { analyze } from './analyze'; | ||
export { IAnalyzeInput, IPackageInput, IPackage } from './types'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { IPackage, TImportsReport, TTreatCallbackDep, TTreatTypes } from '../types'; | ||
import { getTreatIcon } from './get-treat-icon'; | ||
|
||
export function checkLocal( | ||
pkg: IPackage, | ||
treatAs: TTreatTypes | TTreatCallbackDep | ||
): { report: TImportsReport; hasErrors: boolean; hasWarnings: boolean } { | ||
const allDependencies: { [name: string]: string } = { | ||
...(pkg.jsonData.dependencies || {}), | ||
...(pkg.jsonData.devDependencies || {}), | ||
...(pkg.jsonData.peerDependencies || {}), | ||
}; | ||
const packageDeps: string[] = Array.from(new Set(Object.keys(allDependencies))); | ||
|
||
const absoluteImports: string[] = pkg.imports.importsUnique.filter( | ||
(item: string) => !item.includes('./') | ||
); | ||
|
||
const tempReport = new Map<string, { data: string; treat: TTreatTypes }>([]); | ||
absoluteImports.forEach((item: string) => { | ||
if (!packageDeps.some((dep: string) => item.startsWith(dep))) { | ||
const treat: TTreatTypes = | ||
typeof treatAs === 'function' ? treatAs(pkg.name, 'local', item) : treatAs; | ||
tempReport.set(item, { data: getTreatIcon(treat) + 'Not listed in local package.json', treat }); | ||
} | ||
}); | ||
|
||
const reportEntries = Array.from(tempReport.entries()); | ||
const report: TImportsReport = new Map<string, string>([]); | ||
reportEntries.forEach(([key, item]: [string, { data: string; treat: TTreatTypes }]) => { | ||
report.set(key, item.data); | ||
}); | ||
|
||
return { | ||
report, | ||
hasErrors: reportEntries.some(([, item]) => item.treat === 'err'), | ||
hasWarnings: reportEntries.some(([, item]) => item.treat === 'warn'), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { resolve } from 'path'; | ||
|
||
import { IObjectTypes, IPackage, TImportsReport, TTreatCallbackImport, TTreatTypes } from '../types'; | ||
import { getTreatIcon } from './get-treat-icon'; | ||
|
||
export function checkImports( | ||
pkg: IPackage, | ||
packages: IPackage[], | ||
treatAs: TTreatTypes | TTreatCallbackImport | ||
): { report: TImportsReport; hasErrors: boolean; hasWarnings: boolean } { | ||
const tempReport = new Map<string, { data: IObjectTypes; treat: TTreatTypes }>([]); | ||
|
||
const selfImports: string[] = pkg.imports.importsUnique.filter((item: string) => | ||
item.startsWith(pkg.name) | ||
); | ||
|
||
selfImports.forEach((item: string) => { | ||
const treat: TTreatTypes = | ||
typeof treatAs === 'function' ? treatAs(pkg.name, 'absSame', item) : treatAs; | ||
tempReport.set(item, { data: getTreatIcon(treat) + 'Absolute import from the same package', treat }); | ||
}); | ||
|
||
const relativeImports: string[] = pkg.imports.importsUnique.filter((item: string) => | ||
item.includes('../') | ||
); | ||
|
||
if (relativeImports.length > 0) { | ||
const otherPackages: string[] = packages | ||
.map((pkg: IPackage) => pkg.path) | ||
.filter((path: string) => path !== pkg.path); | ||
|
||
relativeImports.forEach((item: string) => { | ||
const path: string = resolve(pkg.path + '/' + item); | ||
if (otherPackages.some((otherPkg: string) => path.includes(otherPkg))) { | ||
const treat: TTreatTypes = | ||
typeof treatAs === 'function' ? treatAs(pkg.name, 'relExt', item) : treatAs; | ||
tempReport.set(item, { data: getTreatIcon(treat) + 'External relative import', treat }); | ||
} | ||
}); | ||
} | ||
|
||
const reportEntries = Array.from(tempReport.entries()); | ||
const report: TImportsReport = new Map<string, IObjectTypes>([]); | ||
reportEntries.forEach(([key, item]: [string, { data: IObjectTypes; treat: TTreatTypes }]) => { | ||
report.set(key, item.data); | ||
}); | ||
|
||
return { | ||
report, | ||
hasErrors: reportEntries.some(([, item]) => item.treat === 'err'), | ||
hasWarnings: reportEntries.some(([, item]) => item.treat === 'warn'), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { IPackage, IPackageJsonData, TTreatCallbackVersion, TTreatTypes } from '../types'; | ||
import { getTreatIcon } from './get-treat-icon'; | ||
|
||
export function checkPackageVersion( | ||
pkg: IPackage, | ||
rootJson: IPackageJsonData, | ||
treatAs: TTreatTypes | TTreatCallbackVersion | ||
): { report: string; hasErrors: boolean; hasWarnings: boolean } { | ||
if (!pkg.jsonData.version) { | ||
const treat: TTreatTypes = typeof treatAs === 'function' ? treatAs(pkg.name) : treatAs; | ||
return { | ||
report: getTreatIcon(treat) + 'Package version is not set', | ||
hasErrors: treat === 'err', | ||
hasWarnings: treat === 'warn', | ||
}; | ||
} | ||
if (pkg.jsonData.version !== rootJson.version) { | ||
const treat: TTreatTypes = typeof treatAs === 'function' ? treatAs(pkg.name) : treatAs; | ||
return { | ||
report: | ||
pkg.jsonData.version + | ||
', ' + | ||
getTreatIcon(treat) + | ||
'does not match project version: ' + | ||
rootJson.version, | ||
hasErrors: treat === 'err', | ||
hasWarnings: treat === 'warn', | ||
}; | ||
} | ||
return { | ||
report: pkg.jsonData.version, | ||
hasErrors: false, | ||
hasWarnings: false, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { IPackageImports, TImportsReport } from '../types'; | ||
|
||
export function countImportHits(imports: IPackageImports): TImportsReport { | ||
const report: TImportsReport = new Map<string, string>([]); | ||
|
||
imports.importsUnique.forEach((item: string) => { | ||
const hits = imports.importsMatched.filter((imp: string) => imp === item); | ||
report.set(item, hits.length); | ||
}); | ||
|
||
return report; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Dirent, readdirSync } from 'fs'; | ||
import { resolve } from 'path'; | ||
|
||
export function getFiles(dir: string): string[] { | ||
const entries = readdirSync(dir, { withFileTypes: true }); | ||
return entries | ||
.map((entry: Dirent) => { | ||
const res = resolve(dir, entry.name); | ||
return entry.isDirectory() ? getFiles(res) : res; | ||
}) | ||
.flat(); | ||
} |
Oops, something went wrong.