diff --git a/src/cli.ts b/src/cli.ts index 14373664..21e5068f 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -20,6 +20,7 @@ import { checkCIConfig, checkConfigurationFilesAreExtended, checkContributors, + checkCopyrightYears, checkDependencies, checkLicenses, checkNeededFiles, @@ -86,10 +87,12 @@ suggestOverwrite = suggestOverwrite || checkedNYCConfiguration[1]; packageJsonChanged = packageJsonChanged || checkScripts(rules, packageJson, commander.replace); -checkContributors(packageJson); +checkContributors(path, packageJson); checkCIConfig(rules, path); +checkCopyrightYears(path, 'src'); + if (packageJsonChanged) { writeFileSync(resolve(path, 'package.json'), JSON.stringify(packageJson, null, 2)); consoleLog(`Changes were written to '${packageJsonPath}'.`); diff --git a/src/common.ts b/src/common.ts index ce987df2..d54c6df9 100644 --- a/src/common.ts +++ b/src/common.ts @@ -1,6 +1,6 @@ import chalk from 'chalk'; import {execSync} from 'child_process'; -import {copyFileSync, existsSync, readFileSync} from 'fs'; +import {copyFileSync, existsSync, lstatSync, PathLike, readdirSync, readFileSync} from 'fs'; import {join, resolve, sep} from 'path'; import {satisfies, valid} from 'semver'; import {isDeepStrictEqual} from 'util'; @@ -345,10 +345,11 @@ export function checkScripts(rules: Rules, packageJson: any, replaceFlag: boolea /** * Check contributors * + * @param path Path to directory * @param packageJson package.json to check contributors in */ -export function checkContributors(packageJson: any): void { - const execBuffer = execSync('git log --format=\'%aN\' | sort -u'); +export function checkContributors(path: PathLike, packageJson: any): void { + const execBuffer = execSync(`git --git-dir=${path}/.git --work-tree=${path} log --format=\'%aN\' | sort -u`); for (let author of execBuffer.toString().split('\n')) { author = author.trim(); @@ -406,6 +407,88 @@ ${stringify(rules.ciConfig)}`); } } +/** + * Check copyright years in files + * + * @param path Path to project root + * @param subDir Subordinated directory to examine + */ +export function checkCopyrightYears(path: PathLike, subDir: PathLike): void { + const fileSystemObjects = readdirSync(resolve(path.toString(), subDir.toString())); + + for (const fileSystemObject of fileSystemObjects) { + const fileSystemObjectPath = resolve(path.toString(), subDir.toString(), fileSystemObject); + + // tslint:disable-next-line:max-line-length + const execBuffer = execSync(`git --git-dir=${path}/.git --work-tree=${path} log --oneline --format='%cI' -- ${fileSystemObjectPath}`); + + const seen: number[] = []; + const changedYears = execBuffer + .toString() + .split('\n') + .map((date) => parseInt(date.split('-')[0], 10)) + .filter((year) => { + if (seen.indexOf(year) >= 0 || !year.toString().match(/[0-9]{4}/)) { + return false; + } + + seen.push(year); + return true; + }) + .sort(); + + const fileStats = lstatSync(fileSystemObjectPath); + + if (fileStats.isFile()) { + const content = readFileSync(fileSystemObjectPath).toString().split('\n'); + + let copyrightYearsString: string = ''; + for (const line of content) { + const match = line.match(/^ \* Copyright \(C\) ([0-9\-,\s]*) StApps$/); + + if (Array.isArray(match) && match.length === 2) { + copyrightYearsString = match[1]; + } + } + + if (copyrightYearsString === '') { + consoleWarn(`Copyright line for file '${fileSystemObjectPath}' could not be found!`); + } else { + const copyrightYearsWithIntervals = copyrightYearsString.split(',').map((year) => year.trim()); + const copyrightYears: number[] = []; + + for (const copyrightYear of copyrightYearsWithIntervals) { + if (copyrightYear.indexOf('-') >= 0) { + const [startString, endString] = copyrightYear.split('-'); + const start = parseInt(startString.trim(), 10); + const end = parseInt(endString.trim(), 10); + for (let year = start; year <= end; year++) { + copyrightYears.push(year); + } + } else { + copyrightYears.push(parseInt(copyrightYear, 10)); + } + } + + for (const copyrightYear of copyrightYears) { + const idx = changedYears.indexOf(copyrightYear); + + if (idx >= 0) { + changedYears.splice(idx, 1); + } + } + + if (changedYears.length > 0) { + // tslint:disable-next-line:max-line-length + consoleWarn(`File '${join(subDir.toString(), fileSystemObject)}' is missing '${changedYears.join(', ')}' as year(s) in the copyright line.`); + } + } + } else if (fileStats.isDirectory()) { + checkCopyrightYears(path, join(subDir.toString(), fileSystemObject)); + } + } +} + /** * Get configuration *