angular-cli/tools/ng_cli_schema_generator.js
Alan Agius 4b0223b64e build: automate @angular/cli schema.json generation
With this change we automate the generation of `@angular/cli/lib/config/schema.json`. While on paper we could use quicktype for this. Quicktype doesn't handle `patternProperties` and `oneOf` that well.

How does this works?
Relative `$ref` will be resolved and inlined as part of the root schema definitions.

Example
```json
"@schematics/angular:enum": {
    "$ref": "../../../../schematics/angular/enum/schema.json"
},
```

Will be parsed and transformed to
```json
"@schematics/angular:enum": {
  "$ref": "#/definitions/SchematicsAngularEnumSchema"
},
"definitions: {
  "SchematicsAngularEnumSchema": {
    "title": "Angular Enum Options Schema",
    "type": "object",
    "description": "Generates a new, generic enum definition for the given or default project.",
    "properties": {...}
   }
}
```
2021-03-11 21:51:37 +01:00

92 lines
2.6 KiB
JavaScript

/**
* @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
*/
const { readFileSync, writeFileSync, mkdirSync } = require('fs');
const { resolve, dirname } = require('path');
/**
* Generator the Angular CLI workspace schema file.
*/
function generate(inPath, outPath) {
// While on paper we could use quicktype for this.
// Quicktype doesn't handle `patternProperties` and `oneOf` that well.
const jsonSchema = readFileSync(inPath, 'utf8');
const nestedDefinitions = {};
const schemaParsed = JSON.parse(jsonSchema, (key, value) => {
if (key === '$ref' && typeof value === 'string' && !value.startsWith('#')) {
// Resolve $ref and camelize key
const definitionKey = value
.replace(/(\.json|src)/g, '')
.split(/\\|\/|_|-|\./)
.filter(p => !!p)
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
.join('');
const nestedSchemaPath = resolve(dirname(inPath), value);
const nestedSchema = readFileSync(nestedSchemaPath, 'utf8');
const nestedSchemaJson = JSON.parse(nestedSchema, (key, value) => {
switch (key) {
case '$ref':
if (value.startsWith('#/definitions/')) {
return value.replace('#/definitions/', `#/definitions/${definitionKey}/definitions/`);
} else {
throw new Error(`Error while resolving $ref ${value} in ${nestedSchemaPath}.`);
}
case '$id':
case '$id':
case '$schema':
case 'id':
case 'required':
return undefined;
default:
return value;
}
});
nestedDefinitions[definitionKey] = nestedSchemaJson;
return `#/definitions/${definitionKey}`;
}
return key === ''
? {
...value,
definitions: {
...value.definitions,
...nestedDefinitions,
}
}
: value;
});
const buildWorkspaceDirectory = process.env['BUILD_WORKSPACE_DIRECTORY'] || '.';
outPath = resolve(buildWorkspaceDirectory, outPath);
mkdirSync(dirname(outPath), { recursive: true });
writeFileSync(outPath, JSON.stringify(schemaParsed, undefined, 2));
}
if (require.main === module) {
const argv = process.argv.slice(2);
if (argv.length !== 2) {
console.error('Must include 2 arguments.');
process.exit(1);
}
const [inPath, outPath] = argv;
try {
generate(inPath, outPath);
} catch (error) {
console.error('An error happened:');
console.error(err);
process.exit(127);
}
}
exports.generate = generate;