Skip to content

Commit eef66f0

Browse files
filipesilvaalexeagle
authored andcommitted
fix(@angular/cli): error out when command json is invalid
1 parent 7ca3bb5 commit eef66f0

File tree

5 files changed

+91
-11
lines changed

5 files changed

+91
-11
lines changed

packages/angular/cli/commands/generate-impl.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export class GenerateCommand extends SchematicCommand<GenerateCommandSchema> {
3636
schematic.description.path,
3737
this._workflow.registry,
3838
schematic.description.schemaJson,
39-
this.logger,
4039
);
4140
} else {
4241
continue;

packages/angular/cli/models/command-runner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export async function runCommand(
9292
}
9393

9494
commandMap[name] =
95-
await parseJsonSchemaToCommandDescription(name, schemaPath, registry, schema, logger);
95+
await parseJsonSchemaToCommandDescription(name, schemaPath, registry, schema);
9696
}
9797

9898
let commandName: string | undefined = undefined;

packages/angular/cli/utilities/json-schema.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import { json, logging } from '@angular-devkit/core';
8+
import { BaseException, json } from '@angular-devkit/core';
99
import { ExportStringRef } from '@angular-devkit/schematics/tools';
1010
import { readFileSync } from 'fs';
1111
import { dirname, resolve } from 'path';
@@ -19,6 +19,13 @@ import {
1919
Value,
2020
} from '../models/interface';
2121

22+
23+
export class CommandJsonPathException extends BaseException {
24+
constructor(public readonly path: string, public readonly name: string) {
25+
super(`File ${path} was not found while constructing the subcommand ${name}.`);
26+
}
27+
}
28+
2229
function _getEnumFromValue<E, T extends E[keyof E]>(
2330
value: json.JsonValue,
2431
enumeration: E,
@@ -40,7 +47,6 @@ export async function parseJsonSchemaToSubCommandDescription(
4047
jsonPath: string,
4148
registry: json.schema.SchemaRegistry,
4249
schema: json.JsonObject,
43-
logger: logging.Logger,
4450
): Promise<SubCommandDescription> {
4551
const options = await parseJsonSchemaToOptions(registry, schema);
4652

@@ -69,7 +75,7 @@ export async function parseJsonSchemaToSubCommandDescription(
6975
try {
7076
longDescription = readFileSync(ldPath, 'utf-8');
7177
} catch (e) {
72-
logger.warn(`File ${ldPath} was not found while constructing the subcommand ${name}.`);
78+
throw new CommandJsonPathException(ldPath, name);
7379
}
7480
}
7581
let usageNotes = '';
@@ -78,7 +84,7 @@ export async function parseJsonSchemaToSubCommandDescription(
7884
try {
7985
usageNotes = readFileSync(unPath, 'utf-8');
8086
} catch (e) {
81-
logger.warn(`File ${unPath} was not found while constructing the subcommand ${name}.`);
87+
throw new CommandJsonPathException(unPath, name);
8288
}
8389
}
8490

@@ -99,10 +105,9 @@ export async function parseJsonSchemaToCommandDescription(
99105
jsonPath: string,
100106
registry: json.schema.SchemaRegistry,
101107
schema: json.JsonObject,
102-
logger: logging.Logger,
103108
): Promise<CommandDescription> {
104109
const subcommand =
105-
await parseJsonSchemaToSubCommandDescription(name, jsonPath, registry, schema, logger);
110+
await parseJsonSchemaToSubCommandDescription(name, jsonPath, registry, schema);
106111

107112
// Before doing any work, let's validate the implementation.
108113
if (typeof schema.$impl != 'string') {
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*
8+
*/
9+
import { schema } from '@angular-devkit/core';
10+
import { readFileSync } from 'fs';
11+
import { join } from 'path';
12+
import { of } from 'rxjs';
13+
import { CommandJsonPathException, parseJsonSchemaToCommandDescription } from './json-schema';
14+
15+
describe('parseJsonSchemaToCommandDescription', () => {
16+
let registry: schema.CoreSchemaRegistry;
17+
const baseSchemaJson = {
18+
'$schema': 'http://json-schema.org/schema',
19+
'$id': 'ng-cli://commands/version.json',
20+
'description': 'Outputs Angular CLI version.',
21+
'$longDescription': 'not a file ref',
22+
23+
'$aliases': ['v'],
24+
'$scope': 'all',
25+
'$impl': './version-impl#VersionCommand',
26+
27+
'type': 'object',
28+
'allOf': [
29+
{ '$ref': './definitions.json#/definitions/base' },
30+
],
31+
};
32+
33+
beforeEach(() => {
34+
registry = new schema.CoreSchemaRegistry([]);
35+
registry.registerUriHandler((uri: string) => {
36+
if (uri.startsWith('ng-cli://')) {
37+
const content = readFileSync(
38+
join(__dirname, '..', uri.substr('ng-cli://'.length)), 'utf-8');
39+
40+
return of(JSON.parse(content));
41+
} else {
42+
return null;
43+
}
44+
});
45+
});
46+
47+
it(`should throw on invalid $longDescription path`, async () => {
48+
const name = 'version';
49+
const schemaPath = join(__dirname, './bad-sample.json');
50+
const schemaJson = { ...baseSchemaJson, $longDescription: 'not a file ref' };
51+
try {
52+
await parseJsonSchemaToCommandDescription(name, schemaPath, registry, schemaJson);
53+
} catch (error) {
54+
const refPath = join(__dirname, schemaJson.$longDescription);
55+
expect(error).toEqual(new CommandJsonPathException(refPath, name));
56+
57+
return;
58+
}
59+
expect(true).toBe(false, 'function should have thrown');
60+
});
61+
62+
it(`should throw on invalid $usageNotes path`, async () => {
63+
const name = 'version';
64+
const schemaPath = join(__dirname, './bad-sample.json');
65+
const schemaJson = { ...baseSchemaJson, $usageNotes: 'not a file ref' };
66+
try {
67+
await parseJsonSchemaToCommandDescription(name, schemaPath, registry, schemaJson);
68+
} catch (error) {
69+
const refPath = join(__dirname, schemaJson.$usageNotes);
70+
expect(error).toEqual(new CommandJsonPathException(refPath, name));
71+
72+
return;
73+
}
74+
expect(true).toBe(false, 'function should have thrown');
75+
});
76+
});

scripts/snapshots.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,9 @@ export default async function(opts: SnapshotsOptions, logger: logging.Logger) {
184184
const options = { cwd: newProjectRoot };
185185
const childLogger = logger.createChild(commandName);
186186
const stdout = _exec(ngPath, [commandName, '--help=json'], options, childLogger);
187-
if (stdout.trim()) {
188-
fs.writeFileSync(path.join(helpOutputRoot, commandName + '.json'), stdout);
189-
}
187+
// Make sure the output is JSON before printing it, and format it as well.
188+
const jsonOutput = JSON.stringify(JSON.parse(stdout.trim()), undefined, 2);
189+
fs.writeFileSync(path.join(helpOutputRoot, commandName + '.json'), jsonOutput);
190190
}
191191

192192
if (!githubToken) {

0 commit comments

Comments
 (0)