Skip to content

Commit 373f8f4

Browse files
committed
refactor(config): NgConfig
1 parent f1808c3 commit 373f8f4

File tree

9 files changed

+217
-149
lines changed

9 files changed

+217
-149
lines changed

addon/ng2/commands/get.ts

+39-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import * as chalk from 'chalk';
22
import * as Command from 'ember-cli/lib/models/command';
3+
import * as jp from 'jsonpath';
4+
import * as chalk from 'chalk';
5+
import * as path from 'path';
36
import {CliConfig} from '../models/config';
47

58

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

11-
availableOptions: [],
14+
availableOptions: [
15+
{ name: 'global', type: Boolean, default: false, aliases: ['g'] }
16+
],
1217

1318
run: function (commandOptions, rawArgs): Promise<void> {
1419
return new Promise(resolve => {
15-
const value = new CliConfig().get(rawArgs[0]);
16-
if (value === null) {
17-
console.error(chalk.red('Value cannot be found.'));
18-
} else if (typeof value == 'object') {
19-
console.log(JSON.stringify(value));
20+
const cliConfig = new CliConfig();
21+
const config = commandOptions.global ? cliConfig.global : cliConfig.project;
22+
const value = cliConfig.get(rawArgs[0]);
23+
24+
if (!value) {
25+
const lastProp = _lastProp(rawArgs[0]);
26+
let results = jp.query(config, `$..${lastProp}`);
27+
let paths = jp.paths(config, `$..${lastProp}`);
28+
if (results.length) {
29+
let result;
30+
let foundPath;
31+
this.ui.writeLine('We could not find value on the path you were requested.');
32+
if (results.length > 1) {
33+
this.ui.writeLine('But, we found values on other paths:');
34+
results.forEach((r, i) => {
35+
result = chalk.green(JSON.stringify(results[i]));
36+
foundPath = chalk.green(paths[i].filter((p, idx) => idx !== 0).join('.'));
37+
this.ui.writeLine(`${result} on path ${foundPath}?`);
38+
});
39+
} else {
40+
result = chalk.green(JSON.stringify(results[0]));
41+
foundPath = paths[0].filter((p, i) => i !== 0).join('.');
42+
this.ui.writeLine(`Looking for ${result} on path ${chalk.green(foundPath)}?`);
43+
}
44+
} else {
45+
this.ui.writeLine(chalk.red('Value not found.'));
46+
}
2047
} else {
21-
console.log(value);
48+
this.ui.writeLine(JSON.stringify(value, null, ' '));
2249
}
50+
2351
resolve();
2452
});
2553
}
2654
});
2755

56+
private static function _lastProp (jsonPath: string): string {
57+
return (jsonPath.match(/\./)) ? path.extname(jsonPath) : jsonPath;
58+
}
59+
2860
module.exports = GetCommand;

addon/ng2/commands/set.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
11
import * as Command from 'ember-cli/lib/models/command';
22
import {CliConfig} from '../models/config';
3-
3+
import * as chalk from 'chalk';
44

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

1010
availableOptions: [
11-
{ name: 'global', type: Boolean, default: false, aliases: ['g'] },
11+
{ name: 'global', type: Boolean, default: false, aliases: ['g'] }
1212
],
1313

