Skip to content

Commit 1ecd72d

Browse files
authored
refactor(config): refactor the config object. (#1809)
This also will load angular-cli.json in the HOME directory as a fallback, supports more stuff from the JSON Schema (like default values) than the old one, and actually verify that what you inputs is the right thing. This will be its own NPM package at some point, as other people will probably be interested in having a JSON Schema loader that gives type safety and provides fallbacks and metadata. Closes #1763
1 parent 0d914b2 commit 1ecd72d

24 files changed

+1092
-234
lines changed

addon/ng2/commands/get.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ const GetCommand = Command.extend({
1212

1313
run: function (commandOptions, rawArgs): Promise<void> {
1414
return new Promise(resolve => {
15-
const value = new CliConfig().get(rawArgs[0]);
15+
const config = CliConfig.fromProject();
16+
const value = config.get(rawArgs[0]);
17+
1618
if (value === null) {
1719
console.error(chalk.red('Value cannot be found.'));
1820
} else if (typeof value == 'object') {

addon/ng2/commands/github-pages-deploy.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ module.exports = Command.extend({
2727
type: String,
2828
default: 'new gh-pages version',
2929
description: 'The commit message to include with the build, must be wrapped in quotes.'
30-
}, {
30+
}, {
3131
name: 'target',
3232
type: String,
33-
default: 'production',
33+
default: 'production',
3434
aliases: ['t', { 'dev': 'development' }, { 'prod': 'production' }]
35-
}, {
35+
}, {
3636
name: 'environment',
3737
type: String,
3838
default: '',
@@ -72,12 +72,12 @@ module.exports = Command.extend({
7272
}
7373
if (options.target === 'production') {
7474
options.environment = 'prod';
75-
}
75+
}
7676
}
7777

7878
var projectName = this.project.pkg.name;
7979

80-
const outDir = CliConfig.fromProject().apps[0].outDir;
80+
const outDir = CliConfig.fromProject().config.apps[0].outDir;
8181

8282
let ghPagesBranch = 'gh-pages';
8383
let destinationBranch = options.userPage ? 'master' : ghPagesBranch;

addon/ng2/commands/set.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as SilentError from 'silent-error';
12
import * as Command from 'ember-cli/lib/models/command';
23
import {CliConfig} from '../models/config';
34

@@ -11,10 +12,38 @@ const SetCommand = Command.extend({
1112
{ name: 'global', type: Boolean, default: false, aliases: ['g'] },
1213
],
1314

15+
asBoolean: function (raw: string): boolean {
16+
if (raw == 'true' || raw == '1') {
17+
return true;
18+
} else if (raw == 'false' || raw == '' || raw == '0') {
19+
return false;
20+
} else {
21+
throw new SilentError(`Invalid boolean value: "${raw}"`);
22+
}
23+
},
24+
asNumber: function (raw: string): number {
25+
if (Number.isNaN(+raw)) {
26+
throw new SilentError(`Invalid number value: "${raw}"`);
27+
}
28+
return +raw;
29+
},
30+
1431
run: function (commandOptions, rawArgs): Promise<void> {
1532
return new Promise(resolve => {
16-
const config = new CliConfig();
17-
config.set(rawArgs[0], rawArgs[1], commandOptions.force);
33+
const [jsonPath, rawValue] = rawArgs;
34+
const config = CliConfig.fromProject();
35+
const type = config.typeOf(jsonPath);
36+
let value: any = rawValue;
37+
38+
switch (type) {
39+
case 'boolean': value = this.asBoolean(rawValue); break;
40+
case 'number': value = this.asNumber(rawValue); break;
41+
case 'string': value = rawValue; break;
42+
43+
default: value = JSON.parse(rawValue);
44+
}
45+
46+
config.set(jsonPath, value);
1847
config.save();
1948
resolve();
2049
});

addon/ng2/commands/test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as TestCommand from 'ember-cli/lib/commands/test';
2-
import * as config from '../models/config';
32
import * as TestTask from '../tasks/test';
3+
import {CliConfig} from '../models/config';
44

55
module.exports = TestCommand.extend({
66
availableOptions: [
@@ -14,7 +14,7 @@ module.exports = TestCommand.extend({
1414
],
1515

1616
run: function (commandOptions) {
17-
this.project.ngConfig = this.project.ngConfig || config.CliConfig.fromProject();
17+
this.project.ngConfig = this.project.ngConfig || CliConfig.fromProject();
1818

1919
var testTask = new TestTask({
2020
ui: this.ui,
File renamed without changes.

addon/ng2/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module.exports = {
77
name: 'ng2',
88

99
config: function () {
10-
this.project.ngConfig = this.project.ngConfig || config.CliConfig.fromProject();
10+
this.project.ngConfig = this.project.ngConfig || config.CliConfig.fromProject().config;
1111
},
1212

1313
includedCommands: function () {

addon/ng2/models/config.ts

Lines changed: 21 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
import {CliConfig as CliConfigBase} from './config/config';
2+
import {CliConfig as ConfigInterface} from '../../../lib/config/schema';
3+
import * as chalk from 'chalk';
14
import * as fs from 'fs';
25
import * as path from 'path';
3-
import * as chalk from 'chalk';
46

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

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

1113
function _findUp(name: string, from: string) {
1214
let currentDir = from;
@@ -16,175 +18,46 @@ function _findUp(name: string, from: string) {
1618
return p;
1719
}
1820

19-
currentDir = path.resolve(currentDir, '..');
21+
currentDir = path.dirname(currentDir);
2022
}
2123

2224
return null;
2325
}
2426

2527

26-
export class CliConfig {
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 = CliConfig.fromProject();
39-
}
40-
}
41-
42-
save(path: string = CliConfig._configFilePath()) {
43-
if (!path) {
44-
throw new Error('Could not find config path.');
45-
}
46-
47-
fs.writeFileSync(path, JSON.stringify(this._config, null, 2), { encoding: 'utf-8' });
48-
}
49-
50-
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-
59-
let { parent, name, remaining } = this._findParent(jsonPath);
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.`);
66-
}
67-
properties = o.properties;
68-
additionalProperties = o.additionalProperties;
69-
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.`);
76-
}
77-
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-
}
100-
}
101-
102-
get(jsonPath: string): any {
103-
let { parent, name, remaining } = this._findParent(jsonPath);
104-
if (remaining || !(name in parent)) {
105-
return null;
106-
} else {
107-
return parent[name];
108-
}
109-
}
110-
111-
private _validatePath(jsonPath: string) {
112-
if (!jsonPath.match(/^(?:[-_\w\d]+(?:\[\d+\])*\.)*(?:[-_\w\d]+(?:\[\d+\])*)$/)) {
113-
throw `Invalid JSON path: "${jsonPath}"`;
114-
}
115-
}
116-
117-
private _findParent(jsonPath: string): { parent: any, name: string | number, remaining?: string } {
118-
this._validatePath(jsonPath);
119-
120-
let parent: any = null;
121-
let current: any = this._config;
122-
123-
const splitPath = jsonPath.split('.');
124-
let name: string | number = '';
125-
126-
while (splitPath.length > 0) {
127-
const m = splitPath.shift().match(/^(.*?)(?:\[(\d+)\])*$/);
128-
129-
name = m[1];
130-
const index: string = m[2];
131-
parent = current;
132-
current = current[name];
133-
134-
if (current === null || current === undefined) {
135-
return {
136-
parent,
137-
name,
138-
remaining: (!isNaN(index) ? `[${index}]` : '') + splitPath.join('.')
139-
};
140-
}
141-
142-
if (!isNaN(index)) {
143-
name = index;
144-
parent = current;
145-
current = current[index];
146-
147-
if (current === null || current === undefined) {
148-
return {
149-
parent,
150-
name,
151-
remaining: splitPath.join('.')
152-
};
153-
}
154-
}
155-
}
28+
function getUserHome() {
29+
return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'];
30+
}
15631

157-
return { parent, name };
158-
}
15932

33+
export class CliConfig extends CliConfigBase<ConfigInterface> {
16034
private static _configFilePath(projectPath?: string): string {
16135
// Find the configuration, either where specified, in the angular-cli project
16236
// (if it's in node_modules) or from the current process.
16337
return (projectPath && _findUp(CLI_CONFIG_FILE_NAME, projectPath))
164-
|| _findUp(CLI_CONFIG_FILE_NAME, __dirname)
165-
|| _findUp(CLI_CONFIG_FILE_NAME, process.cwd());
38+
|| _findUp(CLI_CONFIG_FILE_NAME, process.cwd())
39+
|| _findUp(CLI_CONFIG_FILE_NAME, __dirname);
16640
}
16741

168-
public static fromProject(): any {
169-
const configPath = CliConfig._configFilePath();
42+
43+
static fromProject(): CliConfig {
44+
const configPath = this._configFilePath();
45+
const globalConfigPath = path.join(getUserHome(), CLI_CONFIG_FILE_NAME);
17046

17147
if (!configPath) {
17248
return {};
17349
}
17450

175-
let config = require(configPath);
176-
177-
if (config.defaults.sourceDir || config.defaults.prefix) {
178-
config.apps[0].root = config.apps[0].root || config.defaults.sourceDir;
179-
config.apps[0].prefix = config.apps[0].prefix || config.defaults.prefix;
180-
51+
const cliConfig = CliConfigBase.fromConfigPath(CliConfig._configFilePath(), [globalConfigPath]);
52+
if (cliConfig.alias('apps.0.root', 'defaults.sourceDir')
53+
+ cliConfig.alias('apps.0.prefix', 'defaults.prefix')) {
18154
console.error(chalk.yellow(
182-
'The "defaults.prefix" and "defaults.sourceDir" properties of angular-cli.json '
55+
'The "defaults.prefix" and "defaults.sourceDir" properties of angular-cli.json\n'
18356
+ 'are deprecated in favor of "apps[0].root" and "apps[0].prefix".\n'
18457
+ 'Please update in order to avoid errors in future versions of angular-cli.'
18558
));
18659
}
187-
188-
return config;
60+
61+
return cliConfig as CliConfig;
18962
}
19063
}

0 commit comments

Comments
 (0)