Skip to content

Commit f38ac7a

Browse files
committed
refactor(config): refactor the config object.
1 parent 0cdd3c3 commit f38ac7a

22 files changed

+837
-169
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 = new CliConfig();
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: 30 additions & 1 deletion
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) {
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) {
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 => {
33+
const [jsonPath, rawValue] = rawArgs;
1634
const config = new CliConfig();
17-
config.set(rawArgs[0], rawArgs[1], commandOptions.force);
35+
const type = config.typeOf(rawArgs[0]);
36+
let value = 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: 22 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
import {CliConfig as CliConfigBase} from './config/config';
2+
import * as chalk from 'chalk';
13
import * as fs from 'fs';
24
import * as path from 'path';
3-
import * as chalk from 'chalk';
45

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

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

1112
function _findUp(name: string, from: string) {
1213
let currentDir = from;
@@ -16,175 +17,53 @@ function _findUp(name: string, from: string) {
1617
return p;
1718
}
1819

19-
currentDir = path.resolve(currentDir, '..');
20+
currentDir = path.dirname(currentDir);
2021
}
2122

2223
return null;
2324
}
2425

2526

26-
export class CliConfig {
27-
private _config: any;
27+
function getUserHome() {
28+
return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'];
29+
}
30+
2831

32+
export class CliConfig extends CliConfigBase {
2933
constructor(path?: string) {
3034
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-
}
35+
return CliConfigBase.fromConfigPath(path) as CliConfig;
3736
} 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.');
37+
return CliConfig.fromProject();
4538
}
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-
}
156-
157-
return { parent, name };
15839
}
15940

16041
private static _configFilePath(projectPath?: string): string {
16142
// Find the configuration, either where specified, in the angular-cli project
16243
// (if it's in node_modules) or from the current process.
16344
return (projectPath && _findUp(CLI_CONFIG_FILE_NAME, projectPath))
164-
|| _findUp(CLI_CONFIG_FILE_NAME, __dirname)
165-
|| _findUp(CLI_CONFIG_FILE_NAME, process.cwd());
45+
|| _findUp(CLI_CONFIG_FILE_NAME, process.cwd())
46+
|| _findUp(CLI_CONFIG_FILE_NAME, __dirname);
16647
}
16748

168-
public static fromProject(): any {
169-
const configPath = CliConfig._configFilePath();
49+
static fromProject(): CliConfig {
50+
const configPath = this._configFilePath();
51+
const globalConfigPath = path.join(getUserHome(), CLI_CONFIG_FILE_NAME);
17052

17153
if (!configPath) {
17254
return {};
17355
}
17456

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-
57+
const cliConfig = CliConfigBase.fromConfigPath(CliConfig._configFilePath(), [globalConfigPath]);
58+
if (cliConfig.alias('apps.0.root', 'defaults.sourceDir')
59+
+ cliConfig.alias('apps.0.prefix', 'defaults.prefix')) {
18160
console.error(chalk.yellow(
182-
'The "defaults.prefix" and "defaults.sourceDir" properties of angular-cli.json '
61+
'The "defaults.prefix" and "defaults.sourceDir" properties of angular-cli.json\n'
18362
+ 'are deprecated in favor of "apps[0].root" and "apps[0].prefix".\n'
18463
+ 'Please update in order to avoid errors in future versions of angular-cli.'
18564
));
18665
}
187-
188-
return config;
66+
67+
return cliConfig as CliConfig;
18968
}
19069
}

addon/ng2/models/config/config.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
4+
import {CliConfig as Config} from '../../../../lib/config/schema';
5+
import {SchemaClass, SchemaClassFactory} from '../json-schema/schema-class-factory';
6+
7+
8+
const DEFAULT_CONFIG_SCHEMA_PATH = path.join(__dirname, '../../../../lib/config/schema.json');
9+
10+
11+
export class InvalidConfigError extends Error {
12+
constructor(err: Error) {
13+
super(err.message);
14+
}
15+
}
16+
17+
export class CliConfig {
18+
private _config: SchemaClass<Config>;
19+
20+
private constructor(private _configPath: string,
21+
configJson: Config,
22+
schema: Object,
23+
fallbacks: Config[] = []) {
24+
this._config = new (SchemaClassFactory<Config>(schema))(configJson, ...fallbacks);
25+
}
26+
27+
get config(): Config { return this._config; }
28+
29+
save(path: string = this._configPath) {
30+
return fs.writeFileSync(path, this._config.$$serialize(), 'utf-8');
31+
}
32+
33+
alias(path: string, newPath: string): boolean {
34+
return this._config.$$alias(path, newPath);
35+
}
36+
37+
get(jsonPath: string) {
38+
return this._config.$$get(jsonPath);
39+
}
40+
41+
typeOf(jsonPath: string): string {
42+
return this._config.$$schema(jsonPath).type;
43+
}
44+
45+
set(jsonPath: string, value: any) {
46+
this._config.$$set(jsonPath, value);
47+
}
48+
49+
static fromConfigPath(configPath: string, otherPath: string[] = []): CliConfig {
50+
const configContent = fs.readFileSync(configPath, 'utf-8');
51+
const schemaContent = fs.readFileSync(DEFAULT_CONFIG_SCHEMA_PATH, 'utf-8');
52+
const otherContents = otherPath
53+
.map(path => fs.existsSync(path) && fs.readFileSync(path, 'utf-8'))
54+
.filter(content => !!content);
55+
56+
let content;
57+
let schema;
58+
let others;
59+
60+
try {
61+
content = JSON.parse(configContent);
62+
schema = JSON.parse(schemaContent);
63+
others = otherContents.map(content => JSON.parse(content));
64+
} catch (err) {
65+
throw new InvalidConfigError(err);
66+
}
67+
68+
return new CliConfig(configPath, content, schema, others);
69+
}
70+
}

0 commit comments

Comments
 (0)