file-system-engine-host-base.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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.dev/license
  8. */
  9. Object.defineProperty(exports, "__esModule", { value: true });
  10. exports.FileSystemEngineHostBase = exports.SchematicNameCollisionException = exports.SchematicMissingDescriptionException = exports.SchematicMissingFieldsException = exports.CollectionMissingFieldsException = exports.CollectionMissingSchematicsMapException = exports.FactoryCannotBeResolvedException = exports.SchematicMissingFactoryException = exports.InvalidCollectionJsonException = exports.CollectionCannotBeResolvedException = void 0;
  11. const core_1 = require("@angular-devkit/core");
  12. const node_1 = require("@angular-devkit/core/node");
  13. const fs_1 = require("fs");
  14. const path_1 = require("path");
  15. const rxjs_1 = require("rxjs");
  16. const src_1 = require("../src");
  17. const file_system_utility_1 = require("./file-system-utility");
  18. class CollectionCannotBeResolvedException extends core_1.BaseException {
  19. constructor(name) {
  20. super(`Collection ${JSON.stringify(name)} cannot be resolved.`);
  21. }
  22. }
  23. exports.CollectionCannotBeResolvedException = CollectionCannotBeResolvedException;
  24. class InvalidCollectionJsonException extends core_1.BaseException {
  25. constructor(_name, path, jsonException) {
  26. let msg = `Collection JSON at path ${JSON.stringify(path)} is invalid.`;
  27. if (jsonException) {
  28. msg = `${msg} ${jsonException.message}`;
  29. }
  30. super(msg);
  31. }
  32. }
  33. exports.InvalidCollectionJsonException = InvalidCollectionJsonException;
  34. class SchematicMissingFactoryException extends core_1.BaseException {
  35. constructor(name) {
  36. super(`Schematic ${JSON.stringify(name)} is missing a factory.`);
  37. }
  38. }
  39. exports.SchematicMissingFactoryException = SchematicMissingFactoryException;
  40. class FactoryCannotBeResolvedException extends core_1.BaseException {
  41. constructor(name) {
  42. super(`Schematic ${JSON.stringify(name)} cannot resolve the factory.`);
  43. }
  44. }
  45. exports.FactoryCannotBeResolvedException = FactoryCannotBeResolvedException;
  46. class CollectionMissingSchematicsMapException extends core_1.BaseException {
  47. constructor(name) {
  48. super(`Collection "${name}" does not have a schematics map.`);
  49. }
  50. }
  51. exports.CollectionMissingSchematicsMapException = CollectionMissingSchematicsMapException;
  52. class CollectionMissingFieldsException extends core_1.BaseException {
  53. constructor(name) {
  54. super(`Collection "${name}" is missing fields.`);
  55. }
  56. }
  57. exports.CollectionMissingFieldsException = CollectionMissingFieldsException;
  58. class SchematicMissingFieldsException extends core_1.BaseException {
  59. constructor(name) {
  60. super(`Schematic "${name}" is missing fields.`);
  61. }
  62. }
  63. exports.SchematicMissingFieldsException = SchematicMissingFieldsException;
  64. class SchematicMissingDescriptionException extends core_1.BaseException {
  65. constructor(name) {
  66. super(`Schematics "${name}" does not have a description.`);
  67. }
  68. }
  69. exports.SchematicMissingDescriptionException = SchematicMissingDescriptionException;
  70. class SchematicNameCollisionException extends core_1.BaseException {
  71. constructor(name) {
  72. super(`Schematics/alias ${JSON.stringify(name)} collides with another alias or schematic` +
  73. ' name.');
  74. }
  75. }
  76. exports.SchematicNameCollisionException = SchematicNameCollisionException;
  77. /**
  78. * A EngineHost base class that uses the file system to resolve collections. This is the base of
  79. * all other EngineHost provided by the tooling part of the Schematics library.
  80. */
  81. class FileSystemEngineHostBase {
  82. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  83. _transforms = [];
  84. _contextTransforms = [];
  85. _taskFactories = new Map();
  86. listSchematicNames(collection, includeHidden) {
  87. const schematics = [];
  88. for (const key of Object.keys(collection.schematics)) {
  89. const schematic = collection.schematics[key];
  90. if ((schematic.hidden && !includeHidden) || schematic.private) {
  91. continue;
  92. }
  93. // If extends is present without a factory it is an alias, do not return it
  94. // unless it is from another collection.
  95. if (!schematic.extends || schematic.factory) {
  96. schematics.push(key);
  97. }
  98. else if (schematic.extends && schematic.extends.indexOf(':') !== -1) {
  99. schematics.push(key);
  100. }
  101. }
  102. return schematics;
  103. }
  104. registerOptionsTransform(t) {
  105. this._transforms.push(t);
  106. }
  107. registerContextTransform(t) {
  108. this._contextTransforms.push(t);
  109. }
  110. /**
  111. *
  112. * @param name
  113. * @return {{path: string}}
  114. */
  115. createCollectionDescription(name, requester) {
  116. const path = this._resolveCollectionPath(name, requester?.path);
  117. const jsonValue = (0, file_system_utility_1.readJsonFile)(path);
  118. if (!jsonValue || typeof jsonValue != 'object' || Array.isArray(jsonValue)) {
  119. throw new InvalidCollectionJsonException(name, path);
  120. }
  121. // normalize extends property to an array
  122. if (typeof jsonValue['extends'] === 'string') {
  123. jsonValue['extends'] = [jsonValue['extends']];
  124. }
  125. const description = this._transformCollectionDescription(name, {
  126. ...jsonValue,
  127. path,
  128. });
  129. if (!description || !description.name) {
  130. throw new InvalidCollectionJsonException(name, path);
  131. }
  132. // Validate aliases.
  133. const allNames = Object.keys(description.schematics);
  134. for (const schematicName of Object.keys(description.schematics)) {
  135. const aliases = description.schematics[schematicName].aliases || [];
  136. for (const alias of aliases) {
  137. if (allNames.indexOf(alias) != -1) {
  138. throw new SchematicNameCollisionException(alias);
  139. }
  140. }
  141. allNames.push(...aliases);
  142. }
  143. return description;
  144. }
  145. createSchematicDescription(name, collection) {
  146. // Resolve aliases first.
  147. for (const schematicName of Object.keys(collection.schematics)) {
  148. const schematicDescription = collection.schematics[schematicName];
  149. if (schematicDescription.aliases && schematicDescription.aliases.indexOf(name) != -1) {
  150. name = schematicName;
  151. break;
  152. }
  153. }
  154. if (!(name in collection.schematics)) {
  155. return null;
  156. }
  157. const collectionPath = (0, path_1.dirname)(collection.path);
  158. const partialDesc = collection.schematics[name];
  159. if (!partialDesc) {
  160. return null;
  161. }
  162. if (partialDesc.extends) {
  163. const index = partialDesc.extends.indexOf(':');
  164. const collectionName = index !== -1 ? partialDesc.extends.slice(0, index) : null;
  165. const schematicName = index === -1 ? partialDesc.extends : partialDesc.extends.slice(index + 1);
  166. if (collectionName !== null) {
  167. const extendCollection = this.createCollectionDescription(collectionName);
  168. return this.createSchematicDescription(schematicName, extendCollection);
  169. }
  170. else {
  171. return this.createSchematicDescription(schematicName, collection);
  172. }
  173. }
  174. // Use any on this ref as we don't have the OptionT here, but we don't need it (we only need
  175. // the path).
  176. if (!partialDesc.factory) {
  177. throw new SchematicMissingFactoryException(name);
  178. }
  179. const resolvedRef = this._resolveReferenceString(partialDesc.factory, collectionPath, collection);
  180. if (!resolvedRef) {
  181. throw new FactoryCannotBeResolvedException(name);
  182. }
  183. let schema = partialDesc.schema;
  184. let schemaJson = undefined;
  185. if (schema) {
  186. if (!(0, path_1.isAbsolute)(schema)) {
  187. schema = (0, path_1.join)(collectionPath, schema);
  188. }
  189. schemaJson = (0, file_system_utility_1.readJsonFile)(schema);
  190. }
  191. // The schematic path is used to resolve URLs.
  192. // We should be able to just do `dirname(resolvedRef.path)` but for compatibility with
  193. // Bazel under Windows this directory needs to be resolved from the collection instead.
  194. // This is needed because on Bazel under Windows the data files (such as the collection or
  195. // url files) are not in the same place as the compiled JS.
  196. const maybePath = (0, path_1.join)(collectionPath, partialDesc.factory);
  197. const path = (0, fs_1.existsSync)(maybePath) && (0, fs_1.statSync)(maybePath).isDirectory() ? maybePath : (0, path_1.dirname)(maybePath);
  198. return this._transformSchematicDescription(name, collection, {
  199. ...partialDesc,
  200. schema,
  201. schemaJson,
  202. name,
  203. path,
  204. factoryFn: resolvedRef.ref,
  205. collection,
  206. });
  207. }
  208. createSourceFromUrl(url) {
  209. switch (url.protocol) {
  210. case null:
  211. case 'file:':
  212. return (context) => {
  213. // Check if context has necessary FileSystemSchematicContext path property
  214. const fileDescription = context.schematic.description;
  215. if (fileDescription.path === undefined) {
  216. throw new Error('Unsupported schematic context. Expected a FileSystemSchematicContext.');
  217. }
  218. // Resolve all file:///a/b/c/d from the schematic's own path, and not the current
  219. // path.
  220. const root = (0, core_1.normalize)((0, path_1.resolve)(fileDescription.path, url.path || ''));
  221. return new src_1.HostCreateTree(new core_1.virtualFs.ScopedHost(new node_1.NodeJsSyncHost(), root));
  222. };
  223. }
  224. return null;
  225. }
  226. transformOptions(schematic, options, context) {
  227. const transform = async () => {
  228. let transformedOptions = options;
  229. for (const transformer of this._transforms) {
  230. const transformerResult = transformer(schematic, transformedOptions, context);
  231. transformedOptions = await ((0, rxjs_1.isObservable)(transformerResult)
  232. ? (0, rxjs_1.lastValueFrom)(transformerResult)
  233. : transformerResult);
  234. }
  235. return transformedOptions;
  236. };
  237. return (0, rxjs_1.from)(transform());
  238. }
  239. transformContext(context) {
  240. return this._contextTransforms.reduce((acc, curr) => curr(acc), context);
  241. }
  242. getSchematicRuleFactory(schematic, _collection) {
  243. return schematic.factoryFn;
  244. }
  245. registerTaskExecutor(factory, options) {
  246. this._taskFactories.set(factory.name, () => (0, rxjs_1.from)(factory.create(options)));
  247. }
  248. createTaskExecutor(name) {
  249. const factory = this._taskFactories.get(name);
  250. if (factory) {
  251. return factory();
  252. }
  253. return (0, rxjs_1.throwError)(new src_1.UnregisteredTaskException(name));
  254. }
  255. hasTaskExecutor(name) {
  256. return this._taskFactories.has(name);
  257. }
  258. }
  259. exports.FileSystemEngineHostBase = FileSystemEngineHostBase;