UNPKG

9.47 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright Google Inc. All Rights Reserved.
5 *
6 * Use of this source code is governed by an MIT-style license that can be
7 * found in the LICENSE file at https://angular.io/license
8 */
9Object.defineProperty(exports, "__esModule", { value: true });
10const core_1 = require("@angular-devkit/core");
11const fs_1 = require("fs");
12const uuid_1 = require("uuid");
13const command_1 = require("../models/command");
14const interface_1 = require("../models/interface");
15const config_1 = require("../utilities/config");
16function _validateBoolean(value) {
17 if (('' + value).trim() === 'true') {
18 return true;
19 }
20 else if (('' + value).trim() === 'false') {
21 return false;
22 }
23 else {
24 throw new Error(`Invalid value type; expected Boolean, received ${JSON.stringify(value)}.`);
25 }
26}
27function _validateNumber(value) {
28 const numberValue = Number(value);
29 if (!Number.isFinite(numberValue)) {
30 return numberValue;
31 }
32 throw new Error(`Invalid value type; expected Number, received ${JSON.stringify(value)}.`);
33}
34function _validateString(value) {
35 return value;
36}
37function _validateAnalytics(value) {
38 if (value === '') {
39 // Disable analytics.
40 return null;
41 }
42 else {
43 return value;
44 }
45}
46function _validateAnalyticsSharingUuid(value) {
47 if (value == '') {
48 return uuid_1.v4();
49 }
50 else {
51 return value;
52 }
53}
54function _validateAnalyticsSharingTracking(value) {
55 if (!value.match(/^GA-\d+-\d+$/)) {
56 throw new Error(`Invalid GA property ID: ${JSON.stringify(value)}.`);
57 }
58 return value;
59}
60const validCliPaths = new Map([
61 ['cli.warnings.versionMismatch', _validateBoolean],
62 ['cli.defaultCollection', _validateString],
63 ['cli.packageManager', _validateString],
64 ['cli.analytics', _validateAnalytics],
65 ['cli.analyticsSharing.tracking', _validateAnalyticsSharingTracking],
66 ['cli.analyticsSharing.uuid', _validateAnalyticsSharingUuid],
67]);
68/**
69 * Splits a JSON path string into fragments. Fragments can be used to get the value referenced
70 * by the path. For example, a path of "a[3].foo.bar[2]" would give you a fragment array of
71 * ["a", 3, "foo", "bar", 2].
72 * @param path The JSON string to parse.
73 * @returns {(string|number)[]} The fragments for the string.
74 * @private
75 */
76function parseJsonPath(path) {
77 const fragments = (path || '').split(/\./g);
78 const result = [];
79 while (fragments.length > 0) {
80 const fragment = fragments.shift();
81 if (fragment == undefined) {
82 break;
83 }
84 const match = fragment.match(/([^\[]+)((\[.*\])*)/);
85 if (!match) {
86 throw new Error('Invalid JSON path.');
87 }
88 result.push(match[1]);
89 if (match[2]) {
90 const indices = match[2]
91 .slice(1, -1)
92 .split('][')
93 .map(x => (/^\d$/.test(x) ? +x : x.replace(/\"|\'/g, '')));
94 result.push(...indices);
95 }
96 }
97 return result.filter(fragment => fragment != null);
98}
99function getValueFromPath(root, path) {
100 const fragments = parseJsonPath(path);
101 try {
102 return fragments.reduce((value, current) => {
103 if (value == undefined || typeof value != 'object') {
104 return undefined;
105 }
106 else if (typeof current == 'string' && !Array.isArray(value)) {
107 return value[current];
108 }
109 else if (typeof current == 'number' && Array.isArray(value)) {
110 return value[current];
111 }
112 else {
113 return undefined;
114 }
115 }, root);
116 }
117 catch (_a) {
118 return undefined;
119 }
120}
121function setValueFromPath(root, path, newValue) {
122 const fragments = parseJsonPath(path);
123 try {
124 return fragments.reduce((value, current, index) => {
125 if (value == undefined || typeof value != 'object') {
126 return undefined;
127 }
128 else if (typeof current == 'string' && !Array.isArray(value)) {
129 if (index === fragments.length - 1) {
130 value[current] = newValue;
131 }
132 else if (value[current] == undefined) {
133 if (typeof fragments[index + 1] == 'number') {
134 value[current] = [];
135 }
136 else if (typeof fragments[index + 1] == 'string') {
137 value[current] = {};
138 }
139 }
140 return value[current];
141 }
142 else if (typeof current == 'number' && Array.isArray(value)) {
143 if (index === fragments.length - 1) {
144 value[current] = newValue;
145 }
146 else if (value[current] == undefined) {
147 if (typeof fragments[index + 1] == 'number') {
148 value[current] = [];
149 }
150 else if (typeof fragments[index + 1] == 'string') {
151 value[current] = {};
152 }
153 }
154 return value[current];
155 }
156 else {
157 return undefined;
158 }
159 }, root);
160 }
161 catch (_a) {
162 return undefined;
163 }
164}
165function normalizeValue(value, path) {
166 const cliOptionType = validCliPaths.get(path);
167 if (cliOptionType) {
168 return cliOptionType('' + value);
169 }
170 if (typeof value === 'string') {
171 try {
172 return core_1.parseJson(value, core_1.JsonParseMode.Loose);
173 }
174 catch (e) {
175 if (e instanceof core_1.InvalidJsonCharacterException && !value.startsWith('{')) {
176 return value;
177 }
178 else {
179 throw e;
180 }
181 }
182 }
183 return value;
184}
185class ConfigCommand extends command_1.Command {
186 async run(options) {
187 const level = options.global ? 'global' : 'local';
188 if (!options.global) {
189 await this.validateScope(interface_1.CommandScope.InProject);
190 }
191 let config = await config_1.getWorkspace(level);
192 if (options.global && !config) {
193 try {
194 if (config_1.migrateLegacyGlobalConfig()) {
195 config = await config_1.getWorkspace(level);
196 this.logger.info(core_1.tags.oneLine `
197 We found a global configuration that was used in Angular CLI 1.
198 It has been automatically migrated.`);
199 }
200 }
201 catch (_a) { }
202 }
203 if (options.value == undefined) {
204 if (!config) {
205 this.logger.error('No config found.');
206 return 1;
207 }
208 const workspace = config
209 ._workspace;
210 return this.get(workspace, options);
211 }
212 else {
213 return this.set(options);
214 }
215 }
216 get(config, options) {
217 let value;
218 if (options.jsonPath) {
219 if (options.jsonPath === 'cli.warnings.typescriptMismatch') {
220 // NOTE: Remove this in 9.0.
221 this.logger.warn('The "typescriptMismatch" warning has been removed in 8.0.');
222 // Since there is no actual warning, this value is always false.
223 this.logger.info('false');
224 return 0;
225 }
226 value = getValueFromPath(config, options.jsonPath);
227 }
228 else {
229 value = config;
230 }
231 if (value === undefined) {
232 this.logger.error('Value cannot be found.');
233 return 1;
234 }
235 else if (typeof value == 'object') {
236 this.logger.info(JSON.stringify(value, null, 2));
237 }
238 else {
239 this.logger.info(value.toString());
240 }
241 return 0;
242 }
243 async set(options) {
244 if (!options.jsonPath || !options.jsonPath.trim()) {
245 throw new Error('Invalid Path.');
246 }
247 if (options.jsonPath === 'cli.warnings.typescriptMismatch') {
248 // NOTE: Remove this in 9.0.
249 this.logger.warn('The "typescriptMismatch" warning has been removed in 8.0.');
250 return 0;
251 }
252 if (options.global &&
253 !options.jsonPath.startsWith('schematics.') &&
254 !validCliPaths.has(options.jsonPath)) {
255 throw new Error('Invalid Path.');
256 }
257 const [config, configPath] = config_1.getWorkspaceRaw(options.global ? 'global' : 'local');
258 if (!config || !configPath) {
259 this.logger.error('Confguration file cannot be found.');
260 return 1;
261 }
262 // TODO: Modify & save without destroying comments
263 const configValue = config.value;
264 const value = normalizeValue(options.value || '', options.jsonPath);
265 const result = setValueFromPath(configValue, options.jsonPath, value);
266 if (result === undefined) {
267 this.logger.error('Value cannot be found.');
268 return 1;
269 }
270 try {
271 await config_1.validateWorkspace(configValue);
272 }
273 catch (error) {
274 this.logger.fatal(error.message);
275 return 1;
276 }
277 const output = JSON.stringify(configValue, null, 2);
278 fs_1.writeFileSync(configPath, output);
279 return 0;
280 }
281}
282exports.ConfigCommand = ConfigCommand;