node-modules-architect-host.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
  10. if (k2 === undefined) k2 = k;
  11. var desc = Object.getOwnPropertyDescriptor(m, k);
  12. if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
  13. desc = { enumerable: true, get: function() { return m[k]; } };
  14. }
  15. Object.defineProperty(o, k2, desc);
  16. }) : (function(o, m, k, k2) {
  17. if (k2 === undefined) k2 = k;
  18. o[k2] = m[k];
  19. }));
  20. var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
  21. Object.defineProperty(o, "default", { enumerable: true, value: v });
  22. }) : function(o, v) {
  23. o["default"] = v;
  24. });
  25. var __importStar = (this && this.__importStar) || (function () {
  26. var ownKeys = function(o) {
  27. ownKeys = Object.getOwnPropertyNames || function (o) {
  28. var ar = [];
  29. for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
  30. return ar;
  31. };
  32. return ownKeys(o);
  33. };
  34. return function (mod) {
  35. if (mod && mod.__esModule) return mod;
  36. var result = {};
  37. if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
  38. __setModuleDefault(result, mod);
  39. return result;
  40. };
  41. })();
  42. Object.defineProperty(exports, "__esModule", { value: true });
  43. exports.WorkspaceNodeModulesArchitectHost = void 0;
  44. exports.loadEsmModule = loadEsmModule;
  45. const node_fs_1 = require("node:fs");
  46. const node_module_1 = require("node:module");
  47. const path = __importStar(require("node:path"));
  48. const node_url_1 = require("node:url");
  49. const node_v8_1 = require("node:v8");
  50. const internal_1 = require("../src/internal");
  51. // TODO_ESM: Update to use import.meta.url
  52. const localRequire = (0, node_module_1.createRequire)(__filename);
  53. function clone(obj) {
  54. try {
  55. return (0, node_v8_1.deserialize)((0, node_v8_1.serialize)(obj));
  56. }
  57. catch {
  58. return JSON.parse(JSON.stringify(obj));
  59. }
  60. }
  61. function findProjectTarget(workspace, project, target) {
  62. const projectDefinition = workspace.projects.get(project);
  63. if (!projectDefinition) {
  64. throw new Error(`Project "${project}" does not exist.`);
  65. }
  66. const targetDefinition = projectDefinition.targets.get(target);
  67. if (!targetDefinition) {
  68. throw new Error('Project target does not exist.');
  69. }
  70. if (!targetDefinition.builder) {
  71. throw new Error(`A builder is not set for target '${target}' in project '${project}'.`);
  72. }
  73. return targetDefinition;
  74. }
  75. class WorkspaceNodeModulesArchitectHost {
  76. _root;
  77. workspaceHost;
  78. constructor(workspaceOrHost, _root) {
  79. this._root = _root;
  80. if ('getBuilderName' in workspaceOrHost) {
  81. this.workspaceHost = workspaceOrHost;
  82. }
  83. else {
  84. this.workspaceHost = {
  85. async getBuilderName(project, target) {
  86. const { builder } = findProjectTarget(workspaceOrHost, project, target);
  87. return builder;
  88. },
  89. async getOptions(project, target, configuration) {
  90. const targetDefinition = findProjectTarget(workspaceOrHost, project, target);
  91. if (configuration === undefined) {
  92. return (targetDefinition.options ?? {});
  93. }
  94. if (!targetDefinition.configurations?.[configuration]) {
  95. throw new Error(`Configuration '${configuration}' for target '${target}' in project '${project}' is not set in the workspace.`);
  96. }
  97. return (targetDefinition.configurations?.[configuration] ?? {});
  98. },
  99. async getMetadata(project) {
  100. const projectDefinition = workspaceOrHost.projects.get(project);
  101. if (!projectDefinition) {
  102. throw new Error(`Project "${project}" does not exist.`);
  103. }
  104. return {
  105. root: projectDefinition.root,
  106. sourceRoot: projectDefinition.sourceRoot,
  107. prefix: projectDefinition.prefix,
  108. ...clone(workspaceOrHost.extensions),
  109. ...clone(projectDefinition.extensions),
  110. };
  111. },
  112. async hasTarget(project, target) {
  113. return !!workspaceOrHost.projects.get(project)?.targets.has(target);
  114. },
  115. async getDefaultConfigurationName(project, target) {
  116. return workspaceOrHost.projects.get(project)?.targets.get(target)?.defaultConfiguration;
  117. },
  118. };
  119. }
  120. }
  121. async getBuilderNameForTarget(target) {
  122. return this.workspaceHost.getBuilderName(target.project, target.target);
  123. }
  124. /**
  125. * Resolve a builder. This needs to be a string which will be used in a dynamic `import()`
  126. * clause. This should throw if no builder can be found. The dynamic import will throw if
  127. * it is unsupported.
  128. * @param builderStr The name of the builder to be used.
  129. * @returns All the info needed for the builder itself.
  130. */
  131. resolveBuilder(builderStr, basePath = this._root, seenBuilders) {
  132. if (seenBuilders?.has(builderStr)) {
  133. throw new Error('Circular builder alias references detected: ' + [...seenBuilders, builderStr]);
  134. }
  135. const [packageName, builderName] = builderStr.split(':', 2);
  136. if (!builderName) {
  137. throw new Error('No builder name specified.');
  138. }
  139. // Resolve and load the builders manifest from the package's `builders` field, if present
  140. const packageJsonPath = localRequire.resolve(packageName + '/package.json', {
  141. paths: [basePath],
  142. });
  143. const packageJson = JSON.parse((0, node_fs_1.readFileSync)(packageJsonPath, 'utf-8'));
  144. const buildersManifestRawPath = packageJson['builders'];
  145. if (!buildersManifestRawPath) {
  146. throw new Error(`Package ${JSON.stringify(packageName)} has no builders defined.`);
  147. }
  148. let buildersManifestPath = path.normalize(buildersManifestRawPath);
  149. if (path.isAbsolute(buildersManifestRawPath) || buildersManifestRawPath.startsWith('..')) {
  150. throw new Error(`Package "${packageName}" has an invalid builders manifest path: "${buildersManifestRawPath}"`);
  151. }
  152. buildersManifestPath = path.join(path.dirname(packageJsonPath), buildersManifestPath);
  153. const buildersManifest = JSON.parse((0, node_fs_1.readFileSync)(buildersManifestPath, 'utf-8'));
  154. const buildersManifestDirectory = path.dirname(buildersManifestPath);
  155. // Attempt to locate an entry for the specified builder by name
  156. const builder = buildersManifest.builders?.[builderName];
  157. if (!builder) {
  158. throw new Error(`Cannot find builder ${JSON.stringify(builderStr)}.`);
  159. }
  160. // Resolve alias reference if entry is a string
  161. if (typeof builder === 'string') {
  162. return this.resolveBuilder(builder, path.dirname(packageJsonPath), (seenBuilders ?? new Set()).add(builderStr));
  163. }
  164. // Determine builder implementation path (relative within package only)
  165. const implementationPath = builder.implementation && path.normalize(builder.implementation);
  166. if (!implementationPath) {
  167. throw new Error('Could not find the implementation for builder ' + builderStr);
  168. }
  169. if (path.isAbsolute(implementationPath) || implementationPath.startsWith('..')) {
  170. throw new Error(`Package "${packageName}" has an invalid builder implementation path: "${builderName}" --> "${builder.implementation}"`);
  171. }
  172. // Determine builder option schema path (relative within package only)
  173. let schemaPath = builder.schema;
  174. if (!schemaPath) {
  175. throw new Error('Could not find the schema for builder ' + builderStr);
  176. }
  177. if (path.isAbsolute(schemaPath) || path.normalize(schemaPath).startsWith('..')) {
  178. throw new Error(`Package "${packageName}" has an invalid builder schema path: "${builderName}" --> "${builder.schema}"`);
  179. }
  180. // The file could be either a package reference or in the local manifest directory.
  181. if (schemaPath.startsWith('.')) {
  182. schemaPath = path.join(buildersManifestDirectory, schemaPath);
  183. }
  184. else {
  185. const manifestRequire = (0, node_module_1.createRequire)(buildersManifestDirectory + '/');
  186. schemaPath = manifestRequire.resolve(schemaPath);
  187. }
  188. const schemaText = (0, node_fs_1.readFileSync)(schemaPath, 'utf-8');
  189. return Promise.resolve({
  190. name: builderStr,
  191. builderName,
  192. description: builder['description'],
  193. optionSchema: JSON.parse(schemaText),
  194. import: path.join(buildersManifestDirectory, implementationPath),
  195. });
  196. }
  197. async getCurrentDirectory() {
  198. return process.cwd();
  199. }
  200. async getWorkspaceRoot() {
  201. return this._root;
  202. }
  203. async getOptionsForTarget(target) {
  204. if (!(await this.workspaceHost.hasTarget(target.project, target.target))) {
  205. return null;
  206. }
  207. let options = await this.workspaceHost.getOptions(target.project, target.target);
  208. const targetConfiguration = target.configuration ||
  209. (await this.workspaceHost.getDefaultConfigurationName(target.project, target.target));
  210. if (targetConfiguration) {
  211. const configurations = targetConfiguration.split(',').map((c) => c.trim());
  212. for (const configuration of configurations) {
  213. options = {
  214. ...options,
  215. ...(await this.workspaceHost.getOptions(target.project, target.target, configuration)),
  216. };
  217. }
  218. }
  219. return clone(options);
  220. }
  221. async getProjectMetadata(target) {
  222. const projectName = typeof target === 'string' ? target : target.project;
  223. const metadata = this.workspaceHost.getMetadata(projectName);
  224. return metadata;
  225. }
  226. async loadBuilder(info) {
  227. const builder = await getBuilder(info.import);
  228. if (builder[internal_1.BuilderSymbol]) {
  229. return builder;
  230. }
  231. // Default handling code is for old builders that incorrectly export `default` with non-ESM module
  232. if (builder?.default[internal_1.BuilderSymbol]) {
  233. return builder.default;
  234. }
  235. throw new Error('Builder is not a builder');
  236. }
  237. }
  238. exports.WorkspaceNodeModulesArchitectHost = WorkspaceNodeModulesArchitectHost;
  239. /**
  240. * Lazily compiled dynamic import loader function.
  241. */
  242. let load;
  243. /**
  244. * This uses a dynamic import to load a module which may be ESM.
  245. * CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript
  246. * will currently, unconditionally downlevel dynamic import into a require call.
  247. * require calls cannot load ESM code and will result in a runtime error. To workaround
  248. * this, a Function constructor is used to prevent TypeScript from changing the dynamic import.
  249. * Once TypeScript provides support for keeping the dynamic import this workaround can
  250. * be dropped.
  251. *
  252. * @param modulePath The path of the module to load.
  253. * @returns A Promise that resolves to the dynamically imported module.
  254. */
  255. function loadEsmModule(modulePath) {
  256. load ??= new Function('modulePath', `return import(modulePath);`);
  257. return load(modulePath);
  258. }
  259. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  260. async function getBuilder(builderPath) {
  261. switch (path.extname(builderPath)) {
  262. case '.mjs':
  263. // Load the ESM configuration file using the TypeScript dynamic import workaround.
  264. // Once TypeScript provides support for keeping the dynamic import this workaround can be
  265. // changed to a direct dynamic import.
  266. return (await loadEsmModule((0, node_url_1.pathToFileURL)(builderPath))).default;
  267. case '.cjs':
  268. return localRequire(builderPath);
  269. default:
  270. // The file could be either CommonJS or ESM.
  271. // CommonJS is tried first then ESM if loading fails.
  272. try {
  273. return localRequire(builderPath);
  274. }
  275. catch (e) {
  276. if (e.code === 'ERR_REQUIRE_ESM') {
  277. // Load the ESM configuration file using the TypeScript dynamic import workaround.
  278. // Once TypeScript provides support for keeping the dynamic import this workaround can be
  279. // changed to a direct dynamic import.
  280. return (await loadEsmModule((0, node_url_1.pathToFileURL)(builderPath))).default;
  281. }
  282. throw e;
  283. }
  284. }
  285. }