Skip to content

Commit 8ae3cb6

Browse files
jkurihansl
authored andcommitted
chore(config): ng-config (angular#506)
1 parent 8ede7a6 commit 8ae3cb6

File tree

10 files changed

+224
-30
lines changed

10 files changed

+224
-30
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
11
{
2-
"routes": []
3-
}
2+
"project": {
3+
"version": "<%= version %>",
4+
"name": "<%= htmlComponentName %>"
5+
},
6+
"apps": [
7+
{"main": "src/client/main.ts", "tsconfig": "src/client/tsconfig.json"}
8+
],
9+
"addons": [],
10+
"packages": [],
11+
"e2e": {
12+
"protractor": {
13+
"config": "protractor.conf.js"
14+
}
15+
},
16+
"test": {
17+
"karma": {
18+
"config": "karma.conf.js"
19+
}
20+
}
21+
}

addon/ng2/blueprints/ng2/index.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1-
var stringUtils = require('ember-cli/lib/utilities/string');
1+
const path = require('path');
2+
const stringUtils = require('ember-cli/lib/utilities/string');
23

34
module.exports = {
45
description: '',
56

67
locals: function(options) {
78
//TODO: pull value from config
89
this.styleExt = 'css';
10+
this.version = require(path.resolve(__dirname, '..', '..', '..', '..', 'package.json')).version;
911

1012
return {
1113
htmlComponentName: stringUtils.dasherize(options.entity.name),
1214
jsComponentName: stringUtils.classify(options.entity.name),
13-
styleExt: this.styleExt
15+
styleExt: this.styleExt,
16+
version: this.version
1417
};
1518
},
1619

addon/ng2/commands/get.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {CliConfig} from '../models/config';
66
const GetCommand = Command.extend({
77
name: 'get',
88
description: 'Set a value in the configuration.',
9-
works: 'outsideProject',
9+
works: 'everywhere',
1010

1111
availableOptions: [],
1212

@@ -26,4 +26,3 @@ const GetCommand = Command.extend({
2626
});
2727

2828
module.exports = GetCommand;
29-
module.exports.overrideCore = true;

addon/ng2/commands/set.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {CliConfig} from '../models/config';
55
const SetCommand = Command.extend({
66
name: 'set',
77
description: 'Set a value in the configuration.',
8-
works: 'outsideProject',
8+
works: 'everywhere',
99

1010
availableOptions: [
1111
{ name: 'global', type: Boolean, default: false, aliases: ['g'] },
@@ -22,4 +22,3 @@ const SetCommand = Command.extend({
2222
});
2323

2424
module.exports = SetCommand;
25-
module.exports.overrideCore = true;

addon/ng2/index.js

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

4+
const config = require('./models/config');
5+
46
module.exports = {
57
name: 'ng2',
8+
9+
config: function () {
10+
this.project.config = this.project.config || config.CliConfig.fromProject();
11+
},
12+
613
includedCommands: function () {
714
return {
815
'new': require('./commands/new'),

addon/ng2/models/config.ts

+66-19
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
33

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

57
export const CLI_CONFIG_FILE_NAME = 'angular-cli.json';
6-
7-
8-
export interface CliConfigJson {
9-
routes?: { [name: string]: any },
10-
packages?: { [name: string]: any }
11-
}
8+
export const ARRAY_METHODS = ['push', 'splice', 'sort', 'reverse', 'pop', 'shift'];
129

1310

1411
function _findUp(name: string, from: string) {
1512
let currentDir = from;
16-
while (currentDir && currentDir != '/') {
13+
while (currentDir && currentDir !== path.parse(currentDir).root) {
1714
const p = path.join(currentDir, name);
1815
if (fs.existsSync(p)) {
1916
return p;
@@ -27,9 +24,22 @@ function _findUp(name: string, from: string) {
2724

2825

2926
export class CliConfig {
30-
constructor(private _config: CliConfigJson = CliConfig.fromProject()) {}
27+
private _config: any;
28+
29+
constructor(path?: string) {
30+
if (path) {
31+
try {
32+
fs.accessSync(path);
33+
this._config = require(path);
34+
} catch (e) {
35+
throw new Error(`Config file does not exits.`);
36+
}
37+
} else {
38+
this._config = this._fromProject();
39+
}
40+
}
3141

32-
save(path: string = CliConfig.configFilePath()) {
42+
save(path: string = this._configFilePath()) {
3343
if (!path) {
3444
throw new Error('Could not find config path.');
3545
}
@@ -38,18 +48,55 @@ export class CliConfig {
3848
}
3949

4050
set(jsonPath: string, value: any, force: boolean = false): boolean {
51+
let method: any = null;
52+
let splittedPath = jsonPath.split('.');
53+
if (ARRAY_METHODS.indexOf(splittedPath[splittedPath.length - 1]) != -1) {
54+
method = splittedPath[splittedPath.length - 1];
55+
splittedPath.splice(splittedPath.length - 1, 1);
56+
jsonPath = splittedPath.join('.');
57+
}
58+
4159
let { parent, name, remaining } = this._findParent(jsonPath);
42-
while (force && remaining) {
43-
if (remaining.indexOf('.') != -1) {
44-
// Create an empty map.
45-
// TODO: create the object / array based on the Schema of the configuration.
46-
parent[name] = {};
60+
let properties: any;
61+
let additionalProperties: boolean;
62+
63+
const checkPath = jsonPath.split('.').reduce((o, i) => {
64+
if (!o || !o.properties) {
65+
throw new Error(`Invalid config path.`);
4766
}
67+
properties = o.properties;
68+
additionalProperties = o.additionalProperties;
4869

70+
return o.properties[i];
71+
}, schema);
72+
const configPath = jsonPath.split('.').reduce((o, i) => o[i], this._config);
73+
74+
if (!properties[name] && !additionalProperties) {
75+
throw new Error(`${name} is not a known property.`);
4976
}
5077

51-
parent[name] = value;
52-
return true;
78+
if (method) {
79+
if (Array.isArray(configPath) && checkPath.type === 'array') {
80+
[][method].call(configPath, value);
81+
return true;
82+
} else {
83+
throw new Error(`Trying to use array method on non-array property type.`);
84+
}
85+
}
86+
87+
if (typeof checkPath.type === 'string' && isNaN(value)) {
88+
parent[name] = value;
89+
return true;
90+
}
91+
92+
if (typeof checkPath.type === 'number' && !isNaN(value)) {
93+
parent[name] = value;
94+
return true;
95+
}
96+
97+
if (typeof value != checkPath.type) {
98+
throw new Error(`Invalid value type. Trying to set ${typeof value} to ${path.type}`);
99+
}
53100
}
54101

55102
get(jsonPath: string): any {
@@ -110,16 +157,16 @@ export class CliConfig {
110157
return { parent, name };
111158
}
112159

113-
static configFilePath(projectPath?: string): string {
160+
private _configFilePath(projectPath?: string): string {
114161
// Find the configuration, either where specified, in the angular-cli project
115162
// (if it's in node_modules) or from the current process.
116163
return (projectPath && _findUp(CLI_CONFIG_FILE_NAME, projectPath))
117164
|| _findUp(CLI_CONFIG_FILE_NAME, __dirname)
118165
|| _findUp(CLI_CONFIG_FILE_NAME, process.cwd());
119166
}
120167

121-
static fromProject(): CliConfigJson {
122-
const configPath = this.configFilePath();
168+
private _fromProject(): any {
169+
const configPath = this._configFilePath();
123170
return configPath ? require(configPath) : {};
124171
}
125172
}

lib/cli/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,8 @@ module.exports = function(options) {
9696
// ensure the environemnt variable for dynamic paths
9797
process.env.PWD = process.env.PWD || process.cwd();
9898

99+
100+
process.env.CLI_ROOT = process.env.CLI_ROOT || path.resolve(__dirname, '..', '..');
101+
99102
return cli(options);
100103
}

lib/config/schema.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"items": {
2323
"type": "object",
2424
"properties": {
25-
"root": "string",
25+
"main": "string",
2626
"tsconfig": "string"
2727
},
2828
"additionalProperties": false

tests/models/config.spec.ts

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import {CliConfig} from '../../addon/ng2/models/config';
2+
import * as fs from 'fs';
3+
import * as path from 'path';
4+
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');
8+
9+
function getContents() {
10+
return require(configCopy);
11+
}
12+
13+
describe('Config Tests', () => {
14+
before(() => {
15+
process.chdir(process.cwd());
16+
});
17+
18+
beforeEach(() => {
19+
let contents = JSON.parse(fs.readFileSync(config, 'utf8'));
20+
fs.writeFileSync(configCopy, JSON.stringify(contents, null, 2), 'utf8');
21+
});
22+
23+
afterEach(() => {
24+
try {
25+
fs.accessSync(configCopy);
26+
fs.unlinkSync(configCopy);
27+
} catch (e) { /* */ }
28+
});
29+
30+
it('Throws an error if config file not exists', () => {
31+
fs.unlinkSync(configCopy);
32+
33+
let fn = () => {
34+
return new CliConfig('foobar.json');
35+
}
36+
37+
expect(fn).to.throw(Error);
38+
});
39+
40+
it('Updates property of type `string` successfully', () => {
41+
let c = new CliConfig(configCopy);
42+
c.set('project.name', 'new-project-name');
43+
c.save();
44+
45+
let contents = getContents();
46+
47+
expect(contents).to.be.an('object');
48+
expect(contents.project.name).to.exist;
49+
expect(contents.project.name).to.be.equal('new-project-name');
50+
});
51+
52+
it('Throws an error if try to assign property that does not exists', () => {
53+
let c = new CliConfig(configCopy);
54+
55+
let fn = () => {
56+
c.set('project.foo', 'bar');
57+
c.save();
58+
}
59+
60+
expect(fn).to.throw(Error);
61+
});
62+
63+
it('Throws an error if try to use array method on property of type `string`', () => {
64+
let c = new CliConfig(configCopy);
65+
66+
let fn = () => {
67+
c.set('project.name.push', 'new-project-name');
68+
c.save();
69+
}
70+
71+
expect(fn).to.throw(Error);
72+
});
73+
74+
it('Throws an error if try to use `number` on property of type `string`', () => {
75+
let c = new CliConfig(configCopy);
76+
77+
let fn = () => {
78+
c.set('project.name', 42);
79+
c.save();
80+
}
81+
82+
expect(fn).to.throw(Error);
83+
});
84+
85+
});

tests/runner.js

+35-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,45 @@
1+
/* eslint-disable no-console */
12
'use strict';
23

4+
const fs = require('fs');
5+
const ts = require('typescript');
6+
const old = require.extensions['.ts'];
7+
8+
require.extensions['.ts'] = function(m, filename) {
9+
if (!filename.match(/angular-cli/) && filename.match(/node_modules/)) {
10+
if (old) {
11+
return old(m, filename);
12+
}
13+
return m._compile(fs.readFileSync(filename), filename);
14+
}
15+
16+
const source = fs.readFileSync(filename).toString();
17+
18+
try {
19+
const result = ts.transpile(source, {
20+
target: ts.ScriptTarget.ES5,
21+
module: ts.ModuleKind.CommonJs
22+
});
23+
24+
// Send it to node to execute.
25+
return m._compile(result, filename);
26+
} catch (err) {
27+
console.error('Error while running script "' + filename + '":');
28+
console.error(err.stack);
29+
throw err;
30+
}
31+
};
32+
333
var Mocha = require('mocha');
434
var glob = require('glob');
35+
var path = require('path');
536

6-
var root = 'tests/{unit,acceptance,e2e}';
7-
var specFiles = glob.sync(root + '/**/*.spec.js');
37+
var root = 'tests/{acceptance,models,e2e}';
38+
var specFiles = glob.sync(root + '/**/*.spec.*');
839
var mocha = new Mocha({ timeout: 5000, reporter: 'spec' });
940

41+
process.env.CLI_ROOT = process.env.CLI_ROOT || path.resolve(__dirname, '..');
42+
1043
specFiles.forEach(mocha.addFile.bind(mocha));
1144

1245
mocha.run(function (failures) {

0 commit comments

Comments
 (0)