diff --git a/packages/angular/cli/commands/generate-impl.ts b/packages/angular/cli/commands/generate-impl.ts index 49d9f60056..b47808a7e1 100644 --- a/packages/angular/cli/commands/generate-impl.ts +++ b/packages/angular/cli/commands/generate-impl.ts @@ -36,7 +36,6 @@ export class GenerateCommand extends SchematicCommand { schematic.description.path, this._workflow.registry, schematic.description.schemaJson, - this.logger, ); } else { continue; diff --git a/packages/angular/cli/models/command-runner.ts b/packages/angular/cli/models/command-runner.ts index 4828b4239e..28a3c371f3 100644 --- a/packages/angular/cli/models/command-runner.ts +++ b/packages/angular/cli/models/command-runner.ts @@ -92,7 +92,7 @@ export async function runCommand( } commandMap[name] = - await parseJsonSchemaToCommandDescription(name, schemaPath, registry, schema, logger); + await parseJsonSchemaToCommandDescription(name, schemaPath, registry, schema); } let commandName: string | undefined = undefined; diff --git a/packages/angular/cli/utilities/json-schema.ts b/packages/angular/cli/utilities/json-schema.ts index 40714c9065..a857867490 100644 --- a/packages/angular/cli/utilities/json-schema.ts +++ b/packages/angular/cli/utilities/json-schema.ts @@ -5,7 +5,7 @@ * 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 { json, logging } from '@angular-devkit/core'; +import { BaseException, json } from '@angular-devkit/core'; import { ExportStringRef } from '@angular-devkit/schematics/tools'; import { readFileSync } from 'fs'; import { dirname, resolve } from 'path'; @@ -19,6 +19,13 @@ import { Value, } from '../models/interface'; + +export class CommandJsonPathException extends BaseException { + constructor(public readonly path: string, public readonly name: string) { + super(`File ${path} was not found while constructing the subcommand ${name}.`); + } +} + function _getEnumFromValue( value: json.JsonValue, enumeration: E, @@ -40,7 +47,6 @@ export async function parseJsonSchemaToSubCommandDescription( jsonPath: string, registry: json.schema.SchemaRegistry, schema: json.JsonObject, - logger: logging.Logger, ): Promise { const options = await parseJsonSchemaToOptions(registry, schema); @@ -69,7 +75,7 @@ export async function parseJsonSchemaToSubCommandDescription( try { longDescription = readFileSync(ldPath, 'utf-8'); } catch (e) { - logger.warn(`File ${ldPath} was not found while constructing the subcommand ${name}.`); + throw new CommandJsonPathException(ldPath, name); } } let usageNotes = ''; @@ -78,7 +84,7 @@ export async function parseJsonSchemaToSubCommandDescription( try { usageNotes = readFileSync(unPath, 'utf-8'); } catch (e) { - logger.warn(`File ${unPath} was not found while constructing the subcommand ${name}.`); + throw new CommandJsonPathException(unPath, name); } } @@ -99,10 +105,9 @@ export async function parseJsonSchemaToCommandDescription( jsonPath: string, registry: json.schema.SchemaRegistry, schema: json.JsonObject, - logger: logging.Logger, ): Promise { const subcommand = - await parseJsonSchemaToSubCommandDescription(name, jsonPath, registry, schema, logger); + await parseJsonSchemaToSubCommandDescription(name, jsonPath, registry, schema); // Before doing any work, let's validate the implementation. if (typeof schema.$impl != 'string') { diff --git a/packages/angular/cli/utilities/json-schema_spec.ts b/packages/angular/cli/utilities/json-schema_spec.ts new file mode 100644 index 0000000000..c26601227c --- /dev/null +++ b/packages/angular/cli/utilities/json-schema_spec.ts @@ -0,0 +1,76 @@ +/** + * @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 { schema } from '@angular-devkit/core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; +import { of } from 'rxjs'; +import { CommandJsonPathException, parseJsonSchemaToCommandDescription } from './json-schema'; + +describe('parseJsonSchemaToCommandDescription', () => { + let registry: schema.CoreSchemaRegistry; + const baseSchemaJson = { + '$schema': 'http://json-schema.org/schema', + '$id': 'ng-cli://commands/version.json', + 'description': 'Outputs Angular CLI version.', + '$longDescription': 'not a file ref', + + '$aliases': ['v'], + '$scope': 'all', + '$impl': './version-impl#VersionCommand', + + 'type': 'object', + 'allOf': [ + { '$ref': './definitions.json#/definitions/base' }, + ], + }; + + beforeEach(() => { + registry = new schema.CoreSchemaRegistry([]); + registry.registerUriHandler((uri: string) => { + if (uri.startsWith('ng-cli://')) { + const content = readFileSync( + join(__dirname, '..', uri.substr('ng-cli://'.length)), 'utf-8'); + + return of(JSON.parse(content)); + } else { + return null; + } + }); + }); + + it(`should throw on invalid $longDescription path`, async () => { + const name = 'version'; + const schemaPath = join(__dirname, './bad-sample.json'); + const schemaJson = { ...baseSchemaJson, $longDescription: 'not a file ref' }; + try { + await parseJsonSchemaToCommandDescription(name, schemaPath, registry, schemaJson); + } catch (error) { + const refPath = join(__dirname, schemaJson.$longDescription); + expect(error).toEqual(new CommandJsonPathException(refPath, name)); + + return; + } + expect(true).toBe(false, 'function should have thrown'); + }); + + it(`should throw on invalid $usageNotes path`, async () => { + const name = 'version'; + const schemaPath = join(__dirname, './bad-sample.json'); + const schemaJson = { ...baseSchemaJson, $usageNotes: 'not a file ref' }; + try { + await parseJsonSchemaToCommandDescription(name, schemaPath, registry, schemaJson); + } catch (error) { + const refPath = join(__dirname, schemaJson.$usageNotes); + expect(error).toEqual(new CommandJsonPathException(refPath, name)); + + return; + } + expect(true).toBe(false, 'function should have thrown'); + }); +}); diff --git a/scripts/snapshots.ts b/scripts/snapshots.ts index 318af975a8..83fb0c7bc2 100644 --- a/scripts/snapshots.ts +++ b/scripts/snapshots.ts @@ -184,9 +184,9 @@ export default async function(opts: SnapshotsOptions, logger: logging.Logger) { const options = { cwd: newProjectRoot }; const childLogger = logger.createChild(commandName); const stdout = _exec(ngPath, [commandName, '--help=json'], options, childLogger); - if (stdout.trim()) { - fs.writeFileSync(path.join(helpOutputRoot, commandName + '.json'), stdout); - } + // Make sure the output is JSON before printing it, and format it as well. + const jsonOutput = JSON.stringify(JSON.parse(stdout.trim()), undefined, 2); + fs.writeFileSync(path.join(helpOutputRoot, commandName + '.json'), jsonOutput); } if (!githubToken) {