UNPKG

7.22 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 */
9var __importDefault = (this && this.__importDefault) || function (mod) {
10 return (mod && mod.__esModule) ? mod : { "default": mod };
11};
12var _a;
13Object.defineProperty(exports, "__esModule", { value: true });
14exports.SchematicEngineHost = void 0;
15const schematics_1 = require("@angular-devkit/schematics");
16const tools_1 = require("@angular-devkit/schematics/tools");
17const fs_1 = require("fs");
18const jsonc_parser_1 = require("jsonc-parser");
19const module_1 = __importDefault(require("module"));
20const path_1 = require("path");
21const vm_1 = require("vm");
22/**
23 * Environment variable to control schematic package redirection
24 * Default: Angular schematics only
25 */
26const schematicRedirectVariable = (_a = process.env['NG_SCHEMATIC_REDIRECT']) === null || _a === void 0 ? void 0 : _a.toLowerCase();
27function shouldWrapSchematic(schematicFile) {
28 // Check environment variable if present
29 if (schematicRedirectVariable !== undefined) {
30 switch (schematicRedirectVariable) {
31 case '0':
32 case 'false':
33 case 'off':
34 case 'none':
35 return false;
36 case 'all':
37 return true;
38 }
39 }
40 const normalizedSchematicFile = schematicFile.replace(/\\/g, '/');
41 // Never wrap the internal update schematic when executed directly
42 // It communicates with the update command via `global`
43 // But we still want to redirect schematics located in `@angular/cli/node_modules`.
44 if (normalizedSchematicFile.includes('node_modules/@angular/cli/') &&
45 !normalizedSchematicFile.includes('node_modules/@angular/cli/node_modules/')) {
46 return false;
47 }
48 // Default is only first-party Angular schematic packages
49 // Angular schematics are safe to use in the wrapped VM context
50 return /\/node_modules\/@(?:angular|schematics|nguniversal)\//.test(normalizedSchematicFile);
51}
52class SchematicEngineHost extends tools_1.NodeModulesEngineHost {
53 _resolveReferenceString(refString, parentPath) {
54 const [path, name] = refString.split('#', 2);
55 // Mimic behavior of ExportStringRef class used in default behavior
56 const fullPath = path[0] === '.' ? path_1.resolve(parentPath !== null && parentPath !== void 0 ? parentPath : process.cwd(), path) : path;
57 const schematicFile = require.resolve(fullPath, { paths: [parentPath] });
58 if (shouldWrapSchematic(schematicFile)) {
59 const schematicPath = path_1.dirname(schematicFile);
60 const moduleCache = new Map();
61 const factoryInitializer = wrap(schematicFile, schematicPath, moduleCache, name || 'default');
62 const factory = factoryInitializer();
63 if (!factory || typeof factory !== 'function') {
64 return null;
65 }
66 return { ref: factory, path: schematicPath };
67 }
68 // All other schematics use default behavior
69 return super._resolveReferenceString(refString, parentPath);
70 }
71}
72exports.SchematicEngineHost = SchematicEngineHost;
73/**
74 * Minimal shim modules for legacy deep imports of `@schematics/angular`
75 */
76const legacyModules = {
77 '@schematics/angular/utility/config': {
78 getWorkspace(host) {
79 const path = '/.angular.json';
80 const data = host.read(path);
81 if (!data) {
82 throw new schematics_1.SchematicsException(`Could not find (${path})`);
83 }
84 return jsonc_parser_1.parse(data.toString(), [], { allowTrailingComma: true });
85 },
86 },
87 '@schematics/angular/utility/project': {
88 buildDefaultPath(project) {
89 const root = project.sourceRoot ? `/${project.sourceRoot}/` : `/${project.root}/src/`;
90 return `${root}${project.projectType === 'application' ? 'app' : 'lib'}`;
91 },
92 },
93};
94/**
95 * Wrap a JavaScript file in a VM context to allow specific Angular dependencies to be redirected.
96 * This VM setup is ONLY intended to redirect dependencies.
97 *
98 * @param schematicFile A JavaScript schematic file path that should be wrapped.
99 * @param schematicDirectory A directory that will be used as the location of the JavaScript file.
100 * @param moduleCache A map to use for caching repeat module usage and proper `instanceof` support.
101 * @param exportName An optional name of a specific export to return. Otherwise, return all exports.
102 */
103function wrap(schematicFile, schematicDirectory, moduleCache, exportName) {
104 const scopedRequire = module_1.default.createRequire(schematicFile);
105 const customRequire = function (id) {
106 if (legacyModules[id]) {
107 // Provide compatibility modules for older versions of @angular/cdk
108 return legacyModules[id];
109 }
110 else if (id.startsWith('@angular-devkit/') || id.startsWith('@schematics/')) {
111 // Resolve from inside the `@angular/cli` project
112 const packagePath = require.resolve(id);
113 return require(packagePath);
114 }
115 else if (id.startsWith('.') || id.startsWith('@angular/cdk')) {
116 // Wrap relative files inside the schematic collection
117 // Also wrap `@angular/cdk`, it contains helper utilities that import core schematic packages
118 // Resolve from the original file
119 const modulePath = scopedRequire.resolve(id);
120 // Use cached module if available
121 const cachedModule = moduleCache.get(modulePath);
122 if (cachedModule) {
123 return cachedModule;
124 }
125 // Do not wrap vendored third-party packages or JSON files
126 if (!/[\/\\]node_modules[\/\\]@schematics[\/\\]angular[\/\\]third_party[\/\\]/.test(modulePath) &&
127 !modulePath.endsWith('.json')) {
128 // Wrap module and save in cache
129 const wrappedModule = wrap(modulePath, path_1.dirname(modulePath), moduleCache)();
130 moduleCache.set(modulePath, wrappedModule);
131 return wrappedModule;
132 }
133 }
134 // All others are required directly from the original file
135 return scopedRequire(id);
136 };
137 // Setup a wrapper function to capture the module's exports
138 const schematicCode = fs_1.readFileSync(schematicFile, 'utf8');
139 // `module` is required due to @angular/localize ng-add being in UMD format
140 const headerCode = '(function() {\nvar exports = {};\nvar module = { exports };\n';
141 const footerCode = exportName ? `\nreturn exports['${exportName}'];});` : '\nreturn exports;});';
142 const script = new vm_1.Script(headerCode + schematicCode + footerCode, {
143 filename: schematicFile,
144 lineOffset: 3,
145 });
146 const context = {
147 __dirname: schematicDirectory,
148 __filename: schematicFile,
149 Buffer,
150 console,
151 process,
152 get global() {
153 return this;
154 },
155 require: customRequire,
156 };
157 const exportsFactory = script.runInNewContext(context);
158 return exportsFactory;
159}