Charles Lyding be5e2fbfe5 fix(@angular/cli): remove redundant typescript/compiler-cli compatibility check
Angular 5.0+ has a full peer dependencies setup (with 6.0+ also having a configurable runtime error check) to ensure that an appropriate version of typescript is available for compilation.  Angular CLI 8.0+ does not support Angular versions prior to these and therefore the warning is redundant.  For the case where the developer wishes to use an unsupported TypeScript version, the developer would need to adjust two similar but differently name settings in two different configuration files.
2019-02-20 10:36:52 -08:00

294 lines
7.8 KiB
TypeScript

/**
* @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
*/
import {
InvalidJsonCharacterException,
JsonArray,
JsonObject,
JsonParseMode,
JsonValue,
experimental,
parseJson,
tags,
} from '@angular-devkit/core';
import { writeFileSync } from 'fs';
import { Command } from '../models/command';
import { Arguments, CommandScope } from '../models/interface';
import {
getWorkspace,
getWorkspaceRaw,
migrateLegacyGlobalConfig,
validateWorkspace,
} from '../utilities/config';
import { Schema as ConfigCommandSchema, Value as ConfigCommandSchemaValue } from './config';
const validCliPaths = new Map([
['cli.warnings.versionMismatch', 'boolean'],
['cli.defaultCollection', 'string'],
['cli.packageManager', 'string'],
]);
/**
* Splits a JSON path string into fragments. Fragments can be used to get the value referenced
* by the path. For example, a path of "a[3].foo.bar[2]" would give you a fragment array of
* ["a", 3, "foo", "bar", 2].
* @param path The JSON string to parse.
* @returns {string[]} The fragments for the string.
* @private
*/
function parseJsonPath(path: string): string[] {
const fragments = (path || '').split(/\./g);
const result: string[] = [];
while (fragments.length > 0) {
const fragment = fragments.shift();
if (fragment == undefined) {
break;
}
const match = fragment.match(/([^\[]+)((\[.*\])*)/);
if (!match) {
throw new Error('Invalid JSON path.');
}
result.push(match[1]);
if (match[2]) {
const indices = match[2].slice(1, -1).split('][');
result.push(...indices);
}
}
return result.filter(fragment => !!fragment);
}
function getValueFromPath<T extends JsonArray | JsonObject>(
root: T,
path: string,
): JsonValue | undefined {
const fragments = parseJsonPath(path);
try {
return fragments.reduce((value: JsonValue, current: string | number) => {
if (value == undefined || typeof value != 'object') {
return undefined;
} else if (typeof current == 'string' && !Array.isArray(value)) {
return value[current];
} else if (typeof current == 'number' && Array.isArray(value)) {
return value[current];
} else {
return undefined;
}
}, root);
} catch {
return undefined;
}
}
function setValueFromPath<T extends JsonArray | JsonObject>(
root: T,
path: string,
newValue: JsonValue,
): JsonValue | undefined {
const fragments = parseJsonPath(path);
try {
return fragments.reduce((value: JsonValue, current: string | number, index: number) => {
if (value == undefined || typeof value != 'object') {
return undefined;
} else if (typeof current == 'string' && !Array.isArray(value)) {
if (index === fragments.length - 1) {
value[current] = newValue;
} else if (value[current] == undefined) {
if (typeof fragments[index + 1] == 'number') {
value[current] = [];
} else if (typeof fragments[index + 1] == 'string') {
value[current] = {};
}
}
return value[current];
} else if (typeof current == 'number' && Array.isArray(value)) {
if (index === fragments.length - 1) {
value[current] = newValue;
} else if (value[current] == undefined) {
if (typeof fragments[index + 1] == 'number') {
value[current] = [];
} else if (typeof fragments[index + 1] == 'string') {
value[current] = {};
}
}
return value[current];
} else {
return undefined;
}
}, root);
} catch {
return undefined;
}
}
function normalizeValue(value: ConfigCommandSchemaValue, path: string): JsonValue {
const cliOptionType = validCliPaths.get(path);
if (cliOptionType) {
switch (cliOptionType) {
case 'boolean':
if (('' + value).trim() === 'true') {
return true;
} else if (('' + value).trim() === 'false') {
return false;
}
break;
case 'number':
const numberValue = Number(value);
if (!Number.isFinite(numberValue)) {
return numberValue;
}
break;
case 'string':
return value;
}
throw new Error(`Invalid value type; expected a ${cliOptionType}.`);
}
if (typeof value === 'string') {
try {
return parseJson(value, JsonParseMode.Loose);
} catch (e) {
if (e instanceof InvalidJsonCharacterException && !value.startsWith('{')) {
return value;
} else {
throw e;
}
}
}
return value;
}
export class ConfigCommand extends Command<ConfigCommandSchema> {
public async run(options: ConfigCommandSchema & Arguments) {
const level = options.global ? 'global' : 'local';
if (!options.global) {
await this.validateScope(CommandScope.InProject);
}
let config =
(getWorkspace(level) as {} as { _workspace: experimental.workspace.WorkspaceSchema });
if (options.global && !config) {
try {
if (migrateLegacyGlobalConfig()) {
config =
(getWorkspace(level) as {} as { _workspace: experimental.workspace.WorkspaceSchema });
this.logger.info(tags.oneLine`
We found a global configuration that was used in Angular CLI 1.
It has been automatically migrated.`);
}
} catch {}
}
if (options.value == undefined) {
if (!config) {
this.logger.error('No config found.');
return 1;
}
return this.get(config._workspace, options);
} else {
return this.set(options);
}
}
private get(config: experimental.workspace.WorkspaceSchema, options: ConfigCommandSchema) {
let value;
if (options.jsonPath) {
if (options.jsonPath === 'cli.warnings.typescriptMismatch') {
// NOTE: Remove this in 9.0.
this.logger.warn('The "typescriptMismatch" warning has been removed in 8.0.');
// Since there is no actual warning, this value is always false.
this.logger.info('false');
return 0;
}
value = getValueFromPath(config as {} as JsonObject, options.jsonPath);
} else {
value = config;
}
if (value === undefined) {
this.logger.error('Value cannot be found.');
return 1;
} else if (typeof value == 'object') {
this.logger.info(JSON.stringify(value, null, 2));
} else {
this.logger.info(value.toString());
}
return 0;
}
private set(options: ConfigCommandSchema) {
if (!options.jsonPath || !options.jsonPath.trim()) {
throw new Error('Invalid Path.');
}
if (options.jsonPath === 'cli.warnings.typescriptMismatch') {
// NOTE: Remove this in 9.0.
this.logger.warn('The "typescriptMismatch" warning has been removed in 8.0.');
return 0;
}
if (options.global
&& !options.jsonPath.startsWith('schematics.')
&& !validCliPaths.has(options.jsonPath)) {
throw new Error('Invalid Path.');
}
const [config, configPath] = getWorkspaceRaw(options.global ? 'global' : 'local');
if (!config || !configPath) {
this.logger.error('Confguration file cannot be found.');
return 1;
}
// TODO: Modify & save without destroying comments
const configValue = config.value;
const value = normalizeValue(options.value || '', options.jsonPath);
const result = setValueFromPath(configValue, options.jsonPath, value);
if (result === undefined) {
this.logger.error('Value cannot be found.');
return 1;
}
try {
validateWorkspace(configValue);
} catch (error) {
this.logger.fatal(error.message);
return 1;
}
const output = JSON.stringify(configValue, null, 2);
writeFileSync(configPath, output);
return 0;
}
}