UNPKG

@gobstones/gobstones-scripts

Version:

Scripts to abstract away build configuration of Gobstones Project's libraries and modules.

1,088 lines (1,026 loc) 44.9 kB
/* * ***************************************************************************** * Copyright (C) National University of Quilmes 2018-2024 * Gobstones (TM) is a trademark of the National University of Quilmes. * * This program is free software distributed under the terms of the * GNU Affero General Public License version 3. * Additional terms added in compliance to section 7 of such license apply. * * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ /** * @module Config * @author Alan Rodas Bonjour <alanrodas@gmail.com> */ import fs from 'fs'; import path from 'path'; import { testServer, version } from './about'; import { getBin } from '../Helpers/getBin'; import { getGobstonesScriptsRootPath } from '../Helpers/getGobstonesScriptsRootPath'; import { getInUsePackageManager } from '../Helpers/getInUsePackageManager'; import { getProjectRootPath } from '../Helpers/getProjectRootPath'; import { getToolingFile } from '../Helpers/getToolingFile'; import { isMacos } from '../Helpers/isMacos'; import { isWindows } from '../Helpers/isWindows'; import { LogLevel, logger } from '../Helpers/Logger'; import { PackageJsonReader } from '../Helpers/PackageJsonReader'; // ========================================== // #region Identifiers Types // ========================================== /** * Models the possible values of operating systems. */ export type OSType = 'macos' | 'posix' | 'windows'; /** * Models the different type of executable scripts. */ export type ScriptType = 'node' | 'sh' | 'pwsh' | 'cmd'; /** * Models the possible values of package managers. */ export type PackageManager = keyof ConfigPackageManagers; /** * Models the possible values of project types. */ export type ProjectType = keyof ConfigProjectTypes; /** * Models the possible values of project type's file. */ export type FileName = keyof ProjectTypeDefinition; // ========================================== // #endregion Identifiers Types // ========================================== // ========================================== // #region Data Definition Types // ========================================== /** * Models a package manager definition and basic commands and folder it has. */ export interface PackageManagerDefinition { /** The name of this package manager */ name: string; /** The regular command name. */ cmd: string; /** The command used to install dependencies. */ install: string; /** The command used to execute a binary related to the package manager. */ run: string; /** A set of module folders that the package manager uses. */ modulesFolders: string[]; /** A set of binary folders that the package manager uses. */ binFolders: string[]; } /** * Models a project type's file definition and the expected behavior of * the file. */ export interface FileDefinition { /** * The internal name for this file descriptor. Will automatically * be set to the key name when creating the list of files. */ name: FileName; /** The location of one or more files in the gobstones-script's path. */ gobstonesScriptsLocation: string[]; /** The location that the files should have in the local project's folder. */ projectLocation: string[]; /** Whether this file should be copied on project initialization. */ copyOnInit: boolean; /** Whether this file should be copied on project update. */ copyOnUpdate: boolean; /** Whether this file should be copied on project ejection. */ copyOnEject: boolean; /** * Whether this file represents tooling configuration files * that can be overwritten with local configurations. */ isOverridable: boolean; /** * Whether this file contains reference to the generic project * name that should be updates. */ requiresReferenceUpdate: boolean; /** * Whether this file requires the data for testing (verdaccio server * data) to be inserted to it. */ requiresTestDataInjection: boolean; } /** * Models a project type's file definition that has tooling content */ export interface FileDefinitionWithTooling extends FileDefinition { /** * The detected tooling file to use. Only present if * the file is overridable. The full path of the file is saved. * It's automatically calculated. */ toolingFile: string; } /** * Models a project type's file definitions. */ export interface ProjectTypeDefinition { /** The package.json file of the project type. */ packageJson: FileDefinition; /** The LICENSE file of the project type. */ license: FileDefinition; /** The README.md file of the project type. */ readme: FileDefinition; /** The CHANGELOG.md file of the project type. */ changelog: FileDefinition; /** The CONTRIBUTING.md file of the project type. */ contributing: FileDefinition; /** The .gitignore folder of the project type. */ git: FileDefinition; /** The .npmignore and .npmrc files of the project type. */ npm: FileDefinition; /** The src folder of the project type. */ src: FileDefinition; /** The test folder of the project type. */ test: FileDefinition; /** The .husky folder of the project type. */ husky: FileDefinition; /** The .vscode folder of the project type. */ vscode: FileDefinition; /** The .github folder of the project type. */ github: FileDefinition; /** The .editorconfig file of the project type. */ editorconfig: FileDefinition; /** The .prettierrc and .prettierignore files of the project type. */ prettier: FileDefinition; /** The .commitlint and .czrc files of the project type. */ commitlint: FileDefinition; /** The .eslint file of the project type. */ eslint: FileDefinition; /** The package-scripts.js file of the project type. */ nps: FileDefinitionWithTooling; /** The .tsconfig.js file of the project type. */ typescript: FileDefinitionWithTooling; /** The .rollup.config.js file of the project type. */ rollup: FileDefinitionWithTooling; /** The .typedoc.config.js file of the project type. */ typedoc: FileDefinitionWithTooling; /** The .jest.config.js file of the project type. */ jest: FileDefinitionWithTooling; /** The .jestproxies folder of the project type. */ jestproxies?: FileDefinition; /** The demos folder of the project type. */ demos?: FileDefinition; /** The .vite.config.js file of the project type. */ vite?: FileDefinition; /** The stories folder of the project type. */ stories?: FileDefinition; /** The .storybook folder of the project type. */ storybook?: FileDefinition; /** The .tconfig.json file of the project type. */ tsConfigJSON: FileDefinitionWithTooling; /** The LICENSE_HEADER file of the project type. */ licenseHeader: FileDefinitionWithTooling; /** The license.config.js file of the project type. */ licenseHeaderConfig: FileDefinitionWithTooling; } /** * Models a project type's file definition names, after * being filtered by category. */ export interface FilteredFilesDefinition { /** The list of file names to be copied on init. */ copiedOnInit: FileName[]; /** The list of file names to be copied on update. */ copiedOnUpdate: FileName[]; /** The list of file names to be copied on eject. */ copiedOnEject: FileName[]; /** The list of file names that are part of the tooling * and require to identify the configuration file location. */ toolingFiles: FileName[]; } // ========================================== // #endregion Data Definition Types // ========================================== // ========================================== // #region Configuration Part Types // ========================================== /** * Models the configuration for all the available * package managers. It's one of the main {@link Config} * sections. */ export interface ConfigPackageManagers { /** The configuration for **npm**. */ npm: PackageManagerDefinition; /** The configuration for **yarn**. */ yarn: PackageManagerDefinition; /** The configuration for **pnpm**. */ pnpm: PackageManagerDefinition; } /** * Models the configuration for the system's * environment, as detected by node. Is one of the * main {@link Config} sections. */ export interface ConfigEnvironment { /** The running tool version. */ toolVersion: string; /** The running tool test server. */ toolTestServer: string; /** The current working directory, as detected through environment. */ workingDirectory: string; /** The current operating system, as detected through environment. */ operatingSystem: OSType; /** The current package manager, as detected through environment. */ detectedPackageManager: PackageManager; } /** * Models the configuration for all the different * locations this tool manages. It's one of the main * {@link Config} sections. */ export interface ConfigLocations { /** The root of the currently running project. */ projectRoot: string; /** The root of the gobstones-scripts Library. */ gobstonesScriptsRoot: string; /** The root of the gobstones-scripts Library project files. */ gobstonesScriptsProjectsRoot: string; } /** * Models the configuration for the current execution * environment. That is, the loaded state of execution * for this particular run. Is one of the main * {@link Config} sections. */ export interface ConfigExecutionEnvironment { /** The currently in use project type. */ projectType: ProjectType; /** The currently in use package manager. */ packageManager: keyof ConfigPackageManagers; /** Whether the tool should use full paths when displaying any. */ useFullPaths: boolean; /** Whether the tool is running in debug mode. */ debug: boolean; /** Whether the tool is running in test mode. */ test: boolean; /** Whether the tool should expect a local tsconfig.json file instead of building from a .js one. */ useLocalTsconfigJson: boolean; } /** * Models the configuration for the different types * of project templates that exist. It's one of the main * {@link Config} sections. */ export interface ConfigProjectTypes { /** The **Library** project type. */ Library: ProjectTypeDefinition; /** The **CLILibrary** project type. */ CLILibrary: ProjectTypeDefinition; /** The **ReactLibrary** project type. */ ReactLibrary: ProjectTypeDefinition; /** The **NonCode** project type. */ NonCode: ProjectTypeDefinition; } /** * Models the configuration of filtered file definitions * for the different types of project templates that exist. * It's one of the main {@link Config} sections. */ export interface ConfigFilteredProjectTypes { /** The **Library** filtered project type files. */ Library: FilteredFilesDefinition; /** The **cli-Library** filtered project type files. */ CLILibrary: FilteredFilesDefinition; /** The **react-Library** filtered project type files. */ ReactLibrary: FilteredFilesDefinition; /** The **NonCode** project type. */ NonCode: FilteredFilesDefinition; } // ========================================== // #endregion Configuration Part Types // ========================================== // ========================================== // #region Script File Types // ========================================== /** * Models an executable file path and characteristics. */ export interface ExecutableScriptDefinition { /** The node package name this executable belongs to. */ packageName: string; /** The binary name of this executable. */ binName: string; /** The script file that should be executed. */ scriptFile: string; /** The command to execute in the terminal */ command: string; /** * The mode on which such binary file should run. * It may be a full JS file to be executed by node, * a Shell script supported by any POSIX file, * of a Windows "PowerShell" script or "cmd" script. */ mode: ScriptType; } // ========================================== // #endregion Script File Types // ========================================== // ========================================== // #region Config // ========================================== /** * This class represents the main configuration object generated by the application. * The configuration is automatically loaded once the {@link init} method is called. * This object is also the main entry point to obtain configuration options of the tool * as to obtain the located directories, tooling files and obtain location for the * executable scripts for different tools. */ export class Config { // ------------------------------------------ // #region Private Properties // ------------------------------------------ /** Whether the configuration has been initialized. */ private _lastInitializationValues?: { apiGivenProjectType?: string; apiGivenPackageManager?: string; debug: boolean; test: boolean; useLocalTsconfigJson: boolean; }; /** The subpart of the configuration corresponding to package managers. */ private _packageManagers: ConfigPackageManagers; /** The subpart of the configuration corresponding to the environment. */ private _environment: ConfigEnvironment; /** The subpart of the configuration corresponding to the different path locations. */ private _locations: ConfigLocations; /** The subpart of the configuration corresponding to the current execution environment. */ private _executionEnvironment: ConfigExecutionEnvironment; /** The subpart of the configuration corresponding to the different project types. */ private _projectTypes: ConfigProjectTypes; /** The subpart of the configuration corresponding to the different project type filtered files. */ private _filteredProjectTypes: ConfigFilteredProjectTypes; /** A cache for the executable scripts already detected. */ private _binaryFilesCache: Record<string, ExecutableScriptDefinition | undefined>; // ------------------------------------------ // #endregion Private Properties // ------------------------------------------ /** * Create a new instance of the configuration. */ public constructor() { /* * We need to set the logger on if debug was sent from the CLI, * as this i the first thing that happens, even after running any code. * This is clearly a violation of the concern of 'config', but * there doesn't seem to be a better way without recurring to * dynamic imports, which implies changing the build system. */ if (process.argv.includes('-D') || process.argv.includes('--debug')) { logger.level = LogLevel.Debug; logger.on(); } logger.debug('[config] Creating configuration object'); // has not yet been initialized this._lastInitializationValues = undefined; this._binaryFilesCache = {}; } // ------------------------------------------ // #region Accessing // ------------------------------------------ /** Returns the subpart of the configuration corresponding to package managers. */ public get packageManagers(): ConfigPackageManagers { return this._packageManagers; } /** Returns subpart of the configuration corresponding to the environment. */ public get environment(): ConfigEnvironment { return this._environment; } /** Returns the subpart of the configuration corresponding to the different path locations. */ public get locations(): ConfigLocations { return this._locations; } /** Returns the subpart of the configuration corresponding to the current execution environment. */ public get executionEnvironment(): ConfigExecutionEnvironment { return this._executionEnvironment; } /** Returns the subpart of the configuration corresponding to the different project types. */ public get projectTypes(): ConfigProjectTypes { return this._projectTypes; } /** The subpart of the configuration corresponding to the different project type filtered files. */ public get filteredProjectTypes(): ConfigFilteredProjectTypes { return this._filteredProjectTypes; } public get packageManager(): PackageManagerDefinition { return this._packageManagers[this._executionEnvironment.packageManager]; } public get projectType(): ProjectTypeDefinition { return this._projectTypes[this._executionEnvironment.projectType]; } public get projectTypeFilteredFiles(): FilteredFilesDefinition { return this._filteredProjectTypes[this._executionEnvironment.projectType]; } // ------------------------------------------ // #endregion Accessing // ------------------------------------------ // ------------------------------------------ // #region Initialization // ------------------------------------------ /** * Orchestrate the initialization of the Config object. * This initialization is needed in order to access any of the * sub-configuration sections, except for retrieving * executable scripts. */ public init( apiGivenProjectType?: string, apiGivenPackageManager?: string, debug?: boolean, test?: boolean, useLocalTsconfigJson?: boolean ): this { if (!this._lastInitializationValues) { logger.debug(`[config] Initializing configuration from scratch`); this._initAvailablePackageManagers(); this._detectEnvironment(); this._initializeLocations(); this._loadProjectTypeDefinitions(); this._loadProcessedProjectTypeDefinitions(); } if ( !this._lastInitializationValues || (apiGivenProjectType && this._lastInitializationValues.apiGivenProjectType !== apiGivenProjectType) || (apiGivenPackageManager && this._lastInitializationValues.apiGivenPackageManager !== apiGivenPackageManager) || (debug && this._lastInitializationValues.debug !== debug) || (test && this._lastInitializationValues.test !== test) ) { logger.debug(`[config] Already initialized, updating CLI/API parameters`); this._initializeExecutionEnvironment( apiGivenProjectType, apiGivenPackageManager, debug, test, useLocalTsconfigJson ); this._lastInitializationValues = this._executionEnvironment; } return this; } // ------------------------------------------ // #endregion Initialization // ------------------------------------------ // ------------------------------------------ // #region Public API // ------------------------------------------ /** * Change the current directory of the process to another one. * Additionally, update the global configuration to match. * * @param dir - The directory to change to */ public changeDir(dir: string): string { process.chdir(dir); this._environment.workingDirectory = dir; this._locations.projectRoot = dir; return dir; } /** * Return the information for executing a binary file, if it can be found * by the configuration system. Additionally, and differently from the * simple {@link getBin} helper, this method provides caching, as to not * attempt to find the element twice. * * @param packageName - The package name that contains the binary file. * @param binName - The binary file to execute. * * @returns The executable to run, or undefined if not found. */ public getBinary(packageName: string, binName: string): ExecutableScriptDefinition | undefined { if (!this._binaryFilesCache[`${packageName}-${binName}`]) { this._binaryFilesCache[`${packageName}-${binName}`] = getBin( this._locations.projectRoot, this.packageManager, packageName, binName ); } return this._binaryFilesCache[`${packageName}-${binName}`]; } // ------------------------------------------ // #endregion Public API // ------------------------------------------ /* ******************************************************** */ // ------------------------------------------ // #region Private Initialization // ------------------------------------------ /** Initialize the different available package managers. */ private _initAvailablePackageManagers(): void { this._packageManagers = { npm: { name: 'npm', cmd: 'npm', install: 'npm install', run: 'npx', modulesFolders: ['node_modules'], binFolders: ['node_modules/bin'] }, yarn: { name: 'yarn', cmd: 'yarn', install: 'yarn install', run: 'npx', modulesFolders: ['node_modules'], binFolders: ['node_modules/bin'] }, pnpm: { name: 'pnpm', cmd: 'pnpm', install: 'pnpm install', run: 'pnpm exec', modulesFolders: ['node_modules', 'node_modules/@gobstones/gobstones-scripts/node_modules'], binFolders: ['node_modules/bin', 'node_modules/@gobstones/gobstones-scripts/node_modules/bin'] } }; } /** Initialize the current environment from detected information. */ private _detectEnvironment(): void { this._environment = { toolVersion: version, toolTestServer: testServer, operatingSystem: isWindows() ? 'windows' : isMacos() ? 'macos' : 'posix', workingDirectory: process.env.PWD ?? process.cwd() ?? path.resolve('.'), detectedPackageManager: getInUsePackageManager(this._packageManagers, 'npm') }; } /** * Initialize the different locations by attempting to detect the current * folder containing a project and the folder containing the gobstones-scripts Library. */ private _initializeLocations(): void { const projectRoot = getProjectRootPath(this._environment.operatingSystem); const gobstonesScriptsRoot = getGobstonesScriptsRootPath(this._environment.operatingSystem, projectRoot); this._locations = { projectRoot, gobstonesScriptsRoot, gobstonesScriptsProjectsRoot: path.join(gobstonesScriptsRoot, 'project-types') }; } /** * Initialize the current execution environment. This is obtained by a * mix between possible CLI/API given parameters (such as using -t or -m), * given as input, and the information read in the current's project package.json, * as well as defaults in case no configuration is provided. * * Priority is given to CLI/API given, then the package.json configuration * and lastly defaults. * * @param apiGivenProjectType - The CLI/API given value for project type to use, if any. * @param apiGivenPackageManager - The CLI/API given value for package manager to use, if any. * @param debug - The CLI/API given value to know if we are running in debug mode. * @param test - The CLI/API given value to know if we are running in test mode. * @param useLocalTsconfigJson - The CLI/API given value to know if we should use the default tsconfig.json file. */ private _initializeExecutionEnvironment( apiGivenProjectType?: string, apiGivenPackageManager?: string, debug?: boolean, test?: boolean, useLocalTsconfigJson?: boolean ): void { if (apiGivenProjectType && !Object.keys(this._projectTypes).includes(apiGivenProjectType)) { throw new Error('Invalid project type'); } if (apiGivenPackageManager && !Object.keys(this._packageManagers).includes(apiGivenPackageManager)) { throw new Error('Invalid package manager'); } const pkgReader = new PackageJsonReader(path.join(this._locations.projectRoot, 'package.json')); this._executionEnvironment = { projectType: (apiGivenProjectType ?? pkgReader.getValueAt('config.gobstones-scripts.type') ?? 'Library') as keyof ConfigProjectTypes, packageManager: (apiGivenPackageManager ?? pkgReader.getValueAt('config.gobstones-scripts.manager') ?? 'npm') as keyof ConfigPackageManagers, useFullPaths: (pkgReader.getValueAt('config.gobstones-scripts.use-full-paths') ?? false) as boolean, useLocalTsconfigJson: (useLocalTsconfigJson ?? pkgReader.getValueAt('config.gobstones-scripts.use-local-tsconfig-json') ?? fs.existsSync(path.join(this._locations.projectRoot, 'package.json'))) as boolean, debug: (debug ?? pkgReader.getValueAt('config.gobstones-scripts.debug') ?? false) as boolean, test: (test ?? pkgReader.getValueAt('config.gobstones-scripts.test') ?? false) as boolean }; } /** * Initialize the different project type definitions with all their * file information. */ private _loadProjectTypeDefinitions(): void { this._projectTypes = { Library: this._joinProjectTypeDefinitions( this._getCommonProjectTypeDefinition( 'Library', ['src', 'test', 'packageJson', 'typescript', 'rollup', 'nps'], ['jestproxies', 'vite', 'stories', 'storybook', 'demos'] ) ), CLILibrary: this._joinProjectTypeDefinitions( this._getCommonProjectTypeDefinition( 'CLILibrary', ['src', 'test', 'packageJson', 'typescript', 'rollup', 'nps'], ['jestproxies', 'vite', 'stories', 'storybook', 'demos'] ) ), ReactLibrary: this._joinProjectTypeDefinitions( this._getCommonProjectTypeDefinition( 'ReactLibrary', [ 'src', 'test', 'packageJson', 'typescript', 'rollup', 'nps', 'typedoc', 'jestproxies', 'vite', 'stories', 'storybook' ], ['demos'] ) ), NonCode: this._joinProjectTypeDefinitions( this._getCommonProjectTypeDefinition( 'NonCode', ['src', 'test', 'packageJson'], [ 'eslint', 'tsConfigJSON', 'typescript', 'rollup', 'typedoc', 'jest', 'jestproxies', 'vite', 'stories', 'storybook', 'demos' ] ), { // On node code, nps should always be copied to the project's root nps: this._fileDefinitionWithTooling('nps', 'NonCode', ['nps'], [], { gobstonesScriptsLocation: ['<projectTypePath>/package-scripts.js'], projectLocation: ['package-scripts.js'], copyOnInit: true, isOverridable: true }) } ) }; } /** * Returns the file information for all files that are common to any * project. Expects the route of the project's subfolder. * * @param projectTypePath - The route of the project's subfolder (e.g. 'CLILibrary' or 'NonCode') * @param noCommonFiles - The filenames to search in the project specific folder, instead of the common. * @param excludedFiles - Files from the common folder to exclude from this project definition. * * @returns A partial ProjectTypeDefinition. */ private _getCommonProjectTypeDefinition( projectTypePath: string, noCommonFiles: FileName[], excludedFiles: FileName[] ): ProjectTypeDefinition { return { // only on init src: this._fileDefinition('src', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/src'], projectLocation: ['src'], copyOnInit: true }), test: this._fileDefinition('test', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/test'], projectLocation: ['test'], copyOnInit: true }), changelog: this._fileDefinition('changelog', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/CHANGELOG.md'], projectLocation: ['CHANGELOG.md'], copyOnInit: true }), packageJson: this._fileDefinition('packageJson', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/package-definition.json'], projectLocation: ['package.json'], copyOnInit: true, requiresReferenceUpdate: true }), readme: this._fileDefinition('readme', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/README.md'], projectLocation: ['README.md'], copyOnInit: true, requiresReferenceUpdate: true }), // on init but also on any update husky: this._fileDefinition('husky', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/husky'], projectLocation: ['.husky'], copyOnInit: true, copyOnUpdate: true }), github: this._fileDefinition('github', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/github'], projectLocation: ['.github'], copyOnInit: true, copyOnUpdate: true }), vscode: this._fileDefinition('vscode', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/vscode'], projectLocation: ['.vscode'], copyOnInit: true, copyOnUpdate: true }), license: this._fileDefinition('license', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/LICENSE'], projectLocation: ['LICENSE'], copyOnInit: true, copyOnUpdate: true }), contributing: this._fileDefinition('contributing', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/CONTRIBUTING.md'], projectLocation: ['CONTRIBUTING.md'], copyOnInit: true, copyOnUpdate: true }), editorconfig: this._fileDefinition('editorconfig', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/editorconfig'], projectLocation: ['.editorconfig'], copyOnInit: true, copyOnUpdate: true }), prettier: this._fileDefinition('prettier', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/prettierrc'], projectLocation: ['.prettierrc'], copyOnInit: true, copyOnUpdate: true }), npm: this._fileDefinition('npm', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/npmignore', '<projectTypePath>/npmrc'], projectLocation: ['.npmignore', '.npmrc'], copyOnInit: true, copyOnUpdate: true, requiresTestDataInjection: true }), eslint: this._fileDefinition('eslint', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/eslint.config.mjs'], projectLocation: ['eslint.config.mjs'], copyOnInit: true, copyOnUpdate: true }), git: this._fileDefinition('git', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/gitignore'], projectLocation: ['.gitignore'], copyOnInit: true, copyOnUpdate: true }), commitlint: this._fileDefinition('commitlint', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/czrc', '<projectTypePath>/commitlint.config.mjs'], projectLocation: ['.czrc', 'commitlint.config.mjs'], copyOnInit: true, copyOnUpdate: true }), // only on eject nps: this._fileDefinitionWithTooling('nps', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/package-scripts.js'], projectLocation: ['package-scripts.js'], copyOnEject: true, isOverridable: true }), rollup: this._fileDefinitionWithTooling('rollup', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/rollup.config.mjs'], projectLocation: ['rollup.config.mjs'], copyOnEject: true, isOverridable: true }), typescript: this._fileDefinitionWithTooling('typescript', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/tsconfig.json'], projectLocation: ['tsconfig.json'], copyOnInit: true, copyOnEject: true, isOverridable: true }), tsConfigJSON: this._fileDefinitionWithTooling( 'tsConfigJSON', projectTypePath, noCommonFiles, excludedFiles, { // This file descriptor is used to find // The tsconfig.json generated after running // tsconfig.js, that's why it's not a real // file in any project type, but it can be. gobstonesScriptsLocation: ['<projectTypePath>/tsconfig.json'], projectLocation: ['tsconfig.json'], isOverridable: true } ), licenseHeader: this._fileDefinitionWithTooling( 'licenseHeader', projectTypePath, noCommonFiles, excludedFiles, { // This file descriptor is used to find // The LICENSE_HEADER. In principle it should not be // overridden at all. gobstonesScriptsLocation: ['Common/LICENSE_HEADER'], projectLocation: ['LICENSE_HEADER'], isOverridable: true } ), licenseHeaderConfig: this._fileDefinitionWithTooling( 'licenseHeaderConfig', projectTypePath, noCommonFiles, excludedFiles, { // This file descriptor is used to find // The license.config.js. In principle it should not be // overridden at all. gobstonesScriptsLocation: ['Common/license.config.cjs'], projectLocation: ['license.config.cjs'], isOverridable: true } ), typedoc: this._fileDefinitionWithTooling('typedoc', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/typedoc.config.mjs'], projectLocation: ['typedoc.config.mjs'], copyOnEject: true, isOverridable: true }), jest: this._fileDefinitionWithTooling('jest', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/jest.config.mjs'], projectLocation: ['jest.config.mjs'], copyOnEject: true, isOverridable: true }), // Project specific jestproxies: this._fileDefinition('jestproxies', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/jest'], projectLocation: ['.jest'], copyOnInit: true, copyOnUpdate: true }), vite: this._fileDefinition('vite', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/vite.config.mjs'], projectLocation: ['vite.config.mjs'], copyOnInit: true, copyOnUpdate: true }), stories: this._fileDefinition('stories', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/stories'], projectLocation: ['stories'], copyOnInit: true }), storybook: this._fileDefinition('storybook', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/storybook'], projectLocation: ['.storybook'], copyOnInit: true, copyOnUpdate: true }), demos: this._fileDefinition('demos', projectTypePath, noCommonFiles, excludedFiles, { gobstonesScriptsLocation: ['<projectTypePath>/demos'], projectLocation: ['demos'], copyOnInit: true, isOverridable: false }) }; } /** * Initialize the different project type definitions with all their * file information filtered accordingly to their type. */ private _loadProcessedProjectTypeDefinitions(): void { const retainKeysMatching = (o: ProjectTypeDefinition, onKey: string): FileName[] => (Object.keys(o) as (keyof ProjectTypeDefinition)[]).filter((e) => o[e]?.[onKey]); const getProcessed = (projectType: ProjectType): FilteredFilesDefinition => ({ copiedOnInit: retainKeysMatching(this._projectTypes[projectType], 'copyOnInit'), copiedOnEject: retainKeysMatching(this._projectTypes[projectType], 'copyOnEject'), copiedOnUpdate: retainKeysMatching(this._projectTypes[projectType], 'copyOnUpdate'), toolingFiles: retainKeysMatching(this._projectTypes[projectType], 'isOverridable') }); const filteredProjectTypes: Partial<ConfigFilteredProjectTypes> = {}; for (const projectType of Object.keys(this._projectTypes) as ProjectType[]) { filteredProjectTypes[projectType] = getProcessed(projectType); } this._filteredProjectTypes = filteredProjectTypes as ConfigFilteredProjectTypes; } // ------------------------------------------ // #endregion Private Initialization // ------------------------------------------ // ------------------------------------------ // #region Private Initialization Helpers // ------------------------------------------ /** * Joins multiple partial project type definitions into a single cohesive one. * Does not verify that the result contains all keys. */ private _joinProjectTypeDefinitions(...partialInfos: Partial<ProjectTypeDefinition>[]): ProjectTypeDefinition { return Object.assign({}, ...partialInfos) as ProjectTypeDefinition; } /** * Return a file definition with defaults, that will be overwritten by the * partial file definition given. * * @param name - The name of the file definition. * @param partialFileInfo - The partial information for this file definition. * @returns A full file definition. */ private _fileDefinition( name: FileName, projectTypePath: string, noCommonFiles: FileName[], excludedFiles: FileName[], partialFileInfo: Partial<FileDefinition> ): FileDefinition { // An empty FileDefinition will be ignored when processed const baseElement: FileDefinition = { name, gobstonesScriptsLocation: [], projectLocation: [], copyOnInit: false, copyOnUpdate: false, copyOnEject: false, isOverridable: false, requiresReferenceUpdate: false, requiresTestDataInjection: false }; // If this file is ought to be excluded, just return the empty element if (excludedFiles.includes(name)) { return baseElement; } // This is a not excluded file, overwrite default values with the ones provided const projectTypeInfo: FileDefinition = Object.assign(baseElement, partialFileInfo); // Update the internal paths to the corresponding ones projectTypeInfo.gobstonesScriptsLocation = projectTypeInfo.gobstonesScriptsLocation.map((e) => e.replace('<projectTypePath>', noCommonFiles.includes(name) ? projectTypePath : 'Common') ); return projectTypeInfo; } /** * Return a file definition with defaults, that will be overwritten by the * partial file definition given. * * @param name - The name of the file definition. * @param partialFileInfo - The partial information for this file definition. * @returns A full file definition. */ private _fileDefinitionWithTooling( name: FileName, projectTypePath: string, noCommonFiles: FileName[], excludedFiles: FileName[], partialFileInfo: Partial<FileDefinition> ): FileDefinitionWithTooling { const projectTypeInfo = this._fileDefinition( name, projectTypePath, noCommonFiles, excludedFiles, partialFileInfo ) as FileDefinitionWithTooling; // If it's overridable, update the tooling file reference if (projectTypeInfo.isOverridable) { const toolingFile = getToolingFile( this.locations.projectRoot, this.locations.gobstonesScriptsProjectsRoot, projectTypeInfo ); if (toolingFile) { projectTypeInfo.toolingFile = toolingFile; } } return projectTypeInfo; } // ------------------------------------------ // #endregion Private Initialization Helpers // ------------------------------------------ } // ========================================== // #endregion Config // ========================================== // ========================================== // #region Config Instance // ========================================== /** * The config object exports all configuration functions in * a convenient element. * * @internal */ export const config: Config = new Config(); // ========================================== // #endregion Config Instance // ==========================================