Skip to content

feat(config): ngConfig #452

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions addon/ng2/commands/get.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
import * as chalk from 'chalk';
import * as Command from 'ember-cli/lib/models/command';
import {CliConfig} from '../models/config';
import {Config} from '../models/config';


const GetCommand = Command.extend({
name: 'get',
description: 'Set a value in the configuration.',
works: 'outsideProject',
description: 'Get a value from the configuration.',
works: 'everywhere',

availableOptions: [],

run: function (commandOptions, rawArgs): Promise<void> {
return new Promise(resolve => {
const value = new CliConfig().get(rawArgs[0]);
if (value === null) {
const config = new Config();
config.validatePath(rawArgs[0]);
const val = config.get(config.config, rawArgs[0]);

if (val === null) {
console.error(chalk.red('Value cannot be found.'));
} else if (typeof value == 'object') {
console.log(JSON.stringify(value));
} else {
console.log(value);
console.log(val);
}

resolve();
});
}
});

module.exports = GetCommand;
module.exports.overrideCore = true;
15 changes: 10 additions & 5 deletions addon/ng2/commands/set.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import * as Command from 'ember-cli/lib/models/command';
import {CliConfig} from '../models/config';
import {Config} from '../models/config';


const SetCommand = Command.extend({
name: 'set',
description: 'Set a value in the configuration.',
works: 'outsideProject',
works: 'everywhere',

availableOptions: [
{ name: 'global', type: Boolean, default: false, aliases: ['g'] },
],

run: function (commandOptions, rawArgs): Promise<void> {
return new Promise(resolve => {
const config = new CliConfig();
config.set(rawArgs[0], rawArgs[1], commandOptions.force);
let config = new Config();

for (let arg of rawArgs) {
let [key, value] = arg.split('=');
config.validatePath(key);
config.set(key, value);
}

config.save();
resolve();
});
}
});

module.exports = SetCommand;
module.exports.overrideCore = true;
221 changes: 141 additions & 80 deletions addon/ng2/models/config.ts
Original file line number Diff line number Diff line change
@@ -1,125 +1,186 @@
import * as fs from 'fs';
import * as path from 'path';

import * as Proxy from 'harmony-proxy';
import * as Reflect from 'harmony-reflect';
import * as ObjectAssign from '../utilities/object-assign';

export const CLI_CONFIG_FILE_NAME = 'angular-cli.json';

export const ArrayMethods: Array<string> = ['pop', 'push', 'reverse', 'shift', 'sort', 'unshift'];

export interface CliConfigJson {
routes?: { [name: string]: any },
packages?: { [name: string]: any }
}
export const handler = {
get: function(target: any, key: any, receiver: any) {
if (key === 'toJSON') {
return () => target;
}

if (key === 'length') {
return;
}

function _findUp(name: string, from: string) {
let currentDir = from;
while (currentDir && currentDir != '/') {
const p = path.join(currentDir, name);
if (fs.existsSync(p)) {
return p;
if (key === 'inspect') {
return target;
}

currentDir = path.resolve(currentDir, '..');
}
if (target.type === 'array') {
const arr: Array<any> = [];
arr[key].call(target.enum);
}

return null;
}
if (ArrayMethods.indexOf(key) === -1) {
if (!(key in target)) {
target[key] = new Proxy({ type: 'object' }, handler);
}
return Reflect.get(target, key, receiver);
} else {
return Reflect.get([], key, receiver);
}
},
set: function(target: any, key: any, value: any) {
if (key === 'length') {
return;
}

if (!isNaN(key) && parseInt(key, 10) === 0) {
if (!target.enum) {
target.type = 'array';
target.enum = [];
}

export class CliConfig {
constructor(private _config: CliConfigJson = CliConfig.fromProject()) {}
if (value) {
target.enum.push(value);
}
} else {
if (target[key] && target[key].type) {
let type: string = target[key].type;
let assigningType: string;

if (!value && value === null) {
assigningType = 'null';
} else if (value && Array.isArray(value) && typeof value !== 'string') {
assigningType = 'array';
} else {
assigningType = typeof value;
}

save(path: string = CliConfig.configFilePath()) {
if (!path) {
throw new Error('Could not find config path.');
}
if (type !== assigningType) {
throw new Error(`Cannot assign value of type '${assigningType}' to an property with type '${type}'.`);
}
}

fs.writeFileSync(path, JSON.stringify(this._config, null, 2), { encoding: 'utf-8' });
if (!value && value === null) {
target[key] = { type: 'null', value: value };
} else if (value && Array.isArray(value) && typeof value !== 'string') {
target[key] = { type: 'array', enum: value };
} else {
if (typeof value === 'object' && Object.getOwnPropertyNames(value).length === 0) {
target[key] = { type: typeof value };
} else {
target[key] = { type: typeof value, value: value };
}
}
}
}
};

export interface ConfigJson {
routes?: { [name: string]: any },
packages?: { [name: string]: any }
}

set(jsonPath: string, value: any, force: boolean = false): boolean {
let { parent, name, remaining } = this._findParent(jsonPath);
while (force && remaining) {
if (remaining.indexOf('.') != -1) {
// Create an empty map.
// TODO: create the object / array based on the Schema of the configuration.
parent[name] = {};
export class Config {
path: ConfigJson;
config: Proxy;

constructor(path?: string) {
if (path) {
try {
fs.accessSync(path);
this.path = path;
this.config = new Proxy({}, handler);
} catch (e) {
throw new Error(`${path} not found.`);
}
} else {
this.path = this._configFilePath();
this.config = new Proxy(this._fromProject(), handler);
}
}

public save(): void {
try {
let config = ObjectAssign({}, JSON.parse(fs.readFileSync(this.path, 'utf8')), this.config.toJSON());
fs.writeFileSync(this.path, JSON.stringify(config, null, 2), 'utf8');
} catch (e) {
throw new Error(`Error while saving config.`);
}
}

parent[name] = value;
return true;
public set(path: string, value: any): void {
const levels = path.split('.');
let current = this.config;
let i = 0;
while (i < levels.length - 1) {
delete current[levels[i]];
current = current[levels[i]];
i += 1;
}

current[levels[levels.length - 1]] = value;
}

get(jsonPath: string): any {
let { parent, name, remaining } = this._findParent(jsonPath);
if (remaining || !(name in parent)) {
return null;
public get(obj: any, path: string): any {
const levels = path.split('.');
let current = obj;
let i = 0;
while (i < levels.length) {
if (current[levels[i]]) {
current = current[levels[i]];
i += 1;
} else {
return null;
}
}

if (current.type === 'array') {
return current.enum;
} else if (current.type === 'object') {
return current;
} else {
return parent[name];
return current.value;
}
}

private _validatePath(jsonPath: string) {
public validatePath(jsonPath: string) {
if (!jsonPath.match(/^(?:[-_\w\d]+(?:\[\d+\])*\.)*(?:[-_\w\d]+(?:\[\d+\])*)$/)) {
throw `Invalid JSON path: "${jsonPath}"`;
}
}

private _findParent(jsonPath: string): { parent: any, name: string | number, remaining?: string } {
this._validatePath(jsonPath);

let parent: any = null;
let current: any = this._config;

const splitPath = jsonPath.split('.');
let name: string | number = '';

while (splitPath.length > 0) {
const m = splitPath.shift().match(/^(.*?)(?:\[(\d+)\])*$/);

name = m[1];
const index: string = m[2];
parent = current;
current = current[name];

if (current === null || current === undefined) {
return {
parent,
name,
remaining: (!isNaN(index) ? `[${index}]` : '') + splitPath.join('.')
};
private _findUp(name: string, from: string): string {
let currentDir = from;
while (currentDir && currentDir != '/') {
const p = path.join(currentDir, name);
if (fs.existsSync(p)) {
return p;
}

if (!isNaN(index)) {
name = index;
parent = current;
current = current[index];

if (current === null || current === undefined) {
return {
parent,
name,
remaining: splitPath.join('.')
};
}
}
currentDir = path.resolve(currentDir, '..');
}

return { parent, name };
return null;
}

static configFilePath(projectPath?: string): string {
private _configFilePath(projectPath?: string): string {
// Find the configuration, either where specified, in the angular-cli project
// (if it's in node_modules) or from the current process.
return (projectPath && _findUp(CLI_CONFIG_FILE_NAME, projectPath))
|| _findUp(CLI_CONFIG_FILE_NAME, __dirname)
|| _findUp(CLI_CONFIG_FILE_NAME, process.cwd());
return (projectPath && this._findUp(CLI_CONFIG_FILE_NAME, projectPath))
|| this._findUp(CLI_CONFIG_FILE_NAME, __dirname)
|| this._findUp(CLI_CONFIG_FILE_NAME, process.cwd());
}

static fromProject(): CliConfigJson {
const configPath = this.configFilePath();
private _fromProject(): ConfigJson {
const configPath = this._configFilePath();
return configPath ? require(configPath) : {};
}
}
40 changes: 40 additions & 0 deletions addon/ng2/utilities/object-assign.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* eslint-disable no-unused-vars */
'use strict';

var hasOwnProperty = Object.prototype.hasOwnProperty;
var propIsEnumerable = Object.prototype.propertyIsEnumerable;

function toObject(val) {
if (val === null || val === undefined) {
throw new TypeError('Object.assign cannot be called with null or undefined');
}

return Object(val);
}

module.exports = Object.assign || function (target, source) {
var from;
var to = toObject(target);
var symbols;

for (var s = 1; s < arguments.length; s++) {
from = Object(arguments[s]);

for (var key in from) {
if (hasOwnProperty.call(from, key)) {
to[key] = from[key];
}
}

if (Object.getOwnPropertySymbols) {
symbols = Object.getOwnPropertySymbols(from);
for (var i = 0; i < symbols.length; i++) {
if (propIsEnumerable.call(from, symbols[i])) {
to[symbols[i]] = from[symbols[i]];
}
}
}
}

return to;
};
2 changes: 1 addition & 1 deletion bin/ng
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env node
#!node --harmony-proxies
'use strict';

// Provide a title to the process in `ps`
Expand Down
Loading