diff --git a/addon/index.ts b/addon/index.ts index 27db2ba2..9441a81b 100644 --- a/addon/index.ts +++ b/addon/index.ts @@ -1,5 +1,5 @@ -import memberAction from './utils/member-action'; import collectionAction from './utils/collection-action'; +import memberAction from './utils/member-action'; import serializeAndPush from './utils/serialize-and-push'; export const classOp = collectionAction; diff --git a/addon/utils/build-url.ts b/addon/utils/build-url.ts index 1559de87..d84474e0 100644 --- a/addon/utils/build-url.ts +++ b/addon/utils/build-url.ts @@ -1,23 +1,21 @@ -/* eslint-disable no-unused-vars */ -import Model from 'ember-data/model'; -import Store from 'ember-data/store'; import { getOwner } from '@ember/application'; -import EngineInstance from '@ember/engine/instance'; +import DS from 'ember-data'; +import Model from 'ember-data/model'; +import { EmberDataRequestType } from './types'; + /** * Given a record, obtain the ember-data model class - * @param {Model} record - * @return {typeof Model} + * @param record */ -export function _getModelClass(record) { - return /** @type {typeof Model} */ (record.constructor); +export function _getModelClass(record: InstanceType): M { + return record.constructor as M; } /** * Given an ember-data model class, obtain its name - * @param {typeof Model} clazz - * @returns {string} + * @param clazz */ -export function _getModelName(clazz) { +export function _getModelName(clazz: typeof Model): string { return ( // prettier-ignore clazz.modelName // modern use @@ -28,29 +26,36 @@ export function _getModelName(clazz) { /** * Given an ember-data-record, obtain the related Store - * @param {Model} record - * @return {Store} + * @param record */ -export function _getStoreFromRecord(record) { - /** @type {EngineInstance} */ +export function _getStoreFromRecord(record: Model) { const owner = getOwner(record); return owner.lookup('service:store'); } +function snapshotFromRecord(model: Model): DS.Snapshot { + return (model as any)._createSnapshot(); +} + /** * - * @param {Model} record - * @param {string} opPath - * @param {'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'} urlType - * @param {boolean} [instance] + * @param record + * @param opPath + * @param urlType + * @param instance */ -export function buildOperationUrl(record, opPath, urlType, instance = true) { +export function buildOperationUrl( + record: M, + opPath: string, + urlType: EmberDataRequestType, + instance = true +) { const modelClass = _getModelClass(record); const modelName = _getModelName(modelClass); const store = _getStoreFromRecord(record); const adapter = store.adapterFor(modelName); const path = opPath; - const snapshot = record._createSnapshot(); + const snapshot = snapshotFromRecord(record); const baseUrl = adapter.buildURL(modelName, instance ? record.get('id') : null, snapshot, urlType); if (!path) { diff --git a/addon/utils/collection-action.ts b/addon/utils/collection-action.ts index cf15df2a..5afbefab 100644 --- a/addon/utils/collection-action.ts +++ b/addon/utils/collection-action.ts @@ -1,22 +1,36 @@ import { merge } from '@ember/polyfills'; -import { buildOperationUrl, _getStoreFromRecord, _getModelName, _getModelClass } from './build-url'; +import Model from 'ember-data/model'; +import { Value as JSONValue } from 'json-typescript'; +import { _getModelClass, _getModelName, _getStoreFromRecord, buildOperationUrl } from './build-url'; +import { EmberDataRequestType, Hook, HTTPVerb, strictifyHttpVerb } from './types'; -export default function instanceOp(options) { - return function(payload) { +export interface CollectionOperationOptions { + type?: HTTPVerb; + path: string; + urlType?: EmberDataRequestType; + ajaxOptions?: any; + before?: Hook; + after?: Hook; +} + +export default function collectionOp(options: CollectionOperationOptions) { + return function runCollectionOp(this: Model, payload: IN): Promise { const recordClass = _getModelClass(this); const modelName = _getModelName(recordClass); const store = _getStoreFromRecord(this); - const requestType = (options.type || 'PUT').toUpperCase(); - const urlType = options.urlType || requestType; + const requestType: HTTPVerb = strictifyHttpVerb(options.type || 'put'); + const urlType: EmberDataRequestType = options.urlType || 'updateRecord'; const adapter = store.adapterFor(modelName); const fullUrl = buildOperationUrl(this, options.path, urlType, false); const data = (options.before && options.before.call(this, payload)) || payload; - return adapter.ajax(fullUrl, requestType, merge(options.ajaxOptions || {}, { data })).then(response => { - if (options.after && !this.isDestroyed) { - return options.after.call(this, response); - } + return adapter + .ajax(fullUrl, requestType, merge(options.ajaxOptions || {}, { data })) + .then((response: JSONValue) => { + if (options.after && !this.isDestroyed) { + return options.after.call(this, response); + } - return response; - }); + return response; + }); }; } diff --git a/addon/utils/member-action.ts b/addon/utils/member-action.ts index 01c797da..c70c2e8f 100644 --- a/addon/utils/member-action.ts +++ b/addon/utils/member-action.ts @@ -1,19 +1,31 @@ import { merge } from '@ember/polyfills'; -import { buildOperationUrl, _getModelClass, _getModelName, _getStoreFromRecord } from './build-url'; +import Model from 'ember-data/model'; +import { Value as JSONValue } from 'json-typescript'; +import { _getModelClass, _getModelName, _getStoreFromRecord, buildOperationUrl } from './build-url'; +import { EmberDataRequestType, Hook, HTTPVerb, strictifyHttpVerb } from './types'; -export default function instanceOp(options) { - return function(payload) { +export interface InstanceOperationOptions { + type?: HTTPVerb; + path: string; + urlType?: EmberDataRequestType; + ajaxOptions?: any; + before?: Hook; + after?: Hook; +} + +export default function instanceOp(options: InstanceOperationOptions) { + return function runInstanceOp(this: Model, payload: IN): Promise { const recordClass = _getModelClass(this); const modelName = _getModelName(recordClass); const store = _getStoreFromRecord(this); - const requestType = (options.type || 'PUT').toUpperCase(); - const urlType = options.urlType || requestType; + const { ajaxOptions, path, before, after, type = 'put', urlType = 'updateRecord' } = options; + const requestType: HTTPVerb = strictifyHttpVerb(type); const adapter = store.adapterFor(modelName); - const fullUrl = buildOperationUrl(this, options.path, urlType); - const data = (options.before && options.before.call(this, payload)) || payload; - return adapter.ajax(fullUrl, requestType, merge(options.ajaxOptions || {}, { data })).then(response => { - if (options.after && !this.isDestroyed) { - return options.after.call(this, response); + const fullUrl = buildOperationUrl(this, path, urlType); + const data = (before && before.call(this, payload)) || payload; + return adapter.ajax(fullUrl, requestType, merge(ajaxOptions || {}, { data })).then((response: JSONValue) => { + if (after && !this.isDestroyed) { + return after.call(this, response); } return response; diff --git a/addon/utils/serialize-and-push.ts b/addon/utils/serialize-and-push.ts index 79411879..35422c8f 100644 --- a/addon/utils/serialize-and-push.ts +++ b/addon/utils/serialize-and-push.ts @@ -1,19 +1,45 @@ import { isArray } from '@ember/array'; +import { typeOf } from '@ember/utils'; +import Model from 'ember-data/model'; +import JSONSerializer from 'ember-data/serializers/json'; +import SerializerRegistry from 'ember-data/types/registries/serializer'; +import { CollectionResourceDoc, Document as JSONApiDoc, DocWithData, SingleResourceDoc } from 'jsonapi-typescript'; import { _getModelClass, _getModelName, _getStoreFromRecord } from './build-url'; -export default function serializeAndPush(response) { - const isJsonApi = response.jsonapi && response.jsonapi.version; - if (!isJsonApi) { - // eslint-disable-next-line no-console - console.warn('serializeAndPush may only be used with a JSON API document. Ignoring response. Document must have a mandatory JSON API object. See https://jsonapi.org/format/#document-jsonapi-object.'); +function isJsonApi(raw: any): raw is JSONApiDoc { + return raw.jsonapi && raw.jsonapi.version; +} +function isDocWithData(doc: any): doc is DocWithData { + return isJsonApi(doc) && ['object', 'array'].indexOf(typeOf((doc as DocWithData).data)) >= 0; +} + +export default function serializeAndPush(this: Model, response: any) { + if (!isDocWithData(response)) { + // tslint:disable-next-line:no-console + console.warn( + 'serializeAndPush may only be used with a JSON API document. Ignoring response. ' + + 'Document must have a mandatory JSON API object. See https://jsonapi.org/format/#document-jsonapi-object.' + ); return response; } - + const recordClass = _getModelClass(this); const modelName = _getModelName(recordClass); const store = _getStoreFromRecord(this); - const serializer = store.serializerFor(modelName); - const normalized = isArray(response.data) ? serializer.normalizeArrayResponse(store, recordClass, response) : - serializer.normalizeSingleResponse(store, recordClass, response); - return this.store.push(normalized); + const serializer: JSONSerializer = store.serializerFor(modelName as keyof SerializerRegistry); + let normalized: {}; + if (isArray(response.data)) { + const doc = response as CollectionResourceDoc; + normalized = serializer.normalizeArrayResponse(store, recordClass as any, doc, null as any, 'findAll'); + } else { + const doc = response as SingleResourceDoc; + normalized = serializer.normalizeSingleResponse( + store, + recordClass as any, + doc, + `${doc.data.id || '(unknown)'}`, + 'findRecord' + ); + } + return store.push(normalized); } diff --git a/addon/utils/types.ts b/addon/utils/types.ts new file mode 100644 index 00000000..9ddcafe2 --- /dev/null +++ b/addon/utils/types.ts @@ -0,0 +1,53 @@ +import Model from 'ember-data/model'; + +export type StrictHTTPVerb = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD'; +export type HTTPVerb = + | 'GET' + | 'POST' + | 'PUT' + | 'DELETE' + | 'PATCH' + | 'OPTIONS' + | 'HEAD' + | 'get' + | 'post' + | 'put' + | 'delete' + | 'patch' + | 'options' + | 'head'; + +export interface HTTPVerbStrictMap { + GET: 'GET'; + POST: 'POST'; + PUT: 'PUT'; + DELETE: 'DELETE'; + PATCH: 'PATCH'; + OPTIONS: 'OPTIONS'; + HEAD: 'HEAD'; + get: 'GET'; + post: 'POST'; + put: 'PUT'; + delete: 'DELETE'; + patch: 'PATCH'; + options: 'OPTIONS'; + head: 'HEAD'; +} + +export function strictifyHttpVerb(notStrict: K): HTTPVerbStrictMap[K] { + return `${notStrict}`.toUpperCase() as HTTPVerbStrictMap[K]; +} + +export type EmberDataRequestType = + | 'findRecord' + | 'findAll' + | 'query' + | 'queryRecord' + | 'findMany' + | 'findHasMany' + | 'findBelongsTo' + | 'createRecord' + | 'updateRecord' + | 'deleteRecord'; + +export type Hook = (this: Model, payload: IN) => OUT; diff --git a/package.json b/package.json index dd6636ec..4211c6a0 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "ember-cli-qunit": "^4.4.0", "ember-cli-shims": "^1.1.0", "ember-cli-test-loader": "^2.2.0", + "ember-cli-tslint": "^0.1.4", "ember-cli-typescript-blueprints": "^2.0.0-beta.1", "ember-cli-uglify": "^2.0.0", "ember-code-snippet": "^2.1.0", @@ -60,6 +61,7 @@ "loader.js": "^4.7.0", "qunit-dom": "^0.8.0", "travis-deploy-once": "^5.0.7", + "tslint-config-prettier": "^1.15.0", "typescript": "^3.1.5" }, "optionalDependencies": { diff --git a/tests/acceptance/index-test.ts b/tests/acceptance/index-test.ts index 8b2a0842..6d4a8a83 100644 --- a/tests/acceptance/index-test.ts +++ b/tests/acceptance/index-test.ts @@ -1,11 +1,11 @@ -import { module, test } from 'qunit'; -import { visit, click } from '@ember/test-helpers'; +import { click, visit } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import Pretender from 'pretender'; +import { module, test } from 'qunit'; -module('Acceptance | index2', function(hooks) { +module('Acceptance | index2', hooks => { setupApplicationTest(hooks); - let server; + let server: any; hooks.beforeEach(() => { server = new Pretender(); }); @@ -13,31 +13,31 @@ module('Acceptance | index2', function(hooks) { server.shutdown(); }); - test('visiting /', async function(assert) { + test('visiting /', async assert => { await visit('/'); assert.expect(8); - server.put('/fruits/:id/doRipen', request => { - let data = JSON.parse(request.requestBody); + server.put('/fruits/:id/doRipen', (request: { url: string; requestBody: string }) => { + const data = JSON.parse(request.requestBody); assert.deepEqual(data, { id: '1', name: 'apple' }, 'member action - request payload is correct'); assert.equal(request.url, '/fruits/1/doRipen', 'request was made to "doRipen"'); return [200, {}, '{"status": "ok"}']; }); - server.put('/fruits/ripenEverything', request => { - let data = JSON.parse(request.requestBody); + server.put('/fruits/ripenEverything', (request: { url: string; requestBody: string }) => { + const data = JSON.parse(request.requestBody); assert.deepEqual(data, { test: 'ok' }, 'collection action - request payload is correct'); assert.ok(true, 'request was made to "ripenEverything"'); return [200, {}, '{"status": "ok"}']; }); - server.get('/fruits/:id/info', request => { + server.get('/fruits/:id/info', (request: { url: string; requestBody: string }) => { assert.equal(request.url, '/fruits/1/info?fruitId=1'); assert.ok(true, 'request was made to "ripenEverything"'); return [200, {}, '{"status": "ok"}']; }); - server.get('/fruits/fresh', request => { + server.get('/fruits/fresh', (request: { url: string; requestBody: string }) => { assert.equal(request.url, '/fruits/fresh?month=July'); assert.ok(true, 'request was made to "ripenEverything"'); return [200, {}, '{"status": "ok"}']; @@ -52,14 +52,14 @@ module('Acceptance | index2', function(hooks) { await click('.all-fruit .fresh-type-button'); }); - test('before/after hooks and serializeAndPush helper', async function(assert) { + test('before/after hooks and serializeAndPush helper', async assert => { await visit('/'); assert.expect(7); - server.put('/fruits/:id/doEat', request => { - let data = JSON.parse(request.requestBody); + server.put('/fruits/:id/doEat', (request: { url: string; requestBody: string }) => { + const data = JSON.parse(request.requestBody); - let expectedData = { + const expectedData = { data: { type: 'fruits', attributes: { @@ -84,10 +84,10 @@ module('Acceptance | index2', function(hooks) { return [200, {}, JSON.stringify(response)]; }); - server.put('/fruits/doEatAll', request => { - let data = JSON.parse(request.requestBody); + server.put('/fruits/doEatAll', (request: { url: string; requestBody: string }) => { + const data = JSON.parse(request.requestBody); - let expectedData = { + const expectedData = { data: { type: 'fruits', attributes: { diff --git a/tests/dummy/app/controllers/index.ts b/tests/dummy/app/controllers/index.ts index 479bb88f..ea6c5e2c 100644 --- a/tests/dummy/app/controllers/index.ts +++ b/tests/dummy/app/controllers/index.ts @@ -1,30 +1,32 @@ import { A } from '@ember/array'; import Controller from '@ember/controller'; +import Fruit from '../models/fruit'; export default Controller.extend({ + requests: [] as any[], init() { this._super(...arguments); this.set('requests', A([])); }, // BEGIN-SNIPPET controller actions: { - ripenFruit(fruit) { + ripenFruit(fruit: InstanceType) { fruit.ripen(fruit.getProperties(['id', 'name'])); }, - fruitInfo(fruit) { - let { id } = fruit.getProperties(['id', 'name']); + fruitInfo(fruit: InstanceType) { + const { id } = fruit.getProperties(['id', 'name']); fruit.info({ fruitId: id }); }, - ripenAllFruit(fruit) { + ripenAllFruit(fruit: InstanceType) { fruit.ripenAll({ test: 'ok' }); }, - getAllFreshFruit(fruit) { + getAllFreshFruit(fruit: InstanceType) { fruit.getFresh({ month: 'July' }); }, - eatFruit(fruit) { + eatFruit(fruit: InstanceType) { fruit.eat({ was_eaten: true }); }, - eatAll(fruit) { + eatAll(fruit: InstanceType) { fruit.eatAll({ was_eaten: true }); } } diff --git a/tests/dummy/app/models/fruit.ts b/tests/dummy/app/models/fruit.ts index bb284c79..3a451dbc 100644 --- a/tests/dummy/app/models/fruit.ts +++ b/tests/dummy/app/models/fruit.ts @@ -1,17 +1,16 @@ // BEGIN-SNIPPET fruit-model -import DS from 'ember-data'; - -import { memberAction, collectionAction, serializeAndPush } from 'ember-api-actions'; import { assign } from '@ember/polyfills'; +import { collectionAction, memberAction, serializeAndPush } from 'ember-api-actions'; +import DS from 'ember-data'; +import { SingleResourceDoc } from 'jsonapi-typescript'; const { attr, Model } = DS; -function mergeAttributes(attributes) { - let payload = this.serialize(); - payload.data.attributes = assign(payload.data.attributes, attributes); +function mergeAttributes(this: DS.Model, attributes: any) { + const payload: SingleResourceDoc<'fruit', any> = this.serialize() as any; + payload.data.attributes = assign(payload.data.attributes || {}, attributes); return payload; } - export default Model.extend({ name: attr('string'), ripen: memberAction({ path: 'doRipen' }), diff --git a/tests/dummy/app/routes/index.ts b/tests/dummy/app/routes/index.ts index 66474057..97669ba0 100644 --- a/tests/dummy/app/routes/index.ts +++ b/tests/dummy/app/routes/index.ts @@ -1,6 +1,6 @@ -import Ember from 'ember'; -import Route from '@ember/routing/route'; import { A } from '@ember/array'; +import Route from '@ember/routing/route'; +import Ember from 'ember'; import Pretender from 'pretender'; const { testing } = Ember; @@ -90,14 +90,14 @@ export default Route.extend({ }, _setupPretender() { - let server = new Pretender(); + const server = new Pretender(); // server.get('/fruits', request => { // return [200, {}, JSON.stringify({ // fruits: // })]; // }); server.put('/fruits/:id/doRipen', request => { - let controller = this.get('controller'); + const controller = this.get('controller'); controller.get('requests').addObject({ url: request.url, data: JSON.parse(request.requestBody) @@ -105,7 +105,7 @@ export default Route.extend({ return [200, {}, '{"status": "ok"}']; }); server.put('/fruits/ripenEverything', request => { - let controller = this.get('controller'); + const controller = this.get('controller'); controller.get('requests').addObject({ url: request.url, data: JSON.parse(request.requestBody) @@ -113,7 +113,7 @@ export default Route.extend({ return [200, {}, '{"status": "ok"}']; }); server.get('/fruits/:id/info', request => { - let controller = this.get('controller'); + const controller = this.get('controller'); controller.get('requests').addObject({ url: request.url }); @@ -121,7 +121,7 @@ export default Route.extend({ }); server.get('/fruits/fresh', request => { - let controller = this.get('controller'); + const controller = this.get('controller'); controller.get('requests').addObject({ url: request.url }); diff --git a/tests/unit/utils/collection-action-test.ts b/tests/unit/utils/collection-action-test.ts index 60e1e257..7ca09e7c 100644 --- a/tests/unit/utils/collection-action-test.ts +++ b/tests/unit/utils/collection-action-test.ts @@ -1,10 +1,10 @@ import collectionAction from 'ember-api-actions/utils/collection-action'; import { module, test } from 'qunit'; -module('Unit | Utility | collection action', function() { +module('Unit | Utility | collection action', () => { // Replace this with your real tests. - test('it works', function(assert) { - let result = collectionAction({ path: '/bar' }); + test('it works', assert => { + const result = collectionAction({ path: '/bar' }); assert.ok(result); }); }); diff --git a/tests/unit/utils/member-action-test.ts b/tests/unit/utils/member-action-test.ts index 3cf28d56..e3000589 100644 --- a/tests/unit/utils/member-action-test.ts +++ b/tests/unit/utils/member-action-test.ts @@ -1,10 +1,10 @@ import memberAction from 'ember-api-actions/utils/member-action'; import { module, test } from 'qunit'; -module('Unit | Utility | member action', function() { +module('Unit | Utility | member action', () => { // Replace this with your real tests. - test('it works', function(assert) { - let result = memberAction({ path: '/foo' }); + test('it works', assert => { + const result = memberAction({ path: '/foo' }); assert.ok(result); }); }); diff --git a/tsconfig.json b/tsconfig.json index 5cb782f2..d2fc436a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,12 @@ "moduleResolution": "node", "allowSyntheticDefaultImports": true, "noEmitOnError": false, + "noImplicitAny": true, + "noImplicitThis": true, + "strict": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictPropertyInitialization": true, "noEmit": true, "inlineSourceMap": true, "inlineSources": true, diff --git a/tslint.json b/tslint.json new file mode 100644 index 00000000..50befdd7 --- /dev/null +++ b/tslint.json @@ -0,0 +1,10 @@ +{ + "defaultSeverity": "error", + "extends": ["tslint:recommended", "tslint-config-prettier"], + "jsRules": {}, + "rules": { + "interface-name": false, + "object-literal-sort-keys": false + }, + "rulesDirectory": [] +} diff --git a/yarn.lock b/yarn.lock index 9528d3b3..30ee623c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1356,6 +1356,13 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" +aot-test-generators@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/aot-test-generators/-/aot-test-generators-0.1.0.tgz#43f0f615f97cb298d7919c1b0b4e6b7310b03cd0" + integrity sha1-Q/D2Ffl8spjXkZwbC05rcxCwPNA= + dependencies: + jsesc "^2.5.0" + aproba@^1.0.3, aproba@^1.1.1, aproba@^1.1.2, aproba@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -1572,7 +1579,7 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== -babel-code-frame@^6.26.0: +babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= @@ -2696,6 +2703,25 @@ broccoli-persistent-filter@^1.1.6, broccoli-persistent-filter@^1.4.3: symlink-or-copy "^1.0.1" walk-sync "^0.3.1" +broccoli-persistent-filter@^1.2.0: + version "1.4.6" + resolved "https://registry.yarnpkg.com/broccoli-persistent-filter/-/broccoli-persistent-filter-1.4.6.tgz#80762d19000880a77da33c34373299c0f6a3e615" + integrity sha512-0RejLwoC95kv4kta8KAa+FmECJCK78Qgm8SRDEK7YyU0N9Cx6KpY3UCDy9WELl3mCXLN8TokNxc7/hp3lL4lfw== + dependencies: + async-disk-cache "^1.2.1" + async-promise-queue "^1.0.3" + broccoli-plugin "^1.0.0" + fs-tree-diff "^0.5.2" + hash-for-dep "^1.0.2" + heimdalljs "^0.2.1" + heimdalljs-logger "^0.1.7" + mkdirp "^0.5.1" + promise-map-series "^0.2.1" + rimraf "^2.6.1" + rsvp "^3.0.18" + symlink-or-copy "^1.0.1" + walk-sync "^0.3.1" + broccoli-plugin@^1.0.0, broccoli-plugin@^1.1.0, broccoli-plugin@^1.2.0, broccoli-plugin@^1.2.1, broccoli-plugin@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/broccoli-plugin/-/broccoli-plugin-1.3.0.tgz#bee704a8e42da08cb58e513aaa436efb7f0ef1ee" @@ -2764,6 +2790,16 @@ broccoli-stew@^2.0.0: symlink-or-copy "^1.2.0" walk-sync "^0.3.0" +broccoli-tslinter@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/broccoli-tslinter/-/broccoli-tslinter-3.0.1.tgz#f30b167356cd2b96c604e8a56f1557de1e2048ed" + integrity sha512-M7xgEOPEbNJmUO34GYGwOS/uhjQu1J4rHqHtVih4L4Lk3+Y7MpWYq49PSJZ+tWkjYbHfXlw9NTdpOgbmvwKSug== + dependencies: + aot-test-generators "^0.1.0" + broccoli-persistent-filter "^1.2.0" + chalk "^2.0.1" + exists-sync "0.0.4" + broccoli-uglify-sourcemap@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/broccoli-uglify-sourcemap/-/broccoli-uglify-sourcemap-2.2.0.tgz#2ff49389bdf342a550c3596750ba2dde95a8f7d4" @@ -2865,7 +2901,7 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -builtin-modules@^1.0.0: +builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= @@ -3361,7 +3397,7 @@ commander@2.8.x: dependencies: graceful-readlink ">= 1.0.0" -commander@^2.15.1: +commander@^2.12.1, commander@^2.15.1: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== @@ -3881,7 +3917,7 @@ dezalgo@^1.0.0, dezalgo@~1.0.3: asap "^2.0.0" wrappy "1" -diff@^3.5.0: +diff@^3.2.0, diff@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== @@ -4189,6 +4225,17 @@ ember-cli-test-loader@^2.2.0: dependencies: ember-cli-babel "^6.8.1" +ember-cli-tslint@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/ember-cli-tslint/-/ember-cli-tslint-0.1.4.tgz#704980595e90caf8c080845737bc97663cdbe729" + integrity sha512-YVAz76VSKJ8IQqaJ7rFDLQWBc1okkWPtO1qMtNxgUkkaNViMAsYZp+1ddV6+IW2XnzZMBV/5WqXkpuTH0P3KGA== + dependencies: + broccoli-funnel "^2.0.1" + broccoli-tslinter "^3.0.1" + rsvp "^4.7.0" + tslint "^5.5.0" + walk-sync "^0.3.2" + ember-cli-typescript-blueprints@^2.0.0-beta.1: version "2.0.0-beta.1" resolved "https://registry.yarnpkg.com/ember-cli-typescript-blueprints/-/ember-cli-typescript-blueprints-2.0.0-beta.1.tgz#2db2e34ad01b5a50a4459c2f7cfc8f132b21fcea" @@ -6718,7 +6765,7 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.12.0, js-yaml@^3.9.0: +js-yaml@^3.12.0, js-yaml@^3.7.0, js-yaml@^3.9.0: version "3.12.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A== @@ -6744,7 +6791,7 @@ jsesc@^1.3.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= -jsesc@^2.5.1: +jsesc@^2.5.0, jsesc@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe" integrity sha1-5CGiqOINawgZ3yiQj3glJrlt0f4= @@ -10996,6 +11043,41 @@ trim-right@^1.0.1: resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= +tslib@^1.8.0, tslib@^1.8.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" + integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== + +tslint-config-prettier@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.15.0.tgz#76b9714399004ab6831fdcf76d89b73691c812cf" + integrity sha512-06CgrHJxJmNYVgsmeMoa1KXzQRoOdvfkqnJth6XUkNeOz707qxN0WfxfhYwhL5kXHHbYJRby2bqAPKwThlZPhw== + +tslint@^5.5.0: + version "5.11.0" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.11.0.tgz#98f30c02eae3cde7006201e4c33cb08b48581eed" + integrity sha1-mPMMAurjzecAYgHkwzywi0hYHu0= + dependencies: + babel-code-frame "^6.22.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^3.2.0" + glob "^7.1.1" + js-yaml "^3.7.0" + minimatch "^3.0.4" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.8.0" + tsutils "^2.27.2" + +tsutils@^2.27.2: + version "2.29.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"