diff --git a/ts/addon.ts b/ts/addon.ts index 01d47a0af..fe4c2882e 100644 --- a/ts/addon.ts +++ b/ts/addon.ts @@ -3,7 +3,8 @@ import { Remote } from 'stagehand'; import { connect } from 'stagehand/lib/adapters/child-process'; import { hasPlugin, addPlugin } from 'ember-cli-babel-plugin-helpers'; import Addon from 'ember-cli/lib/models/addon'; -import { addon } from './lib/utilities/ember-cli-entities'; +import EmberApp from 'ember-cli/lib/broccoli/ember-app'; +import Project from 'ember-cli/lib/models/project'; import fork from './lib/utilities/fork'; import TypecheckWorker from './lib/typechecking/worker'; import TypecheckMiddleware from './lib/typechecking/middleware'; @@ -13,11 +14,31 @@ import fs from 'fs-extra'; export const ADDON_NAME = 'ember-cli-typescript'; -export default addon({ - name: ADDON_NAME, - - included() { - this._super.included.apply(this, arguments); +export default class EmberCLITypeScriptAddon extends Addon { + /** + * Since `CoreObject` executes the `init` chain _during_ `super()`, the + * `Addon` base class's `init` runs _before_ class instance property + * assignments in the `EmberCLITypeScriptAddon` sub-class. + * + * Since `Addon#init` checks for the presence of `this.name`, we need to + * assign the property here as opposed to just using a class instance property + * assignment, like: + * + * ```ts + * class EmberCLITypeScriptAddon extends Addon { + * name = ADDON_NAME; + * } + * ``` + * + * @see https://github.com/ember-cli/ember-cli/blob/3af9f60cfe6e16caab0a972c7d25e8bb8017db26/lib/models/addon.js#L286-L288 + */ + init(...args: any[]) { + this.name = ADDON_NAME; + super.init(...args); + } + + included(includer: EmberApp | Project) { + super.included(includer); this._checkDevelopment(); this._checkAddonAppFiles(); this._checkBabelVersion(); @@ -29,7 +50,7 @@ export default addon({ this._checkInstallationLocation(); this._checkEmberCLIVersion(); } - }, + } includedCommands() { if (this.project.isEmberCLIAddon()) { @@ -38,19 +59,19 @@ export default addon({ 'ts:clean': require('./lib/commands/clean').default, }; } - }, + } blueprintsPath() { return `${__dirname}/blueprints`; - }, + } - serverMiddleware({ app }) { + serverMiddleware({ app }: { app: Application }) { this._addTypecheckMiddleware(app); - }, + } - testemMiddleware(app) { + testemMiddleware(app: Application) { this._addTypecheckMiddleware(app); - }, + } async postBuild() { // This code makes the fundamental assumption that the TS compiler's fs watcher @@ -67,9 +88,9 @@ export default addon({ // with nice highlighting and formatting separately. throw new Error('Typechecking failed'); } - }, + } - setupPreprocessorRegistry(type) { + setupPreprocessorRegistry(type: 'self' | 'parent') { if (type !== 'parent') return; // Normally this is the sort of logic that would live in `included()`, but @@ -86,15 +107,15 @@ export default addon({ after: ['@babel/plugin-proposal-class-properties'], }); } - }, + } - shouldIncludeChildAddon(addon) { + shouldIncludeChildAddon(addon: 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); - }, + } - _checkBabelVersion() { + private _checkBabelVersion() { let babel = this.parent.addons.find(addon => addon.name === 'ember-cli-babel'); let version = babel && babel.pkg.version; if (!babel || !(semver.gte(version!, '7.7.3') && semver.lt(version!, '8.0.0'))) { @@ -104,9 +125,9 @@ export default addon({ 'your TypeScript files may not be transpiled correctly.' ); } - }, + } - _checkEmberCLIVersion() { + private _checkEmberCLIVersion() { let cliPackage = this.project.require('ember-cli/package.json') as { version: string; }; @@ -117,18 +138,18 @@ export default addon({ 'compiler needs to keep track of.' ); } - }, + } - _checkDevelopment() { + private _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() { + private _checkAddonAppFiles() { // Emit a warning for addons that are under active development... let isDevelopingAddon = !this.app && (this.parent as Addon).isDevelopingAddon(); @@ -147,9 +168,9 @@ export default addon({ ); } } - }, + } - _checkInstallationLocation() { + private _checkInstallationLocation() { if ( this.project.isEmberCLIAddon() && this.project.pkg.devDependencies && @@ -159,14 +180,14 @@ export default addon({ '`ember-cli-typescript` should be included in your `dependencies`, not `devDependencies`' ); } - }, + } - _getConfigurationTarget() { + private _getConfigurationTarget() { // If `this.app` isn't present, we know `this.parent` is an addon return this.app || (this.parent as Addon); - }, + } - _registerBabelExtension() { + private _registerBabelExtension() { let target = this._getConfigurationTarget(); let options: Record = target.options || (target.options = {}); let babelAddonOptions: Record = @@ -177,25 +198,25 @@ export default addon({ if (!extensions.includes('ts')) { extensions.push('ts'); } - }, + } - _addTypecheckMiddleware(app: Application) { + private _addTypecheckMiddleware(app: Application) { let workerPromise = this._getTypecheckWorker(); let middleware = new TypecheckMiddleware(this.project, workerPromise); middleware.register(app); - }, + } - _typecheckWorker: undefined as Promise> | undefined, + private _typecheckWorker?: Promise>; - _getTypecheckWorker() { + private _getTypecheckWorker() { if (!this._typecheckWorker) { this._typecheckWorker = this._forkTypecheckWorker(); } return this._typecheckWorker; - }, + } - async _forkTypecheckWorker() { + private async _forkTypecheckWorker() { let childProcess = fork(`${__dirname}/lib/typechecking/worker/launch`); let worker = await connect(childProcess); @@ -208,5 +229,5 @@ export default addon({ await worker.start(this.project.root); return worker; - }, -}); + } +} diff --git a/ts/lib/commands/clean.ts b/ts/lib/commands/clean.ts index 841fceda9..31ff1aa51 100644 --- a/ts/lib/commands/clean.ts +++ b/ts/lib/commands/clean.ts @@ -1,13 +1,12 @@ import fs from 'fs'; -import { command } from '../utilities/ember-cli-entities'; +import Command from 'ember-cli/lib/models/command'; import { PRECOMPILE_MANIFEST } from './precompile'; -export default command({ - name: 'ts:clean', - works: 'insideProject', - description: 'Cleans up compiled JS and declaration files generated by `ember ts:precompile`.', +export default class CleanCommend extends Command { + works = 'insideProject' as const; + description = 'Cleans up compiled JS and declaration files generated by `ember ts:precompile`.'; - availableOptions: [{ name: 'manifest-path', type: String, default: PRECOMPILE_MANIFEST }], + availableOptions = [{ name: 'manifest-path', type: String, default: PRECOMPILE_MANIFEST }]; run(options: { manifestPath: string }) { let manifestPath = options.manifestPath; @@ -30,5 +29,8 @@ export default command({ } } fs.unlinkSync(manifestPath); - }, -}); + } +} + +// https://github.com/ember-cli/ember-cli/blob/cf55d3c36118e6a04ace1cf183951f310cfca9cd/lib/cli/lookup-command.js#L18 +CleanCommend.prototype.name = 'ts:clean'; diff --git a/ts/lib/commands/precompile.ts b/ts/lib/commands/precompile.ts index f4c5a2b3b..42412edc8 100644 --- a/ts/lib/commands/precompile.ts +++ b/ts/lib/commands/precompile.ts @@ -2,18 +2,17 @@ import os from 'os'; import execa from 'execa'; import fs from 'fs-extra'; import path from 'path'; -import { command } from '../utilities/ember-cli-entities'; +import Command from 'ember-cli/lib/models/command'; import copyDeclarations from '../utilities/copy-declarations'; export const PRECOMPILE_MANIFEST = 'dist/.ts-precompile-manifest'; -export default command({ - name: 'ts:precompile', - works: 'insideProject', - description: - 'Generates JS and declaration files from TypeScript sources in preparation for publishing.', +export default class PrecompileCommand extends Command { + works = 'insideProject' as const; + description = + 'Generates JS and declaration files from TypeScript sources in preparation for publishing.'; - availableOptions: [{ name: 'manifest-path', type: String, default: PRECOMPILE_MANIFEST }], + availableOptions = [{ name: 'manifest-path', type: String, default: PRECOMPILE_MANIFEST }]; async run(options: { manifestPath: string }) { let outDir = `${os.tmpdir()}/e-c-ts-precompile-${process.pid}`; @@ -54,9 +53,9 @@ export default command({ fs.mkdirsSync(path.dirname(manifestPath)); fs.writeFileSync(manifestPath, JSON.stringify(createdFiles.reverse())); fs.remove(outDir); - }, + } - _loadConfig(outDir: string) { + private _loadConfig(outDir: string) { let ts = this.project.require('typescript') as typeof import('typescript'); let configPath = ts.findConfigFile(this.project.root, ts.sys.fileExists); if (!configPath) { @@ -83,5 +82,8 @@ export default command({ ]; return { rootDir, paths, pathRoots }; - }, -}); + } +} + +// https://github.com/ember-cli/ember-cli/blob/cf55d3c36118e6a04ace1cf183951f310cfca9cd/lib/cli/lookup-command.js#L18 +PrecompileCommand.prototype.name = 'ts:precompile'; diff --git a/ts/lib/utilities/ember-cli-entities.ts b/ts/lib/utilities/ember-cli-entities.ts deleted file mode 100644 index 21df8fa14..000000000 --- a/ts/lib/utilities/ember-cli-entities.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ExtendOptions, ExtendThisType } from 'core-object'; -import Addon from 'ember-cli/lib/models/addon'; -import Command from 'ember-cli/lib/models/command'; - -/* - * This module contains identity functions that accept and return config - * hashes for various Ember CLI entities, ensuring that they're compatible - * with the expected config signature and that any methods have the correct - * `this` type. - */ - -/** Configuration for defining an Ember CLI addon */ -export function addon>(options: T & ExtendThisType): T { - return options; -} - -/** Configuration for defining an Ember CLI command */ -export function command>( - options: T & ExtendThisType -): T { - return options; -}