1
0
mirror of https://github.com/angular/angular-cli.git synced 2025-05-23 07:19:58 +08:00

feat: remove ngtools entirely from this repository

This commit is contained in:
Hans Larsen 2018-03-28 21:23:35 -07:00 committed by Hans
parent b85a7dccf3
commit 9a9bc00a45
50 changed files with 61 additions and 2672 deletions

@ -19,7 +19,6 @@ import {
NodeModulesEngineHost,
validateOptionsWithSchema
} from '@angular-devkit/schematics/tools';
import { SchemaClassFactory } from '@ngtools/json-schema';
const SilentError = require('silent-error');
@ -39,21 +38,8 @@ export function getEngine(): Engine<FileSystemCollectionDesc, FileSystemSchemati
return engine;
}
export function getCollection(collectionName: string): Collection<any, any> {
const engineHost = getEngineHost();
const engine = getEngine();
// Add support for schemaJson.
engineHost.registerOptionsTransform((schematic: FileSystemSchematicDesc, options: any) => {
if (schematic.schema) {
const SchemaMetaClass = SchemaClassFactory<any>(schematic.schemaJson!);
const schemaClass = new SchemaMetaClass(options);
return schemaClass.$$root();
}
return options;
});
const collection = engine.createCollection(collectionName);
if (collection === null) {

@ -1,32 +0,0 @@
{
"name": "@ngtools/json-schema",
"version": "1.2.0",
"description": "Schema validating and reading for configurations, similar to Angular CLI config.",
"main": "./src/index.js",
"typings": "src/index.d.ts",
"license": "MIT",
"keywords": [
"angular",
"json",
"json-schema",
"schema",
"config"
],
"repository": {
"type": "git",
"url": "https://github.com/angular/angular-cli.git"
},
"author": "angular",
"bugs": {
"url": "https://github.com/angular/angular-cli/issues"
},
"homepage": "https://github.com/angular/angular-cli/tree/master/packages/@ngtools/json-schema",
"engines": {
"node": ">= 8.9.0",
"npm": ">= 5.5.1"
},
"dependencies": {
},
"peerDependencies": {
}
}

@ -1,12 +0,0 @@
export class JsonSchemaErrorBase extends Error {
constructor(message?: string) {
super();
if (message) {
this.message = message;
} else {
this.message = (<any>this.constructor).name;
}
}
}

@ -1 +0,0 @@
export {SchemaClass, SchemaClassFactory} from './schema-class-factory';

@ -1,34 +0,0 @@
import {JsonSchemaErrorBase} from './error';
import {Serializer, WriterFn} from './serializer';
import {JsonSerializer} from './serializers/json';
import {DTsSerializer} from './serializers/dts';
export class UnknownMimetype extends JsonSchemaErrorBase {}
export function createSerializerFromMimetype(mimetype: string,
writer: WriterFn,
...opts: any[]): Serializer {
let Klass: { new (writer: WriterFn, ...args: any[]): Serializer } = null;
switch (mimetype) {
case 'application/json': Klass = JsonSerializer; break;
case 'text/json': Klass = JsonSerializer; break;
case 'text/x.typescript': Klass = DTsSerializer; break;
case 'text/x.dts': Klass = DTsSerializer; break;
default: throw new UnknownMimetype();
}
return new Klass(writer, ...opts);
}
declare module './serializer' {
namespace Serializer {
export let fromMimetype: typeof createSerializerFromMimetype;
}
}
Serializer.fromMimetype = createSerializerFromMimetype;

@ -1,42 +0,0 @@
import {Serializer} from './serializer';
// A TypeScript Type. This can be used to do `new tsType(value)`.
// `null` implies any type; be careful.
export type TypeScriptType = typeof Number
| typeof Boolean
| typeof String
| typeof Object
| typeof Array
| null;
// The most generic interface for a schema node. This is used by the serializers.
export interface SchemaNode {
readonly name: string;
readonly type: string;
readonly tsType: TypeScriptType;
readonly defined: boolean;
readonly dirty: boolean;
readonly frozen: boolean;
readonly readOnly: boolean;
readonly defaultValue: any | null;
readonly required: boolean;
readonly parent: SchemaNode | null;
// Schema related properties.
readonly description: string | null;
// Object-only properties. `null` for everything else.
readonly children: { [key: string]: SchemaNode } | null;
// Array-only properties. `null` for everything else.
readonly items: SchemaNode[] | null;
readonly itemPrototype: SchemaNode | null;
// Mutable properties.
value: any;
// Serialization.
serialize(serializer: Serializer): void;
}

@ -1,203 +0,0 @@
import {Serializer} from './serializer';
import {RootSchemaTreeNode, SchemaTreeNode} from './schema-tree';
import {JsonSchemaErrorBase} from './error';
import './mimetypes';
export class InvalidJsonPath extends JsonSchemaErrorBase {}
// The schema tree node property of the SchemaClass.
const kSchemaNode = Symbol('schema-node');
// The value property of the SchemaClass.
const kOriginalRoot = Symbol('schema-value');
/**
* 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();
const match = fragment.match(/([^\[]+)((\[.*\])*)/);
if (!match) {
throw new InvalidJsonPath();
}
result.push(match[1]);
if (match[2]) {
const indices = match[2].slice(1, -1).split('][');
result.push(...indices);
}
}
return result.filter(fragment => !!fragment);
}
/** Get a SchemaTreeNode from the JSON path string. */
function _getSchemaNodeForPath<T>(rootMetaData: SchemaTreeNode<T>,
path: string): SchemaTreeNode<any> {
let fragments = _parseJsonPath(path);
// TODO: make this work with union (oneOf) schemas
return fragments.reduce((md: SchemaTreeNode<any>, current: string) => {
if (md && md.children) {
return md.children[current];
} else if (md && md.items) {
return md.items[parseInt(current, 10)];
} else {
return md;
}
}, rootMetaData);
}
/** The interface the SchemaClassFactory returned class implements. */
export interface SchemaClass<JsonType> extends Object {
$$root(): JsonType;
$$get(path: string): any;
$$set(path: string, value: any): void;
$$alias(source: string, destination: string): boolean;
$$dispose(): void;
// Metadata of the schema.
$$typeOf(path: string): string;
$$defined(path: string): boolean;
$$delete(path: string): void;
// Direct access to the schema.
$$schema(): RootSchemaTreeNode;
$$serialize(mimetype?: string, ...args: any[]): string;
}
class SchemaClassBase<T> implements SchemaClass<T> {
constructor(schema: Object, value: T, ...fallbacks: T[]) {
(this as any)[kOriginalRoot] = value;
const forward = fallbacks.length > 0
? (new SchemaClassBase<T>(schema, fallbacks.pop(), ...fallbacks).$$schema())
: null;
(this as any)[kSchemaNode] = new RootSchemaTreeNode(this, {
forward,
value,
schema
});
}
$$root(): T { return this as any; }
$$schema(): RootSchemaTreeNode { return (this as any)[kSchemaNode] as RootSchemaTreeNode; }
$$originalRoot(): T { return (this as any)[kOriginalRoot] as T; }
/** Sets the value of a destination if the value is currently undefined. */
$$alias(source: string, destination: string) {
let sourceSchemaTreeNode = _getSchemaNodeForPath(this.$$schema(), source);
if (!sourceSchemaTreeNode) {
return false;
}
const fragments = _parseJsonPath(destination);
const maybeValue = fragments.reduce((value: any, current: string) => {
return value && value[current];
}, this.$$originalRoot());
if (maybeValue !== undefined) {
sourceSchemaTreeNode.set(maybeValue);
return true;
}
return false;
}
/** Destroy all links between schemas to allow for GC. */
$$dispose() {
this.$$schema().dispose();
}
/** Get a value from a JSON path. */
$$get(path: string): any {
const node = _getSchemaNodeForPath(this.$$schema(), path);
return node ? node.get() : undefined;
}
/** Set a value from a JSON path. */
$$set(path: string, value: any): void {
const node = _getSchemaNodeForPath(this.$$schema(), path);
if (node) {
node.set(value);
} else {
// This might be inside an object that can have additionalProperties, so
// a TreeNode would not exist.
const splitPath = _parseJsonPath(path);
if (!splitPath) {
return undefined;
}
const parent: any = splitPath
.slice(0, -1)
.reduce((parent: any, curr: string) => parent && parent[curr], this);
if (parent) {
parent[splitPath[splitPath.length - 1]] = value;
}
}
}
/** Get the Schema associated with a path. */
$$typeOf(path: string): string {
const node = _getSchemaNodeForPath(this.$$schema(), path);
return node ? node.type : null;
}
$$defined(path: string): boolean {
const node = _getSchemaNodeForPath(this.$$schema(), path);
return node ? node.defined : false;
}
$$delete(path: string) {
const node = _getSchemaNodeForPath(this.$$schema(), path);
if (node) {
node.destroy();
}
}
/** Serialize into a string. */
$$serialize(mimetype = 'application/json', ...options: any[]): string {
let str = '';
const serializer = Serializer.fromMimetype(mimetype, (s) => str += s, ...options);
serializer.start();
this.$$schema().serialize(serializer);
serializer.end();
return str;
}
}
export interface SchemaClassFactoryReturn<T> {
new (value: T, ...fallbacks: T[]): SchemaClass<T>;
}
/**
* Create a class from a JSON SCHEMA object. Instanciating that class with an object
* allows for extended behaviour.
* This is the base API to access the Configuration in the CLI.
* @param schema
* @returns {GeneratedSchemaClass}
* @constructor
*/
export function SchemaClassFactory<T>(schema: Object): SchemaClassFactoryReturn<T> {
class GeneratedSchemaClass extends SchemaClassBase<T> {
constructor(value: T, ...fallbacks: T[]) {
super(schema, value, ...fallbacks);
}
}
return GeneratedSchemaClass;
}

@ -1,93 +0,0 @@
import {readFileSync} from 'fs';
import {join} from 'path';
import {RootSchemaTreeNode} from './schema-tree';
describe('@ngtools/json-schema', () => {
describe('OneOfSchemaTreeNode', () => {
const schemaJsonFilePath = join(__dirname, '../tests/schema1.json');
const schemaJson = JSON.parse(readFileSync(schemaJsonFilePath, 'utf-8'));
const valueJsonFilePath = join(__dirname, '../tests/value1-1.json');
const valueJson = JSON.parse(readFileSync(valueJsonFilePath, 'utf-8'));
it('works', () => {
const proto: any = Object.create(null);
// tslint:disable-next-line
new RootSchemaTreeNode(proto, {
value: valueJson,
schema: schemaJson
});
expect(proto.oneOfKey2 instanceof Array).toBe(true);
expect(proto.oneOfKey2.length).toBe(2);
// Set it to a string, which is valid.
proto.oneOfKey2 = 'hello';
expect(proto.oneOfKey2 instanceof Array).toBe(false);
});
it('returns undefined for values that are non-existent', () => {
const proto: any = Object.create(null);
const root = new RootSchemaTreeNode(proto, { value: valueJson, schema: schemaJson });
const value = root.children['objectKey1'].children['objectKey'].children['stringKey'].get();
expect(value).toBe(undefined);
});
});
describe('EnumSchemaTreeNode', () => {
const schemaJsonFilePath = join(__dirname, '../tests/schema2.json');
const schemaJson = JSON.parse(readFileSync(schemaJsonFilePath, 'utf-8'));
const valueJsonFilePath = join(__dirname, '../tests/value2-1.json');
const valueJson = JSON.parse(readFileSync(valueJsonFilePath, 'utf-8'));
it('works', () => {
const proto: any = Object.create(null);
// tslint:disable-next-line
new RootSchemaTreeNode(proto, {
value: valueJson,
schema: schemaJson
});
expect(proto.a instanceof Array).toBe(true);
expect(proto.a).toEqual(['v1', 'v3']);
// Set it to a string, which is valid.
proto.a[0] = 'v2';
expect(proto.a).toEqual(['v2', 'v3']);
});
it('supports default values', () => {
const proto: any = Object.create(null);
const schema = new RootSchemaTreeNode(proto, {
value: valueJson,
schema: schemaJson
});
expect(schema.children['b'].get()).toEqual('default');
});
it('should throw error when setting invalid value', () => {
const proto: any = Object.create(null);
// tslint:disable-next-line
new RootSchemaTreeNode(proto, {
value: valueJson,
schema: schemaJson
});
try {
proto.a[0] = 'INVALID';
} catch (error) {
expect(error.message).toBe('Invalid value can only be one of these: v1,v2,v3');
}
});
});
});

@ -1,543 +0,0 @@
import {JsonSchemaErrorBase} from './error';
import {Serializer} from './serializer';
import {SchemaNode, TypeScriptType} from './node';
export class InvalidSchema extends JsonSchemaErrorBase {}
export class InvalidValueError extends JsonSchemaErrorBase {}
export class MissingImplementationError extends JsonSchemaErrorBase {}
export class SettingReadOnlyPropertyError extends JsonSchemaErrorBase {}
export class InvalidUpdateValue extends JsonSchemaErrorBase {}
export interface Schema {
[key: string]: any;
}
/** This interface is defined to simplify the arguments passed in to the SchemaTreeNode. */
export type TreeNodeConstructorArgument<T> = {
parent?: SchemaTreeNode<T>;
name?: string;
value: T;
forward?: SchemaTreeNode<any>;
schema: Schema;
};
/**
* Holds all the information, including the value, of a node in the schema tree.
*/
export abstract class SchemaTreeNode<T> implements SchemaNode {
// Hierarchy objects
protected _parent: SchemaTreeNode<any>;
protected _defined = false;
protected _dirty = false;
protected _schema: Schema;
protected _name: string;
protected _value: T;
protected _forward: SchemaTreeNode<any>;
constructor(nodeMetaData: TreeNodeConstructorArgument<T>) {
this._schema = nodeMetaData.schema;
this._name = nodeMetaData.name;
this._value = nodeMetaData.value;
this._forward = nodeMetaData.forward;
this._parent = nodeMetaData.parent;
}
dispose() {
this._parent = null;
this._schema = null;
this._value = null;
if (this._forward) {
this._forward.dispose();
}
this._forward = null;
}
get defined() { return this._defined; }
get dirty() { return this._dirty; }
set dirty(v: boolean) {
if (v) {
this._defined = true;
this._dirty = true;
if (this._parent) {
this._parent.dirty = true;
}
}
}
get value(): T { return this.get(); }
abstract get type(): string;
abstract get tsType(): TypeScriptType;
abstract destroy(): void;
abstract get defaultValue(): any | null;
get name() { return this._name; }
get readOnly(): boolean { return this._schema['readOnly']; }
get frozen(): boolean { return true; }
get description() {
return 'description' in this._schema ? this._schema['description'] : null;
}
get required() {
if (!this._parent) {
return false;
}
return this._parent.isChildRequired(this.name);
}
isChildRequired(_name: string) { return false; }
get parent(): SchemaTreeNode<any> { return this._parent; }
get children(): { [key: string]: SchemaTreeNode<any> } | null { return null; }
get items(): SchemaTreeNode<any>[] | null { return null; }
get itemPrototype(): SchemaTreeNode<any> | null { return null; }
abstract get(): T;
set(_v: T, _init = false, _force = false) {
if (!this.readOnly) {
throw new MissingImplementationError();
}
throw new SettingReadOnlyPropertyError();
}
isCompatible(_v: any) { return false; }
abstract serialize(serializer: Serializer): void;
protected static _defineProperty<T>(proto: any, treeNode: SchemaTreeNode<T>): void {
if (treeNode.readOnly) {
Object.defineProperty(proto, treeNode.name, {
enumerable: true,
get: () => treeNode.get()
});
} else {
Object.defineProperty(proto, treeNode.name, {
enumerable: true,
get: () => treeNode.get(),
set: (v: T) => treeNode.set(v)
});
}
}
}
/** Base Class used for Non-Leaves TreeNode. Meaning they can have children. */
export abstract class NonLeafSchemaTreeNode<T> extends SchemaTreeNode<T> {
dispose() {
for (const key of Object.keys(this.children || {})) {
this.children[key].dispose();
}
for (let item of this.items || []) {
item.dispose();
}
super.dispose();
}
get() {
if (this.defined) {
return this._value;
} else {
return undefined;
}
}
destroy() {
this._defined = false;
this._value = null;
}
// Helper function to create a child based on its schema.
protected _createChildProperty<T>(name: string, value: T, forward: SchemaTreeNode<T>,
schema: Schema, define = true): SchemaTreeNode<T> {
const type: string =
('oneOf' in schema) ? 'oneOf' :
('enum' in schema) ? 'enum' : schema['type'];
let Klass: { new (arg: TreeNodeConstructorArgument<any>): SchemaTreeNode<any> } = null;
switch (type) {
case 'object': Klass = ObjectSchemaTreeNode; break;
case 'array': Klass = ArraySchemaTreeNode; break;
case 'string': Klass = StringSchemaTreeNode; break;
case 'boolean': Klass = BooleanSchemaTreeNode; break;
case 'number': Klass = NumberSchemaTreeNode; break;
case 'integer': Klass = IntegerSchemaTreeNode; break;
case 'null': Klass = NullSchemaTreeNode; break;
case 'enum': Klass = EnumSchemaTreeNode; break;
case 'oneOf': Klass = OneOfSchemaTreeNode; break;
default:
throw new InvalidSchema('Type ' + type + ' not understood by SchemaClassFactory.');
}
const metaData = new Klass({ parent: this, forward, value, schema, name });
if (define) {
SchemaTreeNode._defineProperty(this._value, metaData);
}
return metaData;
}
}
export class OneOfSchemaTreeNode extends NonLeafSchemaTreeNode<any> {
protected _typesPrototype: SchemaTreeNode<any>[];
protected _currentTypeHolder: SchemaTreeNode<any> | null;
constructor(metaData: TreeNodeConstructorArgument<any>) {
super(metaData);
let { value, forward, schema } = metaData;
this._typesPrototype = schema['oneOf'].map((schema: Object) => {
return this._createChildProperty('', '', forward, schema, false);
});
this._currentTypeHolder = null;
this._set(value, true, false);
}
_set(v: any, init: boolean, force: boolean) {
if (!init && this.readOnly && !force) {
throw new SettingReadOnlyPropertyError();
}
// Find the first type prototype that is compatible with the
let proto: SchemaTreeNode<any> = null;
for (let i = 0; i < this._typesPrototype.length; i++) {
const p = this._typesPrototype[i];
if (p.isCompatible(v)) {
proto = p;
break;
}
}
if (proto == null) {
return;
}
if (!init) {
this.dirty = true;
}
this._currentTypeHolder = proto;
this._currentTypeHolder.set(v, false, true);
}
set(v: any, _init = false, force = false) {
return this._set(v, false, force);
}
get(): any {
return this._currentTypeHolder ? this._currentTypeHolder.get() : null;
}
get defaultValue(): any | null {
return null;
}
get defined() { return this._currentTypeHolder ? this._currentTypeHolder.defined : false; }
get items() { return this._typesPrototype; }
get type() { return 'oneOf'; }
get tsType(): null { return null; }
serialize(serializer: Serializer) { serializer.outputOneOf(this); }
}
/** A Schema Tree Node that represents an object. */
export class ObjectSchemaTreeNode extends NonLeafSchemaTreeNode<{[key: string]: any}> {
// The map of all children metadata.
protected _children: { [key: string]: SchemaTreeNode<any> };
protected _frozen = false;
constructor(metaData: TreeNodeConstructorArgument<any>) {
super(metaData);
this._set(metaData.value, true, false);
}
_set(value: any, init: boolean, force: boolean) {
if (!init && this.readOnly && !force) {
throw new SettingReadOnlyPropertyError();
}
const schema = this._schema;
const forward = this._forward;
this._defined = !!value;
this._children = Object.create(null);
this._value = Object.create(null);
this._dirty = this._dirty || !init;
if (schema['properties']) {
for (const name of Object.keys(schema['properties'])) {
const propertySchema = schema['properties'][name];
this._children[name] = this._createChildProperty(
name,
value ? value[name] : undefined,
forward ? (forward as ObjectSchemaTreeNode).children[name] : null,
propertySchema);
}
} else if (!schema['additionalProperties']) {
throw new InvalidSchema('Schema does not have a properties, but doesnt allow for '
+ 'additional properties.');
}
if (!schema['additionalProperties']) {
this._frozen = true;
Object.freeze(this._value);
Object.freeze(this._children);
} else if (value) {
// Set other properties which don't have a schema.
for (const key of Object.keys(value)) {
if (!this._children[key]) {
this._value[key] = value[key];
}
}
}
}
set(v: any, force = false) {
return this._set(v, false, force);
}
get frozen(): boolean { return this._frozen; }
get children(): { [key: string]: SchemaTreeNode<any> } | null { return this._children; }
get type() { return 'object'; }
get tsType() { return Object; }
get defaultValue(): any | null { return null; }
isCompatible(v: any) { return typeof v == 'object' && v !== null; }
isChildRequired(name: string) {
if (this._schema['required']) {
return this._schema['required'].indexOf(name) != -1;
}
return false;
}
serialize(serializer: Serializer) { serializer.object(this); }
}
/** A Schema Tree Node that represents an array. */
export class ArraySchemaTreeNode extends NonLeafSchemaTreeNode<Array<any>> {
// The map of all items metadata.
protected _items: SchemaTreeNode<any>[];
protected _itemPrototype: SchemaTreeNode<any>;
constructor(metaData: TreeNodeConstructorArgument<Array<any>>) {
super(metaData);
this._set(metaData.value, true, false);
// Keep the item's schema as a schema node. This is important to keep type information.
this._itemPrototype = this._createChildProperty(
'', undefined, null, metaData.schema['items'], false);
}
_set(value: any, init: boolean, _force: boolean) {
const schema = this._schema;
const forward = this._forward;
this._value = Object.create(null);
this._dirty = this._dirty || !init;
if (value) {
this._defined = true;
} else {
this._defined = false;
value = [];
}
this._items = [];
this._value = [];
for (let index = 0; index < value.length; index++) {
this._items[index] = this._createChildProperty(
'' + index,
value && value[index],
forward && (forward as ArraySchemaTreeNode).items[index],
schema['items']
);
}
}
set(v: any, init = false, force = false) {
return this._set(v, init, force);
}
isCompatible(v: any) { return Array.isArray(v); }
get type() { return 'array'; }
get tsType() { return Array; }
get items(): SchemaTreeNode<any>[] { return this._items; }
get itemPrototype(): SchemaTreeNode<any> { return this._itemPrototype; }
get defaultValue(): any | null { return null; }
serialize(serializer: Serializer) { serializer.array(this); }
}
/**
* The root class of the tree node. Receives a prototype that will be filled with the
* properties of the Schema root.
*/
export class RootSchemaTreeNode extends ObjectSchemaTreeNode {
constructor(proto: any, metaData: TreeNodeConstructorArgument<Object>) {
super(metaData);
for (const key of Object.keys(this._children)) {
if (this._children[key]) {
SchemaTreeNode._defineProperty(proto, this._children[key]);
}
}
}
}
/** A leaf in the schema tree. Must contain a single primitive value. */
export abstract class LeafSchemaTreeNode<T> extends SchemaTreeNode<T> {
protected _default: T;
constructor(metaData: TreeNodeConstructorArgument<T>) {
super(metaData);
this._defined = metaData.value !== undefined;
if ('default' in metaData.schema) {
this._default = this.convert(metaData.schema['default']);
}
}
get() {
if (!this.defined && this._forward) {
return this._forward.get();
}
if (!this.defined) {
return 'default' in this._schema ? this._default : undefined;
}
return this._value === undefined
? undefined
: (this._value === null ? null : this.convert(this._value));
}
set(v: T, init = false, force = false) {
if (this.readOnly && !force) {
throw new SettingReadOnlyPropertyError();
}
let convertedValue: T | null = this.convert(v);
if (convertedValue === null || convertedValue === undefined) {
if (this.required) {
throw new InvalidValueError(`Invalid value "${v}" on a required field.`);
}
}
this.dirty = !init;
this._value = convertedValue;
}
destroy() {
this._defined = false;
this._value = null;
}
get defaultValue(): T {
return this.hasDefault ? this._default : null;
}
get hasDefault() {
return 'default' in this._schema;
}
abstract convert(v: any): T;
abstract isCompatible(v: any): boolean;
serialize(serializer: Serializer) {
serializer.outputValue(this);
}
}
/** Basic primitives for JSON Schema. */
class StringSchemaTreeNode extends LeafSchemaTreeNode<string> {
serialize(serializer: Serializer) { serializer.outputString(this); }
isCompatible(v: any) { return typeof v == 'string' || v instanceof String; }
convert(v: any) { return v === undefined ? undefined : '' + v; }
get type() { return 'string'; }
get tsType() { return String; }
}
class NullSchemaTreeNode extends LeafSchemaTreeNode<string> {
isCompatible(v: any) { return v == undefined || v == null; }
convert(_v: any): null { return null; }
get type() { return 'null'; }
get tsType() { return Object; }
}
class EnumSchemaTreeNode extends LeafSchemaTreeNode<any> {
constructor(metaData: TreeNodeConstructorArgument<any>) {
super(metaData);
if (!Array.isArray(metaData.schema['enum'])) {
throw new InvalidSchema();
}
if (this.hasDefault && !this._isInEnum(this._default)) {
throw new InvalidSchema();
}
this.set(metaData.value, true, true);
}
protected _isInEnum(value: string) {
return this._schema['enum'].some((v: string) => v === value);
}
get items() { return this._schema['enum']; }
set(value: string, init = false, force = false) {
if (!(value === undefined || this._isInEnum(value))) {
throw new InvalidUpdateValue('Invalid value can only be one of these: ' + this.items);
}
super.set(value, init, force);
}
isCompatible(v: any) {
return this._isInEnum(v);
}
convert(v: any) {
if (v === undefined) {
return undefined;
}
if (!this._isInEnum(v)) {
return undefined;
}
return v;
}
get type() {
return this._schema['type'] || 'any';
}
get tsType(): null { return null; }
serialize(serializer: Serializer) { serializer.outputEnum(this); }
}
class BooleanSchemaTreeNode extends LeafSchemaTreeNode<boolean> {
serialize(serializer: Serializer) { serializer.outputBoolean(this); }
isCompatible(v: any) { return typeof v == 'boolean' || v instanceof Boolean; }
convert(v: any) { return v === undefined ? undefined : !!v; }
get type() { return 'boolean'; }
get tsType() { return Boolean; }
}
class NumberSchemaTreeNode extends LeafSchemaTreeNode<number> {
serialize(serializer: Serializer) { serializer.outputNumber(this); }
isCompatible(v: any) { return typeof v == 'number' || v instanceof Number; }
convert(v: any) { return v === undefined ? undefined : +v; }
get type() { return 'number'; }
get tsType() { return Number; }
}
class IntegerSchemaTreeNode extends NumberSchemaTreeNode {
convert(v: any) { return v === undefined ? undefined : Math.floor(+v); }
}

@ -1,27 +0,0 @@
import {JsonSchemaErrorBase} from './error';
import {SchemaNode} from './node';
export class InvalidStateError extends JsonSchemaErrorBase {}
export interface WriterFn {
(str: string): void;
}
export abstract class Serializer {
abstract start(): void;
abstract end(): void;
abstract object(node: SchemaNode): void;
abstract property(node: SchemaNode): void;
abstract array(node: SchemaNode): void;
abstract outputOneOf(node: SchemaNode): void;
abstract outputEnum(node: SchemaNode): void;
abstract outputString(node: SchemaNode): void;
abstract outputNumber(node: SchemaNode): void;
abstract outputBoolean(node: SchemaNode): void;
// Fallback when the value does not have metadata.
abstract outputValue(node: SchemaNode): void;
}

@ -1,37 +0,0 @@
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {DTsSerializer} from './dts';
import {SchemaClassFactory} from '../schema-class-factory';
import {RootSchemaTreeNode} from '../schema-tree';
describe('DtsSerializer', () => {
for (const nb of [1, 2, 3]) {
it(`works (${nb})`, () => {
const schemaJsonFilePath = path.join(__dirname, `../../tests/serializer/schema${nb}.json`);
const schemaJson = JSON.parse(fs.readFileSync(schemaJsonFilePath, 'utf-8'));
const valueDTsFilePath = path.join(__dirname, `../../tests/serializer/value${nb}.d.ts`);
const valueDTs = fs.readFileSync(valueDTsFilePath, 'utf-8');
const valueSourceFile = ts.createSourceFile('test.d.ts', valueDTs, ts.ScriptTarget.Latest);
const schemaClass = new (SchemaClassFactory(schemaJson))({});
const schema: RootSchemaTreeNode = schemaClass.$$schema();
let str = '';
function writer(s: string) {
str += s;
}
const serializer = new DTsSerializer(writer);
serializer.start();
schema.serialize(serializer);
serializer.end();
const sourceFile = ts.createSourceFile('test.d.ts', str, ts.ScriptTarget.Latest);
expect(sourceFile).toEqual(valueSourceFile);
});
}
});

@ -1,165 +0,0 @@
import {SchemaNode} from '../node';
import {Serializer, WriterFn, InvalidStateError} from '../serializer';
interface DTsSerializerState {
empty?: boolean;
type?: string;
property?: boolean;
}
export class DTsSerializer implements Serializer {
private _state: DTsSerializerState[] = [];
constructor(private _writer: WriterFn, private interfaceName?: string, private _indentDelta = 4) {
if (interfaceName) {
_writer(`export interface ${interfaceName} `);
} else {
_writer('interface _ ');
}
}
private _willOutputValue() {
if (this._state.length > 0) {
const top = this._top();
top.empty = false;
if (!top.property) {
this._indent();
}
}
}
private _top(): DTsSerializerState {
return this._state[this._state.length - 1] || {};
}
private _indent(): string {
if (this._indentDelta == 0) {
return;
}
let str = '\n';
let i = this._state.length * this._indentDelta;
while (i--) {
str += ' ';
}
this._writer(str);
}
start() {}
end() {
if (this._indentDelta) {
this._writer('\n');
}
if (!this.interfaceName) {
this._writer('export default _;\n');
}
}
object(node: SchemaNode) {
this._willOutputValue();
this._writer('{');
this._state.push({ empty: true, type: 'object' });
for (const key of Object.keys(node.children)) {
this.property(node.children[key]);
}
// Fallback to direct value output for additional properties.
if (!node.frozen) {
this._indent();
this._writer('[name: string]: any;');
}
this._state.pop();
if (!this._top().empty) {
this._indent();
}
this._writer('}');
}
property(node: SchemaNode) {
this._willOutputValue();
if (node.description) {
this._writer('/**');
this._indent();
node.description.split('\n').forEach(line => {
this._writer(' * ' + line);
this._indent();
});
this._writer(' */');
this._indent();
}
this._writer(node.name);
if (!node.required) {
this._writer('?');
}
this._writer(': ');
this._top().property = true;
node.serialize(this);
this._top().property = false;
this._writer(';');
}
array(node: SchemaNode) {
this._willOutputValue();
node.itemPrototype.serialize(this);
this._writer('[]');
}
outputOneOf(node: SchemaNode) {
this._willOutputValue();
if (!node.items) {
throw new InvalidStateError();
}
this._writer('(');
for (let i = 0; i < node.items.length; i++) {
node.items[i].serialize(this);
if (i != node.items.length - 1) {
this._writer(' | ');
}
}
this._writer(')');
}
outputEnum(node: SchemaNode) {
this._willOutputValue();
this._writer('(');
for (let i = 0; i < node.items.length; i++) {
this._writer(JSON.stringify(node.items[i]));
if (i != node.items.length - 1) {
this._writer(' | ');
}
}
this._writer(')');
}
outputValue(_node: SchemaNode) {
this._willOutputValue();
this._writer('any');
}
outputString(_node: SchemaNode) {
this._willOutputValue();
this._writer('string');
}
outputNumber(_node: SchemaNode) {
this._willOutputValue();
this._writer('number');
}
outputInteger(_node: SchemaNode) {
this._willOutputValue();
this._writer('number');
}
outputBoolean(_node: SchemaNode) {
this._willOutputValue();
this._writer('boolean');
}
}

@ -1,34 +0,0 @@
import * as path from 'path';
import * as fs from 'fs';
import {JsonSerializer} from './json';
import {SchemaClassFactory} from '../schema-class-factory';
import {RootSchemaTreeNode} from '../schema-tree';
describe('JsonSerializer', () => {
for (const nb of [1, 2, 3]) {
it(`works (${nb})`, () => {
const schemaJsonFilePath = path.join(__dirname, `../../tests/serializer/schema${nb}.json`);
const schemaJson = JSON.parse(fs.readFileSync(schemaJsonFilePath, 'utf-8'));
const valueJsonFilePath = path.join(__dirname, `../../tests/serializer/value${nb}.json`);
const valueJson = JSON.parse(fs.readFileSync(valueJsonFilePath, 'utf-8'));
const schemaClass = new (SchemaClassFactory(schemaJson))(valueJson);
const schema: RootSchemaTreeNode = schemaClass.$$schema();
let str = '';
function writer(s: string) {
str += s;
}
const serializer = new JsonSerializer(writer);
serializer.start();
schema.serialize(serializer);
serializer.end();
expect(JSON.stringify(JSON.parse(str))).toEqual(JSON.stringify(valueJson));
});
}
});

@ -1,159 +0,0 @@
import {SchemaNode} from '../node';
import {Serializer, WriterFn} from '../serializer';
interface JsonSerializerState {
empty?: boolean;
type?: string;
property?: boolean;
}
export class JsonSerializer implements Serializer {
private _state: JsonSerializerState[] = [];
constructor(private _writer: WriterFn, private _indentDelta = 2) {}
private _willOutputValue() {
if (this._state.length > 0) {
const top = this._top();
const wasEmpty = top.empty;
top.empty = false;
if (!wasEmpty && !top.property) {
this._writer(',');
}
if (!top.property) {
this._indent();
}
}
}
private _top(): JsonSerializerState {
return this._state[this._state.length - 1] || {};
}
private _indent(): string {
if (this._indentDelta == 0) {
return;
}
let str = '\n';
let i = this._state.length * this._indentDelta;
while (i--) {
str += ' ';
}
this._writer(str);
}
start() {}
end() {
if (this._indentDelta) {
this._writer('\n');
}
}
object(node: SchemaNode) {
if (node.defined == false) {
return;
}
this._willOutputValue();
this._writer('{');
this._state.push({ empty: true, type: 'object' });
for (const key of Object.keys(node.children)) {
this.property(node.children[key]);
}
// Fallback to direct value output for additional properties.
if (!node.frozen) {
for (const key of Object.keys(node.value)) {
if (key in node.children) {
continue;
}
this._willOutputValue();
this._writer(JSON.stringify(key));
this._writer(': ');
this._writer(JSON.stringify(node.value[key]));
}
}
this._state.pop();
if (!this._top().empty) {
this._indent();
}
this._writer('}');
}
property(node: SchemaNode) {
if (node.defined == false) {
return;
}
this._willOutputValue();
this._writer(JSON.stringify(node.name));
this._writer(': ');
this._top().property = true;
node.serialize(this);
this._top().property = false;
}
array(node: SchemaNode) {
if (node.defined == false) {
return;
}
this._willOutputValue();
if (node.items.length === 0) {
this._writer('[]');
return;
}
this._writer('[');
this._state.push({ empty: true, type: 'array' });
for (let i = 0; i < node.items.length; i++) {
node.items[i].serialize(this);
}
this._state.pop();
if (!this._top().empty) {
this._indent();
}
this._writer(']');
}
outputOneOf(node: SchemaNode) {
this.outputValue(node);
}
outputEnum(node: SchemaNode) {
this.outputValue(node);
}
outputValue(node: SchemaNode) {
this._willOutputValue();
this._writer(JSON.stringify(node.value, null, this._indentDelta));
}
outputString(node: SchemaNode) {
this._willOutputValue();
this._writer(JSON.stringify(node.value));
}
outputNumber(node: SchemaNode) {
this._willOutputValue();
this._writer(JSON.stringify(node.value));
}
outputInteger(node: SchemaNode) {
this._willOutputValue();
this._writer(JSON.stringify(node.value));
}
outputBoolean(node: SchemaNode) {
this._willOutputValue();
this._writer(JSON.stringify(node.value));
}
}

@ -1,84 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "JsonSchema",
"type": "object",
"properties": {
"requiredKey": {
"type": "number"
},
"stringKeyDefault": {
"type": "string",
"default": "defaultValue"
},
"stringKey": {
"type": "string"
},
"booleanKey": {
"type": "boolean"
},
"numberKey": {
"type": "number"
},
"oneOfKey1": {
"oneOf": [
{ "type": "string" },
{ "type": "number" }
]
},
"oneOfKey2": {
"oneOf": [
{ "type": "string" },
{ "type": "array", "items": { "type": "string" } }
]
},
"objectKey1": {
"type": "object",
"properties": {
"stringKey": {
"type": "string"
},
"objectKey": {
"type": "object",
"properties": {
"stringKey": {
"type": "string"
}
}
}
}
},
"objectKey2": {
"type": "object",
"properties": {
"stringKey": {
"type": "string",
"default": "default objectKey2.stringKey"
}
},
"additionalProperties": true
},
"arrayKey1": {
"type": "array",
"items": {
"type": "object",
"properties": {
"stringKey": {
"type": "string"
}
}
}
},
"arrayKey2": {
"type": "array",
"items": {
"type": "object",
"properties": {
"stringKey": {
"type": "string"
}
}
}
}
},
"required": ["requiredKey"]
}

@ -1,17 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "JsonSchema",
"type": "object",
"properties": {
"a": {
"type": "array",
"items": {
"enum": [ "v1", "v2", "v3" ]
}
},
"b": {
"enum": [ "default", "v1", "v2" ],
"default": "default"
}
}
}

