UNPKG

8.83 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright Google LLC 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 });
10exports.readJsonWorkspace = void 0;
11const json_1 = require("../../json");
12const definitions_1 = require("../definitions");
13const metadata_1 = require("./metadata");
14const utilities_1 = require("./utilities");
15async function readJsonWorkspace(path, host) {
16 const raw = await host.readFile(path);
17 if (raw === undefined) {
18 throw new Error('Unable to read workspace file.');
19 }
20 const ast = json_1.parseJsonAst(raw, json_1.JsonParseMode.Loose);
21 if (ast.kind !== 'object') {
22 throw new Error('Invalid workspace file - expected JSON object.');
23 }
24 // Version check
25 const versionNode = ast.properties.find((pair) => pair.key.value === 'version');
26 if (!versionNode) {
27 throw new Error('Unknown format - version specifier not found.');
28 }
29 const formatVersion = versionNode.value.value;
30 if (formatVersion !== 1) {
31 throw new Error(`Invalid format version detected - Expected:[ 1 ] Found: [ ${formatVersion} ]`);
32 }
33 const context = {
34 host,
35 metadata: new metadata_1.JsonWorkspaceMetadata(path, ast, raw),
36 trackChanges: true,
37 error(message, _node) {
38 // TODO: Diagnostic reporting support
39 throw new Error(message);
40 },
41 warn(_message, _node) {
42 // TODO: Diagnostic reporting support
43 },
44 };
45 const workspace = parseWorkspace(ast, context);
46 return workspace;
47}
48exports.readJsonWorkspace = readJsonWorkspace;
49const specialWorkspaceExtensions = ['cli', 'defaultProject', 'newProjectRoot', 'schematics'];
50const specialProjectExtensions = ['cli', 'schematics', 'projectType'];
51function parseWorkspace(workspaceNode, context) {
52 const jsonMetadata = context.metadata;
53 let projects;
54 let projectsNode;
55 let extensions;
56 if (!context.trackChanges) {
57 extensions = Object.create(null);
58 }
59 for (const { key, value } of workspaceNode.properties) {
60 const name = key.value;
61 if (name === '$schema' || name === 'version') {
62 // skip
63 }
64 else if (name === 'projects') {
65 if (value.kind !== 'object') {
66 context.error('Invalid "projects" field found; expected an object.', value);
67 continue;
68 }
69 projectsNode = value;
70 projects = parseProjectsObject(value, context);
71 }
72 else {
73 if (!specialWorkspaceExtensions.includes(name) && !/^[a-z]{1,3}-.*/.test(name)) {
74 context.warn(`Project extension with invalid name found.`, key);
75 }
76 if (extensions) {
77 extensions[name] = value.value;
78 }
79 }
80 }
81 let collectionListener;
82 if (context.trackChanges && projectsNode) {
83 const parentNode = projectsNode;
84 collectionListener = (name, action, newValue) => {
85 jsonMetadata.addChange(action, `/projects/${utilities_1.escapeKey(name)}`, parentNode, newValue, 'project');
86 };
87 }
88 const projectCollection = new definitions_1.ProjectDefinitionCollection(projects, collectionListener);
89 return {
90 [metadata_1.JsonWorkspaceSymbol]: jsonMetadata,
91 projects: projectCollection,
92 // If not tracking changes the `extensions` variable will contain the parsed
93 // values. Otherwise the extensions are tracked via a virtual AST object.
94 extensions: extensions ||
95 utilities_1.createVirtualAstObject(workspaceNode, {
96 exclude: ['$schema', 'version', 'projects'],
97 listener(op, path, node, value) {
98 jsonMetadata.addChange(op, path, node, value);
99 },
100 }),
101 };
102}
103function parseProjectsObject(projectsNode, context) {
104 const projects = Object.create(null);
105 for (const { key, value } of projectsNode.properties) {
106 if (value.kind !== 'object') {
107 context.warn('Skipping invalid project value; expected an object.', value);
108 continue;
109 }
110 const name = key.value;
111 projects[name] = parseProject(name, value, context);
112 }
113 return projects;
114}
115function parseProject(projectName, projectNode, context) {
116 const jsonMetadata = context.metadata;
117 let targets;
118 let targetsNode;
119 let extensions;
120 let properties;
121 if (!context.trackChanges) {
122 // If not tracking changes, the parser will store the values directly in standard objects
123 extensions = Object.create(null);
124 properties = Object.create(null);
125 }
126 for (const { key, value } of projectNode.properties) {
127 const name = key.value;
128 switch (name) {
129 case 'targets':
130 case 'architect':
131 if (value.kind !== 'object') {
132 context.error(`Invalid "${name}" field found; expected an object.`, value);
133 break;
134 }
135 targetsNode = value;
136 targets = parseTargetsObject(projectName, value, context);
137 break;
138 case 'prefix':
139 case 'root':
140 case 'sourceRoot':
141 if (value.kind !== 'string') {
142 context.warn(`Project property "${name}" should be a string.`, value);
143 }
144 if (properties) {
145 properties[name] = value.value;
146 }
147 break;
148 default:
149 if (!specialProjectExtensions.includes(name) && !/^[a-z]{1,3}-.*/.test(name)) {
150 context.warn(`Project extension with invalid name found.`, key);
151 }
152 if (extensions) {
153 extensions[name] = value.value;
154 }
155 break;
156 }
157 }
158 let collectionListener;
159 if (context.trackChanges) {
160 if (targetsNode) {
161 const parentNode = targetsNode;
162 collectionListener = (name, action, newValue) => {
163 jsonMetadata.addChange(action, `/projects/${projectName}/targets/${utilities_1.escapeKey(name)}`, parentNode, newValue, 'target');
164 };
165 }
166 else {
167 let added = false;
168 collectionListener = (_name, action, _new, _old, collection) => {
169 if (added || action !== 'add') {
170 return;
171 }
172 jsonMetadata.addChange('add', `/projects/${projectName}/targets`, projectNode, collection, 'targetcollection');
173 added = true;
174 };
175 }
176 }
177 const base = {
178 targets: new definitions_1.TargetDefinitionCollection(targets, collectionListener),
179 // If not tracking changes the `extensions` variable will contain the parsed
180 // values. Otherwise the extensions are tracked via a virtual AST object.
181 extensions: extensions ||
182 utilities_1.createVirtualAstObject(projectNode, {
183 exclude: ['architect', 'prefix', 'root', 'sourceRoot', 'targets'],
184 listener(op, path, node, value) {
185 jsonMetadata.addChange(op, `/projects/${projectName}${path}`, node, value);
186 },
187 }),
188 };
189 let project;
190 if (context.trackChanges) {
191 project = utilities_1.createVirtualAstObject(projectNode, {
192 base,
193 include: ['prefix', 'root', 'sourceRoot'],
194 listener(op, path, node, value) {
195 jsonMetadata.addChange(op, `/projects/${projectName}${path}`, node, value);
196 },
197 });
198 }
199 else {
200 project = {
201 ...base,
202 ...properties,
203 };
204 }
205 return project;
206}
207function parseTargetsObject(projectName, targetsNode, context) {
208 const jsonMetadata = context.metadata;
209 const targets = Object.create(null);
210 for (const { key, value } of targetsNode.properties) {
211 if (value.kind !== 'object') {
212 context.warn('Skipping invalid target value; expected an object.', value);
213 continue;
214 }
215 const name = key.value;
216 if (context.trackChanges) {
217 targets[name] = utilities_1.createVirtualAstObject(value, {
218 include: ['builder', 'options', 'configurations', 'defaultConfiguration'],
219 listener(op, path, node, value) {
220 jsonMetadata.addChange(op, `/projects/${projectName}/targets/${name}${path}`, node, value);
221 },
222 });
223 }
224 else {
225 targets[name] = value.value;
226 }
227 }
228 return targets;
229}