mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-18 15:42:54 +00:00
818 lines
23 KiB
TypeScript
818 lines
23 KiB
TypeScript
/* eslint-disable unicorn/prefer-module */
|
|
/*
|
|
* Copyright (C) 2018, 2019 StApps
|
|
* This program is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation, version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
import chalk from 'chalk';
|
|
import {execSync} from 'child_process';
|
|
import {copyFileSync, existsSync, lstatSync, PathLike, readdirSync, readFileSync} from 'fs';
|
|
import path from 'path';
|
|
import {satisfies, valid} from 'semver';
|
|
import {isDeepStrictEqual} from 'util';
|
|
import {parse, stringify} from 'yaml';
|
|
|
|
interface NYCConfiguration {
|
|
[prop: string]: boolean | number | string[];
|
|
}
|
|
|
|
interface PackageJSONPerson {
|
|
/**
|
|
* Email of the author
|
|
*/
|
|
email?: string;
|
|
/**
|
|
* Name of the author
|
|
*/
|
|
name: string;
|
|
/**
|
|
* URL of the author
|
|
*/
|
|
url?: string;
|
|
}
|
|
|
|
interface PackageJSON {
|
|
/**
|
|
* Author of the package
|
|
*/
|
|
author: string | PackageJSONPerson;
|
|
|
|
/**
|
|
* Contributors of the package
|
|
*/
|
|
contributors: Array<string | PackageJSONPerson>;
|
|
|
|
/**
|
|
* Dependencies
|
|
*/
|
|
dependencies?: {
|
|
[dependency: string]: string;
|
|
};
|
|
|
|
/**
|
|
* Development dependencies
|
|
*/
|
|
devDependencies?: {
|
|
[devDependency: string]: string;
|
|
};
|
|
|
|
/**
|
|
* License
|
|
*/
|
|
license: string;
|
|
|
|
/**
|
|
* NYC configuration
|
|
*/
|
|
nyc: NYCConfiguration;
|
|
|
|
/**
|
|
* Openstapps configuration
|
|
*/
|
|
openstappsConfiguration: Configuration;
|
|
|
|
/**
|
|
* Scripts
|
|
*/
|
|
scripts: {
|
|
[name: string]: string;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Configuration for the configuration check
|
|
*/
|
|
export interface Configuration {
|
|
/**
|
|
* Whether or not the project is meant to be packaged
|
|
*/
|
|
forPackaging: boolean;
|
|
/**
|
|
* Whether or not the project has a CLI
|
|
*/
|
|
hasCli: boolean;
|
|
/**
|
|
* A list of CI entries to ignore while checking
|
|
*/
|
|
ignoreCiEntries: string[];
|
|
/**
|
|
* A list of script names to ignore while checking
|
|
*/
|
|
ignoreScripts: string[];
|
|
/**
|
|
* Whether or not the project is meant to be executed server side
|
|
*/
|
|
serverSide: boolean;
|
|
/**
|
|
* Whether or not the standard build procedure is meant to be used
|
|
*/
|
|
standardBuild: boolean;
|
|
/**
|
|
* Whether or not the standard documentation procedure is meant to be used
|
|
*/
|
|
standardDocumentation: boolean;
|
|
}
|
|
|
|
/**
|
|
* Rules for the configuration check
|
|
*/
|
|
export interface Rules {
|
|
/**
|
|
* Expected CI config
|
|
*/
|
|
ciConfig: {[k: string]: string | object};
|
|
/**
|
|
* Expected dependencies
|
|
*/
|
|
dependencies: string[];
|
|
/**
|
|
* Expected dev dependencies
|
|
*/
|
|
devDependencies: string[];
|
|
/**
|
|
* Expected files
|
|
*/
|
|
files: string[];
|
|
/**
|
|
* Expected licenses
|
|
*/
|
|
licenses: string[];
|
|
/**
|
|
* Expected NYC configuration
|
|
*/
|
|
nycConfiguration: NYCConfiguration;
|
|
/**
|
|
* Expected scripts
|
|
*/
|
|
scripts: {[k: string]: string};
|
|
}
|
|
|
|
/**
|
|
* Wrapper for console.info that outputs every argument in cyan
|
|
*
|
|
* @param arguments_ Arguments to output
|
|
*/
|
|
export function consoleInfo(...arguments_: string[]): void {
|
|
for (const argument of arguments_) {
|
|
/* tslint:disable-next-line:no-console */
|
|
console.info(`\n${chalk.cyan(argument)}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrapper for console.warn that outputs every argument in red
|
|
*
|
|
* @param arguments_ Arguments to output
|
|
*/
|
|
export function consoleWarn(...arguments_: string[]): void {
|
|
for (const argument of arguments_) {
|
|
const lines = argument.split('\n');
|
|
/* tslint:disable-next-line:no-console */
|
|
console.warn(`\n${chalk.red.bold(lines[0])}`);
|
|
for (const line of lines.slice(1)) {
|
|
/* tslint:disable-next-line:no-console */
|
|
console.info(line);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrapper for console.log that outputs every argument in green
|
|
*
|
|
* @param arguments_ Arguments to output
|
|
*/
|
|
export function consoleLog(...arguments_: string[]): void {
|
|
for (const argument of arguments_) {
|
|
/* tslint:disable-next-line:no-console */
|
|
console.log(`\n${chalk.green.bold(argument)}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check dependencies are installed
|
|
*
|
|
* @param rules Rules for check
|
|
* @param packageJson package.json to check dependencies in
|
|
*/
|
|
export function checkDependencies(rules: Rules, packageJson: PackageJSON): void {
|
|
if (typeof packageJson.dependencies === 'object') {
|
|
for (const dependency of rules.dependencies) {
|
|
const [name, version] = dependency.split(':');
|
|
const installedVersion = packageJson.dependencies[name];
|
|
|
|
if (
|
|
typeof packageJson.dependencies === 'undefined' ||
|
|
typeof packageJson.dependencies[name] === 'undefined'
|
|
) {
|
|
consoleWarn(`Dependency '${name}' is missing.
|
|
Please install with 'npm install --save-exact ${name}'.`);
|
|
} else if (
|
|
typeof version !== 'undefined' &&
|
|
typeof valid(version) === 'string' &&
|
|
!satisfies(installedVersion, version)
|
|
) {
|
|
consoleWarn(
|
|
`Version '${installedVersion}' of dependency '${name} does not satisfy constraint '${version}'.`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (typeof packageJson.devDependencies === 'object') {
|
|
for (const developmentDependency of rules.devDependencies) {
|
|
const [developmentName, developmentVersion] = developmentDependency.split(':');
|
|
let installedVersion = packageJson.devDependencies[developmentName];
|
|
if (typeof packageJson.dependencies === 'object') {
|
|
const [name] = developmentDependency.split(':');
|
|
if (typeof packageJson.devDependencies[name] === 'string') {
|
|
installedVersion = packageJson.dependencies[name];
|
|
}
|
|
}
|
|
|
|
if (
|
|
(typeof packageJson.dependencies === 'undefined' ||
|
|
typeof packageJson.dependencies[developmentName] === 'undefined') &&
|
|
(typeof packageJson.devDependencies === 'undefined' ||
|
|
typeof packageJson.devDependencies[developmentName] === 'undefined')
|
|
) {
|
|
consoleWarn(`Dev dependency '${developmentName}' is missing.
|
|
Please install with 'npm install --save-exact --save-dev ${developmentName}'.`);
|
|
} else if (
|
|
typeof developmentVersion !== 'undefined' &&
|
|
typeof valid(developmentVersion) === 'string' &&
|
|
!satisfies(installedVersion, developmentVersion)
|
|
) {
|
|
consoleWarn(
|
|
`Version '${installedVersion}' of dev dependency '${developmentName} does not satisfy constraint '${developmentVersion}'.`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check that configuration files are extended
|
|
*
|
|
* @param configPath Path, where configuration files are located
|
|
*/
|
|
export function checkConfigurationFilesAreExtended(configPath: string): void {
|
|
// check if configuration files are extended
|
|
for (const file of ['tsconfig.json', 'eslintrc.json']) {
|
|
const fileToCheck = path.resolve(configPath, file);
|
|
const expectedPath = `./node_modules/@openstapps/configuration/${file}`;
|
|
|
|
if (existsSync(fileToCheck)) {
|
|
const configFile = JSON.parse(readFileSync(fileToCheck).toString());
|
|
|
|
const configFileExtended =
|
|
(typeof configFile.extends === 'string' && configFile.extends === expectedPath) ||
|
|
(file === 'eslintrc.json' &&
|
|
Array.isArray(configFile.extends) &&
|
|
configFile.extends.includes('@openstapps'));
|
|
|
|
if (!configFileExtended) {
|
|
consoleWarn(`File '${fileToCheck}' should extend '${expectedPath}'!
|
|
|
|
Example:
|
|
${readFileSync(path.resolve(__dirname, '..', 'templates', `template-${file}`))}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check needed files
|
|
*
|
|
* @param rules Rules for check
|
|
* @param filePath Path to files to check
|
|
* @param replaceFlag Whether or not to replace files
|
|
* @returns Whether or not overwrite is suggested
|
|
*/
|
|
export function checkNeededFiles(rules: Rules, filePath: string, replaceFlag: boolean): boolean {
|
|
let suggestOverwrite = false;
|
|
|
|
// copy needed files
|
|
for (let file of rules.files) {
|
|
let destinationFile = file;
|
|
|
|
// remove templates directory for destination files
|
|
if (destinationFile.indexOf('templates') === 0) {
|
|
destinationFile = destinationFile.split(path.sep).slice(1).join(path.sep);
|
|
file = path.join('templates', `template-${destinationFile}`);
|
|
}
|
|
|
|
const source = path.resolve(__dirname, '..', file);
|
|
const destination = path.resolve(filePath, destinationFile);
|
|
|
|
// check if file exists or replace flag is set
|
|
if (!existsSync(destination) || replaceFlag) {
|
|
copyFileSync(source, destination);
|
|
consoleInfo(`Copied file '${source}' to '${destination}'.`);
|
|
} else if (destinationFile === '.npmignore') {
|
|
const npmIgnore = readFileSync(destination).toString();
|
|
|
|
const ignoredPatterns = npmIgnore.split('\n');
|
|
|
|
let ignoresEverything = false;
|
|
// eslint-disable-next-line unicorn/prevent-abbreviations
|
|
let includeDocs = false;
|
|
|
|
for (const ignoredPattern of ignoredPatterns) {
|
|
if (ignoredPattern === '/*') {
|
|
ignoresEverything = true;
|
|
}
|
|
|
|
if (ignoredPattern === '!docs') {
|
|
includeDocs = true;
|
|
}
|
|
}
|
|
|
|
if (!ignoresEverything) {
|
|
consoleWarn(`'.npmignore' should have '/*' as first pattern to ignore everything.`);
|
|
|
|
suggestOverwrite = true;
|
|
}
|
|
|
|
if (includeDocs) {
|
|
consoleWarn(`'.npmignore' contains '!docs' and thus the package will contain the documentation.
|
|
Consider creating a CI job to publish those files, rather than committing this folder to the npm repo.
|
|
Please double check that this is desired behavior since the docs can become huge:
|
|
https://gitlab.com/openstapps/configuration/issues/11`);
|
|
|
|
suggestOverwrite = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return suggestOverwrite;
|
|
}
|
|
|
|
/**
|
|
* Check licenses
|
|
*
|
|
* @param rules Rules for check
|
|
* @param packageJson package.json to check license in
|
|
*/
|
|
export function checkLicenses(rules: Rules, packageJson: PackageJSON): void {
|
|
// check if license is one of the expected ones
|
|
if (!rules.licenses.includes(packageJson.license)) {
|
|
consoleWarn(`License should be one of '${rules.licenses.join(', ')}'!`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check NYC configuration
|
|
*
|
|
* @param rules Rules for check
|
|
* @param packageJson package.json to check NYC configuration in
|
|
* @param replaceFlag Whether or not to replace NYC configuration
|
|
* @returns Whether or not package.json was changed and if overwrite is suggested
|
|
*/
|
|
export function checkNYCConfiguration(
|
|
rules: Rules,
|
|
packageJson: PackageJSON,
|
|
replaceFlag: boolean,
|
|
): [boolean, boolean] {
|
|
let packageJsonChanged = false;
|
|
let suggestOverwrite = false;
|
|
|
|
// check if nyc is a dependency
|
|
if (
|
|
typeof packageJson.devDependencies === 'object' &&
|
|
Object.keys(packageJson.devDependencies).includes('nyc')
|
|
) {
|
|
if (typeof packageJson.nyc === 'undefined' || replaceFlag) {
|
|
// add NYC configuration
|
|
packageJson.nyc = rules.nycConfiguration;
|
|
|
|
packageJsonChanged = true;
|
|
|
|
consoleLog(`Added NYC configuration in to 'package.json'.`);
|
|
} else if (!isDeepStrictEqual(packageJson.nyc, rules.nycConfiguration)) {
|
|
consoleInfo(
|
|
`NYC configuration in 'package.json' differs from the proposed one. Please check manually.`,
|
|
);
|
|
|
|
suggestOverwrite = true;
|
|
}
|
|
}
|
|
|
|
return [packageJsonChanged, suggestOverwrite];
|
|
}
|
|
|
|
/**
|
|
* Check scripts
|
|
*
|
|
* @param rules Rules for check
|
|
* @param packageJson package.json to check scripts in
|
|
* @param replaceFlag Whether or not to replace scripts
|
|
* @returns Whether or not the package.json was changed
|
|
*/
|
|
export function checkScripts(rules: Rules, packageJson: PackageJSON, replaceFlag: boolean): boolean {
|
|
let packageJsonChanged = false;
|
|
|
|
// check if scripts is a map
|
|
if (typeof packageJson.scripts !== 'object') {
|
|
packageJson.scripts = {};
|
|
|
|
packageJsonChanged = true;
|
|
}
|
|
|
|
for (const scriptName in rules.scripts) {
|
|
if (!rules.scripts.hasOwnProperty(scriptName)) {
|
|
continue;
|
|
}
|
|
|
|
const scriptToCheck = packageJson.scripts[scriptName];
|
|
|
|
// check if script exists
|
|
if (typeof scriptToCheck === 'undefined' || replaceFlag) {
|
|
packageJson.scripts[scriptName] = rules.scripts[scriptName];
|
|
|
|
packageJsonChanged = true;
|
|
|
|
consoleInfo(`Added '${scriptName}' script to 'package.json'.`);
|
|
} else if (typeof scriptToCheck === 'string' && scriptToCheck !== rules.scripts[scriptName]) {
|
|
consoleWarn(`Script '${scriptName}' in 'package.json' should be:
|
|
\"${rules.scripts[scriptName].replace('\n', '\\n')}\" .`);
|
|
}
|
|
}
|
|
|
|
return packageJsonChanged;
|
|
}
|
|
|
|
/**
|
|
* Check contributors
|
|
*
|
|
* @param path Path to directory
|
|
* @param packageJson package.json to check contributors in
|
|
*/
|
|
export function checkContributors(path: PathLike, packageJson: PackageJSON): void {
|
|
const execBuffer = execSync(
|
|
`git --git-dir=${path}/.git --work-tree=${path} log --format=\'%aN\' | sort -u`,
|
|
);
|
|
for (let person of execBuffer.toString().split('\n')) {
|
|
person = person.trim();
|
|
|
|
if (person === '') {
|
|
continue;
|
|
}
|
|
|
|
let authorIsAttributed = false;
|
|
|
|
authorIsAttributed =
|
|
authorIsAttributed ||
|
|
(typeof packageJson.author === 'string' && packageJson.author.includes(person)) ||
|
|
(Array.isArray(packageJson.contributors) &&
|
|
packageJson.contributors.findIndex(contributor => {
|
|
return typeof contributor === 'string' && contributor.includes(person);
|
|
}) >= 0);
|
|
|
|
if (!authorIsAttributed) {
|
|
consoleWarn(`'${person}' should be attributed as author or contributor.`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check CI config
|
|
*
|
|
* @param rules Rules for check
|
|
* @param configPath Path to CI config
|
|
*/
|
|
export function checkCIConfig(rules: Rules, configPath: string): void {
|
|
const pathToCiConfig = path.resolve(configPath, '.gitlab-ci.yml');
|
|
|
|
// check CI config if it exists
|
|
if (existsSync(pathToCiConfig)) {
|
|
// read CI config
|
|
const content = readFileSync(pathToCiConfig).toString();
|
|
|
|
let ciConfigWithoutTemplates = '';
|
|
|
|
for (const line of content.split('\n')) {
|
|
const match = line.trim().match(/^<</);
|
|
|
|
if (match === null) {
|
|
ciConfigWithoutTemplates += `${line}\n`;
|
|
}
|
|
}
|
|
|
|
try {
|
|
const ciConfig = parse(ciConfigWithoutTemplates);
|
|
|
|
// check entries
|
|
for (const entry in rules.ciConfig) {
|
|
if (!rules.ciConfig.hasOwnProperty(entry)) {
|
|
continue;
|
|
}
|
|
|
|
if (!isDeepStrictEqual(rules.ciConfig[entry], ciConfig[entry])) {
|
|
const completeEntry: {[k: string]: string | object} = {};
|
|
completeEntry[entry] = rules.ciConfig[entry];
|
|
consoleWarn(`Entry '${entry}' in '${pathToCiConfig}' is incorrect. Expected value is:
|
|
${stringify(completeEntry)}`);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
consoleWarn(`Could not parse ${pathToCiConfig} because of '${(error as Error).message}'.
|
|
Please ensure consistency of CI config manually.
|
|
${stringify(rules.ciConfig)}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check copyright years in files
|
|
*
|
|
* @param projectPath Path to project root
|
|
* @param checkPathFragment Subordinated directory to examine
|
|
*/
|
|
export function checkCopyrightYears(projectPath: PathLike, checkPathFragment: PathLike): void {
|
|
const fileSystemObjects = readdirSync(path.resolve(projectPath.toString(), checkPathFragment.toString()));
|
|
|
|
for (const fileSystemObject of fileSystemObjects) {
|
|
const fileSystemObjectPath = path.resolve(
|
|
projectPath.toString(),
|
|
checkPathFragment.toString(),
|
|
fileSystemObject,
|
|
);
|
|
|
|
// tslint:disable-next-line:max-line-length
|
|
const execBuffer = execSync(
|
|
`git --git-dir=${projectPath}/.git --work-tree=${projectPath} log --oneline --format='%cI' -- ${fileSystemObjectPath}`,
|
|
);
|
|
|
|
const seen: number[] = [];
|
|
let changedYears = execBuffer
|
|
.toString()
|
|
.split('\n')
|
|
.map(date => Number.parseInt(date.split('-')[0], 10))
|
|
.filter(year => {
|
|
const stringYear = year.toString();
|
|
if (seen.includes(year) || stringYear.match(/[0-9]{4}/) === null) {
|
|
return false;
|
|
}
|
|
|
|
seen.push(year);
|
|
|
|
return true;
|
|
})
|
|
.sort();
|
|
changedYears = [changedYears[0], changedYears[changedYears.length - 1]];
|
|
changedYears = [...new Set(changedYears)].sort();
|
|
|
|
const fileStats = lstatSync(fileSystemObjectPath);
|
|
|
|
if (fileStats.isFile()) {
|
|
if (fileSystemObject.match(/\.ts$/) === null) {
|
|
continue;
|
|
}
|
|
|
|
const content = readFileSync(fileSystemObjectPath).toString().split('\n');
|
|
|
|
let copyrightYearsString = '';
|
|
for (const line of content) {
|
|
const match = line.match(/^ \* Copyright \(C\) ([0-9]{4}-[0-9]{4})|([0-9]{4}) StApps$/m);
|
|
const expectedMatchLength = 3;
|
|
|
|
if (Array.isArray(match) && match.length === expectedMatchLength) {
|
|
// tslint:disable-next-line:no-magic-numbers
|
|
copyrightYearsString = match[1] ?? match[2];
|
|
}
|
|
}
|
|
|
|
if (copyrightYearsString === '') {
|
|
consoleWarn(`Copyright line for file '${fileSystemObjectPath}' could not be found!`);
|
|
} else {
|
|
const copyrightYearsWithIntervals = copyrightYearsString.split('-').map(year => year.trim());
|
|
const copyrightYears: number[] = copyrightYearsWithIntervals
|
|
.map(year => Number.parseInt(year, 10))
|
|
.sort();
|
|
|
|
let copyrightYearNeedsUpdate = false;
|
|
if (typeof copyrightYears[0] !== 'undefined' && typeof changedYears[0] !== 'undefined') {
|
|
copyrightYearNeedsUpdate = copyrightYears[0] !== changedYears[0];
|
|
}
|
|
if (
|
|
typeof copyrightYears[1] !== 'undefined' &&
|
|
typeof changedYears[1] !== 'undefined' &&
|
|
!copyrightYearNeedsUpdate
|
|
) {
|
|
copyrightYearNeedsUpdate = copyrightYears[1] !== changedYears[1];
|
|
}
|
|
if (copyrightYears.length !== changedYears.length) {
|
|
copyrightYearNeedsUpdate = true;
|
|
}
|
|
|
|
if (copyrightYearNeedsUpdate) {
|
|
// tslint:disable-next-line:max-line-length
|
|
consoleWarn(
|
|
`File '${path.join(
|
|
checkPathFragment.toString(),
|
|
fileSystemObject,
|
|
)}' has to specify '${changedYears.join('-')}' as year(s) in the copyright line.`,
|
|
);
|
|
}
|
|
}
|
|
} else if (fileStats.isDirectory()) {
|
|
checkCopyrightYears(projectPath, path.join(checkPathFragment.toString(), fileSystemObject));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get configuration
|
|
*
|
|
* @param packageJson package.json to get configuration from
|
|
*/
|
|
export function getConfiguration(packageJson: PackageJSON): Configuration {
|
|
const defaultConfiguration: Configuration = {
|
|
forPackaging: true,
|
|
hasCli: true,
|
|
ignoreCiEntries: [],
|
|
ignoreScripts: [],
|
|
serverSide: true,
|
|
standardBuild: true,
|
|
standardDocumentation: true,
|
|
};
|
|
|
|
if (typeof packageJson.openstappsConfiguration !== 'undefined') {
|
|
return {
|
|
...defaultConfiguration,
|
|
...packageJson.openstappsConfiguration,
|
|
};
|
|
}
|
|
|
|
return defaultConfiguration;
|
|
}
|
|
|
|
/**
|
|
* Get rules for check
|
|
*
|
|
* @param configuration Configuration for check
|
|
*/
|
|
export function getRules(configuration: Configuration): Rules {
|
|
// expected dependencies
|
|
const dependencies: string[] = [];
|
|
|
|
// expected dev dependencies
|
|
const devDependencies = [
|
|
'@typescript-eslint/eslint-plugin',
|
|
'@typescript-eslint/parser',
|
|
'@openstapps/eslint-config',
|
|
'conventional-changelog-cli',
|
|
'eslint',
|
|
'eslint-config-prettier',
|
|
'eslint-plugin-jsdoc',
|
|
'eslint-plugin-prettier',
|
|
'eslint-plugin-unicorn',
|
|
'prettier',
|
|
'typescript:4.4.4',
|
|
];
|
|
|
|
// files that need to be copied
|
|
const files = [
|
|
'.editorconfig',
|
|
'.eslintrc.json',
|
|
'.eslintignore',
|
|
path.join('templates', '.gitignore'),
|
|
path.join('templates', 'tsconfig.json'),
|
|
];
|
|
|
|
// configuration for nyc to add to package.json
|
|
const nycConfiguration: NYCConfiguration = {
|
|
'all': true,
|
|
'branches': 95,
|
|
'check-coverage': true,
|
|
'exclude': ['src/cli.ts'],
|
|
'extension': ['.ts'],
|
|
'functions': 95,
|
|
'include': ['src'],
|
|
'lines': 95,
|
|
'per-file': true,
|
|
'reporter': ['html', 'text-summary'],
|
|
'require': ['ts-node/register'],
|
|
'statements': 95,
|
|
};
|
|
|
|
// expected scripts
|
|
const scripts: {[k: string]: string} = {
|
|
/* tslint:disable-next-line:max-line-length */
|
|
'changelog':
|
|
"conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md && git commit -m 'docs: update changelog'",
|
|
'check-configuration': 'openstapps-configuration',
|
|
'postversion': 'npm run changelog',
|
|
'preversion': 'npm run prepublishOnly',
|
|
'push': 'git push && git push origin "v$npm_package_version"',
|
|
'lint': 'eslint --ext .ts src/',
|
|
};
|
|
|
|
// list of expected licenses
|
|
const licenses = ['AGPL-3.0-only', 'GPL-3.0-only'];
|
|
|
|
// expected values in CI config
|
|
const ciConfig = {
|
|
/* tslint:disable:object-literal-sort-keys */
|
|
'image': 'registry.gitlab.com/openstapps/projectmanagement/node',
|
|
'before_script': 'npm ci',
|
|
'build': {
|
|
stage: 'build',
|
|
script: ['npm run build'],
|
|
artifacts: {
|
|
paths: ['lib'],
|
|
},
|
|
},
|
|
'audit': {
|
|
allow_failure: true,
|
|
except: ['schedules'],
|
|
script: ['npm audit'],
|
|
stage: 'audit',
|
|
},
|
|
'scheduled-audit': {
|
|
only: ['schedules'],
|
|
script: ['npm audit --audit-level=high'],
|
|
stage: 'audit',
|
|
},
|
|
'package': {
|
|
dependencies: ['build'],
|
|
tags: ['secrecy'],
|
|
stage: 'publish',
|
|
script: ['echo "//registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN" > ~/.npmrc', 'npm publish'],
|
|
only: ['/^v[0-9]+.[0-9]+.[0-9]+$/'],
|
|
artifacts: {
|
|
paths: ['lib'],
|
|
},
|
|
},
|
|
'pages': {
|
|
artifacts: {
|
|
paths: ['public'],
|
|
},
|
|
only: ['/^v[0-9]+\\.[0-9]+\\.[0-9]+$/'],
|
|
script: ['npm run documentation', 'mv docs public'],
|
|
stage: 'deploy',
|
|
},
|
|
/* tslint:enable */
|
|
};
|
|
|
|
for (const ignoreCiEntry of configuration.ignoreCiEntries) {
|
|
// @ts-expect-error can't be used to index
|
|
delete ciConfig[ignoreCiEntry];
|
|
}
|
|
|
|
if (configuration.forPackaging) {
|
|
scripts.prepublishOnly = 'npm ci && npm run build';
|
|
|
|
files.push(path.join('templates', '.npmignore'));
|
|
} else {
|
|
// @ts-expect-error can't be used to index
|
|
delete ciConfig[`package`];
|
|
}
|
|
|
|
if (configuration.serverSide) {
|
|
dependencies.push('@types/node:^10.0.0');
|
|
}
|
|
|
|
if (configuration.standardBuild || configuration.hasCli) {
|
|
scripts.build = 'npm run lint && npm run compile';
|
|
scripts.compile = 'rimraf lib && tsc';
|
|
devDependencies.push('rimraf');
|
|
|
|
if (configuration.hasCli) {
|
|
devDependencies.push('prepend-file-cli');
|
|
scripts.compile += " && prepend lib/cli.js '#!/usr/bin/env node\n'";
|
|
}
|
|
}
|
|
|
|
if (configuration.standardDocumentation) {
|
|
devDependencies.push('typedoc');
|
|
/* tslint:disable-next-line:max-line-length */
|
|
scripts.documentation =
|
|
'typedoc --includeDeclarations --mode modules --out docs --readme README.md --listInvalidSymbolLinks --entryPointStrategy expand src';
|
|
}
|
|
|
|
for (const ignoreScript of configuration.ignoreScripts) {
|
|
consoleInfo(`Ignoring script '${ignoreScript}'.`);
|
|
delete scripts[ignoreScript];
|
|
}
|
|
|
|
return {
|
|
ciConfig,
|
|
dependencies,
|
|
devDependencies,
|
|
files,
|
|
licenses,
|
|
nycConfiguration,
|
|
scripts,
|
|
};
|
|
}
|