Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 61 additions & 40 deletions ts/addon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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();
Expand All @@ -29,7 +50,7 @@ export default addon({
this._checkInstallationLocation();
this._checkEmberCLIVersion();
}
},
}

includedCommands() {
if (this.project.isEmberCLIAddon()) {
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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'))) {
Expand All @@ -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;
};
Expand All @@ -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();

Expand All @@ -147,9 +168,9 @@ export default addon({
);
}
}
},
}

_checkInstallationLocation() {
private _checkInstallationLocation() {
if (
this.project.isEmberCLIAddon() &&
this.project.pkg.devDependencies &&
Expand All @@ -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<string, any> = target.options || (target.options = {});
let babelAddonOptions: Record<string, any> =
Expand All @@ -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<Remote<TypecheckWorker>> | undefined,
private _typecheckWorker?: Promise<Remote<TypecheckWorker>>;

_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<TypecheckWorker>(childProcess);

Expand All @@ -208,5 +229,5 @@ export default addon({
await worker.start(this.project.root);

return worker;
},
});
}
}
18 changes: 10 additions & 8 deletions ts/lib/commands/clean.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might wanna use a fancy @proto decorator instead. 😄

24 changes: 13 additions & 11 deletions ts/lib/commands/precompile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
Expand Down Expand Up @@ -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) {
Expand All @@ -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';
22 changes: 0 additions & 22 deletions ts/lib/utilities/ember-cli-entities.ts

This file was deleted.