Skip to content

refactor(config): NgConfig #1533

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
46 changes: 39 additions & 7 deletions addon/ng2/commands/get.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as chalk from 'chalk';
import * as Command from 'ember-cli/lib/models/command';
import * as jp from 'jsonpath';
import * as chalk from 'chalk';
import * as path from 'path';
import {CliConfig} from '../models/config';


Expand All @@ -8,21 +11,50 @@ const GetCommand = Command.extend({
description: 'Get a value from the configuration.',
works: 'everywhere',

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

run: function (commandOptions, rawArgs): Promise<void> {
return new Promise(resolve => {
const value = new CliConfig().get(rawArgs[0]);
if (value === null) {
console.error(chalk.red('Value cannot be found.'));
} else if (typeof value == 'object') {
console.log(JSON.stringify(value));
const cliConfig = new CliConfig();
const config = commandOptions.global ? cliConfig.global : cliConfig.project;
const value = cliConfig.get(rawArgs[0]);

if (!value) {
const lastProp = _lastProp(rawArgs[0]);
let results = jp.query(config, `$..${lastProp}`);
let paths = jp.paths(config, `$..${lastProp}`);
if (results.length) {
let result;
let foundPath;
this.ui.writeLine('We could not find value on the path you were requested.');
if (results.length > 1) {
this.ui.writeLine('But, we found values on other paths:');
results.forEach((r, i) => {
result = chalk.green(JSON.stringify(results[i]));
foundPath = chalk.green(paths[i].filter((p, idx) => idx !== 0).join('.'));
this.ui.writeLine(`${result} on path ${foundPath}?`);
});
} else {
result = chalk.green(JSON.stringify(results[0]));
foundPath = paths[0].filter((p, i) => i !== 0).join('.');
this.ui.writeLine(`Looking for ${result} on path ${chalk.green(foundPath)}?`);
}
} else {
this.ui.writeLine(chalk.red('Value not found.'));
}
} else {
console.log(value);
this.ui.writeLine(JSON.stringify(value, null, ' '));
}

resolve();
});
}
});

private static function _lastProp (jsonPath: string): string {
return (jsonPath.match(/\./)) ? path.extname(jsonPath) : jsonPath;
}

module.exports = GetCommand;
23 changes: 19 additions & 4 deletions addon/ng2/commands/set.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
import * as Command from 'ember-cli/lib/models/command';
import {CliConfig} from '../models/config';

import * as chalk from 'chalk';

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

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

run: function (commandOptions, rawArgs): Promise<void> {
return new Promise(resolve => {
return new Promise((resolve, reject) => {
if (rawArgs.length < 2) {
this.ui.writeLine(`
${chalk.red.bold('Error: not enough parameters provided.')}
'Examples:'
${chalk.yellow('ng set project.name "My awesome project"')}
${chalk.yellow('ng set defaults.styleExt sass')}
${chalk.yellow('ng set apps[0].mobile true')}
${chalk.yellow('ng set "apps[0].styles[\'src/styles.css\'].autoImported" = false')}
${chalk.yellow('ng set "apps[0].styles[\'src/app.sass\']" = "{ output: \'app.css\', autoImported: true }"')}
`);
reject();
}

const config = new CliConfig();
config.set(rawArgs[0], rawArgs[1], commandOptions.force);
const value = rawArgs[1] === '=' ? rawArgs[2] : rawArgs[1];

config.set(rawArgs[0], value, commandOptions.force);
config.save();
resolve();
});
Expand Down
6 changes: 1 addition & 5 deletions addon/ng2/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
/* jshint node: true */
'use strict';

const config = require('./models/config');
const NgConfig = require('./models/config');

module.exports = {
name: 'ng2',

config: function () {
this.project.ngConfig = this.project.ngConfig || config.CliConfig.fromProject();
},

includedCommands: function () {
return {
'build': require('./commands/build'),
Expand Down
219 changes: 97 additions & 122 deletions addon/ng2/models/config.ts
Original file line number Diff line number Diff line change
@@ -1,171 +1,146 @@
import * as fs from 'fs';
import * as path from 'path';
import * as jp from 'jsonpath';
import * as chalk from 'chalk';

const schemaPath = path.resolve(process.env.CLI_ROOT, 'lib/config/schema.json');
const schema = require(schemaPath);

export const CLI_CONFIG_FILE_NAME = 'angular-cli.json';
export const ARRAY_METHODS = ['push', 'splice', 'sort', 'reverse', 'pop', 'shift'];

function _findUp(name: string, from: string) {
let currentDir = from;
while (currentDir && currentDir !== path.parse(currentDir).root) {
const p = path.join(currentDir, name);
if (fs.existsSync(p)) {
return p;
}
export class CliConfig {
public project: any;
public global: any;

currentDir = path.resolve(currentDir, '..');
constructor() {
this.global = this._fromCli();
this.project = this._fromProject();
}

return null;
}
save(global = false) {
let config = (global || !this.project) ? this.global : this.project;
fs.writeFileSync(path, JSON.stringify(config, null, 2), { encoding: 'utf-8' });
}

checkValidSchemaPath(jsonPath: Object): boolean {
const parsed = jp.parse(jsonPath);
const invalidMsg = `${jsonPath} does not match schema.`;
let propertiesPath;

export class CliConfig {
private _config: any;
parsed.forEach((p, i) => {
let type = p.expression.type;
let value = p.expression.value;

constructor(path?: string) {
if (path) {
try {
fs.accessSync(path);
this._config = require(path);
} catch (e) {
throw new Error(`Config file does not exits.`);
if (i === parsed.length - 1) {
return;
}
} else {
this._config = CliConfig.fromProject();
}
}

save(path: string = CliConfig._configFilePath()) {
if (!path) {
throw new Error('Could not find config path.');
}
if (!i) {
propertiesPath = `properties.${value}`;
} else {
if (type === 'numeric_literal') {
let prop = propertiesPath.split('.').reduce((prev, curr) => prev[curr], schema);
if (prop.type !== 'array') {
throw new Error(invalidMsg);
} else {
propertiesPath += `.items`;
}
} else {
propertiesPath += `.properties.${value}`;
}
}
});

fs.writeFileSync(path, JSON.stringify(this._config, null, 2), { encoding: 'utf-8' });
if (!propertiesPath.split('.').reduce((prev, curr) => prev[curr], schema)) {
throw new Error(invalidMsg);
} else {
return true;
}
}

set(jsonPath: string, value: any, force: boolean = false): boolean {
let method: any = null;
let splittedPath = jsonPath.split('.');
if (ARRAY_METHODS.indexOf(splittedPath[splittedPath.length - 1]) != -1) {
method = splittedPath[splittedPath.length - 1];
splittedPath.splice(splittedPath.length - 1, 1);
jsonPath = splittedPath.join('.');
}
set(jsonPath: string, value: any, global = false): boolean {
let config = (global || !this.project) ? this.global : this.project;

let { parent, name, remaining } = this._findParent(jsonPath);
let properties: any;
let additionalProperties: boolean;
this._validatePath(jsonPath);
this.checkValidSchemaPath(jsonPath);

const checkPath = jsonPath.split('.').reduce((o, i) => {
if (!o || !o.properties) {
throw new Error(`Invalid config path.`);
if (value.slice(0, 1) === '{' && value.slice(-1) === '}') {
try {
value = JSON.parse(value.replace(/\'/g, '\"'));
} catch (e) {
throw new Error(`Invalid JSON value ${value}`);
}
properties = o.properties;
additionalProperties = o.additionalProperties;
}

return o.properties[i];
}, schema);
const configPath = jsonPath.split('.').reduce((o, i) => o[i], this._config);
let prop = jsonPath.split('.').reduce((prev, curr) => prev[curr], config);

if (!properties[name] && !additionalProperties) {
throw new Error(`${name} is not a known property.`);
}
if (ARRAY_METHODS.indexOf(path.extname(jsonPath).replace('.', '')) !== -1) {
let method = path.extname(jsonPath);
let parentPath = jsonPath.replace(path.extname(jsonPath), '');

if (method) {
if (Array.isArray(configPath) && checkPath.type === 'array') {
[][method].call(configPath, value);
return true;
if (typeof jp.query(config, `$.${parentPath}`)[0] === 'string') {
throw new Error(`Cannot use array method on non-array type.`);
} else {
throw new Error(`Trying to use array method on non-array property type.`);
[][method].call(parent, value);
}
}

if (typeof checkPath.type === 'string' && isNaN(value)) {
parent[name] = value;
return true;
if (!prop) {
throw new Error(`Property does not exists.`);
}

if (typeof checkPath.type === 'number' && !isNaN(value)) {
parent[name] = value;
return true;
}
jp.value(config, `$.${jsonPath}`, value);

if (typeof value != checkPath.type) {
throw new Error(`Invalid value type. Trying to set ${typeof value} to ${path.type}`);
}
return true;
}

get(jsonPath: string): any {
let { parent, name, remaining } = this._findParent(jsonPath);
if (remaining || !(name in parent)) {
return null;
} else {
return parent[name];
}
get(jsonPath: string, global = false): any {
let config = (global || !this.project) ? this.global : this.project;
let results = jp.query(config, `$.${jsonPath}`);

return (results.length) ? results[0] : null;
}

private _validatePath(jsonPath: string) {
if (!jsonPath.match(/^(?:[-_\w\d]+(?:\[\d+\])*\.)*(?:[-_\w\d]+(?:\[\d+\])*)$/)) {
try {
jp.parse(jsonPath);
} catch (e) {
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('.')
};
}

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

if (current === null || current === undefined) {
return {
parent,
name,
remaining: splitPath.join('.')
};
}
}
private _fromProject(): any {
const configPath = this._findUp(CLI_CONFIG_FILE_NAME, process.cwd());
try {
fs.accessSync(configPath);
return JSON.parse(fs.readFileSync(configPath, { encoding: 'utf8' }));
} catch (e) {
return {};
}

return { parent, name };
}

private static _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());
private _fromCli(): any {
const configPath = this._findUp(CLI_CONFIG_FILE_NAME, process.env.CLI_ROOT);
try {
fs.accessSync(configPath);
return JSON.parse(fs.readFileSync(configPath, { encoding: 'utf8' }));
} catch (e) {
return {};
}
}

public static fromProject(): any {
const configPath = CliConfig._configFilePath();
return configPath ? require(configPath) : {};
private _findUp(name: string, from: string): any {
let currentDir = from;
while (currentDir && currentDir !== path.parse(currentDir).root) {
let p = path.join(currentDir, name);
try {
fs.accessSync(p);
return p;
} catch (e) {
currentDir = path.resolve(currentDir, '..');
}
}
return null;
}
}
Loading