UNPKG

@akphi/dev-utils

Version:
254 lines (233 loc) 7.27 kB
import { resolve } from 'path'; import { existsSync, lstatSync, writeFile, writeFileSync } from 'fs'; import { EOL, platform } from 'os'; import micromatch from 'micromatch'; import { execSync } from 'child_process'; import { isBinaryFileSync } from 'isbinaryfile'; import chalk from 'chalk'; import { getFileContent, createRegExp, exitWithError, exitWithSuccess, } from './DevUtils.js'; const GENERIC_INCLUDE_PATTERNS = [ /\.[^/]+$/, // files with extension ]; const GENERIC_EXCLUDE_PATTERNS = [ // nothing ]; export const generateCopyrightComment = ({ text, /** * Optional. This text will be added prior to the copyright content. * This is often useful for bundled code. * e.g. `@license some-package v1.0.0` */ pkg: { name, version }, /** * Boolean flag indicating if we are to generate just the content of the comment * or the opening/closing syntax for it */ onlyGenerateCommentContent, /** * TODO: account for file extension to generate different kinds of comments. * e.g. `html` comment is a tag `<!-- content -->` * e.g. `yaml` comment uses `#` */ file, }) => { // TODO: depending on the file type, these params might differ const headerPrefix = '/**'; const contentPrefix = ' *'; const footerPrefix = ' */'; let lines = text .trim() .split(EOL) .map((line) => `${contentPrefix}${line.length ? ` ${line}` : ''}`); if (!onlyGenerateCommentContent) { lines = [ `${headerPrefix}${ name && version ? ` @license ${name} v${version}` : '' }`, ...lines, footerPrefix, ]; } return lines.join(EOL); }; const getIncludedPatterns = ({ extensions }) => [ ...extensions.map((extension) => createRegExp(`\\.${extension}$`)), ]; const needsCopyrightHeader = (copyrightText, file) => { const fileContent = getFileContent(file); // NOTE: while checking for copyright header, we just generate the copyright comment content // not including the full comment (with opening/closing syntax) because potentially the copyright // comment might have been merged with another comment. const text = generateCopyrightComment({ text: copyrightText, pkg: {}, onlyGenerateCommentContent: true, }); return fileContent.trim().length > 0 && !fileContent.includes(text); }; const hasCopyrightHeader = (copyrightText, file) => { const fileContent = getFileContent(file); // NOTE: while checking for copyright header, we just generate the copyright comment content // not including the full comment (with opening/closing syntax) because potentially the copyright // comment might have been merged with another comment. const text = generateCopyrightComment({ text: copyrightText, pkg: {}, onlyGenerateCommentContent: true, }); return fileContent.trim().length > 0 && fileContent.includes(text); }; // Jest has a fairly sophisticated check for copyright license header that we used as reference // See https://github.com/facebook/jest/blob/master/scripts/checkCopyrightHeaders.js const getInvalidFiles = ({ extensions = [], /* micromatch glob patterns */ excludePatterns = [], copyrightText, onlyApplyToModifiedFiles, }) => { const files = execSync( `git ls-files ${onlyApplyToModifiedFiles ? '--modified' : ''}`, { encoding: 'utf-8' }, ) .trim() .split('\n'); const includePatterns = getIncludedPatterns({ extensions }); return files.filter( (file) => GENERIC_INCLUDE_PATTERNS.some((pattern) => pattern.test(file)) && includePatterns.some((pattern) => pattern.test(file)) && !GENERIC_EXCLUDE_PATTERNS.some((pattern) => pattern.test(file)) && !micromatch.isMatch(file, excludePatterns) && existsSync(file) && !lstatSync(file).isDirectory() && !isBinaryFileSync(file) && needsCopyrightHeader(copyrightText, file), ); }; export const getFilesWithCopyrightHeader = ({ extensions = [], /* micromatch glob patterns */ excludePatterns = [], copyrightText, }) => { const files = execSync('git ls-files', { encoding: 'utf-8' }) .trim() .split('\n'); const includePatterns = getIncludedPatterns({ extensions }); return files.filter( (file) => GENERIC_INCLUDE_PATTERNS.some((pattern) => pattern.test(file)) && includePatterns.some((pattern) => pattern.test(file)) && !GENERIC_EXCLUDE_PATTERNS.some((pattern) => pattern.test(file)) && !micromatch.isMatch(file, excludePatterns) && existsSync(file) && !lstatSync(file).isDirectory() && !isBinaryFileSync(file) && hasCopyrightHeader(copyrightText, file), ); }; export const checkCopyrightHeaders = ({ extensions = [], /* micromatch glob patterns */ excludePatterns = [], copyrightText, helpMessage, }) => { const files = getInvalidFiles({ extensions, excludePatterns, copyrightText, onlyApplyToModifiedFiles: false, }); if (files.length > 0) { exitWithError( `Found ${files.length} file(s) without copyright header:\n${files .map((file) => `${chalk.red('\u2717')} ${file}`) .join('\n')}${helpMessage ? `\n${helpMessage}` : ''}`, ); } else { console.log('No issues found!'); } }; export const updateCopyrightHeaders = async ({ extensions = [], /* micromatch glob patterns */ excludePatterns = [], copyrightText, onlyApplyToModifiedFiles, }) => { const files = getInvalidFiles({ extensions, excludePatterns, copyrightText, onlyApplyToModifiedFiles, }); if (files.length > 0) { console.log( `Found ${files.length} file(s) without copyright header. Processing...`, ); const copyrightComment = generateCopyrightComment({ text: copyrightText, pkg: {}, onlyGenerateCommentContent: false, }); await Promise.all( files.map((file) => writeFile( file, `${copyrightComment}\n\n${getFileContent(file)}`, (err) => { console.log( `${err ? chalk.red('\u2717') : chalk.green('\u2713')} ${file}`, ); }, ), ), ); } else { console.log('All files look good!'); } }; export const addCopyrightHeaderToBundledOutput = async ({ basePath, configPath, file, }) => { // NOTE: Windows requires prefix `file://` for absolute path const config = ( await import(`${platform() === 'win32' ? 'file://' : ''}${configPath}`) ).default; const copyrightText = config?.build?.copyrightText; if (!copyrightText) { exitWithError( `'build.copyrightText' is not specified in config file: ${configPath}`, ); } const bundledOutputFile = resolve(basePath, file); if (!existsSync(bundledOutputFile)) { exitWithError( `Can't find bundled output file '${bundledOutputFile}'. Make sure to build before running this script`, ); } writeFileSync( bundledOutputFile, `${copyrightText}\n\n${getFileContent(bundledOutputFile)}`, (err) => { exitWithError( `Failed to add copyright header to bundled output file: ${bundledOutputFile}. Error:\n${ err.message || err }`, ); }, ); exitWithSuccess( `Added copyright header to bundled output file: ${bundledOutputFile}`, ); };