Skip to content
This repository has been archived by the owner on Dec 31, 2022. It is now read-only.

Commit

Permalink
feat(project): init with 1.0.0-beta.1
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Mullins committed Sep 18, 2021
1 parent f7b160b commit 7e59e6c
Show file tree
Hide file tree
Showing 12 changed files with 441 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@public-js/ng-pkg-keeper",
"version": "0.0.1",
"version": "1.0.0-beta.1",
"description": "Add description",
"scripts": {
"build": "npm run clean:dist && tsc",
Expand Down
204 changes: 204 additions & 0 deletions src/analyze.ts
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();
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { analyze } from './analyze';
export { IAnalyzeInput, IPackageInput, IPackage } from './types';
39 changes: 39 additions & 0 deletions src/utils/check-deps.ts
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'),
};
}
53 changes: 53 additions & 0 deletions src/utils/check-imports.ts
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'),
};
}
35 changes: 35 additions & 0 deletions src/utils/check-package-version.ts
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,
};
}
12 changes: 12 additions & 0 deletions src/utils/count-import-hits.ts
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;
}
12 changes: 12 additions & 0 deletions src/utils/get-files.ts
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();
}
Loading

0 comments on commit 7e59e6c

Please sign in to comment.