|
1 | 1 | import * as fs from 'fs';
|
2 | 2 | import * as path from 'path';
|
| 3 | +import * as jp from 'jsonpath'; |
| 4 | +import * as chalk from 'chalk'; |
3 | 5 |
|
4 | 6 | const schemaPath = path.resolve(process.env.CLI_ROOT, 'lib/config/schema.json');
|
5 | 7 | const schema = require(schemaPath);
|
6 | 8 |
|
7 | 9 | export const CLI_CONFIG_FILE_NAME = 'angular-cli.json';
|
8 | 10 | export const ARRAY_METHODS = ['push', 'splice', 'sort', 'reverse', 'pop', 'shift'];
|
9 | 11 |
|
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; |
17 | 15 |
|
18 |
| - currentDir = path.resolve(currentDir, '..'); |
| 16 | + constructor() { |
| 17 | + this.global = this._fromCli(); |
| 18 | + this.project = this._fromProject(); |
19 | 19 | }
|
20 | 20 |
|
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 | + } |
23 | 25 |
|
| 26 | + checkValidSchemaPath(jsonPath: Object): boolean { |
| 27 | + const parsed = jp.parse(jsonPath); |
| 28 | + const invalidMsg = `${jsonPath} does not match schema.`; |
| 29 | + let propertiesPath; |
24 | 30 |
|
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; |
27 | 34 |
|
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; |
35 | 37 | }
|
36 |
| - } else { |
37 |
| - this._config = CliConfig.fromProject(); |
38 |
| - } |
39 |
| - } |
40 | 38 |
|
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 | + }); |
45 | 54 |
|
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 | + } |
47 | 60 | }
|
48 | 61 |
|
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; |
57 | 64 |
|
58 |
| - let { parent, name, remaining } = this._findParent(jsonPath); |
59 |
| - let properties: any; |
60 |
| - let additionalProperties: boolean; |
| 65 | + this._validatePath(jsonPath); |
| 66 | + this.checkValidSchemaPath(jsonPath); |
61 | 67 |
|
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}`); |
65 | 73 | }
|
66 |
| - properties = o.properties; |
67 |
| - additionalProperties = o.additionalProperties; |
| 74 | + } |
68 | 75 |
|
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); |
72 | 77 |
|
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), ''); |
76 | 81 |
|
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.`); |
81 | 84 | } else {
|
82 |
| - throw new Error(`Trying to use array method on non-array property type.`); |
| 85 | + [][method].call(parent, value); |
83 | 86 | }
|
84 | 87 | }
|
85 | 88 |
|
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.`); |
89 | 91 | }
|
90 | 92 |
|
91 |
| - if (typeof checkPath.type === 'number' && !isNaN(value)) { |
92 |
| - parent[name] = value; |
93 |
| - return true; |
94 |
| - } |
| 93 | + jp.value(config, `$.${jsonPath}`, value); |
95 | 94 |
|
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; |
99 | 96 | }
|
100 | 97 |
|
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; |
108 | 103 | }
|
109 | 104 |
|
110 | 105 | private _validatePath(jsonPath: string) {
|
111 |
| - if (!jsonPath.match(/^(?:[-_\w\d]+(?:\[\d+\])*\.)*(?:[-_\w\d]+(?:\[\d+\])*)$/)) { |
| 106 | + try { |
| 107 | + jp.parse(jsonPath); |
| 108 | + } catch (e) { |
112 | 109 | throw `Invalid JSON path: "${jsonPath}"`;
|
113 | 110 | }
|
114 | 111 | }
|
115 | 112 |
|
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 {}; |
154 | 120 | }
|
155 |
| - |
156 |
| - return { parent, name }; |
157 | 121 | }
|
158 | 122 |
|
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 | + } |
165 | 131 | }
|
166 | 132 |
|
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; |
170 | 145 | }
|
171 | 146 | }
|
0 commit comments