1414
run: function (commandOptions, rawArgs): Promise<void> {
15-
return new Promise(resolve => {
15+
return new Promise((resolve, reject) => {
16+
if (rawArgs.length < 2) {
17+
this.ui.writeLine(`
18+
${chalk.red.bold('Error: not enough parameters provided.')}
19+
'Examples:'
20+
${chalk.yellow('ng set project.name "My awesome project"')}
21+
${chalk.yellow('ng set defaults.styleExt sass')}
22+
${chalk.yellow('ng set apps[0].mobile true')}
23+
${chalk.yellow('ng set "apps[0].styles[\'src/styles.css\'].autoImported" = false')}
24+
${chalk.yellow('ng set "apps[0].styles[\'src/app.sass\']" = "{ output: \'app.css\', autoImported: true }"')}
25+
`);
26+
reject();
27+
}
28+
1629
const config = new CliConfig();
17-
config.set(rawArgs[0], rawArgs[1], commandOptions.force);
30+
const value = rawArgs[1] === '=' ? rawArgs[2] : rawArgs[1];
31+
32+
config.set(rawArgs[0], value, commandOptions.force);
1833
config.save();
1934
resolve();
2035
});

addon/ng2/index.js

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
/* jshint node: true */
22
'use strict';
33

4-
const config = require('./models/config');
4+
const NgConfig = require('./models/config');
55

66
module.exports = {
77
name: 'ng2',
88

9-
config: function () {
10-
this.project.ngConfig = this.project.ngConfig || config.CliConfig.fromProject();
11-
},
12-
139
includedCommands: function () {
1410
return {
1511
'build': require('./commands/build'),

addon/ng2/models/config.ts

+97-122
Original file line numberDiff line numberDiff line change
@@ -1,171 +1,146 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
3+
import * as jp from 'jsonpath';
4+
import * as chalk from 'chalk';
35

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

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

10-
function _findUp(name: string, from: string) {
11-
let currentDir = from;
12-
while (currentDir && currentDir !== path.parse(currentDir).root) {
13-
const p = path.join(currentDir, name);
14-
if (fs.existsSync(p)) {
15-
return p;
16-
}
12+
export class CliConfig {
13+
public project: any;
14+
public global: any;
1715

18-
currentDir = path.resolve(currentDir, '..');
16+
constructor() {
17+
this.global = this._fromCli();
18+
this.project = this._fromProject();
1919
}
2020

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

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

25-
export class CliConfig {
26-
private _config: any;
31+
parsed.forEach((p, i) => {
32+
let type = p.expression.type;
33+
let value = p.expression.value;
2734

28-
constructor(path?: string) {
29-
if (path) {
30-
try {
31-
fs.accessSync(path);
32-
this._config = require(path);
33-
} catch (e) {
34-
throw new Error(`Config file does not exits.`);
35+
if (i === parsed.length - 1) {
36+
return;
3537
}
36-
} else {
37-
this._config = CliConfig.fromProject();
38-
}
39-
}
4038

41-
save(path: string = CliConfig._configFilePath()) {
42-
if (!path) {
43-
throw new Error('Could not find config path.');
44-
}
39+
if (!i) {
40+
propertiesPath = `properties.${value}`;
41+
} else {
42+
if (type === 'numeric_literal') {
43+
let prop = propertiesPath.split('.').reduce((prev, curr) => prev[curr], schema);
44+
if (prop.type !== 'array') {
45+
throw new Error(invalidMsg);
46+
} else {
47+
propertiesPath += `.items`;
48+
}
49+
} else {
50+
propertiesPath += `.properties.${value}`;
51+
}
52+
}
53+
});
4554

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

49-
set(jsonPath: string, value: any, force: boolean = false): boolean {
50-
let method: any = null;
51-
let splittedPath = jsonPath.split('.');
52-
if (ARRAY_METHODS.indexOf(splittedPath[splittedPath.length - 1]) != -1) {
53-
method = splittedPath[splittedPath.length - 1];
54-
splittedPath.splice(splittedPath.length - 1, 1);
55-
jsonPath = splittedPath.join('.');
56-
}
62+
set(jsonPath: string, value: any, global = false): boolean {
63+
let config = (global || !this.project) ? this.global : this.project;
5764

58-
let { parent, name, remaining } = this._findParent(jsonPath);
59-
let properties: any;
60-
let additionalProperties: boolean;
65+
this._validatePath(jsonPath);
66+
this.checkValidSchemaPath(jsonPath);
6167

62-
const checkPath = jsonPath.split('.').reduce((o, i) => {
63-
if (!o || !o.properties) {
64-
throw new Error(`Invalid config path.`);
68+
if (value.slice(0, 1) === '{' && value.slice(-1) === '}') {
69+
try {
70+
value = JSON.parse(value.replace(/\'/g, '\"'));
71+
} catch (e) {
72+
throw new Error(`Invalid JSON value ${value}`);
6573
}
66-
properties = o.properties;
67-
additionalProperties = o.additionalProperties;
74+
}
6875

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

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

77-
if (method) {
78-
if (Array.isArray(configPath) && checkPath.type === 'array') {
79-
[][method].call(configPath, value);
80-
return true;
82+
if (typeof jp.query(config, `$.${parentPath}`)[0] === 'string') {
83+
throw new Error(`Cannot use array method on non-array type.`);
8184
} else {
82-
throw new Error(`Trying to use array method on non-array property type.`);
85+
[][method].call(parent, value);
8386
}
8487
}
8588

86-
if (typeof checkPath.type === 'string' && isNaN(value)) {
87-
parent[name] = value;
88-
return true;
89+
if (!prop) {
90+
throw new Error(`Property does not exists.`);
8991
}
9092

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

96-
if (typeof value != checkPath.type) {
97-
throw new Error(`Invalid value type. Trying to set ${typeof value} to ${path.type}`);
98-
}
95+
return true;
9996
}
10097

101-
get(jsonPath: string): any {
102-
let { parent, name, remaining } = this._findParent(jsonPath);
103-
if (remaining || !(name in parent)) {
104-
return null;
105-
} else {
106-
return parent[name];
107-
}
98+
get(jsonPath: string, global = false): any {
99+
let config = (global || !this.project) ? this.global : this.project;
100+
let results = jp.query(config, `$.${jsonPath}`);
101+
102+
return (results.length) ? results[0] : null;
108103
}
109104

110105
private _validatePath(jsonPath: string) {
111-
if (!jsonPath.match(/^(?:[-_\w\d]+(?:\[\d+\])*\.)*(?:[-_\w\d]+(?:\[\d+\])*)$/)) {
106+
try {
107+
jp.parse(jsonPath);
108+
} catch (e) {
112109
throw `Invalid JSON path: "${jsonPath}"`;
113110
}
114111
}
115112

116-
private _findParent(jsonPath: string): { parent: any, name: string | number, remaining?: string } {
117-
this._validatePath(jsonPath);
118-
119-
let parent: any = null;
120-
let current: any = this._config;
121-
122-
const splitPath = jsonPath.split('.');
123-
let name: string | number = '';
124-
125-
while (splitPath.length > 0) {
126-
const m = splitPath.shift().match(/^(.*?)(?:\[(\d+)\])*$/);
127-
128-
name = m[1];
129-
const index: string = m[2];
130-
parent = current;
131-
current = current[name];
132-
133-
if (current === null || current === undefined) {
134-
return {
135-
parent,
136-
name,
137-
remaining: (!isNaN(index) ? `[${index}]` : '') + splitPath.join('.')
138-
};
139-
}
140-
141-
if (!isNaN(index)) {
142-
name = index;
143-
parent = current;
144-
current = current[index];
145-
146-
if (current === null || current === undefined) {
147-
return {
148-
parent,
149-
name,
150-
remaining: splitPath.join('.')
151-
};
152-
}
153-
}
113+
private _fromProject(): any {
114+
const configPath = this._findUp(CLI_CONFIG_FILE_NAME, process.cwd());
115+
try {
116+
fs.accessSync(configPath);
117+
return JSON.parse(fs.readFileSync(configPath, { encoding: 'utf8' }));
118+
} catch (e) {
119+
return {};
154120
}
155-
156-
return { parent, name };
157121
}
158122

159-
private static _configFilePath(projectPath?: string): string {
160-
// Find the configuration, either where specified, in the angular-cli project
161-
// (if it's in node_modules) or from the current process.
162-
return (projectPath && _findUp(CLI_CONFIG_FILE_NAME, projectPath))
163-
|| _findUp(CLI_CONFIG_FILE_NAME, __dirname)
164-
|| _findUp(CLI_CONFIG_FILE_NAME, process.cwd());
123+
private _fromCli(): any {
124+
const configPath = this._findUp(CLI_CONFIG_FILE_NAME, process.env.CLI_ROOT);
125+
try {
126+
fs.accessSync(configPath);
127+
return JSON.parse(fs.readFileSync(configPath, { encoding: 'utf8' }));
128+
} catch (e) {
129+
return {};
130+
}
165131
}
166132

167-
public static fromProject(): any {
168-
const configPath = CliConfig._configFilePath();
169-
return configPath ? require(configPath) : {};
133+
private _findUp(name: string, from: string): any {
134+
let currentDir = from;
135+
while (currentDir && currentDir !== path.parse(currentDir).root) {
136+
let p = path.join(currentDir, name);
137+
try {
138+
fs.accessSync(p);
139+
return p;
140+
} catch (e) {
141+
currentDir = path.resolve(currentDir, '..');
142+
}
143+
}
144+
return null;
170145
}
171146
}

0 commit comments

Comments
 (0)