ember-cli-typescript
Version:
Allow Ember apps to use TypeScript files.
197 lines (196 loc) • 9.02 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ADDON_NAME = void 0;
const semver_1 = __importDefault(require("semver"));
const child_process_1 = require("stagehand/lib/adapters/child-process");
const ember_cli_entities_1 = require("./lib/utilities/ember-cli-entities");
const fork_1 = __importDefault(require("./lib/utilities/fork"));
const middleware_1 = __importDefault(require("./lib/typechecking/middleware"));
const walk_sync_1 = __importDefault(require("walk-sync"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const debug_1 = __importDefault(require("debug"));
const debug = (0, debug_1.default)('ember-cli-typescript:addon');
exports.ADDON_NAME = 'ember-cli-typescript';
exports.default = (0, ember_cli_entities_1.addon)({
name: exports.ADDON_NAME,
included() {
this._super.included.apply(this, arguments);
this._checkDevelopment();
this._checkAddonAppFiles();
this._checkBabelVersion();
// If we're a direct dependency of the host app, go ahead and start up the
// typecheck worker so we don't wait until the end of the build to check
if (this.parent === this.project) {
this._getTypecheckWorker();
this._checkInstallationLocation();
this._checkEmberCLIVersion();
}
},
includedCommands() {
if (this.project.isEmberCLIAddon()) {
return {
'ts:precompile': require('./lib/commands/precompile').default,
'ts:clean': require('./lib/commands/clean').default,
};
}
},
blueprintsPath() {
return `${__dirname}/blueprints`;
},
serverMiddleware({ app, options }) {
if (!options || !options.path) {
debug('Installing typecheck server middleware');
this._addTypecheckMiddleware(app);
}
else {
debug('Skipping typecheck server middleware');
}
},
testemMiddleware(app, options) {
if (!options || !options.path) {
debug('Installing typecheck testem middleware');
this._addTypecheckMiddleware(app);
}
else {
debug('Skipping typecheck testem middleware');
}
},
async postBuild() {
// This code makes the fundamental assumption that the TS compiler's fs watcher
// will notice a file change before the full Broccoli build completes. Otherwise
// the `getStatus` call here might report the status of the previous check. In
// practice, though, building takes much longer than the time to trigger the
// compiler's "hey, a file changed" hook, and once the typecheck has begun, the
// `getStatus` call will block until it's complete.
let worker = await this._getTypecheckWorker();
let { failed } = await worker.getStatus();
if (failed) {
// The actual details of the errors will already have been printed
// with nice highlighting and formatting separately.
throw new Error('Typechecking failed');
}
},
setupPreprocessorRegistry(type, registry) {
if (type !== 'parent')
return;
// If we're acting on behalf of the root app, issue a warning if we detect
// a situation where a .js file from an addon has the same name as a .ts
// file in the app, as which file wins is nondeterministic.
if (this.parent === this.project) {
this._registerCollisionDetectionPreprocessor(registry);
}
},
shouldIncludeChildAddon(addon) {
// For testing, we have dummy in-repo addons set up, but e-c-ts doesn't depend on them;
// its dummy app does. Otherwise we'd have a circular dependency.
return !['in-repo-a', 'in-repo-b'].includes(addon.name);
},
_registerCollisionDetectionPreprocessor(registry) {
registry.add('js', {
name: 'ember-cli-typescript-collision-check',
toTree: (input, path) => {
if (path !== '/')
return input;
let addon = this;
let checked = false;
let stew = require('broccoli-stew');
return stew.afterBuild(input, function () {
if (!checked) {
checked = true;
addon._checkForFileCollisions(this.inputPaths[0]);
}
});
},
});
},
_checkForFileCollisions(directory) {
let walkSync = require('walk-sync');
let files = new Set(walkSync(directory, ['**/*.{js,ts}']));
let collisions = [];
for (let file of files) {
if (file.endsWith('.js') && files.has(file.replace(/\.js$/, '.ts'))) {
collisions.push(file.replace(/\.js$/, '.{js,ts}'));
}
}
if (collisions.length) {
this.ui.writeWarnLine('Detected collisions between .js and .ts files of the same name. ' +
'This can result in nondeterministic build output; ' +
'see https://git.io/JvIwo for more information.\n - ' +
collisions.join('\n - '));
}
},
_checkBabelVersion() {
let babel = this.parent.addons.find((addon) => addon.name === 'ember-cli-babel');
let version = babel && babel.pkg.version;
if (!babel || !semver_1.default.gte(version, '7.17.0')) {
let versionString = babel
? `version ${babel.pkg.version} installed`
: `no instance of ember-cli-babel installed in your dependencies (check if it's in devDependencies instead?)`;
this.ui.writeWarnLine(`ember-cli-typescript requires ember-cli-babel ^7.17.0, but you have ${versionString}; ` +
'your TypeScript files may not be transpiled correctly.');
}
},
_checkEmberCLIVersion() {
let cliPackage = this.project.require('ember-cli/package.json');
if (semver_1.default.lt(cliPackage.version, '3.5.0')) {
this.ui.writeWarnLine('ember-cli-typescript works best with ember-cli >= 3.5, which uses the system temporary directory ' +
'by default rather than a project-local one, minimizing file system events the TypeScript ' +
'compiler needs to keep track of.');
}
},
_checkDevelopment() {
if (this.isDevelopingAddon() && !process.env.CI && __filename.endsWith('.js')) {
this.ui.writeWarnLine('ember-cli-typescript is in development but not being loaded from `.ts` sources — ' +
'do you have compiled artifacts lingering in `/js`?');
}
},
_checkAddonAppFiles() {
// Emit a warning for addons that are under active development...
let isDevelopingAddon = !this.app && this.parent.isDevelopingAddon();
// ...and are at the root of the project (i.e. not in-repo)...
let isRootAddon = this.parent.root === this.project.root;
// ...and have .ts files in their `app` directory.
let appDir = `${this.parent.root}/app`;
if (isDevelopingAddon && isRootAddon && fs_extra_1.default.existsSync(appDir)) {
let tsFilesInApp = (0, walk_sync_1.default)(appDir, { globs: ['**/*.ts'] });
if (tsFilesInApp.length) {
this.ui.writeWarnLine(`found .ts files in ${appDir}\n` +
"ember-cli-typescript only compiles files in an addon's `addon` folder; " +
'see https://github.com/typed-ember/ember-cli-typescript/issues/562');
}
}
},
_checkInstallationLocation() {
if (this.project.isEmberCLIAddon() &&
this.project.pkg.devDependencies &&
this.project.pkg.devDependencies[this.name]) {
this.ui.writeWarnLine('`ember-cli-typescript` should be included in your `dependencies`, not `devDependencies`');
}
},
_addTypecheckMiddleware(app) {
let workerPromise = this._getTypecheckWorker();
let middleware = new middleware_1.default(this.project, workerPromise);
middleware.register(app);
},
_typecheckWorker: undefined,
_getTypecheckWorker() {
if (!this._typecheckWorker) {
this._typecheckWorker = this._forkTypecheckWorker();
}
return this._typecheckWorker;
},
async _forkTypecheckWorker() {
let childProcess = (0, fork_1.default)(`${__dirname}/lib/typechecking/worker/launch`);
let worker = await (0, child_process_1.connect)(childProcess);
await worker.onTypecheck((status) => {
for (let error of status.errors) {
this.ui.writeLine(error);
}
});
await worker.start(this.project.root);
return worker;
},
});
;