Skip to content

Commit 1673816

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

File tree

6 files changed

+146
-96
lines changed

6 files changed

+146
-96
lines changed

addon/ng2/commands/set.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
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',
@@ -12,9 +12,24 @@ const SetCommand = Command.extend({
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/models/config.ts

+74-84
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
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);
@@ -29,7 +31,7 @@ export class CliConfig {
2931
if (path) {
3032
try {
3133
fs.accessSync(path);
32-
this._config = require(path);
34+
this._config = JSON.parse(fs.readFileSync(path, 'utf8'));
3335
} catch (e) {
3436
throw new Error(`Config file does not exits.`);
3537
}
@@ -46,116 +48,104 @@ export class CliConfig {
4648
fs.writeFileSync(path, JSON.stringify(this._config, null, 2), { encoding: 'utf-8' });
4749
}
4850

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-
}
51+
checkValidSchemaPath(jsonPath: Object): boolean {
52+
const parsed = jp.parse(jsonPath);
53+
const invalidMsg = `${jsonPath} does not match schema.`;
54+
let propertiesPath;
5755

58-
let { parent, name, remaining } = this._findParent(jsonPath);
59-
let properties: any;
60-
let additionalProperties: boolean;
56+
parsed.forEach((p, i) => {
57+
let type = p.expression.type;
58+
let value = p.expression.value;
6159

62-
const checkPath = jsonPath.split('.').reduce((o, i) => {
63-
if (!o || !o.properties) {
64-
throw new Error(`Invalid config path.`);
60+
if (i === parsed.length - 1) {
61+
return;
6562
}
66-
properties = o.properties;
67-
additionalProperties = o.additionalProperties;
6863

69-
return o.properties[i];
70-
}, schema);
71-
const configPath = jsonPath.split('.').reduce((o, i) => o[i], this._config);
64+
if (!i) {
65+
propertiesPath = `properties.${value}`;
66+
} else {
67+
if (type === 'numeric_literal') {
68+
let prop = propertiesPath.split('.').reduce((prev, curr) => prev[curr], schema);
69+
if (prop.type !== 'array') {
70+
throw new Error(invalidMsg);
71+
} else {
72+
propertiesPath += `.items`;
73+
}
74+
} else {
75+
propertiesPath += `.properties.${value}`;
76+
}
77+
}
78+
});
7279

73-
if (!properties[name] && !additionalProperties) {
74-
throw new Error(`${name} is not a known property.`);
80+
if (!propertiesPath.split('.').reduce((prev, curr) => prev[curr], schema)) {
81+
throw new Error(invalidMsg);
7582
}
83+
}
7684

77-
if (method) {
78-
if (Array.isArray(configPath) && checkPath.type === 'array') {
79-
[][method].call(configPath, value);
80-
return true;
81-
} else {
82-
throw new Error(`Trying to use array method on non-array property type.`);
85+
set(jsonPath: string, value: any, force: boolean = false): boolean {
86+
this._validatePath(jsonPath);
87+
this.checkValidSchemaPath(jsonPath);
88+
89+
if (value.slice(0, 1) === '{' && value.slice(-1) === '}') {
90+
try {
91+
value = JSON.parse(value.replace(/\'/g, '\"'));
92+
} catch (e) {
93+
throw new Error(`Invalid JSON value ${value}`);
8394
}
8495
}
8596

86-
if (typeof checkPath.type === 'string' && isNaN(value)) {
87-
parent[name] = value;
88-
return true;
97+
if (typeof value === 'object') {
98+
if (prop.type !== 'object') {
99+
100+
}
89101
}
90102

91-
if (typeof checkPath.type === 'number' && !isNaN(value)) {
92-
parent[name] = value;
93-
return true;
103+
let prop = jsonPath.split('.').reduce((prev, curr) => prev[curr], this._config);
104+
105+
if (ARRAY_METHODS.indexOf(path.extname(jsonPath).replace('.', '')) !== -1) {
106+
let method = path.extname(jsonPath);
107+
let parentPath = jsonPath.replace(path.extname(jsonPath), '');
108+
109+
if (typeof jp.query(this._config, `$.${parentPath}`)[0] === 'string') {
110+
throw new Error(`Cannot use array method on non-array type.`);
111+
} else {
112+
[][method].call(parent, value);
113+
}
94114
}
95115

96-
if (typeof value != checkPath.type) {
97-
throw new Error(`Invalid value type. Trying to set ${typeof value} to ${path.type}`);
116+
if (!prop) {
117+
throw new Error(`Property does not exists.`);
98118
}
119+
120+
jp.value(this._config, `$.${jsonPath}`, value);
99121
}
100122

101123
get(jsonPath: string): any {
102-
let { parent, name, remaining } = this._findParent(jsonPath);
103-
if (remaining || !(name in parent)) {
124+
let results = jp.query(this._config, `$.${jsonPath}`);
125+
if (!results.length) {
126+
let ext = path.extname(jsonPath);
127+
results = jp.query(this._config, `$..${ext}`);
128+
if (results.length) {
129+
console.log(chalk.yellow(`
130+
We could not find value on the path you were requested.
131+
Did you mean: ${results[0]}?`
132+
));
133+
}
134+
104135
return null;
105136
} else {
106-
return parent[name];
137+
return results[0];
107138
}
108139
}
109140

110141
private _validatePath(jsonPath: string) {
111-
if (!jsonPath.match(/^(?:[-_\w\d]+(?:\[\d+\])*\.)*(?:[-_\w\d]+(?:\[\d+\])*)$/)) {
142+
try {
143+
jp.parse(jsonPath);
144+
} catch (e) {
112145
throw `Invalid JSON path: "${jsonPath}"`;
113146
}
114147
}
115148

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-
}
154-
}
155-
156-
return { parent, name };
157-
}
158-
159149
private static _configFilePath(projectPath?: string): string {
160150
// Find the configuration, either where specified, in the angular-cli project
161151
// (if it's in node_modules) or from the current process.
@@ -166,6 +156,6 @@ export class CliConfig {
166156

167157
public static fromProject(): any {
168158
const configPath = CliConfig._configFilePath();
169-
return configPath ? require(configPath) : {};
159+
return configPath ? JSON.parse(fs.readFileSync(configPath, 'utf8')) : {};
170160
}
171161
}

angular-cli.json

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"project": {
3+
"name": "Angular CLI global config"
4+
},
5+
"apps": [
6+
{
7+
"main": "src/main.ts",
8+
"tsconfig": "src/tsconfig.json",
9+
"mobile": false,
10+
"styles": {}
11+
}
12+
],
13+
"addons": [],
14+
"packages": [],
15+
"e2e": {
16+
"protractor": {
17+
"config": "config/protractor.conf.js"
18+
}
19+
},
20+
"test": {
21+
"karma": {
22+
"config": "config/karma.conf.js"
23+
}
24+
},
25+
"defaults": {
26+
"prefix": "app",
27+
"sourceDir": "src",
28+
"styleExt": "css",
29+
"prefixInterfaces": false,
30+
"lazyRoutePrefix": "+"
31+
}
32+
}

lib/config/schema.json

+13-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,19 @@
2424
"properties": {
2525
"main": "string",
2626
"tsconfig": "string",
27-
"mobile": "boolean"
27+
"mobile": "boolean",
28+
"styles": {
29+
"type": "object",
30+
"properties": {
31+
"output": {
32+
"type": "string"
33+
},
34+
"autoImported": {
35+
"type": "boolean"
36+
}
37+
},
38+
"additionalProperties": false
39+
}
2840
},
2941
"additionalProperties": false
3042
},

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"html-webpack-plugin": "^2.19.0",
5656
"istanbul-instrumenter-loader": "^0.2.0",
5757
"json-loader": "^0.5.4",
58+
"jsonpath": "^0.2.6",
5859
"karma-sourcemap-loader": "^0.3.7",
5960
"karma-webpack": "^1.7.0",
6061
"leek": "0.0.21",

tests/models/config.spec.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import {CliConfig} from '../../addon/ng2/models/config';
22
import * as fs from 'fs';
33
import * as path from 'path';
4+
import { expect } from 'chai';
45

5-
const expect = require('chai').expect;
6-
const config = path.resolve(process.cwd(), 'addon/ng2/blueprints/ng2/files/angular-cli.json');
7-
const configCopy = path.resolve(process.cwd(), 'angular-cli.json');
6+
const config = path.resolve(process.cwd(), 'angular-cli.json');
7+
const configCopy = path.resolve(process.cwd(), 'angular-cli-tmp.json');
88

99
function getContents() {
10-
return require(configCopy);
10+
return JSON.parse(fs.readFileSync(configCopy, 'utf8'));
1111
}
1212

1313
// TODO: revisit this test to make non-valid-JSON-friendly.
14-
describe.skip('Config Tests', () => {
14+
describe('Config Tests', () => {
1515
before(() => {
1616
process.chdir(process.cwd());
1717
});
@@ -41,7 +41,7 @@ describe.skip('Config Tests', () => {
4141
it('Updates property of type `string` successfully', () => {
4242
let c = new CliConfig(configCopy);
4343
c.set('project.name', 'new-project-name');
44-
c.save();
44+
c.save(configCopy);
4545

4646
let contents = getContents();
4747

@@ -55,7 +55,7 @@ describe.skip('Config Tests', () => {
5555

5656
let fn = () => {
5757
c.set('project.foo', 'bar');
58-
c.save();
58+
c.save(configCopy);
5959
}
6060

6161
expect(fn).to.throw(Error);
@@ -66,7 +66,7 @@ describe.skip('Config Tests', () => {
6666

6767
let fn = () => {
6868
c.set('project.name.push', 'new-project-name');
69-
c.save();
69+
c.save(configCopy);
7070
}
7171

7272
expect(fn).to.throw(Error);

0 commit comments

Comments
 (0)