Skip to content

Commit

Permalink
fix(@angular/cli): error out when command json is invalid
Browse files Browse the repository at this point in the history
  • Loading branch information
filipesilva committed Oct 11, 2018
1 parent b23ee7a commit 8d1c0bb
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 12 deletions.
2 changes: 2 additions & 0 deletions packages/angular/cli/commands/e2e-long.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Must be executed from within a workspace directory.
When a project name is not supplied, the configured default e2e project of the workspace is used.
2 changes: 1 addition & 1 deletion packages/angular/cli/commands/e2e.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/schema",
"$id": "ng-cli://commands/e2e.json",
"description": "Builds and serves an Angular app, and runs end-to-end tests using Protractor.",
"$longDescription": "Must be executed from within a workspace directory.\n When a project name is not supplied, the configured default e2e project of the workspace is used.",
"$longDescription": "./e2e-long.md",

"$aliases": [ "e" ],
"$scope": "in",
Expand Down
1 change: 0 additions & 1 deletion packages/angular/cli/commands/generate-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export class GenerateCommand extends SchematicCommand<GenerateCommandSchema> {
schematic.description.path,
this._workflow.registry,
schematic.description.schemaJson,
this.logger,
);
} else {
continue;
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/cli/models/command-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,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;
Expand Down
17 changes: 11 additions & 6 deletions packages/angular/cli/utilities/json-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<E, T extends E[keyof E]>(
value: json.JsonValue,
enumeration: E,
Expand All @@ -40,7 +47,6 @@ export async function parseJsonSchemaToSubCommandDescription(
jsonPath: string,
registry: json.schema.SchemaRegistry,
schema: json.JsonObject,
logger: logging.Logger,
): Promise<SubCommandDescription> {
const options = await parseJsonSchemaToOptions(registry, schema);

Expand Down Expand Up @@ -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 = '';
Expand All @@ -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);
}
}

Expand All @@ -99,10 +105,9 @@ export async function parseJsonSchemaToCommandDescription(
jsonPath: string,
registry: json.schema.SchemaRegistry,
schema: json.JsonObject,
logger: logging.Logger,
): Promise<CommandDescription> {
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') {
Expand Down
76 changes: 76 additions & 0 deletions packages/angular/cli/utilities/json-schema_spec.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
6 changes: 3 additions & 3 deletions scripts/snapshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,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) {
Expand Down
8 changes: 8 additions & 0 deletions tests/legacy-cli/e2e/tests/commands/help/help-json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ng } from '../../../utils/process';


export default function () {
return ng('build', '--help=json')
// Output should be valid JSON.
.then(({ stdout }) => JSON.parse(stdout.trim()));
}

0 comments on commit 8d1c0bb

Please sign in to comment.