@ -1,84 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "JsonSchema",
"type": "object",
"properties": {
"requiredKey": {
"type": "number"
},
"stringKeyDefault": {
"type": "string",
"default": "defaultValue"
},
"stringKey": {
"type": "string"
},
"booleanKey": {
"type": "boolean"
},
"numberKey": {
"type": "number"
},
"oneOfKey1": {
"oneOf": [
{ "type": "string" },
{ "type": "number" }
]
},
"oneOfKey2": {
"oneOf": [
{ "type": "string" },
{ "type": "array", "items": { "type": "string" } }
]
},
"objectKey1": {
"type": "object",
"properties": {
"stringKey": {
"type": "string"
},
"objectKey": {
"type": "object",
"properties": {
"stringKey": {
"type": "string"
}
}
}
}
},
"objectKey2": {
"type": "object",
"properties": {
"stringKey": {
"type": "string",
"default": "default objectKey2.stringKey"
}
},
"additionalProperties": true
},
"arrayKey1": {
"type": "array",
"items": {
"type": "object",
"properties": {
"stringKey": {
"type": "string"
}
}
}
},
"arrayKey2": {
"type": "array",
"items": {
"type": "object",
"properties": {
"stringKey": {
"type": "string"
}
}
}
}
},
"required": ["requiredKey"]
}

