| 
									
										
										
										
											2020-03-16 11:31:24 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							| 
									
										
										
										
											2020-05-19 12:08:49 -07:00
										 |  |  |  * Copyright Google LLC All Rights Reserved. | 
					
						
							| 
									
										
										
										
											2020-03-16 11:31:24 -07:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2020-05-04 12:33:14 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-19 11:08:29 +02:00
										 |  |  | import {existsSync} from 'fs'; | 
					
						
							| 
									
										
										
										
											2020-05-20 11:59:42 +02:00
										 |  |  | import {dirname, join} from 'path'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 13:53:03 -07:00
										 |  |  | import {debug, error} from './console'; | 
					
						
							| 
									
										
										
										
											2020-06-05 11:03:32 +03:00
										 |  |  | import {exec} from './shelljs'; | 
					
						
							| 
									
										
										
										
											2020-05-19 11:08:29 +02:00
										 |  |  | import {isTsNodeAvailable} from './ts-node'; | 
					
						
							| 
									
										
										
										
											2020-03-16 11:31:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-27 15:39:31 -07:00
										 |  |  | /** Configuration for Git client interactions. */ | 
					
						
							|  |  |  | export interface GitClientConfig { | 
					
						
							| 
									
										
										
										
											2020-05-15 17:21:01 +02:00
										 |  |  |   /** Owner name of the repository. */ | 
					
						
							|  |  |  |   owner: string; | 
					
						
							|  |  |  |   /** Name of the repository. */ | 
					
						
							|  |  |  |   name: string; | 
					
						
							| 
									
										
										
										
											2020-05-27 15:39:31 -07:00
										 |  |  |   /** If SSH protocol should be used for git interactions. */ | 
					
						
							|  |  |  |   useSsh?: boolean; | 
					
						
							| 
									
										
										
										
											2020-06-25 00:40:15 +02:00
										 |  |  |   /** Whether the specified repository is private. */ | 
					
						
							|  |  |  |   private?: boolean; | 
					
						
							| 
									
										
										
										
											2020-05-15 17:21:01 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-27 15:39:31 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Describes the Github configuration for dev-infra. This configuration is | 
					
						
							|  |  |  |  * used for API requests, determining the upstream remote, etc. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export interface GithubConfig extends GitClientConfig {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-08 14:51:29 -07:00
										 |  |  | /** The common configuration for ng-dev. */ | 
					
						
							|  |  |  | type CommonConfig = { | 
					
						
							| 
									
										
										
										
											2020-05-15 17:21:01 +02:00
										 |  |  |   github: GithubConfig | 
					
						
							| 
									
										
										
										
											2020-05-08 14:51:29 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * The configuration for the specific ng-dev command, providing both the common | 
					
						
							|  |  |  |  * ng-dev config as well as the specific config of a subcommand. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export type NgDevConfig<T = {}> = CommonConfig&T; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-12 15:26:30 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * The filename expected for creating the ng-dev config, without the file | 
					
						
							|  |  |  |  * extension to allow either a typescript or javascript file to be used. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2020-05-15 14:15:10 -07:00
										 |  |  | const CONFIG_FILE_PATH = '.ng-dev/config'; | 
					
						
							| 
									
										
										
										
											2020-05-04 12:33:14 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-08 14:51:29 -07:00
										 |  |  | /** The configuration for ng-dev. */ | 
					
						
							| 
									
										
										
										
											2020-09-11 17:09:58 +02:00
										 |  |  | let cachedConfig: NgDevConfig|null = null; | 
					
						
							| 
									
										
										
										
											2020-05-08 14:51:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 13:53:03 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * The filename expected for local user config, without the file extension to allow a typescript, | 
					
						
							|  |  |  |  * javascript or json file to be used. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const USER_CONFIG_FILE_PATH = '.ng-dev.user'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** The local user configuration for ng-dev. */ | 
					
						
							| 
									
										
										
										
											2020-09-11 17:09:58 +02:00
										 |  |  | let userConfig: {[key: string]: any}|null = null; | 
					
						
							| 
									
										
										
										
											2020-09-03 13:53:03 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-16 11:31:24 -07:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2020-05-19 11:08:29 +02:00
										 |  |  |  * Get the configuration from the file system, returning the already loaded | 
					
						
							|  |  |  |  * copy if it is defined. | 
					
						
							| 
									
										
										
										
											2020-03-16 11:31:24 -07:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2020-05-08 14:51:29 -07:00
										 |  |  | export function getConfig(): NgDevConfig { | 
					
						
							|  |  |  |   // If the global config is not defined, load it from the file system.
 | 
					
						
							| 
									
										
										
										
											2020-09-11 17:09:58 +02:00
										 |  |  |   if (cachedConfig === null) { | 
					
						
							| 
									
										
										
										
											2020-05-08 14:51:29 -07:00
										 |  |  |     // The full path to the configuration file.
 | 
					
						
							| 
									
										
										
										
											2020-05-15 14:15:10 -07:00
										 |  |  |     const configPath = join(getRepoBaseDir(), CONFIG_FILE_PATH); | 
					
						
							| 
									
										
										
										
											2020-09-11 17:09:58 +02:00
										 |  |  |     // Read the configuration and validate it before caching it for the future.
 | 
					
						
							|  |  |  |     cachedConfig = validateCommonConfig(readConfigFile(configPath)); | 
					
						
							| 
									
										
										
										
											2020-05-08 14:51:29 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-11 17:09:58 +02:00
										 |  |  |   // Return a clone of the cached global config to ensure that a new instance of the config
 | 
					
						
							|  |  |  |   // is returned each time, preventing unexpected effects of modifications to the config object.
 | 
					
						
							|  |  |  |   return {...cachedConfig}; | 
					
						
							| 
									
										
										
										
											2020-05-08 14:51:29 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** Validate the common configuration has been met for the ng-dev command. */ | 
					
						
							| 
									
										
										
										
											2020-05-12 15:26:03 -07:00
										 |  |  | function validateCommonConfig(config: Partial<NgDevConfig>) { | 
					
						
							|  |  |  |   const errors: string[] = []; | 
					
						
							|  |  |  |   // Validate the github configuration.
 | 
					
						
							|  |  |  |   if (config.github === undefined) { | 
					
						
							|  |  |  |     errors.push(`Github repository not configured. Set the "github" option.`); | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     if (config.github.name === undefined) { | 
					
						
							|  |  |  |       errors.push(`"github.name" is not defined`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (config.github.owner === undefined) { | 
					
						
							|  |  |  |       errors.push(`"github.owner" is not defined`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-05-19 11:08:29 +02:00
										 |  |  |   assertNoErrors(errors); | 
					
						
							| 
									
										
										
										
											2020-05-12 15:26:03 -07:00
										 |  |  |   return config as NgDevConfig; | 
					
						
							| 
									
										
										
										
											2020-05-08 14:51:29 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 13:53:03 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Resolves and reads the specified configuration file, optionally returning an empty object if the | 
					
						
							|  |  |  |  * configuration file cannot be read. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function readConfigFile(configPath: string, returnEmptyObjectOnError = false): object { | 
					
						
							| 
									
										
										
										
											2020-05-19 11:08:29 +02:00
										 |  |  |   // If the the `.ts` extension has not been set up already, and a TypeScript based
 | 
					
						
							|  |  |  |   // version of the given configuration seems to exist, set up `ts-node` if available.
 | 
					
						
							|  |  |  |   if (require.extensions['.ts'] === undefined && existsSync(`${configPath}.ts`) && | 
					
						
							|  |  |  |       isTsNodeAvailable()) { | 
					
						
							| 
									
										
										
										
											2020-05-20 11:59:42 +02:00
										 |  |  |     // Ensure the module target is set to `commonjs`. This is necessary because the
 | 
					
						
							|  |  |  |     // dev-infra tool runs in NodeJS which does not support ES modules by default.
 | 
					
						
							|  |  |  |     // Additionally, set the `dir` option to the directory that contains the configuration
 | 
					
						
							|  |  |  |     // file. This allows for custom compiler options (such as `--strict`).
 | 
					
						
							|  |  |  |     require('ts-node').register( | 
					
						
							|  |  |  |         {dir: dirname(configPath), transpileOnly: true, compilerOptions: {module: 'commonjs'}}); | 
					
						
							| 
									
										
										
										
											2020-05-19 11:08:29 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   try { | 
					
						
							| 
									
										
										
										
											2020-05-20 14:43:48 -07:00
										 |  |  |     return require(configPath); | 
					
						
							| 
									
										
										
										
											2020-05-19 11:08:29 +02:00
										 |  |  |   } catch (e) { | 
					
						
							| 
									
										
										
										
											2020-09-03 13:53:03 -07:00
										 |  |  |     if (returnEmptyObjectOnError) { | 
					
						
							|  |  |  |       debug(`Could not read configuration file at ${configPath}, returning empty object instead.`); | 
					
						
							|  |  |  |       debug(e); | 
					
						
							|  |  |  |       return {}; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-09-03 13:54:31 -07:00
										 |  |  |     error(`Could not read configuration file at ${configPath}.`); | 
					
						
							| 
									
										
										
										
											2020-05-20 14:43:48 -07:00
										 |  |  |     error(e); | 
					
						
							| 
									
										
										
										
											2020-05-19 11:08:29 +02:00
										 |  |  |     process.exit(1); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-08 14:51:29 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Asserts the provided array of error messages is empty. If any errors are in the array, | 
					
						
							|  |  |  |  * logs the errors and exit the process as a failure. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export function assertNoErrors(errors: string[]) { | 
					
						
							|  |  |  |   if (errors.length == 0) { | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-05-20 14:43:48 -07:00
										 |  |  |   error(`Errors discovered while loading configuration file:`); | 
					
						
							|  |  |  |   for (const err of errors) { | 
					
						
							|  |  |  |     error(`  - ${err}`); | 
					
						
							| 
									
										
										
										
											2020-05-08 14:51:29 -07:00
										 |  |  |   } | 
					
						
							|  |  |  |   process.exit(1); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** Gets the path of the directory for the repository base. */ | 
					
						
							| 
									
										
										
										
											2020-03-10 10:29:44 -07:00
										 |  |  | export function getRepoBaseDir() { | 
					
						
							| 
									
										
										
										
											2020-06-05 11:03:32 +03:00
										 |  |  |   const baseRepoDir = exec(`git rev-parse --show-toplevel`); | 
					
						
							| 
									
										
										
										
											2020-03-16 11:31:24 -07:00
										 |  |  |   if (baseRepoDir.code) { | 
					
						
							|  |  |  |     throw Error( | 
					
						
							|  |  |  |         `Unable to find the path to the base directory of the repository.\n` + | 
					
						
							|  |  |  |         `Was the command run from inside of the repo?\n\n` + | 
					
						
							|  |  |  |         `ERROR:\n ${baseRepoDir.stderr}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return baseRepoDir.trim(); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-09-03 13:53:03 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Get the local user configuration from the file system, returning the already loaded copy if it is | 
					
						
							|  |  |  |  * defined. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @returns The user configuration object, or an empty object if no user configuration file is | 
					
						
							| 
									
										
										
										
											2020-09-03 13:54:31 -07:00
										 |  |  |  * present. The object is an untyped object as there are no required user configurations. | 
					
						
							| 
									
										
										
										
											2020-09-03 13:53:03 -07:00
										 |  |  |  */ | 
					
						
							|  |  |  | export function getUserConfig() { | 
					
						
							|  |  |  |   // If the global config is not defined, load it from the file system.
 | 
					
						
							| 
									
										
										
										
											2020-09-11 17:09:58 +02:00
										 |  |  |   if (userConfig === null) { | 
					
						
							| 
									
										
										
										
											2020-09-03 13:53:03 -07:00
										 |  |  |     // The full path to the configuration file.
 | 
					
						
							|  |  |  |     const configPath = join(getRepoBaseDir(), USER_CONFIG_FILE_PATH); | 
					
						
							|  |  |  |     // Set the global config object.
 | 
					
						
							| 
									
										
										
										
											2020-09-11 17:09:58 +02:00
										 |  |  |     userConfig = readConfigFile(configPath, true); | 
					
						
							| 
									
										
										
										
											2020-09-03 13:53:03 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-11 17:09:58 +02:00
										 |  |  |   // Return a clone of the user config to ensure that a new instance of the config is returned
 | 
					
						
							| 
									
										
										
										
											2020-09-03 13:53:03 -07:00
										 |  |  |   // each time, preventing unexpected effects of modifications to the config object.
 | 
					
						
							| 
									
										
										
										
											2020-09-11 17:09:58 +02:00
										 |  |  |   return {...userConfig}; | 
					
						
							| 
									
										
										
										
											2020-09-03 13:53:03 -07:00
										 |  |  | } |