feat(dev-infra): register ts-node when reading configuration (#37196)

`ts-node` is now an optional peer dependency of the shared dev-infra
package. Whenever a `ng-dev` command runs, and a TypeScript-based
configuration file exists, `ts-node` is set up if available.

That allows consumers of the package (as the components repo) to more
conveniently use a TypeScript-based configuration for dev-infra.

Currently, commands would need to be proxied through `ts-node`
which rather complicates the setup:

```
NG_DEV_COMMAND="ts-node ./node_modules/@angular/dev-infra-private/cli.js"
```

I'm thinking that it should be best-practice to use TypeScript for
writing the configuration files. Given that the tool is used primarily
in Angular projects (for which most sources are TypeScript), this should
be acceptable.

PR Close #37196
This commit is contained in:
Paul Gschwendtner 2020-05-19 11:08:29 +02:00 committed by Kara Erickson
parent 4e96cdc23f
commit 383f04b96d
3 changed files with 48 additions and 6 deletions

View File

@ -24,7 +24,13 @@
"peerDependencies": { "peerDependencies": {
"@bazel/buildifier": "<from-root>", "@bazel/buildifier": "<from-root>",
"clang-format": "<from-root>", "clang-format": "<from-root>",
"ts-node": "<from-root>",
"tslib": "<from-root>", "tslib": "<from-root>",
"typescript": "<from-root>" "typescript": "<from-root>"
},
"peerDependenciesMeta": {
"ts-node": {
"optional": true
}
} }
} }

View File

@ -6,8 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {existsSync} from 'fs';
import {join} from 'path'; import {join} from 'path';
import {exec} from 'shelljs'; import {exec} from 'shelljs';
import {isTsNodeAvailable} from './ts-node';
/** /**
* Describes the Github configuration for dev-infra. This configuration is * Describes the Github configuration for dev-infra. This configuration is
@ -41,17 +43,16 @@ const CONFIG_FILE_NAME = '.ng-dev-config';
let CONFIG: {}|null = null; let CONFIG: {}|null = null;
/** /**
* Get the configuration from the file system, returning the already loaded copy if it * Get the configuration from the file system, returning the already loaded
* is defined. * copy if it is defined.
*/ */
export function getConfig(): NgDevConfig { export function getConfig(): NgDevConfig {
// If the global config is not defined, load it from the file system. // If the global config is not defined, load it from the file system.
if (CONFIG === null) { if (CONFIG === null) {
// The full path to the configuration file. // The full path to the configuration file.
const configPath = join(getRepoBaseDir(), CONFIG_FILE_NAME); const configPath = join(getRepoBaseDir(), CONFIG_FILE_NAME);
// Set the global config object to a clone of the configuration loaded through default exports // Set the global config object.
// from the config file. CONFIG = readConfigFile(configPath);
CONFIG = {...require(configPath)};
} }
// Return a clone of the global config to ensure that a new instance of the config is returned // Return a clone of the global config to ensure that a new instance of the config is returned
// each time, preventing unexpected effects of modifications to the config object. // each time, preventing unexpected effects of modifications to the config object.
@ -72,10 +73,28 @@ function validateCommonConfig(config: Partial<NgDevConfig>) {
errors.push(`"github.owner" is not defined`); errors.push(`"github.owner" is not defined`);
} }
} }
assertNoErrors(errors);
return config as NgDevConfig; return config as NgDevConfig;
} }
/** Resolves and reads the specified configuration file. */
function readConfigFile(configPath: string): object {
// 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()) {
require('ts-node').register({skipProject: true, transpileOnly: true});
}
try {
return require(configPath)
} catch (e) {
console.error('Could not read configuration file.');
console.error(e);
process.exit(1);
}
}
/** /**
* Asserts the provided array of error messages is empty. If any errors are in the array, * 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. * logs the errors and exit the process as a failure.

View File

@ -0,0 +1,17 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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
*/
/** Whether ts-node has been installed and is available to ng-dev. */
export function isTsNodeAvailable(): boolean {
try {
require.resolve('ts-node');
return true;
} catch {
return false;
}
}