@ -1,19 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "JsonSchema",
"type": "object",
"properties": {
"a": {
"type": "array",
"items": {
"enum": [ "v1", "v2", "v3" ]
}
},
"b": {
"type": "array",
"items": {
"enum": [ 0, 1, "string", true, null ]
}
}
}
}

@ -1,234 +0,0 @@
{
"$comment": "Please run `npm run build-config-interface` after changing this file. Thanks!",
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "CliConfig",
"title": "Angular CLI Config Schema",
"type": "object",
"properties": {
"project": {
"description": "The global configuration of the project.",
"type": "object",
"properties": {
"version": {
"type": "string"
},
"name": {
"type": "string"
}
},
"additionalProperties": false
},
"apps": {
"description": "Properties of the different applications in this project.",
"type": "array",
"items": {
"type": "object",
"properties": {
"root": {
"type": "string"
},
"outDir": {
"type": "string",
"default": "dist/"
},
"assets": {
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"default": []
},
"deployUrl": {
"type": "string"
},
"index": {
"type": "string",
"default": "index.html"
},
"main": {
"type": "string"
},
"test": {
"type": "string"
},
"tsconfig": {
"type": "string",
"default": "tsconfig.json"
},
"prefix": {
"type": "string"
},
"styles": {
"description": "Global styles to be included in the build.",
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"input": {
"type": "string"
}
},
"additionalProperties": true
}
]
},
"additionalProperties": false
},
"scripts": {
"description": "Global scripts to be included in the build.",
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"input": {
"type": "string"
}
},
"additionalProperties": true,
"required": ["input"]
}
]
},
"additionalProperties": false
},
"environments": {
"description": "Name and corresponding file for environment config.",
"type": "object",
"additionalProperties": true
}
},
"additionalProperties": false
},
"additionalProperties": false
},
"addons": {
"description": "Configuration reserved for installed third party addons.",
"type": "array",
"items": {
"type": "object",
"properties": {},
"additionalProperties": true
}
},
"packages": {
"description": "Configuration reserved for installed third party packages.",
"type": "array",
"items": {
"type": "object",
"properties": {},
"additionalProperties": true
}
},
"e2e": {
"type": "object",
"properties": {
"protractor": {
"type": "object",
"properties": {
"config": {
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"test": {
"type": "object",
"properties": {
"karma": {
"type": "object",
"properties": {
"config": {
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"defaults": {
"type": "object",
"properties": {
"styleExt": {
"type": "string"
},
"prefixInterfaces": {
"type": "boolean"
},
"poll": {
"type": "number"
},
"viewEncapsulation": {
"type": "string"
},
"changeDetection": {
"type": "string"
},
"inline": {
"type": "object",
"properties": {
"style": {
"type": "boolean",
"default": false
},
"template": {
"type": "boolean",
"default": false
}
}
},
"spec": {
"type": "object",
"properties": {
"class": {
"type": "boolean",
"default": false
},
"component": {
"type": "boolean",
"default": true
},
"directive": {
"type": "boolean",
"default": true
},
"module": {
"type": "boolean",
"default": false
},
"pipe": {
"type": "boolean",
"default": true
},
"service": {
"type": "boolean",
"default": true
}
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}

@ -1,26 +0,0 @@
interface _ {
requiredKey: number;
stringKeyDefault?: string;
stringKey?: string;
booleanKey?: boolean;
numberKey?: number;
oneOfKey1?: (string | number);
oneOfKey2?: (string | string[]);
objectKey1?: {
stringKey?: string;
objectKey?: {
stringKey?: string;
};
};
objectKey2?: {
stringKey?: string;
[name: string]: any;
};
arrayKey1?: {
stringKey?: string;
}[];
arrayKey2?: {
stringKey?: string;
}[];
}
export default _;

@ -1,7 +0,0 @@
{
"requiredKey": 1,
"arrayKey2": [
{ "stringKey": "value1" },
{ "stringKey": "value2" }
]
}

@ -1,5 +0,0 @@
interface _ {
a?: ("v1" | "v2" | "v3")[];
b?: (0 | 1 | "string" | true | null)[];
}
export default _;

@ -1,14 +0,0 @@
{
"a": [
"v2",
"v1",
"v2",
"v3"
],
"b": [
1,
null,
"string",
true
]
}

@ -1,85 +0,0 @@
interface _ {
/**
* The global configuration of the project.
*/
project?: {
version?: string;
name?: string;
};
/**
* Properties of the different applications in this project.
*/
apps?: {
root?: string;
outDir?: string;
assets?: (string | string[]);
deployUrl?: string;
index?: string;
main?: string;
test?: string;
tsconfig?: string;
prefix?: string;
/**
* Global styles to be included in the build.
*/
styles?: (string | {
input?: string;
[name: string]: any;
})[];
/**
* Global scripts to be included in the build.
*/
scripts?: (string | {
input: string;
[name: string]: any;
})[];
/**
* Name and corresponding file for environment config.
*/
environments?: {
[name: string]: any;
};
}[];
/**
* Configuration reserved for installed third party addons.
*/
addons?: {
[name: string]: any;
}[];
/**
* Configuration reserved for installed third party packages.
*/
packages?: {
[name: string]: any;
}[];
e2e?: {
protractor?: {
config?: string;
};
};
test?: {
karma?: {
config?: string;
};
};
defaults?: {
styleExt?: string;
prefixInterfaces?: boolean;
poll?: number;
viewEncapsulation?: string;
changeDetection?: string;
inline?: {
style?: boolean;
template?: boolean;
};
spec?: {
class?: boolean;
component?: boolean;
directive?: boolean;
module?: boolean;
pipe?: boolean;
service?: boolean;
};
};
}
export default _;

@ -1,56 +0,0 @@
{
"project": {
"version": "<%= version %>",
"name": "<%= htmlComponentName %>"
},
"apps": [
{
"root": "<%= sourceDir %>",
"outDir": "dist",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"test": "test.ts",
"tsconfig": "tsconfig.json",
"prefix": "<%= prefix %>",
"styles": [
"styles.<%= styleExt %>"
],
"scripts": [],
"environments": {
"source": "environments/environment.ts",
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "<%= styleExt %>",
"prefixInterfaces": false,
"inline": {
"style": false,
"template": false
},
"spec": {
"class": false,
"component": true,
"directive": false,
"module": false,
"pipe": true,
"service": false
}
}
}

@ -1,8 +0,0 @@
{
"requiredKey": 1,
"arrayKey2": [
{ "stringKey": "value1" },
{ "stringKey": "value2" }
],
"oneOfKey2": [ "hello", "world" ]
}

@ -1,7 +0,0 @@
{
"requiredKey": 1,
"arrayKey2": [
{ "stringKey": "value1" },
{ "stringKey": "value2" }
]
}

@ -1,6 +0,0 @@
{
"a": [
"v1",
"v3"
]
}

@ -1,9 +0,0 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/@ngtools/json-schema",
"rootDir": ".",
"baseUrl": ""
}
}

@ -1,31 +0,0 @@
{
"name": "@ngtools/logger",
"version": "1.1.2",
"description": "",
"main": "./src/index.js",
"typings": "./src/index.d.ts",
"license": "MIT",
"keywords": [
"reporter",
"logger",
"rxjs",
"typescript",
"log"
],
"repository": {
"type": "git",
"url": "https://github.com/angular/angular-cli.git"
},
"author": "angular",
"bugs": {
"url": "https://github.com/angular/angular-cli/issues"
},
"homepage": "https://github.com/angular/angular-cli/tree/master/packages/@ngtools/logger",
"engines": {
"node": ">= 8.9.0",
"npm": ">= 5.5.1"
},
"dependencies": {
"rxjs": "^5.0.1"
}
}

@ -1,76 +0,0 @@
import {LogEntry, Logger} from './logger';
import {ConsoleLoggerStack} from './console-logger-stack';
import {NullLogger} from './null-logger';
import {toArray} from 'rxjs/operators';
describe('ConsoleLoggerStack', () => {
it('works', (done: DoneFn) => {
const logger = ConsoleLoggerStack.start('test');
logger.pipe(toArray())
.toPromise()
.then((observed: LogEntry[]) => {
expect(observed).toEqual([
jasmine.objectContaining({ message: 'hello', level: 'debug', name: 'test' }),
jasmine.objectContaining({ message: 'world', level: 'info', name: 'test' }),
]);
})
.then(() => done(), (err: any) => done.fail(err));
(console as any).debug('hello');
console.log('world');
ConsoleLoggerStack.end();
});
it('works as a stack', (done: DoneFn) => {
const oldConsoleLog = console.log;
const logger = ConsoleLoggerStack.start('test');
expect(console.log).not.toBe(oldConsoleLog);
logger.pipe(toArray())
.toPromise()
.then((observed: LogEntry[]) => {
expect(observed).toEqual([
jasmine.objectContaining({ message: 'red', level: 'info', name: 'test' }),
jasmine.objectContaining({ message: 'blue', level: 'info', name: 'test2' }),
jasmine.objectContaining({ message: 'yellow', level: 'info', name: 'test3' }),
jasmine.objectContaining({ message: 'green', level: 'info', name: 'test2' }),
]);
})
.then(() => done(), (err: any) => done.fail(err));
console.log('red');
ConsoleLoggerStack.push('test2');
console.log('blue');
ConsoleLoggerStack.push('test3');
console.log('yellow');
ConsoleLoggerStack.pop();
console.log('green');
ConsoleLoggerStack.end();
expect(console.log).toBe(oldConsoleLog);
});
it('can push instances or classes', (done: DoneFn) => {
const oldConsoleLog = console.log;
const logger = new Logger('test');
ConsoleLoggerStack.start(logger);
expect(console.log).not.toBe(oldConsoleLog);
logger.pipe(toArray())
.toPromise()
.then((observed: LogEntry[]) => {
expect(observed).toEqual([
jasmine.objectContaining({ message: 'red', level: 'info', name: 'test' }),
jasmine.objectContaining({ message: 'green', level: 'info', name: 'test2' }),
]);
})
.then(() => done(), (err: any) => done.fail(err));
console.log('red');
ConsoleLoggerStack.push(new NullLogger(logger));
console.log('blue');
ConsoleLoggerStack.pop();
ConsoleLoggerStack.push(new Logger('test2', logger));
console.log('green');
ConsoleLoggerStack.end();
expect(console.log).toBe(oldConsoleLog);
});
});

@ -1,123 +0,0 @@
import {Logger} from './logger';
type ConsoleWriter = (message?: any, ...optionalParams: any[]) => void;
let globalConsoleStack: Logger[] | null = null;
let originalConsoleDebug: ConsoleWriter;
let originalConsoleLog: ConsoleWriter;
let originalConsoleWarn: ConsoleWriter;
let originalConsoleError: ConsoleWriter;
function _push(logger: Logger): Logger {
if (!globalConsoleStack) {
throw new Error('ConsoleLoggerStack must be started before pushing a logger.');
}
if (globalConsoleStack.length == 0) {
originalConsoleDebug = (console as any).debug; // Some environment (node) don't have debug.
originalConsoleLog = console.log;
originalConsoleWarn = console.warn;
originalConsoleError = console.error;
(console as any).debug = (msg: string, ...args: any[]) => {
const logger = ConsoleLoggerStack.top();
if (logger) {
logger.debug(msg, { args });
}
};
console.log = (msg: string, ...args: any[]) => {
const logger = ConsoleLoggerStack.top();
if (logger) {
logger.info(msg, { args });
}
};
console.warn = (msg: string, ...args: any[]) => {
const logger = ConsoleLoggerStack.top();
if (logger) {
logger.warn(msg, { args });
}
};
console.error = (msg: string, ...args: any[]) => {
const logger = ConsoleLoggerStack.top();
if (logger) {
logger.error(msg, { args });
}
};
}
globalConsoleStack.push(logger);
return logger;
}
function _pop() {
if (!globalConsoleStack) {
return;
}
globalConsoleStack[globalConsoleStack.length - 1].complete();
globalConsoleStack.pop();
if (globalConsoleStack.length == 0) {
console.log = originalConsoleLog;
console.warn = originalConsoleWarn;
console.error = originalConsoleError;
(console as any).debug = originalConsoleDebug; // Some environment (node) don't have debug.
globalConsoleStack = null;
}
}
export type LoggerConstructor<T extends Logger> = {
new (...args: any[]): T;
};
export class ConsoleLoggerStack {
static push(name: string): Logger;
static push(logger: Logger): Logger;
static push<T extends Logger>(loggerClass: LoggerConstructor<T>, ...args: any[]): Logger;
static push<T extends Logger>(nameOrLogger: string | Logger | LoggerConstructor<T> = '',
...args: any[]): Logger {
if (typeof nameOrLogger == 'string') {
return _push(new Logger(nameOrLogger, this.top()));
} else if (nameOrLogger instanceof Logger) {
if (nameOrLogger.parent !== this.top()) {
throw new Error('Pushing a logger that is not a direct child of the top of the stack.');
}
return _push(nameOrLogger);
} else {
return _push(new nameOrLogger(...args, this.top()));
}
}
static pop(): Logger | null {
_pop();
return this.top();
}
static top(): Logger | null {
return globalConsoleStack && globalConsoleStack[globalConsoleStack.length - 1];
}
static start(name: string): Logger;
static start(logger: Logger): Logger;
static start<T extends Logger>(loggerClass: LoggerConstructor<T>, ...args: any[]): Logger;
static start<T extends Logger>(nameOrLogger: string | Logger | LoggerConstructor<T> = '',
...args: any[]): Logger {
if (globalConsoleStack !== null) {
throw new Error('Cannot start a new console logger stack while one is already going.');
}
globalConsoleStack = [];
if (typeof nameOrLogger == 'string') {
return _push(new Logger(nameOrLogger, this.top()));
} else if (nameOrLogger instanceof Logger) {
return _push(nameOrLogger);
} else {
return _push(new nameOrLogger(...args, this.top()));
}
}
static end() {
while (globalConsoleStack !== null) {
this.pop();
}
}
}

@ -1,33 +0,0 @@
import {LogEntry, Logger} from './logger';
import {IndentLogger} from './indent';
import {toArray} from 'rxjs/operators';
describe('IndentSpec', () => {
it('works', (done: DoneFn) => {
const logger = new IndentLogger('test');
logger.pipe(toArray())
.toPromise()
.then((observed: LogEntry[]) => {
expect(observed).toEqual([
jasmine.objectContaining({ message: 'test', level: 'info', name: 'test' }),
jasmine.objectContaining({ message: ' test2', level: 'info', name: 'test2' }),
jasmine.objectContaining({ message: ' test3', level: 'info', name: 'test3' }),
jasmine.objectContaining({ message: ' test4', level: 'info', name: 'test4' }),
jasmine.objectContaining({ message: 'test5', level: 'info', name: 'test' }),
]);
})
.then(() => done(), (err: any) => done.fail(err));
const logger2 = new Logger('test2', logger);
const logger3 = new Logger('test3', logger2);
const logger4 = new Logger('test4', logger);
logger.info('test');
logger2.info('test2');
logger3.info('test3');
logger4.info('test4');
logger.info('test5');
logger.complete();
});
});

@ -1,36 +0,0 @@
import { map } from 'rxjs/operators';
import {Logger} from './logger';
/**
* Keep an map of indentation => array of indentations based on the level.
* This is to optimize calculating the prefix based on the indentation itself. Since most logs
* come from similar levels, and with similar indentation strings, this will be shared by all
* loggers. Also, string concatenation is expensive so performing concats for every log entries
* is expensive; this alleviates it.
*/
const indentationMap: {[indentationType: string]: string[]} = {};
export class IndentLogger extends Logger {
constructor(name: string, parent: Logger | null = null, indentation = ' ') {
super(name, parent);
indentationMap[indentation] = indentationMap[indentation] || [''];
const indentMap = indentationMap[indentation];
this._observable = this._observable.pipe(map(entry => {
const l = entry.path.length;
if (l >= indentMap.length) {
let current = indentMap[indentMap.length - 1];
while (l >= indentMap.length) {
current += indentation;
indentMap.push(current);
}
}
entry.message = indentMap[l] + entry.message;
return entry;
}));
}
}

@ -1,6 +0,0 @@
export * from './console-logger-stack';
export * from './indent';
export * from './logger';
export * from './null-logger';
export * from './transform-logger';

@ -1,43 +0,0 @@
import {Logger, JsonValue} from './logger';
import {toArray} from 'rxjs/operators';
describe('Logger', () => {
it('works', (done: DoneFn) => {
const logger = new Logger('test');
logger.pipe(toArray())
.toPromise()
.then((observed: JsonValue[]) => {
expect(observed).toEqual([
jasmine.objectContaining({ message: 'hello', level: 'debug', name: 'test' }),
jasmine.objectContaining({ message: 'world', level: 'info', name: 'test' }),
]);
})
.then(() => done(), (err: any) => done.fail(err));
logger.debug('hello');
logger.info('world');
logger.complete();
});
it('works with children', (done: DoneFn) => {
const logger = new Logger('test');
let hasCompleted = false;
logger.pipe(toArray())
.toPromise()
.then((observed: JsonValue[]) => {
expect(observed).toEqual([
jasmine.objectContaining({ message: 'hello', level: 'debug', name: 'child' }),
jasmine.objectContaining({ message: 'world', level: 'info', name: 'child' }),
]);
expect(hasCompleted).toBe(true);
})
.then(() => done(), (err: any) => done.fail(err));
const childLogger = new Logger('child', logger);
childLogger.subscribe(undefined, undefined, () => hasCompleted = true);
childLogger.debug('hello');
childLogger.info('world');
logger.complete();
});
});

@ -1,122 +0,0 @@
import {Observable} from 'rxjs/Observable';
import {Operator} from 'rxjs/Operator';
import {PartialObserver} from 'rxjs/Observer';
import {Subject} from 'rxjs/Subject';
import {Subscription} from 'rxjs/Subscription';
export type JsonValue = boolean | number | string | JsonObject | JsonArray;
export interface JsonObject {
[key: string]: JsonValue;
}
export interface JsonArray extends Array<JsonValue> {}
export interface LoggerMetadata extends JsonObject {
name: string;
path: string[];
}
export interface LogEntry extends LoggerMetadata {
level: LogLevel;
message: string;
timestamp: number;
}
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
export class Logger extends Observable<LogEntry> {
protected readonly _subject: Subject<LogEntry> = new Subject<LogEntry>();
protected _metadata: LoggerMetadata;
private _obs: Observable<LogEntry>;
private _subscription: Subscription | null;
protected get _observable() { return this._obs; }
protected set _observable(v: Observable<LogEntry>) {
if (this._subscription) {
this._subscription.unsubscribe();
}
this._obs = v;
if (this.parent) {
this._subscription = this.subscribe((value: LogEntry) => {
if (this.parent) {
this.parent._subject.next(value);
}
}, (error: any) => {
if (this.parent) {
this.parent._subject.error(error);
}
}, () => {
if (this._subscription) {
this._subscription.unsubscribe();
}
this._subscription = null;
});
}
}
constructor(public readonly name: string, public readonly parent: Logger | null = null) {
super();
let path: string[] = [];
let p = parent;
while (p) {
path.push(p.name);
p = p.parent;
}
this._metadata = { name, path };
this._observable = this._subject.asObservable();
if (this.parent) {
// When the parent completes, complete us as well.
this.parent._subject.subscribe(undefined, undefined, () => this.complete());
}
}
complete() {
this._subject.complete();
}
log(level: LogLevel, message: string, metadata: JsonObject = {}): void {
const entry: LogEntry = Object.assign({}, this._metadata, metadata, {
level, message, timestamp: +Date.now()
});
this._subject.next(entry);
}
debug(message: string, metadata: JsonObject = {}) {
return this.log('debug', message, metadata);
}
info(message: string, metadata: JsonObject = {}) {
return this.log('info', message, metadata);
}
warn(message: string, metadata: JsonObject = {}) {
return this.log('warn', message, metadata);
}
error(message: string, metadata: JsonObject = {}) {
return this.log('error', message, metadata);
}
fatal(message: string, metadata: JsonObject = {}) {
return this.log('fatal', message, metadata);
}
toString() {
return `<Logger(${this.name})>`;
}
lift<R>(operator: Operator<LogEntry, R>): Observable<R> {
return this._observable.lift(operator);
}
subscribe(): Subscription;
subscribe(observer: PartialObserver<LogEntry>): Subscription;
subscribe(next?: (value: LogEntry) => void, error?: (error: any) => void,
complete?: () => void): Subscription;
subscribe(_observerOrNext?: PartialObserver<LogEntry> | ((value: LogEntry) => void),
_error?: (error: any) => void,
_complete?: () => void): Subscription {
return this._observable.subscribe.apply(this._observable, arguments);
}
forEach(next: (value: LogEntry) => void, PromiseCtor?: typeof Promise): Promise<void> {
return this._observable.forEach(next, PromiseCtor);
}
}

@ -1,36 +0,0 @@
import {NullLogger} from './null-logger';
import {LogEntry, Logger} from './logger';
import {toArray} from 'rxjs/operators';
describe('NullLogger', () => {
it('works', (done: DoneFn) => {
const logger = new NullLogger();
logger.pipe(toArray())
.toPromise()
.then((observed: LogEntry[]) => {
expect(observed).toEqual([]);
})
.then(() => done(), (err: any) => done.fail(err));
logger.debug('hello');
logger.info('world');
logger.complete();
});
it('nullifies children', (done: DoneFn) => {
const logger = new Logger('test');
logger.pipe(toArray())
.toPromise()
.then((observed: LogEntry[]) => {
expect(observed).toEqual([]);
})
.then(() => done(), (err: any) => done.fail(err));
const nullLogger = new NullLogger(logger);
const child = new Logger('test', nullLogger);
child.debug('hello');
child.info('world');
logger.complete();
});
});

@ -1,10 +0,0 @@
import { empty } from 'rxjs/observable/empty';
import { Logger } from './logger';
export class NullLogger extends Logger {
constructor(parent: Logger | null = null) {
super('', parent);
this._observable = empty();
}
}

@ -1,29 +0,0 @@
import {TransformLogger} from './transform-logger';
import {LogEntry} from './logger';
import {filter, map, toArray} from 'rxjs/operators';
describe('TransformLogger', () => {
it('works', (done: DoneFn) => {
const logger = new TransformLogger('test', stream => {
return stream.pipe(
filter(entry => entry.message != 'hello'),
map(entry => {
entry.message += '1';
return entry;
}));
});
logger.pipe(toArray())
.toPromise()
.then((observed: LogEntry[]) => {
expect(observed).toEqual([
jasmine.objectContaining({ message: 'world1', level: 'info', name: 'test' }),
]);
})
.then(() => done(), (err: any) => done.fail(err));
logger.debug('hello');
logger.info('world');
logger.complete();
});
});

@ -1,13 +0,0 @@
import {Observable} from 'rxjs/Observable';
import {Logger, LogEntry} from './logger';
export class TransformLogger extends Logger {
constructor(name: string,
transform: (stream: Observable<LogEntry>) => Observable<LogEntry>,
parent: Logger | null = null) {
super(name, parent);
this._observable = transform(this._observable);
}
}

@ -1,9 +0,0 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/@ngtools/logger",
"rootDir": ".",
"baseUrl": ""
}
}

@ -5,7 +5,8 @@ require('../lib/bootstrap-local');
const validateCommitMessage = require('./validate-commit-message');
const execSync = require('child_process').execSync;
const chalk = require('chalk');
const Logger = require('@ngtools/logger').Logger;
const { logging } = require('@angular-devkit/core');
const Logger = logging.Logger;
const filter = require('rxjs/operators').filter;
// Configure logger

@ -3,7 +3,8 @@ require('../lib/bootstrap-local');
const path = require('path');
const chalk = require('chalk');
const spdxSatisfies = require('spdx-satisfies');
const Logger = require('@ngtools/logger').Logger;
const { logging } = require('@angular-devkit/core');
const Logger = logging.Logger;
const filter = require('rxjs/operators').filter;
// Configure logger
@ -23,7 +24,7 @@ logger.subscribe((entry) => {
});
logger
.pipe(filter((entry) => entry.level == 'fatal'))
.pipe(filter((entry) => entry.level === 'fatal'))
.subscribe(() => {
process.stderr.write('A fatal error happened. See details above.');
process.exit(1);

@ -1,11 +1,7 @@
// This may seem awkward but we're using Logger in our e2e. At this point the unit tests
// have run already so it should be "safe", teehee.
import {
ConsoleLoggerStack,
LogEntry,
IndentLogger,
NullLogger
} from '../packages/@ngtools/logger/src/index';
import { logging } from '@angular-devkit/core';
import { createConsoleLogger } from "@angular-devkit/core/node";
import {blue, bold, green, red, yellow, white} from 'chalk';
import {gitClean} from './e2e/utils/git';
import * as glob from 'glob';
@ -63,7 +59,7 @@ const argv = minimist(process.argv.slice(2), {
* Set the error code of the process to 255. This is to ensure that if something forces node
* to exit without finishing properly, the error code will be 255. Right now that code is not used.
*
* When tests succeed we already call `process.exit(0)`, so this doesn't change any correct
* - 1 When tests succeed we already call `process.exit(0)`, so this doesn't change any correct
* behaviour.
*
* One such case that would force node <= v6 to exit with code 0, is a Promise that doesn't resolve.
@ -71,20 +67,36 @@ const argv = minimist(process.argv.slice(2), {
process.exitCode = 255;
ConsoleLoggerStack.start(new IndentLogger('name'))
.pipe(filter((entry: LogEntry) => (entry.level != 'debug' || argv.verbose)))
.subscribe((entry: LogEntry) => {
let color: (s: string) => string = white;
let output = process.stdout;
switch (entry.level) {
case 'info': color = white; break;
case 'warn': color = yellow; break;
case 'error': color = red; output = process.stderr; break;
case 'fatal': color = (x: string) => bold(red(x)); output = process.stderr; break;
}
- 1const logger = createConsoleLogger(argv.verbose);
const logStack = [logger];
function lastLogger() {
return logStack[logStack.length - 1];
}
output.write(color(entry.message) + '\n');
});
(console as any).debug = (msg: string, ...args: any[]) => {
const logger = lastLogger();
if (logger) {
logger.debug(msg, { args });
}
};
console.log = (msg: string, ...args: any[]) => {
const logger = lastLogger();
if (logger) {
logger.info(msg, { args });
}
};
console.warn = (msg: string, ...args: any[]) => {
const logger = lastLogger();
if (logger) {
logger.warn(msg, { args });
}
};
console.error = (msg: string, ...args: any[]) => {
const logger = lastLogger();
if (logger) {
logger.error(msg, { args });
}
};
const testGlob = argv.glob || 'tests/**/*.ts';
let currentFileName = null;
@ -174,9 +186,9 @@ testsToRun.reduce((previous, relativeName, testIndex) => {
return Promise.resolve()
.then(() => printHeader(currentFileName, testIndex))
.then(() => previousDir = process.cwd())
.then(() => ConsoleLoggerStack.push(currentFileName))
.then(() => logStack.push(lastLogger().createChild(currentFileName)))
.then(() => fn(() => clean = false))
.then(() => ConsoleLoggerStack.pop(), (err: any) => { ConsoleLoggerStack.pop(); throw err; })
.then(() => logStack.pop(), (err: any) => { logStack.pop(); throw err; })
.then(() => console.log('----'))
.then(() => {
// If we're not in a setup, change the directory back to where it was before the test.
@ -189,10 +201,10 @@ testsToRun.reduce((previous, relativeName, testIndex) => {
// Only clean after a real test, not a setup step. Also skip cleaning if the test
// requested an exception.
if (allSetups.indexOf(relativeName) == -1 && clean) {
ConsoleLoggerStack.push(NullLogger);
logStack.push(new logging.NullLogger());
return gitClean()
.then(() => ConsoleLoggerStack.pop(), (err: any) => {
ConsoleLoggerStack.pop();
.then(() => logStack.pop(), (err: any) => {
logStack.pop();
throw err;
});
}

@ -1,9 +1,9 @@
import * as fs from 'fs';
import {SchemaClassFactory} from '@ngtools/json-schema';
import {Logger} from '@ngtools/logger';
import { logging } from '@angular-devkit/core';
import { SchemaClassFactory } from '@ngtools/json-schema';
export function buildSchema(inFile: string, _logger: Logger): string {
export function buildSchema(inFile: string, _logger: logging.Logger): string {
const jsonSchema = JSON.parse(fs.readFileSync(inFile, 'utf-8'));
const SchemaClass = SchemaClassFactory(jsonSchema);
const schemaInstance = new SchemaClass({});
@ -12,7 +12,7 @@ export function buildSchema(inFile: string, _logger: Logger): string {
}
export function build(args: string[], _opts: any, logger: Logger): void {
export function build(args: string[], _opts: any, logger: logging.Logger): void {
const inFile = args[1] as string;
const outFile = args[2] as string;
if (!inFile) {

@ -1,4 +1,4 @@
import {Logger} from '@ngtools/logger';
import { logging } from '@angular-devkit/core';
import * as fs from 'fs-extra';
import * as path from 'path';
import { promisify } from 'util';
@ -59,7 +59,7 @@ function getDeps(pkg: any): any {
export default function build(packagesToBuild: string[],
opts: { local: boolean, devkit: string },
logger: Logger): Promise<void> {
logger: logging.Logger): Promise<void> {
const { packages, tools } = require('../../../lib/packages');
const willBuildEverything = packagesToBuild.length == 0;
@ -83,7 +83,7 @@ export default function build(packagesToBuild: string[],
})
.then(() => logger.info('Compiling packages...'))
.then(() => {
const packagesLogger = new Logger('packages', logger);
const packagesLogger = new logging.Logger('packages', logger);
// Order packages in order of dependency.
// We use bubble sort because we need a full topological sort but adding another dependency
// or implementing a full topo sort would be too much work and I'm lazy. We don't anticipate
@ -125,7 +125,7 @@ export default function build(packagesToBuild: string[],
})
.then(() => logger.info('Compiling tools...'))
.then(() => {
const toolsLogger = new Logger('packages', logger);
const toolsLogger = new logging.Logger('packages', logger);
return Object.keys(tools)
.filter(toolName => packagesToBuild.indexOf(toolName) != -1)
@ -221,7 +221,7 @@ export default function build(packagesToBuild: string[],
// Copy LICENSE into all the packages
logger.info('Copying LICENSE...');
const licenseLogger = new Logger('license', logger);
const licenseLogger = new logging.Logger('license', logger);
return Promise.all(Object.keys(packages).map(pkgName => {
const pkg = packages[pkgName];
licenseLogger.info(pkgName);
@ -277,7 +277,7 @@ export default function build(packagesToBuild: string[],
.then(() => {
logger.info('Tarring all packages...');
const tarLogger = new Logger('license', logger);
const tarLogger = new logging.Logger('license', logger);
return Promise.all(Object.keys(packages).map(pkgName => {
const pkg = packages[pkgName];
tarLogger.info(`${pkgName} => ${pkg.tar}`);

@ -8,7 +8,7 @@
* see:
* https://github.com/conventional-changelog/conventional-changelog/blob/v0.2.1/presets/angular.js
*/
import {Logger} from '@ngtools/logger';
import { logging } from '@angular-devkit/core';
import * as fs from 'fs';
const cl = require('conventional-changelog');
@ -30,7 +30,7 @@ function prependDelta() {
}
export default function changelog(args: string[], _opts: any, logger: Logger): void {
export default function changelog(args: string[], _opts: any, logger: logging.Logger): void {
if (args.length == 0) {
logger.fatal('publish changelog <start-tag>');
return;

@ -1,8 +1,7 @@
import {IndentLogger, LogEntry} from '@ngtools/logger';
import { logging } from '@angular-devkit/core';
import chalk from 'chalk';
import * as minimist from 'minimist';
import {filter} from 'rxjs/operators';
@ -12,11 +11,11 @@ const argv = minimist(process.argv.slice(2), {
boolean: ['verbose']
});
const rootLogger = new IndentLogger('cling');
const rootLogger = new logging.IndentLogger('cling');
rootLogger
.pipe(filter((entry: LogEntry) => (entry.level != 'debug' || argv['verbose'])))
.subscribe((entry: LogEntry) => {
.pipe(filter(entry => (entry.level != 'debug' || argv['verbose'])))
.subscribe(entry => {
let color: (s: string) => string = white;
let output = process.stdout;
switch (entry.level) {
@ -30,7 +29,7 @@ rootLogger
});
rootLogger
.pipe(filter((entry: LogEntry) => entry.level == 'fatal'))
.pipe(filter(entry => entry.level == 'fatal'))
.subscribe(() => {
process.stderr.write('A fatal error happened. See details above.');
process.exit(100);

@ -1,9 +1,9 @@
import {Logger} from '@ngtools/logger';
import { logging } from '@angular-devkit/core';
import * as fs from 'fs';
import * as path from 'path';
import {SemVer} from 'semver';
export default function patch(args: string[], opts: any, logger: Logger): void {
export default function patch(args: string[], opts: any, logger: logging.Logger): void {
const newVersion = args[0];
const incFn = opts.patch ? (v: SemVer) => v.inc('patch') : (v: SemVer) => v;