UNPKG

8.93 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.ADDON_NAME = void 0;
7const semver_1 = __importDefault(require("semver"));
8const child_process_1 = require("stagehand/lib/adapters/child-process");
9const ember_cli_entities_1 = require("./lib/utilities/ember-cli-entities");
10const fork_1 = __importDefault(require("./lib/utilities/fork"));
11const middleware_1 = __importDefault(require("./lib/typechecking/middleware"));
12const walk_sync_1 = __importDefault(require("walk-sync"));
13const fs_extra_1 = __importDefault(require("fs-extra"));
14const debug_1 = __importDefault(require("debug"));
15const debug = debug_1.default('ember-cli-typescript:addon');
16exports.ADDON_NAME = 'ember-cli-typescript';
17exports.default = ember_cli_entities_1.addon({
18 name: exports.ADDON_NAME,
19 included() {
20 this._super.included.apply(this, arguments);
21 this._checkDevelopment();
22 this._checkAddonAppFiles();
23 this._checkBabelVersion();
24 // If we're a direct dependency of the host app, go ahead and start up the
25 // typecheck worker so we don't wait until the end of the build to check
26 if (this.parent === this.project) {
27 this._getTypecheckWorker();
28 this._checkInstallationLocation();
29 this._checkEmberCLIVersion();
30 }
31 },
32 includedCommands() {
33 if (this.project.isEmberCLIAddon()) {
34 return {
35 'ts:precompile': require('./lib/commands/precompile').default,
36 'ts:clean': require('./lib/commands/clean').default,
37 };
38 }
39 },
40 blueprintsPath() {
41 return `${__dirname}/blueprints`;
42 },
43 serverMiddleware({ app, options }) {
44 if (!options || !options.path) {
45 debug('Installing typecheck server middleware');
46 this._addTypecheckMiddleware(app);
47 }
48 else {
49 debug('Skipping typecheck server middleware');
50 }
51 },
52 testemMiddleware(app, options) {
53 if (!options || !options.path) {
54 debug('Installing typecheck testem middleware');
55 this._addTypecheckMiddleware(app);
56 }
57 else {
58 debug('Skipping typecheck testem middleware');
59 }
60 },
61 async postBuild() {
62 // This code makes the fundamental assumption that the TS compiler's fs watcher
63 // will notice a file change before the full Broccoli build completes. Otherwise
64 // the `getStatus` call here might report the status of the previous check. In
65 // practice, though, building takes much longer than the time to trigger the
66 // compiler's "hey, a file changed" hook, and once the typecheck has begun, the
67 // `getStatus` call will block until it's complete.
68 let worker = await this._getTypecheckWorker();
69 let { failed } = await worker.getStatus();
70 if (failed) {
71 // The actual details of the errors will already have been printed
72 // with nice highlighting and formatting separately.
73 throw new Error('Typechecking failed');
74 }
75 },
76 setupPreprocessorRegistry(type, registry) {
77 if (type !== 'parent')
78 return;
79 // If we're acting on behalf of the root app, issue a warning if we detect
80 // a situation where a .js file from an addon has the same name as a .ts
81 // file in the app, as which file wins is nondeterministic.
82 if (this.parent === this.project) {
83 this._registerCollisionDetectionPreprocessor(registry);
84 }
85 },
86 shouldIncludeChildAddon(addon) {
87 // For testing, we have dummy in-repo addons set up, but e-c-ts doesn't depend on them;
88 // its dummy app does. Otherwise we'd have a circular dependency.
89 return !['in-repo-a', 'in-repo-b'].includes(addon.name);
90 },
91 _registerCollisionDetectionPreprocessor(registry) {
92 registry.add('js', {
93 name: 'ember-cli-typescript-collision-check',
94 toTree: (input, path) => {
95 if (path !== '/')
96 return input;
97 let addon = this;
98 let checked = false;
99 let stew = require('broccoli-stew');
100 return stew.afterBuild(input, function () {
101 if (!checked) {
102 checked = true;
103 addon._checkForFileCollisions(this.inputPaths[0]);
104 }
105 });
106 },
107 });
108 },
109 _checkForFileCollisions(directory) {
110 let walkSync = require('walk-sync');
111 let files = new Set(walkSync(directory, ['**/*.{js,ts}']));
112 let collisions = [];
113 for (let file of files) {
114 if (file.endsWith('.js') && files.has(file.replace(/\.js$/, '.ts'))) {
115 collisions.push(file.replace(/\.js$/, '.{js,ts}'));
116 }
117 }
118 if (collisions.length) {
119 this.ui.writeWarnLine('Detected collisions between .js and .ts files of the same name. ' +
120 'This can result in nondeterministic build output; ' +
121 'see https://git.io/JvIwo for more information.\n - ' +
122 collisions.join('\n - '));
123 }
124 },
125 _checkBabelVersion() {
126 let babel = this.parent.addons.find((addon) => addon.name === 'ember-cli-babel');
127 let version = babel && babel.pkg.version;
128 if (!babel || !(semver_1.default.gte(version, '7.17.0') && semver_1.default.lt(version, '8.0.0'))) {
129 let versionString = babel ? `version ${babel.pkg.version}` : `no instance of ember-cli-babel`;
130 this.ui.writeWarnLine(`ember-cli-typescript requires ember-cli-babel ^7.17.0, but you have ${versionString} installed; ` +
131 'your TypeScript files may not be transpiled correctly.');
132 }
133 },
134 _checkEmberCLIVersion() {
135 let cliPackage = this.project.require('ember-cli/package.json');
136 if (semver_1.default.lt(cliPackage.version, '3.5.0')) {
137 this.ui.writeWarnLine('ember-cli-typescript works best with ember-cli >= 3.5, which uses the system temporary directory ' +
138 'by default rather than a project-local one, minimizing file system events the TypeScript ' +
139 'compiler needs to keep track of.');
140 }
141 },
142 _checkDevelopment() {
143 if (this.isDevelopingAddon() && !process.env.CI && __filename.endsWith('.js')) {
144 this.ui.writeWarnLine('ember-cli-typescript is in development but not being loaded from `.ts` sources — ' +
145 'do you have compiled artifacts lingering in `/js`?');
146 }
147 },
148 _checkAddonAppFiles() {
149 // Emit a warning for addons that are under active development...
150 let isDevelopingAddon = !this.app && this.parent.isDevelopingAddon();
151 // ...and are at the root of the project (i.e. not in-repo)...
152 let isRootAddon = this.parent.root === this.project.root;
153 // ...and have .ts files in their `app` directory.
154 let appDir = `${this.parent.root}/app`;
155 if (isDevelopingAddon && isRootAddon && fs_extra_1.default.existsSync(appDir)) {
156 let tsFilesInApp = walk_sync_1.default(appDir, { globs: ['**/*.ts'] });
157 if (tsFilesInApp.length) {
158 this.ui.writeWarnLine(`found .ts files in ${appDir}\n` +
159 "ember-cli-typescript only compiles files in an addon's `addon` folder; " +
160 'see https://github.com/typed-ember/ember-cli-typescript/issues/562');
161 }
162 }
163 },
164 _checkInstallationLocation() {
165 if (this.project.isEmberCLIAddon() &&
166 this.project.pkg.devDependencies &&
167 this.project.pkg.devDependencies[this.name]) {
168 this.ui.writeWarnLine('`ember-cli-typescript` should be included in your `dependencies`, not `devDependencies`');
169 }
170 },
171 _addTypecheckMiddleware(app) {
172 let workerPromise = this._getTypecheckWorker();
173 let middleware = new middleware_1.default(this.project, workerPromise);
174 middleware.register(app);
175 },
176 _typecheckWorker: undefined,
177 _getTypecheckWorker() {
178 if (!this._typecheckWorker) {
179 this._typecheckWorker = this._forkTypecheckWorker();
180 }
181 return this._typecheckWorker;
182 },
183 async _forkTypecheckWorker() {
184 let childProcess = fork_1.default(`${__dirname}/lib/typechecking/worker/launch`);
185 let worker = await child_process_1.connect(childProcess);
186 await worker.onTypecheck((status) => {
187 for (let error of status.errors) {
188 this.ui.writeLine(error);
189 }
190 });
191 await worker.start(this.project.root);
192 return worker;
193 },
194});