├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .gitbook.yaml ├── .github ├── .DS_Store ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── ---build-bug.md │ ├── ---build-enhancement.md │ ├── ---types-bug.md │ └── ---types-enhancement.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── .vscode ├── launch.json └── settings.json ├── .watchmanconfig ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── blueprint-files └── ember-cli-typescript │ ├── __config_root__ │ └── config │ │ └── environment.d.ts │ ├── tsconfig.json │ └── types │ ├── __app_name__ │ └── index.d.ts │ ├── ember-data │ └── types │ │ └── registries │ │ └── model.d.ts │ └── global.d.ts ├── commitlint.config.js ├── config ├── ember-try.js └── environment.js ├── debug.sh ├── docs ├── SUMMARY.md ├── configuration.md ├── cookbook │ ├── README.md │ └── working-with-route-models.md ├── ember-data │ ├── README.md │ ├── models.md │ └── transforms.md ├── ember │ ├── README.md │ ├── components.md │ ├── controllers.md │ ├── helpers.md │ ├── routes.md │ ├── services.md │ └── testing.md ├── index.md ├── installation.md ├── legacy │ ├── README.md │ ├── computed-properties.md │ ├── ember-component.md │ ├── ember-object.md │ └── mixins.md ├── troubleshooting │ ├── README.md │ └── conflicting-types.md ├── ts │ ├── README.md │ ├── current-limitations.md │ ├── decorators.md │ ├── package-names.md │ ├── using-ts-effectively.md │ └── with-addons.md └── upgrade-notes.md ├── ember-cli-build.js ├── index.js ├── is_md_only.sh ├── known-typings.md ├── package.json ├── register-ts-node.js ├── renovate.json ├── rfcs ├── 0000-template.md └── README.md ├── test-fixtures └── skeleton-app │ ├── app │ ├── index.html │ └── styles │ │ └── .gitkeep │ ├── config │ └── environment.js │ ├── ember-cli-build.js │ ├── package.json │ ├── testem.js │ ├── tests │ ├── index.html │ └── test-helper.ts │ └── tsconfig.json ├── testem.js ├── tests ├── .jshintrc ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ ├── .gitkeep │ │ │ ├── js-importing-ts.hbs │ │ │ ├── js-importing-ts.js │ │ │ ├── test-one.js │ │ │ ├── ts-component.hbs │ │ │ └── ts-component.ts │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ └── application.ts │ │ ├── helpers │ │ │ ├── .gitkeep │ │ │ ├── js-help.js │ │ │ └── typed-help.ts │ │ ├── index.html │ │ ├── lib │ │ │ ├── generate-avatar.ts │ │ │ └── some-const.ts │ │ ├── models │ │ │ └── .gitkeep │ │ ├── resolver.js │ │ ├── router.js │ │ ├── routes │ │ │ └── .gitkeep │ │ └── styles │ │ │ └── app.css │ ├── config │ │ ├── environment.js │ │ ├── optional-features.json │ │ └── targets.js │ ├── lib │ │ ├── in-repo-a │ │ │ ├── addon-test-support │ │ │ │ └── from-ats.ts │ │ │ ├── addon │ │ │ │ └── test-file.ts │ │ │ ├── app │ │ │ │ └── a.ts │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ └── test-support │ │ │ │ └── from-ts.ts │ │ └── in-repo-b │ │ │ ├── addon │ │ │ └── test-file.ts │ │ │ ├── app │ │ │ └── b.ts │ │ │ ├── index.js │ │ │ └── package.json │ ├── public │ │ ├── crossdomain.xml │ │ └── robots.txt │ └── type-utils.ts ├── helpers │ └── .gitkeep ├── index.html ├── integration │ ├── .gitkeep │ └── components │ │ ├── dummy.ts │ │ ├── js-importing-ts-test.js │ │ └── ts-component-test.js ├── test-helper.js └── unit │ ├── .gitkeep │ ├── build-test.ts │ └── helpers │ ├── js-help-test.js │ └── typed-help-test.js ├── ts ├── addon.ts ├── blueprints │ └── ember-cli-typescript │ │ ├── index.js │ │ └── update-paths-for-addon.js ├── lib │ ├── commands │ │ ├── clean.ts │ │ └── precompile.ts │ ├── typechecking │ │ ├── middleware │ │ │ ├── index.ts │ │ │ └── render-error-page.ts │ │ └── worker │ │ │ ├── index.ts │ │ │ └── launch.ts │ └── utilities │ │ ├── copy-declarations.ts │ │ ├── ember-cli-entities.ts │ │ └── fork.ts ├── tests │ ├── acceptance │ │ └── build-test.mts │ ├── blueprints │ │ └── ember-cli-typescript-test.ts │ ├── commands │ │ ├── clean-test.ts │ │ └── precompile-test.ts │ ├── helpers │ │ ├── skeleton-app.mts │ │ └── stash-published-version.ts │ └── unit │ │ └── copy-declarations-test.ts ├── tsconfig.json └── types │ ├── broccoli-stew │ └── index.d.ts │ ├── ember-cli-blueprint-test-helpers │ └── index.d.ts │ ├── ember-cli-preprocess-registry │ └── index.d.ts │ └── ember-cli │ └── index.d.ts ├── tsconfig.json ├── vendor └── .gitkeep └── yarn.lock /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprint-files/ 3 | /test-fixtures/ 4 | /vendor/ 5 | 6 | # compiled output 7 | /dist/ 8 | /tmp/ 9 | /js/ 10 | 11 | # dependencies 12 | /bower_components/ 13 | /tests/dummy/lib/*/node_modules/ 14 | 15 | # misc 16 | /coverage/ 17 | 18 | # ember-try 19 | /.node_modules.ember-try/ 20 | /bower.json.ember-try 21 | /package.json.ember-try 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | root: true, 4 | parser: '@typescript-eslint/parser', 5 | parserOptions: { 6 | ecmaVersion: 2017, 7 | sourceType: 'module', 8 | }, 9 | plugins: ['ember', '@typescript-eslint', 'prettier'], 10 | extends: ['eslint:recommended', 'plugin:ember/recommended', 'plugin:prettier/recommended'], 11 | env: { 12 | browser: true, 13 | }, 14 | rules: { 15 | 'prettier/prettier': 'error', 16 | }, 17 | settings: { 18 | node: { 19 | // Honor both extensions when enforcing e.g. `node/no-missing-require` 20 | tryExtensions: ['.js', '.ts'], 21 | }, 22 | }, 23 | overrides: [ 24 | // node files 25 | { 26 | files: [ 27 | '.template-lintrc.js', 28 | 'ember-cli-build.js', 29 | 'index.js', 30 | 'register-ts-node.js', 31 | 'testem.js', 32 | 'blueprints/*/index.js', 33 | 'config/**/*.js', 34 | 'tests/dummy/config/**/*.js', 35 | 'ts/**/*.js', 36 | ], 37 | excludedFiles: ['app/**', 'addon/**', 'tests/dummy/app/**'], 38 | parserOptions: { 39 | sourceType: 'script', 40 | ecmaVersion: 2015, 41 | }, 42 | env: { 43 | browser: false, 44 | node: true, 45 | }, 46 | plugins: ['node'], 47 | rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { 48 | // add your custom rules and overrides for node files here 49 | 'ember/avoid-leaking-state-in-ember-objects': 'off', 50 | }), 51 | }, 52 | 53 | // test files 54 | { 55 | files: ['tests/**/*.{js,ts}'], 56 | excludedFiles: ['tests/dummy/**/*.{js,ts}'], 57 | env: { 58 | embertest: true, 59 | }, 60 | }, 61 | 62 | // node test files 63 | { 64 | files: ['ts/tests/**/*.{js,ts}'], 65 | env: { 66 | mocha: true, 67 | }, 68 | rules: { 69 | 'node/no-unpublished-require': 'off', 70 | }, 71 | }, 72 | 73 | // all TypeScript files 74 | { 75 | files: ['**/*.{ts,cts,mts}'], 76 | rules: { 77 | // These are covered by tsc 78 | 'no-undef': 'off', 79 | 'no-unused-vars': 'off', 80 | }, 81 | }, 82 | ], 83 | }; 84 | -------------------------------------------------------------------------------- /.gitbook.yaml: -------------------------------------------------------------------------------- 1 | root: ./docs 2 | 3 | structure: 4 | readme: ./index.md 5 | -------------------------------------------------------------------------------- /.github/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/.github/.DS_Store -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Other Issues' 3 | about: Other issues relating to ember-cli-typescript 4 | --- 5 | 6 | 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---build-bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Build Bug" 3 | about: A problem relating to how TS is built in your ember app 4 | 5 | --- 6 | 7 | 9 | 10 | ### Please paste the output of `ember -v` here 11 | 18 | 19 | ### Please paste the output of `tsc -v` here 20 | 25 | 26 | ### Please paste the version of `ember-cli-typescript` and `ember-cli-typescript-blueprints` here 27 | 28 | 31 | 32 | 35 | 36 | 40 | 41 | ### Please paste your `tsconfig.json` and `tslint.json` or `eslint.json` (if applicable) below 42 | 43 | 44 |
My tsconfig.json
45 | 
46 |   
47 |   
48 | 
49 | 50 |
My tslint.json or eslint.json
51 | 
52 |   
53 | 
54 | 
55 | 56 | ### What are instructions we can follow to reproduce the issue? 57 | ```sh 58 | ember new sample; cd ./sample # Create a new ember app 59 | ember install ember-cli-typescript # Set up typescript support 60 | 61 | >> Your Instructions Go Here << 62 | 63 | ``` 64 | 65 | ##### Reproduction Case 66 | If you can, please try to fork [this codesandbox](https://codesandbox.io/s/github/mike-north/ember-new-output/tree/ts), and give us an example that demonstrates the problem. Paste the link below so that we can see what's going on 67 | 68 | **Link: ** 69 | 70 | 71 | 72 | ### Now about that bug. What did you expect to see? 73 | 74 | 75 | ### What happened instead? 76 | 77 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---build-enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F381 Build Enhancement" 3 | about: An enhancement relating to Ember.js typescript support & infrastructure 4 | 5 | --- 6 | 7 | 9 | 10 | ### Please write a user story for this feature 11 | 12 | Follow the form 13 | 14 | > *As a **role**, I want **feature** so that **reason**.* 15 | 16 | > Example: 17 | > 18 | > As an **addon publisher**, I want tobe able to **precompile my addon's typescript into js**, so that **my consumers can use my code, regardless of whether their app is written in typescript** 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---types-bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Types Bug" 3 | about: A problem with the type information for Ember & Ember-Data 4 | 5 | --- 6 | 7 | 9 | 10 | ### Which package(s) does this problem pertain to? 11 | - [ ] @types/ember 12 | - [ ] @types/ember__string 13 | - [ ] @types/ember__polyfills 14 | - [ ] @types/ember__object 15 | - [ ] @types/ember__utils 16 | - [ ] @types/ember__array 17 | - [ ] @types/ember__engine 18 | - [ ] @types/ember__debug 19 | - [ ] @types/ember__runloop 20 | - [ ] @types/ember__error 21 | - [ ] @types/ember__controller 22 | - [ ] @types/ember__component 23 | - [ ] @types/ember__routing 24 | - [ ] @types/ember__application 25 | - [ ] @types/ember__test 26 | - [ ] @types/ember__test-helpers 27 | - [ ] @types/ember__service 28 | - [ ] @types/ember-data 29 | - [ ] @types/rsvp 30 | - [ ] Other 31 | - [ ] I don't know 32 | 33 | ### What are instructions we can follow to reproduce the issue? 34 | ```sh 35 | ember new sample; cd ./sample # Create a new ember app 36 | ember install ember-cli-typescript # Set up typescript support 37 | 38 | >> Your Instructions Go Here << 39 | 40 | ``` 41 | 42 | ##### Reproduction Case 43 | If you can, please try to fork [this codesandbox](https://codesandbox.io/s/github/mike-north/ember-new-output/tree/ts), and give us an example that demonstrates the problem. Paste the link below so that we can see what's going on 44 | 45 | **Link: ** 46 | 47 | 48 | 49 | 50 | ### Now about that bug. What did you expect to see? 51 | 52 | 53 | ### What happened instead? 54 | 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---types-enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F381 Types Enhancement" 3 | about: A feature request for the type information of Ember & Ember-Data 4 | 5 | --- 6 | 7 | 9 | 10 | ### Which package(s) does this enhancement pertain to? 11 | - [ ] @types/ember 12 | - [ ] @types/ember__string 13 | - [ ] @types/ember__polyfills 14 | - [ ] @types/ember__object 15 | - [ ] @types/ember__utils 16 | - [ ] @types/ember__array 17 | - [ ] @types/ember__engine 18 | - [ ] @types/ember__debug 19 | - [ ] @types/ember__runloop 20 | - [ ] @types/ember__error 21 | - [ ] @types/ember__controller 22 | - [ ] @types/ember__component 23 | - [ ] @types/ember__routing 24 | - [ ] @types/ember__application 25 | - [ ] @types/ember__test 26 | - [ ] @types/ember__test-helpers 27 | - [ ] @types/ember__service 28 | - [ ] @types/ember-data 29 | - [ ] @types/rsvp 30 | - [ ] Other 31 | - [ ] I don't know 32 | 33 | ### Please write a user story for this feature 34 | 35 | Follow the form 36 | 37 | > *As a **role**, I want **feature** so that **reason**.* 38 | 39 | > Example: 40 | > 41 | > As an **addon publisher**, I want tobe able to **precompile my addon's typescript into js**, so that **my consumers can use my code, regardless of whether their app is written in typescript** 42 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # These trigger patterns based on https://github.com/broccolijs/broccoli/pull/436 4 | on: 5 | pull_request: 6 | push: 7 | # filtering branches here prevents duplicate builds from pull_request and push 8 | branches: 9 | - master 10 | - 'v*' 11 | # always run CI for tags 12 | tags: 13 | - '*' 14 | 15 | # early issue detection: run CI weekly on Sundays 16 | schedule: 17 | - cron: '0 6 * * 0' 18 | 19 | env: 20 | CI: true 21 | 22 | jobs: 23 | test-locked-deps: 24 | name: Test (linux, locked dependencies) 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout Code 28 | uses: actions/checkout@v2 29 | - name: Install Node 30 | uses: volta-cli/action@v4 31 | # https://github.com/expo/expo-github-action/issues/20#issuecomment-541676895 32 | - name: Raise Watched File Limit 33 | run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p 34 | - name: Install Dependencies 35 | run: yarn install --frozen-lockfile 36 | - name: Lint JS/TS Code 37 | run: yarn lint:js 38 | - name: Prepare CI Environment 39 | run: yarn ci:prepare 40 | - name: Ember App Tests 41 | run: yarn ci:test:app 42 | - name: Node Tests 43 | run: yarn ci:test:node 44 | 45 | test-windows: 46 | name: Test (windows, locked dependencies) 47 | runs-on: windows-latest 48 | steps: 49 | - name: Checkout Code 50 | uses: actions/checkout@v2 51 | - name: Install Node 52 | uses: volta-cli/action@v4 53 | - name: Install Dependencies 54 | run: yarn install --frozen-lockfile 55 | - name: Prepare CI Environment 56 | run: yarn ci:prepare 57 | - name: Ember App Tests 58 | run: yarn ci:test:app 59 | - name: Node Tests 60 | run: yarn ci:test:node 61 | 62 | test-floating: 63 | name: Test (linux, floating dependencies) 64 | runs-on: ubuntu-latest 65 | needs: [test-locked-deps] 66 | steps: 67 | - name: Checkout Code 68 | uses: actions/checkout@v2 69 | - name: Install Node 70 | uses: volta-cli/action@v4 71 | - name: Raise Watched File Limit 72 | run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p 73 | - name: Install Dependencies 74 | run: yarn install --no-lockfile 75 | - name: Prepare CI Environment 76 | run: yarn ci:prepare 77 | - name: Ember App Tests 78 | run: yarn ci:test:app 79 | - name: Node Tests 80 | run: yarn ci:test:node 81 | 82 | test-ts-cli-matrix: 83 | name: Test 84 | runs-on: ubuntu-latest 85 | needs: [test-locked-deps] 86 | strategy: 87 | fail-fast: false 88 | matrix: 89 | deps: 90 | - ember-cli@latest 91 | - ember-cli@beta 92 | - typescript@latest 93 | - typescript@next 94 | steps: 95 | - name: Checkout Code 96 | uses: actions/checkout@v2 97 | - name: Install Node 98 | uses: volta-cli/action@v4 99 | - name: Raise Watched File Limit 100 | run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p 101 | - name: Install Dependencies 102 | run: yarn add --dev ${{ matrix.deps }} 103 | - name: Prepare CI Environment 104 | run: yarn ci:prepare 105 | - name: Ember App Tests 106 | run: yarn ci:test:app 107 | - name: Node Tests 108 | run: yarn ci:test:node 109 | 110 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | /js/ 7 | 8 | # dependencies 9 | /bower_components/ 10 | /node_modules/ 11 | 12 | # misc 13 | /.sass-cache 14 | /connect.lock 15 | /coverage/ 16 | /libpeerconnection.log 17 | /npm-debug.log* 18 | /testem.log 19 | /yarn-error.log 20 | 21 | # ember-try 22 | .node_modules.ember-try/ 23 | bower.json.ember-try 24 | package.json.ember-try 25 | 26 | # TypeScript 27 | jsconfig.json 28 | *-tests.xml 29 | test-skeleton-app* -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /tmp/ 4 | 5 | # dependencies 6 | /bower_components/ 7 | 8 | # misc 9 | /.bowerrc 10 | /.editorconfig 11 | /.ember-cli 12 | /.env* 13 | /.eslintcache 14 | /.eslintignore 15 | /.eslintrc.js 16 | /.git/ 17 | /.github/ 18 | /.gitignore 19 | /.prettierignore 20 | /.prettierrc.js 21 | /.template-lintrc.js 22 | /.travis.yml 23 | /.watchmanconfig 24 | /bower.json 25 | /config/ember-try.js 26 | /CONTRIBUTING.md 27 | /ember-cli-build.js 28 | /testem.js 29 | /tests/ 30 | /yarn-error.log 31 | /yarn.lock 32 | .gitkeep 33 | 34 | # ember-try 35 | /.node_modules.ember-try/ 36 | /bower.json.ember-try 37 | /npm-shrinkwrap.json.ember-try 38 | /package.json.ember-try 39 | /package-lock.json.ember-try 40 | /yarn.lock.ember-try 41 | 42 | # TS sources and compiled tests 43 | /ts/ 44 | /js/tests/ 45 | /test-fixtures/ 46 | 47 | # custom 48 | /commitlint.config.js 49 | /.vscode/ 50 | /renovate.json 51 | /CODE_OF_CONDUCT.md 52 | /rfcs 53 | /*.sh 54 | /.gitbook.yaml 55 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | blueprint-files/ 2 | test-fixtures/ 3 | node_modules/ 4 | .azure/ 5 | .github/ 6 | .vscode/ 7 | testem.js 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Debug Tests", 11 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 12 | "args": [ 13 | "-r", "register-ts-node", 14 | "-f", "Acceptance: ember-cli-typescript generator", 15 | "-f", "basic app", 16 | "-t", "1000000", 17 | "ts/tests/**/*.{ts,js}" 18 | ], 19 | "internalConsoleOptions": "openOnSessionStart" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "node_modules": true, 9 | "dist": true, 10 | "tmp": true, 11 | "integrated-node-tests": true, 12 | "ember-lts-2.12": true, 13 | "ember-lts-2.16": true, 14 | "ember-release": true, 15 | "ember-beta": true, 16 | "ember-canary": true, 17 | "ember-default": true 18 | }, 19 | "typescript.tsdk": "node_modules/typescript/lib" 20 | } 21 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | The Ember team and community are committed to everyone having a safe and inclusive experience. 2 | 3 | **Our Community Guidelines / Code of Conduct can be found here**: 4 | 5 | https://emberjs.com/guidelines/ 6 | 7 | For a history of updates, see the page history here: 8 | 9 | https://github.com/emberjs/website/commits/master/source/guidelines.html.erb 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /blueprint-files/ember-cli-typescript/__config_root__/config/environment.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type declarations for 3 | * import config from 'my-app/config/environment' 4 | */ 5 | declare const config: { 6 | environment: string; 7 | modulePrefix: string; 8 | podModulePrefix: string; 9 | locationType: 'history' | 'hash' | 'none' | 'auto'; 10 | rootURL: string; 11 | APP: Record; 12 | }; 13 | 14 | export default config; 15 | -------------------------------------------------------------------------------- /blueprint-files/ember-cli-typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember/tsconfig.json", 3 | "compilerOptions": { 4 | 5 | // The combination of `baseUrl` with `paths` allows Ember's classic package 6 | // layout, which is not resolvable with the Node resolution algorithm, to 7 | // work with TypeScript. 8 | "baseUrl": ".", 9 | "paths": <%= pathsFor(dasherizedPackageName) %> 10 | }, 11 | "include": <%= includes %> 12 | } 13 | -------------------------------------------------------------------------------- /blueprint-files/ember-cli-typescript/types/__app_name__/index.d.ts: -------------------------------------------------------------------------------- 1 | <%= indexDeclarations(dasherizedPackageName) %> 2 | -------------------------------------------------------------------------------- /blueprint-files/ember-cli-typescript/types/ember-data/types/registries/model.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Catch-all for ember-data. 3 | */ 4 | export default interface ModelRegistry { 5 | [key: string]: any; 6 | } 7 | -------------------------------------------------------------------------------- /blueprint-files/ember-cli-typescript/types/global.d.ts: -------------------------------------------------------------------------------- 1 | <%= globalDeclarations(dasherizedPackageName) %> 2 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | extends: ['@commitlint/config-conventional'], 6 | rules: { 7 | 'header-max-length': [0, 'always', 288], 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | useYarn: true, 3 | command: 'yarn ci:test', 4 | scenarios: [ 5 | { 6 | name: 'defaults', 7 | npm: { 8 | devDependencies: {}, 9 | }, 10 | }, 11 | { 12 | name: 'typescript-release', 13 | npm: { 14 | devDependencies: { 15 | typescript: 'latest', 16 | }, 17 | }, 18 | }, 19 | { 20 | name: 'typescript-beta', 21 | npm: { 22 | devDependencies: { 23 | typescript: 'next', 24 | }, 25 | }, 26 | }, 27 | { 28 | name: 'ember-cli-release', 29 | npm: { 30 | devDependencies: { 31 | 'ember-cli': 'latest', 32 | }, 33 | }, 34 | }, 35 | { 36 | name: 'ember-cli-beta', 37 | npm: { 38 | devDependencies: { 39 | 'ember-cli': 'beta', 40 | }, 41 | }, 42 | }, 43 | ], 44 | }; 45 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (/* environment, appConfig */) { 4 | return {}; 5 | }; 6 | -------------------------------------------------------------------------------- /debug.sh: -------------------------------------------------------------------------------- 1 | node --inspect --debug-brk node_modules/.bin/ember build 2 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [ember-cli-typescript](index.md) 4 | * [Installation](installation.md) 5 | * [Configuration](configuration.md) 6 | * [TypeScript and Ember](ts/README.md) 7 | * [Using TypeScript With Ember Effectively](ts/using-ts-effectively.md) 8 | * [Decorators](ts/decorators.md) 9 | * [Current limitations](ts/current-limitations.md) 10 | * [Building Addons in TypeScript](ts/with-addons.md) 11 | * [Understanding the @types Package Names](ts/package-names.md) 12 | * [Working With Ember](ember/README.md) 13 | * [Components](ember/components.md) 14 | * [Services](ember/services.md) 15 | * [Routes](ember/routes.md) 16 | * [Controllers](ember/controllers.md) 17 | * [Helpers](ember/helpers.md) 18 | * [Testing](ember/testing.md) 19 | * [Working With Ember Data](ember-data/README.md) 20 | * [Models](ember-data/models.md) 21 | * [Transforms](ember-data/transforms.md) 22 | * [Cookbook](cookbook/README.md) 23 | * [Working with route models](cookbook/working-with-route-models.md) 24 | * [Working With Ember Classic](legacy/README.md) 25 | * [EmberComponent](legacy/ember-component.md) 26 | * [Mixins](legacy/mixins.md) 27 | * [Computed Properties](legacy/computed-properties.md) 28 | * [EmberObject](legacy/ember-object.md) 29 | * [Upgrading from 1.x](upgrade-notes.md) 30 | * [Troubleshooting](troubleshooting/README.md) 31 | * [Conflicting Type Dependencies](troubleshooting/conflicting-types.md) 32 | -------------------------------------------------------------------------------- /docs/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | ## `tsconfig.json` 4 | 5 | We generate a good default [`tsconfig.json`](https://github.com/typed-ember/ember-cli-typescript/blob/master/blueprint-files/ember-cli-typescript/tsconfig.json), which will usually make everything _Just Work™_. In general, you may customize your TypeScript build process as usual using the `tsconfig.json` file. 6 | 7 | However, there are a few things worth noting if you're already familiar with TypeScript and looking to make further or more advanced customizations (but _most_ users can just ignore this section!): 8 | 9 | 1. The generated tsconfig file does not set `"outDir"` and sets `"noEmit"` to `true`. The default configuration we generate allows you to run editors which use the compiler without creating extraneous `.js` files throughout your codebase, leaving the compilation to ember-cli-typescript to manage. 10 | 11 | You _can_ still customize those properties in `tsconfig.json` if your use case requires it, however. For example, to see the output of the compilation in a separate folder you are welcome to set `"outDir"` to some path and set `"noEmit"` to `false`. Then tools which use the TypeScript compiler (e.g. the watcher tooling in JetBrains IDEs) will generate files at that location, while the Ember.js/[Broccoli](https://broccoli.build) pipeline will continue to use its own temp folder. 12 | 13 | 2. Closely related to the previous point: any changes you do make to `outDir` won't have any effect on how _Ember_ builds your application—we run the entire build pipeline through Babel's TypeScript support instead of through the TypeScript compiler. 14 | 3. Since your application is built by Babel, and only _type-checked_ by TypeScript, we set the `target` key in `tsconfig.json` to the current version of the ECMAScript standard so that type-checking uses the latest and greatest from the JavaScript standard library. The Babel configuration in your app's `config/targets.js` and any included polyfills will determine the final build output. 15 | 4. If you make changes to the paths included in or excluded from the build via your `tsconfig.json` (using the `"include"`, `"exclude"`, or `"files"` keys), you will need to restart the server to take the changes into account: ember-cli-typescript does not currently watch the `tsconfig.json` file. For more details, see [the TypeScript reference materials for `tsconfig.json`](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html). 16 | 17 | ## Enabling Sourcemaps 18 | 19 | To enable TypeScript sourcemaps, you'll need to add the corresponding configuration for Babel to your `ember-cli-build.js` file: 20 | 21 | ```typescript 22 | const app = new EmberApp(defaults, { 23 | babel: { 24 | sourceMaps: 'inline', 25 | }, 26 | }); 27 | ``` 28 | 29 | (Note that this _will_ noticeably slow down your app rebuilds.) 30 | 31 | If you are using [Embroider](https://github.com/embroider-build/embroider), you might need to include [devtool](https://webpack.js.org/configuration/devtool/) in your webpack configuration: 32 | 33 | ```ts 34 | return require('@embroider/compat').compatBuild(app, Webpack, { 35 | packagerOptions: { 36 | webpackConfig: { 37 | devtool: 'source-map' 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | If you're updating from an older version of the addon, you may also need to update your `tsconfig.json`. (Current versions generate the correct config at installation.) Either run `ember generate ember-cli-typescript` or verify you have the same sourcemap settings in your `tscsonfig.json` that appear in [the blueprint](https://github.com/typed-ember/ember-cli-typescript/blob/master/blueprint-files/ember-cli-typescript/files/tsconfig.json). 44 | -------------------------------------------------------------------------------- /docs/cookbook/README.md: -------------------------------------------------------------------------------- 1 | # Cookbook 2 | 3 | This “cookbook” section contains recipes for various scenarios you may encounter while working on your app or addon. 4 | 5 | {% hint style="info" %} 6 | Have an idea for an item that should fit here? [Open an issue for it!](https://github.com/typed-ember/ember-cli-typescript/issues/new/choose) We'd love to help you help us make this experience more awesome for everyone. 7 | {% endhint %} 8 | 9 | ## Contents 10 | 11 | * [Working with route models](./working-with-route-models.md) 12 | 13 | -------------------------------------------------------------------------------- /docs/cookbook/working-with-route-models.md: -------------------------------------------------------------------------------- 1 | # Working with route models 2 | 3 | We often use routes’ models throughout our application, since they’re a core ingredient of our application’s data. As such, we want to make sure that we have good types for them! 4 | 5 | We can start by defining some type utilities to let us get the resolved value returned by a route’s model hook: 6 | 7 | ```typescript 8 | import Route from '@ember/routing/route'; 9 | 10 | /** 11 | Get the resolved type of an item. 12 | 13 | - If the item is a promise, the result will be the resolved value type 14 | - If the item is not a promise, the result will just be the type of the item 15 | */ 16 | export type Resolved

= P extends Promise ? T : P; 17 | 18 | /** Get the resolved model value from a route. */ 19 | export type ModelFrom = Resolved>; 20 | ``` 21 | 22 | How that works: 23 | 24 | * `Resolved

` says "if this is a promise, the type here is whatever the promise resolves to; otherwise, it's just the value" 25 | * `ReturnType` gets the return value of a given function 26 | * `R['model']` \(where `R` has to be `Route` itself or a subclass\) uses TS's mapped types to say "the property named `model` on `R` 27 | 28 | Putting those all together, `ModelFrom` ends up giving you the resolved value returned from the `model` hook for a given route: 29 | 30 | ```typescript 31 | type MyRouteModel = ModelFrom; 32 | ``` 33 | 34 | ## `model` on the controller 35 | 36 | We can use this functionality to guarantee that the `model` on a `Controller` is always exactly the type returned by `Route::model` by writing something like this: 37 | 38 | ```typescript 39 | import Controller from '@ember/controller'; 40 | import MyRoute from '../routes/my-route'; 41 | import { ModelFrom } from '../lib/type-utils'; 42 | 43 | export default class ControllerWithModel extends Controller { 44 | declare model: ModelFrom; 45 | } 46 | ``` 47 | 48 | Now, our controller’s `model` property will _always_ stay in sync with the corresponding route’s model hook. 49 | 50 | **Note:** this _only_ works if you do not mutate the `model` in either the `afterModel` or `setupController` hooks on the route! That's generally considered to be a bad practice anyway. If you do change the type there, you'll need to define the type in some other way and make sure your route's model is defined another way. 51 | 52 | -------------------------------------------------------------------------------- /docs/ember-data/README.md: -------------------------------------------------------------------------------- 1 | # Working With Ember Data 2 | 3 | In this section, we cover how to use TypeScript effectively with specific Ember Data APIs \(anything you'd find under the `@ember-data` package namespace\). 4 | 5 | We do _not_ cover general usage of Ember Data; instead, we assume that as background knowledge. Please see the Ember Data [Guides](https://guides.emberjs.com/release/models) and [API docs](https://api.emberjs.com/ember-data/release)! 6 | 7 | -------------------------------------------------------------------------------- /docs/ember-data/models.md: -------------------------------------------------------------------------------- 1 | # Models 2 | 3 | Ember Data models are normal TypeScript classes, but with properties decorated to define how the model represents an API resource and relationships to other resources. The decorators the library supplies "just work" with TypeScript at runtime, but require type annotations to be useful with TypeScript. 4 | 5 | For details about decorator usage, see [our overview of how Ember's decorators work with TypeScript](../ts/decorators.md). 6 | 7 | ## `@attr` 8 | 9 | The type returned by the `@attr` decorator is whatever [Transform](https://api.emberjs.com/ember-data/release/classes/Transform) is applied via the invocation. See [our overview of Transforms for more information](./transforms.md). 10 | 11 | * If you supply no argument to `@attr`, the value is passed through without transformation. 12 | * If you supply one of the built-in transforms, you will get back a corresponding type: 13 | * `@attr('string')` → `string` 14 | * `@attr('number')` → `number` 15 | * `@attr('boolean')` → `boolean` 16 | * `@attr('date')` → `Date` 17 | * If you supply a custom transform, you will get back the type returned by your transform. 18 | 19 | So, for example, you might write a class like this: 20 | 21 | ```typescript 22 | import Model, { attr } from '@ember-data/model'; 23 | import CustomType from '../transforms/custom-transform'; 24 | 25 | export default class User extends Model { 26 | @attr() 27 | declare name?: string; 28 | 29 | @attr('number') 30 | declare age: number; 31 | 32 | @attr('boolean') 33 | declare isAdmin: boolean; 34 | 35 | @attr('custom-transform') 36 | declare myCustomThing: CustomType; 37 | } 38 | ``` 39 | 40 | **Very important:** Even more than with decorators in general, you should be careful when deciding whether to mark a property as optional `?` or definitely present \(no annotation\): Ember Data will default to leaving a property empty if it is not supplied by the API or by a developer when creating it. That is: the _default_ for Ember corresponds to an optional field on the model. 41 | 42 | The _safest_ type you can write for an Ember Data model, therefore, leaves every property optional: this is how models _actually_ behave. If you choose to mark properties as definitely present by leaving off the `?`, you should take care to guarantee that this is a guarantee your API upholds, and that ever time you create a record from within the app, _you_ uphold those guarantees. 43 | 44 | One way to make this safer is to supply a default value using the `defaultValue` on the options hash for the attribute: 45 | 46 | ```typescript 47 | import Model, { attr } from '@ember-data/model'; 48 | 49 | export default class User extends Model { 50 | @attr() 51 | declare name?: string; 52 | 53 | @attr('number', { defaultValue: 13 }) 54 | declare age: number; 55 | 56 | @attr('boolean', { defaultValue: false }) 57 | declare isAdmin: boolean; 58 | } 59 | ``` 60 | 61 | ## Relationships 62 | 63 | Relationships between models in Ember Data rely on importing the related models, like `import User from './user';`. This, naturally, can cause a recursive loop, as `/app/models/post.ts` imports `User` from `/app/models/user.ts`, and `/app/models/user.ts` imports `Post` from `/app/models/post.ts`. Recursive importing triggers an [`import/no-cycle`](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-cycle.md) error from eslint. 64 | 65 | To avoid these errors, use [type-only imports](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html), available since TypeScript 3.8: 66 | 67 | ```ts 68 | import type User from './user'; 69 | ``` 70 | 71 | ### `@belongsTo` 72 | 73 | The type returned by the `@belongsTo` decorator depends on whether the relationship is `{ async: true }` \(which it is by default\). 74 | 75 | * If the value is `true`, the type you should use is `AsyncBelongsTo`, where `Model` is the type of the model you are creating a relationship to. 76 | * If the value is `false`, the type is `Model`, where `Model` is the type of the model you are creating a relationship to. 77 | 78 | So, for example, you might define a class like this: 79 | 80 | ```typescript 81 | import Model, { belongsTo, type AsyncBelongsTo } from '@ember-data/model'; 82 | import type User from './user'; 83 | import type Site from './site'; 84 | 85 | export default class Post extends Model { 86 | @belongsTo('user') 87 | declare user: AsyncBelongsTo; 88 | 89 | @belongsTo('site', { async: false }) 90 | declare site: Site; 91 | } 92 | ``` 93 | 94 | These are _type_-safe to define as always present, that is to leave off the `?` optional marker: 95 | 96 | * accessing an async relationship will always return an `AsyncBelongsTo` object, which itself may or may not ultimately resolve to a value—depending on the API response—but will always be present itself. 97 | * accessing a non-async relationship which is known to be associated but has not been loaded will trigger an error, so all access to the property will be safe _if_ it resolves at all. 98 | 99 | Note, however, that this type-safety is not a guarantee of there being no runtime error: you still need to uphold the contract for non-async relationships \(that is: loading the data first, or side-loading it with the request\) to avoid throwing an error! 100 | 101 | ### `@hasMany` 102 | 103 | The type returned by the `@hasMany` decorator depends on whether the relationship is `{ async: true }` \(which it is by default\). 104 | 105 | * If the value is `true`, the type you should use is `AsyncHasMany`, where `Model` is the type of the model you are creating a relationship to. 106 | * If the value is `false`, the type is `SyncHasMany`, where `Model` is the type of the model you are creating a relationship to. 107 | 108 | So, for example, you might define a class like this: 109 | 110 | ```typescript 111 | import Model, { hasMany, type AsyncHasMany, type SyncHasMany } from '@ember-data/model'; 112 | import type Comment from './comment'; 113 | import type User from './user'; 114 | 115 | export default class Thread extends Model { 116 | @hasMany('comment') 117 | declare comments: AsyncHasMany; 118 | 119 | @hasMany('user', { async: false }) 120 | declare participants: SyncHasMany; 121 | } 122 | ``` 123 | 124 | The same basic rules about the safety of these lookups as with `@belongsTo` apply to these types. The difference is just that in `@hasMany` the resulting types are _arrays_ rather than single objects. 125 | 126 | -------------------------------------------------------------------------------- /docs/ember-data/transforms.md: -------------------------------------------------------------------------------- 1 | # Transforms 2 | 3 | In Ember Data, `attr` defines an attribute on a [Model](https://guides.emberjs.com/release/models/defining-models/). 4 | By default, attributes are passed through as-is, however you can specify an 5 | optional type to have the value automatically transformed. 6 | Ember Data ships with four basic transform types: `string`, `number`, `boolean` and `date`. 7 | 8 | You can define your own transforms by subclassing [Transform](https://guides.emberjs.com/release/models/defining-models/#toc_custom-transforms). 9 | Ember Data transforms are normal TypeScript classes. 10 | The return type of `deserialize` method becomes type of the model class property. 11 | 12 | You may define your own transforms in TypeScript like so: 13 | ```typescript 14 | # app/transforms/coordinate-point.ts 15 | import Transform from '@ember-data/serializer/transform'; 16 | 17 | declare module 'ember-data/types/registries/transform' { 18 | export default interface TransformRegistry { 19 | 'coordinate-point': CoordinatePointTransform; 20 | } 21 | } 22 | 23 | export type CoordinatePoint = { 24 | x: number; 25 | y: number; 26 | }; 27 | 28 | export default class CoordinatePointTransform extends Transform { 29 | deserialize(serialized): CoordinatePoint { 30 | return { x: value[0], y: value[1] }; 31 | } 32 | 33 | serialize(value): number { 34 | return [value.x, value.y]; 35 | } 36 | } 37 | 38 | # app/models/cursor.ts 39 | import Model, { attr } from '@ember-data/model'; 40 | import { CoordinatePoint } from 'agwa-data/transforms/coordinate-point'; 41 | 42 | declare module 'ember-data/types/registries/model' { 43 | export default interface ModelRegistry { 44 | cursor: Cursor; 45 | } 46 | } 47 | 48 | export default class Cursor extends Model { 49 | @attr('coordinate-point') declare position: CoordinatePoint; 50 | } 51 | ``` 52 | 53 | Note that you should declare your own transform under `TransformRegistry` to make `attr` to work with your transform. 54 | -------------------------------------------------------------------------------- /docs/ember/README.md: -------------------------------------------------------------------------------- 1 | # Working With Ember 2 | 3 | In this section, we cover how to use TypeScript effectively with specific Ember APIs \(anything you'd find under the `@ember` package namespace\). 4 | 5 | We do _not_ cover general usage of Ember; instead, we assume that as background knowledge. Please see the Ember [Guides](https://guides.emberjs.com/release/) and [API docs](https://api.emberjs.com/ember/release)! 6 | 7 | ## Outline 8 | 9 | * [Components](./components.md) 10 | * [Services](./services.md) 11 | * [Routes](./routes.md) 12 | * [Controllers](./controllers.md) 13 | * [Helpers](./helpers.md) 14 | * [Testing](./testing.md) 15 | -------------------------------------------------------------------------------- /docs/ember/controllers.md: -------------------------------------------------------------------------------- 1 | # Controllers 2 | 3 | Like [routes](./routes.md), controllers are just normal classes with a few special Ember lifecycle hooks and properties available. 4 | 5 | The main thing you need to be aware of is special handling around query params. In order to provide type safety for query param configuration, Ember's types specify that when defining a query param's `type` attribute, you must supply one of the allowed types: `'boolean'`, `'number'`, `'array'`, or `'string'` \(the default\). However, if you supply these types as you would in JS, like this: 6 | 7 | ```typescript 8 | import Controller from "@ember/controller"; 9 | 10 | export default class HeyoController extends Controller { 11 | queryParams = [ 12 | { 13 | category: { type: "array" }, 14 | }, 15 | ]; 16 | } 17 | ``` 18 | 19 | Then you will see a type error like this: 20 | 21 | ```text 22 | Property 'queryParams' in type 'HeyoController' is not assignable to the same property in base type 'Controller'. 23 | Type '{ category: { type: string; }; }[]' is not assignable to type '(string | Record)[]'. 24 | Type '{ category: { type: string; }; }' is not assignable to type 'string | Record'. 25 | Type '{ category: { type: string; }; }' is not assignable to type 'Record'. 26 | Property 'category' is incompatible with index signature. 27 | Type '{ type: string; }' is not assignable to type 'string | QueryParamConfig | undefined'. 28 | Type '{ type: string; }' is not assignable to type 'QueryParamConfig'. 29 | Types of property 'type' are incompatible. 30 | Type 'string' is not assignable to type '"string" | "number" | "boolean" | "array" | undefined'.ts(2416) 31 | ``` 32 | 33 | This is because TS currently infers the type of `type: "array"` as `type: string`. You can work around this by supplying `as const` after the declaration: 34 | 35 | ```diff 36 | import Controller from "@ember/controller"; 37 | 38 | export default class HeyoController extends Controller { 39 | queryParams = [ 40 | { 41 | - category: { type: "array" }, 42 | + category: { type: "array" as const }, 43 | }, 44 | ]; 45 | } 46 | ``` 47 | 48 | Now it will type-check. 49 | 50 | -------------------------------------------------------------------------------- /docs/ember/helpers.md: -------------------------------------------------------------------------------- 1 | # Helpers 2 | 3 | Helpers in Ember are just functions or classes with a well-defined interface, which means they largely Just Work™ with TypeScript. However, there are a couple things you’ll want to watch out for. 4 | 5 | {% hint style="info" %} 6 | As always, you should start by reading and understanding the [Ember Guide on Helpers](https://guides.emberjs.com/release/templates/writing-helpers/)! 7 | {% endhint %} 8 | 9 | ## Function-based helpers 10 | 11 | The basic type of a helper function in Ember is: 12 | 13 | ```typescript 14 | type FunctionBasedHelper = 15 | (positional: unknown[], named: Record) => string | void; 16 | ``` 17 | 18 | This represents a function which _may_ have an arbitrarily-long list of positional arguments, which _may_ be followed by a single dictionary-style object containing any named arguments. 19 | 20 | There are three important points about this definition: 21 | 22 | 1. `positional` is an array of `unknown`, of unspecified length. 23 | 2. `named` is a `Record`. 24 | 3. Both arguments are always set, but may be empty. 25 | 26 | Let’s walk through each of these. 27 | 28 | ### Handling `positional` arguments 29 | 30 | The type is an array of `unknown` because we don’t \(yet!\) have any way to make templates aware of the information in this definition—so users could pass in _anything_. We can work around this using [type narrowing](https://microsoft.github.io/TypeScript-New-Handbook/chapters/narrowing/)—TypeScript’s way of using runtime checks to inform the types at runtime. 31 | 32 | ```typescript 33 | function totalLength(positional: unknown[]) { 34 | // Account for case where user passes no arguments 35 | assert( 36 | 'all positional args to `total-length` must be strings', 37 | positional.every(arg => typeof arg === 'string') 38 | ); 39 | 40 | // safety: we can cast `positional as string[]` because we asserted above 41 | return (positional as string[]).reduce((sum, s) => sum + s.length, 0); 42 | } 43 | ``` 44 | 45 | ### Handling `named` arguments 46 | 47 | We specified the type of `named` as a `Record`. `Record` is a built-in TypeScript type representing a fairly standard type in JavaScript: an object being used as a simple map of keys to values. Here we set the values to `unknown` and the keys to `string`, since that accurately represents what callers may actually pass to a helper. 48 | 49 | \(As with `positional`, we specify the type here as `unknown` to account for the fact that the template layer isn’t aware of types yet.\) 50 | 51 | ### `positional` and `named` presence 52 | 53 | Note that even if the user passes _no_ arguments, both `positional` and `named` are always present. They will just be _empty_ in that case. For example: 54 | 55 | ```typescript 56 | import { helper } from '@ember/component/helper'; 57 | 58 | const describe = (entries: string): string => (entries.length > 0 ? entries : '(none)'); 59 | 60 | export function showAll(positional: unknown[], named: Record) { 61 | // pretty print each item with its index, like `0: { neat: true }` or 62 | // `1: undefined`. 63 | const positionalEntries = positional 64 | .reduce((items, arg, index) => items.concat(`${index}: ${JSON.stringify(arg)}`), []) 65 | .join(', '); 66 | 67 | // pretty print each item with its name, like `cool: beans` or 68 | // `answer: 42`. 69 | const namedEntries = Object.keys(named) 70 | .reduce( 71 | (items, key) => items.concat(`${key}: ${JSON.stringify(named[key], undefined, 2)}`), 72 | [] 73 | ) 74 | .join(', '); 75 | 76 | return `positional: ${describe(positionalEntries)}\nnamed: ${describe(namedEntries)}`; 77 | } 78 | 79 | export default helper(showAll); 80 | ``` 81 | 82 | ### Putting it all together 83 | 84 | Given those constraints, let’s see what a \(very contrived\) actual helper might look like in practice. Let’s imagine we want to take a pair of strings and join them with a required separator and optional prefix and postfixes: 85 | 86 | ```typescript 87 | import { helper } from '@ember/component/helper'; 88 | import { assert } from '@ember/debug'; 89 | import { is } from '../../type-utils' 90 | 91 | export function join(positional: [unknown, unknown], named: Dict) { 92 | assert( 93 | `'join' requires two 'string' positional parameters`, 94 | is<[string, string]>( 95 | positional, 96 | positional.length === 2 && 97 | positional.every(el => typeof el === 'string') 98 | ) 99 | ); 100 | assert(`'join' requires argument 'separator'`, typeof named.separator === 'string'); 101 | 102 | const joined = positional.join(named.separator); 103 | const prefix = typeof named.prefix === 'string' ? named.prefix : ''; 104 | 105 | return `${prefix}${joined}`; 106 | } 107 | 108 | export default helper(join); 109 | ``` 110 | 111 | ## Class-based helpers 112 | 113 | The basic type of a class-based helper function in Ember is: 114 | 115 | ```typescript 116 | interface ClassBasedHelper { 117 | compute(positional?: unknown[], named?: Record): string | void; 118 | } 119 | ``` 120 | 121 | Notice that the signature of `compute` is the same as the signature for the function-based helper! This means that everything we said above applies in exactly the same way here. The only differences are that we can have local state and, by extending from Ember’s `Helper` class, we can hook into the dependency injection system and use services. 122 | 123 | ```typescript 124 | import Helper from '@ember/component/helper'; 125 | import { inject as service } from '@ember/service'; 126 | import Authentication from 'my-app/services/authentication'; 127 | 128 | export default class Greet extends Helper { 129 | @service authentication: Authentication; 130 | 131 | compute() { 132 | return this.authentication.isAuthenticated 133 | ? `Welcome back, ${authentication.userName}!` 134 | : 'Sign in?'; 135 | } 136 | ``` 137 | 138 | For more details on using decorators, see our [guide to using decorators](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/ember/%28../ts/decorators/%29/README.md). For details on using services, see our [guide to services](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/ember/%28./services/%29/README.md). 139 | 140 | -------------------------------------------------------------------------------- /docs/ember/routes.md: -------------------------------------------------------------------------------- 1 | # Routes 2 | 3 | Working with Routes is in general just working normal TypeScript classes. Ember's types supply the definitions for the various lifecycle events available within route subclasses, which will provide autocomplete and type-checking along the way in general. 4 | 5 | However, there is one thing to watch out for: the types of the arguments passed to methods will _not_ autocomplete as you may expect. This is because in _general_ a subclass may override a superclass method as long as it calls its superclass's method correctly. This is very bad practice, but it is legal JavaScript! This is never a concern for lifecycle hooks in Ember, because they are called by the framework itself. However, TypeScript does not and cannot know that, so we have to provide the types directly. 6 | 7 | Accordingly, and because the `Transition` type is not currently exported as a public type, you may find it convenient to define it using TypeScript's `ReturnType` utility type, which does exactly what it sounds like and gives us a local type which is the type returned by some function. The `RouterService.transitionTo` returns a `Transition`, so we can rely on that as stable public API to define `Transition` locally ourselves: 8 | 9 | ```typescript 10 | import Route from '@ember/routing/route'; 11 | import type RouterService from '@ember/routing/router-service'; 12 | type Transition = ReturnType; 13 | 14 | export default class MyRoute extends Route { 15 | beforeModel(transition: Transition) { 16 | // ... 17 | } 18 | } 19 | ``` 20 | 21 | This inconsistency will be solved in the future. For now, this workaround gets the job done, and also shows the way to using this information to provide the type of the route's model to other consumers: see [Working with Route Models](../cookbook/working-with-route-models.md) for details! 22 | 23 | ```typescript 24 | import Route from '@ember/routing/route'; 25 | 26 | type Resolved

= P extends Promise ? T : P; 27 | 28 | export type MyRouteModel = Resolved>; 29 | 30 | export default class MyRoute extends Route { 31 | model() { 32 | // ... 33 | } 34 | } 35 | ``` 36 | 37 | The `Resolved` utility type takes in any type, and if the type is a `Promise` it transforms the type into whatever the `Promise` resolves to; otherwise it just returns the same type. (If you’re using TypeScript 4.5 or later, you can use the built-in `Awaited` type, which does the same thing but more robustly: it also handles nested promises.) As we saw above, `ReturnType` gets us the return type of the function. So our final `MyRouteModel` type takes the return type from our `model` hook, and uses the `Resolved` type to get the type the promise will resolve to—that is, exactly the type we will have available as `@model` in the template and as `this.model` on a controller. 38 | 39 | This in turn allows us to use the route class to define the type of the model on an associated controller. 40 | 41 | ```typescript 42 | import Controller from '@ember/controller'; 43 | import type { MyRouteModel } from '../routes/my-route'; 44 | 45 | export default class MyController extends Controller { 46 | declare model?: MyRouteModel; 47 | 48 | // ... 49 | } 50 | ``` 51 | 52 | Notice here that the `model` is declared as optional. That’s intentional: the `model` for a given controller is _not_ set when the controller is constructed (that actually happens _either_ when the page corresponding to the controller is created _or_ the first time a `` which links to that page is rendered). Instead, the `model` is set on the controller when the corresponding route is successfully entered, via its `setupController` hook. 53 | -------------------------------------------------------------------------------- /docs/ember/services.md: -------------------------------------------------------------------------------- 1 | # Services 2 | 3 | Ember Services are global singleton classes that can be made available to different parts of an Ember application via dependency injection. Due to their global, shared nature, writing services in TypeScript gives you a build-time-enforcable API for some of the most central parts of your application. 4 | 5 | {% hint style="info" %} 6 | If you are not familiar with Services in Ember, first make sure you have read and understood the [Ember Guide on Services](https://guides.emberjs.com/release/services/)! 7 | {% endhint %} 8 | 9 | ## A basic service 10 | 11 | Let's take this example from the [Ember Guide](https://guides.emberjs.com/release/services/): 12 | 13 | ```typescript 14 | import { A } from '@ember/array'; 15 | import Service from '@ember/service'; 16 | 17 | export default class ShoppingCartService extends Service { 18 | items = A([]); 19 | 20 | add(item) { 21 | this.items.pushObject(item); 22 | } 23 | 24 | remove(item) { 25 | this.items.removeObject(item); 26 | } 27 | 28 | empty() { 29 | this.items.clear(); 30 | } 31 | } 32 | ``` 33 | 34 | Just making this a TypeScript file gives us some type safety without having to add any additional type information. We'll see this when we use the service elsewhere in the application. 35 | 36 | {% hint style="info" %} 37 | When working in Octane, you're better off using a `TrackedArray` from [tracked-built-ins](https://github.com/pzuraq/tracked-built-ins) instead of the classic EmberArray: 38 | 39 | ```typescript 40 | import { TrackedArray } from 'tracked-built-ins'; 41 | import Service from '@ember/service'; 42 | 43 | export default class ShoppingCartService extends Service { 44 | items = new TrackedArray(); 45 | 46 | add(item) { 47 | this.items.push(item); 48 | } 49 | 50 | remove(item) { 51 | this.items.splice(1, this.items.findIndex((i) => i === item)); 52 | } 53 | 54 | empty() { 55 | this.items.clear(); 56 | } 57 | } 58 | ``` 59 | 60 | Notice that here we are using only built-in array operations, not Ember's custom array methods. 61 | {% endhint %} 62 | 63 | ## Using services 64 | 65 | You can use a service in any container-resolved object such as a component or another service. Services are injected into these objects by decorating a property with the `inject` decorator. Because decorators can't affect the type of the property they decorate, we must manually type the property. Also, we must use `declare` modifier to tell the TypeScript compiler to trust that this property will be set up by something outside this component—namely, the decorator. 66 | 67 | Here's an example of using the `ShoppingCartService` we defined above in a component: 68 | 69 | ```typescript 70 | import Component from '@glimmer/component'; 71 | import { inject as service } from '@ember/service'; 72 | import { action } from '@ember/object'; 73 | 74 | import ShoppingCartService from 'my-app/services/shopping-cart'; 75 | 76 | export default class CartContentsComponent extends Component { 77 | @service declare shoppingCart: ShoppingCartService; 78 | 79 | @action 80 | remove(item) { 81 | this.shoppingCart.remove(item); 82 | } 83 | } 84 | ``` 85 | 86 | Any attempt to access a property or method not defined on the service will fail type-checking: 87 | 88 | ```typescript 89 | import Component from '@glimmer/component'; 90 | import { inject as service } from '@ember/service'; 91 | import { action } from '@ember/object'; 92 | 93 | import ShoppingCartService from 'my-app/services/shopping-cart'; 94 | 95 | export default class CartContentsComponent extends Component { 96 | @service declare shoppingCart: ShoppingCartService; 97 | 98 | @action 99 | remove(item) { 100 | // Error: Property 'saveForLater' does not exist on type 'ShoppingCartService'. 101 | this.shoppingCart.saveForLater(item); 102 | } 103 | } 104 | ``` 105 | 106 | Services can also be loaded from the dependency injection container manually: 107 | 108 | ```typescript 109 | import Component from '@glimmer/component'; 110 | import { getOwner } from '@ember/owner'; 111 | import { action } from '@ember/object'; 112 | 113 | import ShoppingCartService from 'my-app/services/shopping-cart'; 114 | 115 | export default class CartContentsComponent extends Component { 116 | get cart() { 117 | return getOwner(this)?.lookup('service:shopping-cart') as ShoppingCartService; 118 | } 119 | 120 | @action 121 | remove(item) { 122 | this.cart.remove(item); 123 | } 124 | } 125 | ``` 126 | 127 | Here we need to cast the lookup result to `ShoppingCartService` in order to get any type-safety because the lookup return type is `any` \(see caution below\). 128 | 129 | {% hint style="danger" %} 130 | This type-cast provides no guarantees that what is returned by the lookup is actually the service you are expecting. Because TypeScript cannot resolve the lookup micro-syntax \(`service:`\) to the service class, a typo would result in returning something other than the specified type. It only gurantees that _if_ the expected service is returned that you are using it correctly. 131 | 132 | There is a merged \(but not yet implemented\) [RFC](https://emberjs.github.io/rfcs/0585-improved-ember-registry-apis.html) which improves this design and makes it straightforward to type-check. Additionally, TypeScript 4.1's introduction of [template types](https://devblogs.microsoft.com/typescript/announcing-typescript-4-1/#template-literal-types) may allow us to supply types that work with the microsyntax. 133 | 134 | For now, however, remember that _the cast is unsafe_! 135 | {% endhint %} 136 | 137 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | {% hint style="success" %} 2 | 3 | ------------------- 4 | 5 | ## TypeScript docs have moved! 🎉 6 | 7 | This documentation is now hosted on the ember guides website here: [Using TypeScript with Ember](https://guides.emberjs.com/release/typescript/) 8 | 9 | ----------------- 10 | 11 | {% endhint %} 12 | 13 | # ember-cli-typescript 14 | 15 | This guide is designed to help you get up and running with TypeScript in an Ember app. 16 | 17 | {% hint style="warning" %} 18 | 19 | **This is _not_ an introduction to TypeScript _or_ Ember. Throughout this guide, we’ll link back to [the TypeScript docs](https://www.typescriptlang.org/docs/home.html) and [the Ember Guides](https://guides.emberjs.com/release/) when there are specific concepts that we will not explain here but which are important for understanding what we’re covering!** 20 | 21 | {% endhint %} 22 | 23 | To get started, check out the instructions in [Getting Started: Installation](./installation.md) 24 | 25 | * If you're totally new to using TypeScript with Ember, start with [TypeScript and Ember](./ts/README.md). 26 | * Once you have a good handle on the basics, you can dive into the guides to working with the APIs specific to [Ember](./ember/README.md) and [Ember Data](./ember-data/README.md). 27 | * If you're working with legacy (pre-Octane) Ember and TypeScript together, you should read [the Legacy Guide](./legacy/README.md). 28 | * Looking for type-checking in Glimmer templates? Check out [Glint](https://typed-ember.gitbook.io/glint/). 29 | 30 | ## Why TypeScript? 31 | 32 | What is TypeScript, and why should you adopt it? 33 | 34 | > TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. 35 | > —[typescriptlang.org](http://www.typescriptlang.org) 36 | 37 | TypeScript lets you build _ambitious web applications_ with confidence—so it’s a perfect fit for Ember apps! 38 | 39 | * Get rid of `undefined is not a function` and `null is not an object` once and for all. 40 | * Enjoy API docs… that are always up-to-date. 41 | * Experience better developer productivity through top-notch editor support, including incredible autocomplete, guided refactorings, automatic imports, and more. 42 | 43 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | You can simply `ember install` the dependency like normal: 4 | 5 | ```bash 6 | ember install ember-cli-typescript@latest 7 | ``` 8 | 9 | All dependencies will be added to your `package.json`, and you're ready to roll! 10 | 11 | **If you're upgrading from a previous release, see (./upgrade-notes.md).** 12 | 13 | Installing ember-cli-typescript modifies your project in two ways: 14 | 15 | * installing a number of other packages to make TypeScript work in your app or addon 16 | * generating a number of files in your project 17 | 18 | ## Other packages this addon installs 19 | 20 | We install all of the following packages at their current "latest" value, : 21 | 22 | * `typescript` 23 | * `ember-cli-typescript-blueprints` 24 | * `@types/ember` 25 | * `@types/ember-data` 26 | * `@types/ember__*` – `@types/ember__object` for `@ember/object` etc. 27 | * `@types/ember-data__*` – `@types/ember-data__model` for `@ember-data/model` etc. 28 | * `@types/rsvp` 29 | 30 | ## Files this addon generates 31 | 32 | We also add the following files to your project: 33 | 34 | * [`tsconfig.json`](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) 35 | * `types//index.d.ts` – the location for any global type declarations you need to write for you own application; see [**Using TS Effectively: Global types for your package**](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/getting-started/docs/ts/using-ts-effectively/README.md#global-types-for-your-package) for information on its default contents and how to use it effectively 36 | * `app/config/environment.d.ts` – a basic set of types defined for the contents of the `config/environment.js` file in your app; see [Environment and configuration typings](installation.md#environment-and-configuration-typings) for details 37 | 38 | -------------------------------------------------------------------------------- /docs/legacy/README.md: -------------------------------------------------------------------------------- 1 | # Working With Ember Classic 2 | 3 | We emphasize the happy path of working with Ember in the [Octane Edition](https://emberjs.com/editions/octane/). However, there are times you’ll need to understand these details: 4 | 5 | 1. Most existing applications make heavy use of the pre-Octane \(“legacy”\) Ember programming model, and we support that model—with caveats. 6 | 2. Several parts of Ember Octane \(specifically: routes, controllers, services, and class-based helpers\) continue to use these concepts under the hood, and our types support that—so understanding them may be important at times. 7 | 8 | The rest of this guide is dedicated to helping you understand how `ember-cli-typescript` and the classic Ember system interact. 9 | 10 | -------------------------------------------------------------------------------- /docs/legacy/computed-properties.md: -------------------------------------------------------------------------------- 1 | # Computed Properties 2 | 3 | There are two variants of Ember’s computed properties you may encounter: 4 | 5 | * the decorator form used with native \(ES6\) classes 6 | * the callback form used with classic classes \(based on EmberObject\) 7 | 8 | ## Decorator form 9 | 10 | ```typescript 11 | import Component from '@ember/component'; 12 | import { computed } from '@ember/object/computed'; 13 | 14 | export default class UserProfile extends Compoennt { 15 | name = 'Chris'; 16 | age = 33; 17 | 18 | @computed('name', 'age') 19 | get bio() { 20 | return `${this.name} is `${this.age}` years old!`; 21 | } 22 | } 23 | ``` 24 | 25 | Note that it is impossible for `@computed` to know whether the keys you pass to it are allowed or not. Migrating to Octane eliminates this issue, since you mark reactive root state with `@tracked` and leave getters undecorated, rather than vice versa. 26 | 27 | ## Callback form 28 | 29 | Computed properties in the classic object model take a callback instead: 30 | 31 | ```typescript 32 | import Component from '@ember/component'; 33 | import { computed } from '@ember/object/computed'; 34 | 35 | const UserProfile = Component.extend({ 36 | name: 'Chris', 37 | age: 32, 38 | 39 | bio: computed('name', 'age', function() { 40 | return `${this.get('name')} is `${this.get('age')}` years old!`; 41 | }), 42 | }) 43 | 44 | export default UserProfile; 45 | ``` 46 | 47 | This definition will not type-check, however. You will need to explicitly write out a `this` type for computed property callbacks for `get` and `set` to type-check correctly: 48 | 49 | ```typescript 50 | import Component from '@ember/component'; 51 | import { computed } from '@ember/object/computed'; 52 | 53 | const UserProfile = Component.extend({ 54 | name: 'Chris', 55 | age: 32, 56 | 57 | bio: computed('name', 'age', function(this: UserProfile) { 58 | // ^---------------^ 59 | // `this` tells TS to use `UserProfile` for `get` and `set` lookups; 60 | // otherwise `this.get` below would not know the types of `'name'` or 61 | // `'age'` or even be able to suggest them for autocompletion. 62 | return `${this.get('name')} is `${this.get('age')}` years old!`; 63 | }), 64 | }) 65 | 66 | export default UserProfile; 67 | ``` 68 | 69 | Note that this _does not always work_: you may get warnings from TypeScript about the item being defined in terms of itself. 70 | 71 | **Accordingly, we strongly recommend migrating classic classes to ES native classes** _**before**_ **adding TypeScript!** 72 | 73 | -------------------------------------------------------------------------------- /docs/legacy/ember-component.md: -------------------------------------------------------------------------------- 1 | # EmberComponent 2 | 3 | -------------------------------------------------------------------------------- /docs/legacy/ember-object.md: -------------------------------------------------------------------------------- 1 | # EmberObject 2 | 3 | When working with the legacy Ember object model, `EmberObject`, there are a number of caveats and limitations you need to be aware of. For today, these caveats and limitations apply to any classes which extend directly from `EmberObject`, or which extend classes which _themselves_ extend `EmberObject`: 4 | 5 | * `Component` – meaning _classic_ Ember components, which imported from `@ember/component`, _not_ Glimmer components which are imported from `@glimmer/component` and do _not_ extend the `EmberObject` base class. 6 | * `Controller` 7 | * `Helper` – note that this applies only to the _class_ form. Function-based helpers do not involve the `EmberObject` base class. 8 | * `Route` 9 | * `Router` 10 | * `Service` 11 | * Ember Data’s `Model` class 12 | 13 | Additionally, Ember’s mixin system is deeply linked to the semantics and implementation details of `EmberObject`, _and_ it has the most caveats and limitations. 14 | 15 | {% hint style="info" %} 16 | In the future, some of these may be able to drop their `EmberObject` base class dependency, but that will not happen till at least the next major version of Ember, and these guides will be updated when that happens. 17 | {% endhint %} 18 | 19 | ## Mixins and classic class syntax 20 | 21 | The Ember mixin system is the legacy Ember construct TypeScript supports _least_ well, as described in [Mixins](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/legacy/mixins/README.md). While this may not be intuitively obvious, the classic class syntax simply _is_ the mixin system. Every classic class creation is a case of mixing together multiple objects to create a new base class with a shared prototype. The result is that any time you see the classic `.extend({ ... })` syntax, regardless of whether there is a named mixin involved, you are dealing with Ember's legacy mixin system. This in turn means that you are dealing with the parts of Ember which TypeScript is _least_ able to handle well. 22 | 23 | While we describe here how to use types with classic \(mixin-based\) classes insofar as they _do_ work, there are many failure modes. As a result, we strongly recommend moving away from both classic classes and mixins, and as quickly as possible. This is the direction the Ember ecosystem as a whole is moving, but it is _especially_ important for TypeScript users. 24 | 25 | {% hint style="info" %} 26 | The [Ember Atlas](https://emberatlas.com) includes guides for migrating [from classic classes to native classes](https://www.notion.so/Native-Classes-55bd67b580ca49f999660caf98aa1897), along with [a variety of patterns](https://www.notion.so/Converting-Classes-with-Mixins-5dc68c0ac3044e51a218fa7aec71c2db) for dealing with specific kinds of mixins in your codebase. 27 | {% endhint %} 28 | 29 | ### Failure modes 30 | 31 | You often need to define `this` in actions hashes, computed properties, etc. That in turn often leads to problems with self-referential `this`: TypeScript simply cannot figure out how to stop recursing through the definitions of the type. 32 | 33 | Additionally, even when you get past the endlessly-recursive type definition problems, when enough mixins are resolved TypeScript will occasionally just give up because it cannot resolve the property or method you're interested in across the many shared base classes. 34 | 35 | Finally, when you have "zebra-striping" of your classes between classic classes and native classes, your types will often stop resolving. 36 | 37 | ## Native classes 38 | 39 | ### `EmberObject` 40 | 41 | In general, we recommend \(following the Ember Octane guides\) that any class which extends directly from the `EmberObject` base class eliminate any use of `EmberObject`-specific API and convert to standalone class, with no base class at all. You can follow the [ember-classic-decorator](https://github.com/emberjs/ember-classic-decorator) workflow to eliminate the base class—switching from `init` to `constructor`, getting rid of uses of methods like `this.set` and `this.get` in favor of using standalone `set` and `get`, and so on. 42 | 43 | ### `EmberObject`-descended classes 44 | 45 | The framework base classes which depend on `EmberObject` cannot follow the exact same path. However, as long as you are using native class syntax, all of these \(`Component`, `Controller`, `Helper`, etc.\) work nicely and safely with TypeScript. In each of these cases, the same caveats apply as with `EmberObject` itself, and you should follow the [ember-classic-decorator](https://github.com/emberjs/ember-classic-decorator) workflow with them as well if you are converting an existing app or addon. However, because these base classes themselves descend from `EmberObject`, you will not be able to remove the base classes as you can with your _own_ classes which descend _directly_ from `EmberObject`. Instead, you will continue to extend from the Ember base classes: 46 | 47 | ```typescript 48 | import Component from '@ember/component'; 49 | export default class Profile extends Component {} 50 | ``` 51 | 52 | ```typescript 53 | import Controller from '@ember/controller'; 54 | export default class IndexController extends Controller {} 55 | ``` 56 | 57 | ```typescript 58 | import Helper from '@ember/component/helper'; 59 | export default class Localize extends Helper {} 60 | ``` 61 | 62 | ```typescript 63 | import Route from '@ember/routing/route'; 64 | export default class ApplicationRoute extends Route {} 65 | ``` 66 | 67 | ```typescript 68 | import EmberRouter from '@ember/routing/router' 69 | export default class AppRouter extends EmberRouter {} 70 | ``` 71 | 72 | ```typescript 73 | import Service from '@ember/service'; 74 | export default class Session extends Service {} 75 | ``` 76 | 77 | ```typescript 78 | import Model from '@ember-data/model'; 79 | export default class User extends Model {} 80 | ``` 81 | 82 | -------------------------------------------------------------------------------- /docs/legacy/mixins.md: -------------------------------------------------------------------------------- 1 | # Mixins 2 | 3 | Mixins are fundamentally hostile to robust typing with TypeScript. While you can supply types for them, you will regularly run into problems with self-referentiality in defining properties within the mixins. 4 | 5 | As a stopgap, you can refer to the type of a mixin using the `typeof` operator. 6 | 7 | In general, however, prefer to use one of the following four strategies for migrating _away_ from mixins before attempting to convert code which relies on them to TypeScript: 8 | 9 | 1. For functionality which encapsulates DOM modification, rewrite as a custom modifier using [ember-modifier](https://github.com/emeber-modifier/ember-modifier). 10 | 2. If the mixin is a way of supplying shared behavior \(not data\), extract it to utility functions, usually just living in module scope and imported and exported as needed. 11 | 3. If the mixin is a way of supplying non-shared state which follows the lifecycle of a given object, replace it with a utility class instantiated in the owning class's `constructor` \(or `init` for legacy classes\). 12 | 4. If the mixin is a way of supplying long-lived, shared state, replace it with a service and inject it where it was used before. This pattern is uncommon, but sometimes appears when mixing functionality into multiple controllers or services. 13 | 14 | You can also use inheritance and class decorators to accomplish some of the same semantics as mixins classically supplied. However, these patterns are more fragile and therefore not recommended. 15 | 16 | -------------------------------------------------------------------------------- /docs/troubleshooting/README.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | Stuck with something? Hopefully one of the documents below can help. If not, file an issue on GitHub and we'll try to help you get it sorted (and it may end up in here). 4 | 5 | ## Outline 6 | 7 | * [Conflicting Type Dependencies](./conflicting-types.md) 8 | -------------------------------------------------------------------------------- /docs/troubleshooting/conflicting-types.md: -------------------------------------------------------------------------------- 1 | # Conflicting Type Dependencies 2 | 3 | You will sometimes see **Duplicate identifier** errors when type-checking your application. 4 | 5 | An example duplicate identifier error \`\`\`sh yarn tsc --noEmit yarn run v1.15.2 $ /Users/chris/dev/teaching/emberconf-2019/node\_modules/.bin/tsc --noEmit node\_modules/@types/ember\_\_object/index.d.ts:23:22 - error TS2300: Duplicate identifier 'EmberObject'. 23 export default class EmberObject extends CoreObject.extend\(Observable\) {} ~~~~~~~~~~~ node\_modules/@types/ember\_\_component/node\_modules/@types/ember\_\_object/index.d.ts:23:22 23 export default class EmberObject extends CoreObject.extend\(Observable\) {} ~~~~~~~~~~~ 'EmberObject' was also declared here. node\_modules/@types/ember\_\_component/node\_modules/@types/ember\_\_object/index.d.ts:23:22 - error TS2300: Duplicate identifier 'EmberObject'. 8 export default class EmberObject extends CoreObject.extend\(Observable\) {} ~~~~~~~~~~~ node\_modules/@types/ember\_\_object/index.d.ts:23:22 23 export default class EmberObject extends CoreObject.extend\(Observable\) {} ~~~~~~~~~~~ 'EmberObject' was also declared here. Found 2 errors. error Command failed with exit code 1. \`\`\` 6 | 7 | This occurs whenever your `yarn.lock` or `package-lock.json` files include more than a single copy of a given set of type definitions—here, types for `@ember/object`, named `@types/ember__object`. See below for details on the package manager behavior, and **Understanding the Package Names** for details on the package names. 8 | 9 | ## Workarounds 10 | 11 | There are currently three recommended workarounds for this: 12 | 13 | * If using `npm`, you can use `npm upgrade --depth=1 @types/ember__object` to upgrade just that specific dependency and anywhere it is used as a transitive dependency of your top-level dependencies. You can also use its `npm dedupe` command, which may resolve the issue. 14 | * If using `yarn`, you can specify a specific version of the package to use in the `"resolutions"` key in `package.json`. For example, if you saw that you had `@types/ember__object@3.0.8` from the default package installs but `@types/ember__object@3.0.5` from `some-cool-ts-addon`, you could force yarn to use `3.0.8` like so: 15 | 16 | ```javascript 17 | { 18 | "resolutions": { 19 | "@types/ember__object": "3.0.8" 20 | } 21 | } 22 | ``` 23 | 24 | * You can identify the dependencies which installed the type dependencies transitively, and uninstall and reinstall them. For example, if running `yarn why` reported you had one version of `@types/ember__object` from [the normally-installed set of packages](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/README.md#other-packages-this-addon-installs), and one from `some-cool-ts-addon`, you could run this: 25 | 26 | ```bash 27 | yarn remove @types/ember some-cool-ts-addon 28 | yarn add -D @types/ember some-cool-ts-addon 29 | ``` 30 | 31 | You may _also_ be able to use [`yarn-deduplicate`](https://github.com/atlassian/yarn-deduplicate), but this does not work 100% of the time, so if you try it and are still seeing the issues, try one of the solutions above. 32 | 33 | ## Understanding the Problem 34 | 35 | When you are using TypeScript in your Ember application, you consume Ember's types through [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped), the tool the TypeScript team built to power the `@types/*` definitions. That tooling examines the dependencies implied by the package imports and generates a `package.json` with those types specified with a `*` dependency version. On initial installation of your dependencies, yarn installs the highest version of the package available, and correctly deduplicates that across both your own package and all the `@types` packages which reference each other. 36 | 37 | However, later installs may introduce conflicting versions of the types, simply by way of yarn's normal update rules. TypeScript requires that there be one and only one type definition a given item can resolve to. Yarn actively avoids changing a previously-installed version of a transitive dependency when a newly installed package depends on the same dependency transitively. Thus, if one of your dependencies _also_ depends on the same package from `@types/*` that you do, and you upgrade your dependence on that type by editing your `package.json` file and running `yarn` or `npm install` again, TypeScript will suddenly start offering the error described in detail above: 38 | 39 | > Duplicate identifier 'EmberObject'.ts\(2300\) 40 | 41 | Let's imagine three packages, `A`, `B`, and `C`, where `A` is _your_ app or library, and `B` and `C` have the following versions and dependencies: 42 | 43 | * `C` is currently at version `1.2.3`. 44 | * `B` is at version `4.5.6`. It depends on `C` with a `*` dependency. So the `dependencies` key in its `package.json` looks like this: 45 | 46 | ```javascript 47 | { 48 | "dependencies": { 49 | "C": "*" 50 | } 51 | } 52 | ``` 53 | 54 | Now, you install _only_ `B` \(this is the equivalent of installing just the basic type definitions in your package\): 55 | 56 | ```javascript 57 | { 58 | "dependencies": { 59 | "B": "~4.5.6" 60 | } 61 | } 62 | ``` 63 | 64 | The first time you install these, you will get a _single_ version of `C` – `1.2.3`. 65 | 66 | Now, let's say that `C` publishes a new version, `1.2.4`, and `A` \(your app or library\) adds a dependency on both `C` like so: 67 | 68 | ```javascript 69 | { 70 | "dependencies": { 71 | "B": "~4.5.6", 72 | "C": "~1.2.0" 73 | } 74 | } 75 | ``` 76 | 77 | When your package manager runs \(especially in the case of `yarn`\), it goes out of its way to leave the _existing_ installation of `C` in place, while adding a _new_ version for you as a top-level consumer. So now you have two versions of `C` installed in your `node_modules` directory: `1.2.3` \(for `B`\) and `1.2.4` \(for `A`, your app or library\). 78 | 79 | What's important to understand here is that this is _exactly_ the behavior you want as the default in the Node ecosystem. Automatically updating a transitive dependency—even when the change is simply a bug fix release—_can_ cause your entire app or library to stop working. If one of your dependencies accidentally depended on that buggy behavior, and adding a direct dependency on the fixed version caused the buggy version to be upgraded, you're just out of luck. Yarn accounts for this by resolving packages to the same version during initial installation, but leaving existing package resolutions as they are when adding new dependencies later. 80 | 81 | Unfortunately, this is also the _opposite_ of what you want for TypeScript, which needs a single source of truth for the types in your app or library. When you install the type definitions, and then _later_ install a package which transitively depends on those type definitions, you end up with multiple sources of truth for the types. 82 | 83 | ## Understanding the Workarounds 84 | 85 | The solutions listed above both make sure npm apd Yarn only install a single version of the package. 86 | 87 | * Explicitly upgrading the dependencies or using `dedupe` resolves to a single version in npm. 88 | * Specifying a version in the `"resolutions"` field in your `package.json` simply forces Yarn to resolve _every_ reference to that package to a single version. This actually works extremely well for types, but it means that every time you either update the types package\(s\) yourself _or_ update a package which transitively depends on them, you have to edit this value manually as well. 89 | * Uninstalling and reinstalling both the impacted packages and _all_ the packages which transitively depend on them gives you the same behavior as an initial install… because that's exactly what you're doing. The downside, of course, is that you have to identify and uninstall and reinstall all top-level packages which transitively depend on the files, and this introduces risk by way of _other_ transitive dependencies being updated. 90 | 91 | -------------------------------------------------------------------------------- /docs/ts/README.md: -------------------------------------------------------------------------------- 1 | # TypeScript and Ember 2 | 3 | This guide covers the common details and "gotchas" of using TypeScript with Ember. Note that we do _not_ cover the use of TypeScript _or_ Ember in general—for those, you should refer to the corresponding documentation: 4 | 5 | * [TypeScript docs](https://www.typescriptlang.org/docs/index.html) 6 | * [TypeScript Deep Dive](https://basarat.gitbook.io/typescript/) 7 | * [Ember docs](https://emberjs.com/learn/) 8 | 9 | ## Outline 10 | 11 | * [Using TypeScript With Ember Effectively](using-ts-effectively.md) 12 | * [Decorators](decorators.md) 13 | * [Current limitations](current-limitations.md) 14 | * [Building Addons in TypeScript](with-addons.md) 15 | * [Understanding the `@types` Package Names](package-names.md) 16 | -------------------------------------------------------------------------------- /docs/ts/current-limitations.md: -------------------------------------------------------------------------------- 1 | # Current Limitations 2 | 3 | While TS already works nicely for many things in Ember, there are a number of corners where it _won't_ help you out. Some of them are just a matter of further work on updating the [existing typings](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/ember); others are a matter of further support landing in TypeScript itself, or changes to Ember's object model. 4 | 5 | ## Some `import`s don't resolve 6 | 7 | You'll frequently see errors for imports which TypeScript doesn't know how to resolve. **These won't stop the build from working;** they just mean TypeScript doesn't know where to find those. 8 | 9 | Writing these missing type definitions is a great way to pitch in! Jump in `#topic-typescript` on the [Ember Community Discord server](https://discord.gg/zT3asNS) and we'll be happy to help you. 10 | 11 | ## Templates 12 | 13 | Templates are currently totally non-type-checked. This means that you lose any safety when moving into a template context, even if using a Glimmer `Component` in Ember Octane. 14 | 15 | Addons need to import templates from the associated `.hbs` file to bind to the layout of any components they export. The TypeScript compiler will report that it cannot resolve the module, since it does not know how to resolve files ending in `.hbs`. To resolve this, you can provide this set of definitions to `my-addon/types/global.d.ts`, which will allow the import to succeed: 16 | 17 | ```ts 18 | declare module '\*/template' { 19 | import { TemplateFactory } from 'ember-cli-htmlbars'; 20 | const template: TemplateFactory; export default template; 21 | } 22 | 23 | 24 | declare module 'app/templates/\*' { 25 | import { TemplateFactory } from 'ember-cli-htmlbars'; 26 | const template: TemplateFactory; export default template; 27 | } 28 | 29 | declare module 'addon/templates/\*' { 30 | import { TemplateFactory } from 'ember-cli-htmlbars'; 31 | const template: TemplateFactory; export default template; 32 | } 33 | ``` 34 | 35 | ## Invoking actions 36 | 37 | TypeScript won't detect a mismatch between this action and the corresponding call in the template: 38 | 39 | ```ts 40 | import Component from '@ember/component'; 41 | import { action } from '@ember/object'; 42 | 43 | export default class MyGame extends Component { 44 | @action turnWheel(degrees: number) { 45 | // ... 46 | } 47 | } 48 | ``` 49 | 50 | ```hbs 51 | 54 | ``` 55 | 56 | Likewise, it won't notice a problem when you use the `send` method: 57 | 58 | ```ts 59 | // TypeScript compiler won't detect this type mismatch 60 | this.send\('turnWheel', 'ALSO-NOT-A-NUMBER'\); 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/ts/decorators.md: -------------------------------------------------------------------------------- 1 | # Decorators 2 | 3 | Ember makes heavy use of decorators, and TypeScript does not currently support deriving type information from decorators. 4 | 5 | As a result, there are three important points that apply to _all_ decorator usage in Ember: 6 | 7 | 1. Whenever using a decorator to declare a class field the framework sets up for you, you should mark it with `declare`. That includes all service and controller injections as well as all Ember Data attributes and relationships. 8 | 9 | Normally, TypeScript determines whether a property is definitely not `null` or `undefined` by checking what you do in the constructor. In the case of service injections, controller injections, or Ember Data model decorations, though, TypeScript does not have visibility into how instances of the class are _initialized_. The `declare` annotation informs TypeScript that a declaration is defined somewhere else, outside its scope. 10 | 11 | 2. For Ember Data Models, you will need to use the optional `?` operator on field declarations if the field is optional \(`?`\). See the Ember Data section of the guide for more details! 12 | 13 | 3. You are responsible to write the type correctly. TypeScript does not currently use decorator information at all in its type information. If you write `@service foo` or even `@service('foo') foo`, _Ember_ knows that this resolves at runtime to the service `Foo`, but TypeScript does not and—for now—_cannot_. 14 | 15 | This means that you are responsible to provide this type information, and that you are responsible to make sure that the information remains correct and up to date 16 | 17 | For examples, see the detailed discussions of the two main places decorators are used in the framework: 18 | 19 | * [Services](../ember/services.md) 20 | * [Ember Data Models](../ember-data/models.md) 21 | -------------------------------------------------------------------------------- /docs/ts/package-names.md: -------------------------------------------------------------------------------- 1 | # Understanding the `@types` Package Names 2 | 3 | You may be wondering why the packages added to your `package.json` and described in [**Installation: Other packages this addon installs**](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/README.md#other-packages-this-addon-installs) are named things like `@types/ember__object` instead of something like `@types/@ember/object`. This is a conventional name used to allow both the compiler and the [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) publishing infrastructure \([types-publisher](https://github.com/Microsoft/types-publisher)\) to handle scoped packages, documented under [**What about scoped packages?**](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master#what-about-scoped-packages) in [the DefinitelyTyped README](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master). 4 | 5 | See also: 6 | 7 | * [Microsoft/types-publisher\#155](https://github.com/Microsoft/types-publisher/issues/155) 8 | * [Microsoft/Typescript\#14819](https://github.com/Microsoft/TypeScript/issues/14819) 9 | 10 | -------------------------------------------------------------------------------- /docs/ts/with-addons.md: -------------------------------------------------------------------------------- 1 | # Building Addons in TypeScript 2 | 3 | Building addons in TypeScript offers many of the same benefits as building apps that way: it puts an extra tool at your disposal to help document your code and ensure its correctness. For addons, though, there's one additional bonus: publishing type information for your addons enables autocomplete and inline documentation for your consumers, even if they're not using TypeScript themselves. 4 | 5 | ## Key Differences from Apps 6 | 7 | To process `.ts` files, `ember-cli-typescript` tells Ember CLI to [register a set of Babel plugins](https://devblogs.microsoft.com/typescript/typescript-and-babel-7/) so that Babel knows how to strip away TypeScript-specific syntax. This means that `ember-cli-typescript` operates according to the same set of rules as other preprocessors when used by other addons. 8 | 9 | * Like other addons that preprocess source files, **`ember-cli-typescript` must be in your addon's `dependencies`, not `devDependencies`**. 10 | * Because addons have no control over how files in `app/` are transpiled, **you cannot have `.ts` files in your addon's `app/` folder**. 11 | 12 | ## Publishing 13 | 14 | When you publish an addon written in TypeScript, the `.ts` files will be consumed and transpiled by Babel as part of building the host application the same way `.js` files are, in order to meet the requirements of the application's `config/targets.js`. This means that no special steps are required for your source code to be consumed by users of your addon. 15 | 16 | Even though you publish the source `.ts` files, though, by default you consumers who also use TypeScript won't be able to benefit from those types, because the TS compiler isn't aware of how `ember-cli` resolves import paths for addon files. For instance, if you write `import { foo } from 'my-addon/bar';`, the typechecker has no way to know that the actual file on disk for that import path is at `my-addon/addon/bar.ts`. 17 | 18 | In order for your addon's users to benefit from type information from your addon, you need to put `.d.ts` _declaration files_ at the location on disk where the compiler expects to find them. This addon provides two commands to help with that: `ember ts:precompile` and `ember ts:clean`. The default `ember-cli-typescript` blueprint will configure your `package.json` to run these commands in the `prepack` and `postpack` phases respectively, but you can also run them by hand to verify that the output looks as you expect. 19 | 20 | The `ts:precompile` command will populate the overall structure of your package with `.d.ts` files laid out to match their import paths. For example, `addon/index.ts` would produce an `index.d.ts` file in the root of your package. 21 | 22 | The `ts:clean` command will remove the generated `.d.ts` files, leaving your working directory back in a pristine state. 23 | 24 | The TypeScript compiler has very particular rules when generating declaration files to avoid letting private types leak out unintentionally. You may find it useful to run `ember ts:precompile` yourself as you're getting a feel for these rules to ensure everything will go smoothly when you publish. 25 | 26 | ## Linking Addons 27 | 28 | Often when developing an addon, it can be useful to run that addon in the context of some other host app so you can make sure it will integrate the way you expect, e.g. using [`yarn link`](https://yarnpkg.com/en/docs/cli/link#search) or [`npm link`](https://docs.npmjs.com/cli/link). 29 | 30 | When you do this for a TypeScript addon, the source files will be picked up in the host app build and everything will execute at runtime as you'd expect. If the host app is also using TypeScript, though, it won't be able to resolve imports from your addon by default, for the reasons outlined above in the Publishing section. 31 | 32 | You could run `ember ts:precompile` in your addon any time you change a file, but for development a simpler option is to temporarily update the `paths` configuration in the host application so that it knows how to resolve types from your linked addon. 33 | 34 | Add entries for `` and `/*` in your `tsconfig.json` like so: 35 | 36 | ```javascript 37 | compilerOptions: { 38 | // ...other options 39 | paths: { 40 | // ...other paths, e.g. for your app/ and tests/ trees 41 | // resolve: import x from 'my-addon'; 42 | "my-addon": [ 43 | "node_modules/my-addon/addon" 44 | ], 45 | // resolve: import y from 'my-addon/utils/y'; 46 | "my-addon/*": [ 47 | "node_modules/my-addon/addon/*" 48 | ] 49 | } 50 | } 51 | ``` 52 | 53 | ## In-Repo Addons 54 | 55 | [In-repo addons](https://ember-cli.com/extending/#detailed-list-of-blueprints-and-their-use) work in much the same way as linked ones. Their `.ts` files are managed automatically by `ember-cli-typescript` in their `dependencies`, and you can ensure imports resolve correctly from the host by adding entries in `paths` in the base `tsconfig.json` file. 56 | 57 | ```javascript 58 | compilerOptions: { 59 | // ...other options 60 | paths: { 61 | // ...other paths, e.g. for your tests/ tree 62 | "my-app": [ 63 | "app/*", 64 | // add addon app directory that will be merged with the host application 65 | "lib/my-addon/app/*" 66 | ], 67 | // resolve: import x from 'my-addon'; 68 | "my-addon": [ 69 | "lib/my-addon/addon" 70 | ], 71 | // resolve: import y from 'my-addon/utils/y'; 72 | "my-addon/*": [ 73 | "lib/my-addon/addon/*" 74 | ] 75 | } 76 | } 77 | ``` 78 | 79 | One difference as compared to regular published addons: you know whether or not the host app is using `ember-cli-typescript`, and if it is, you can safely put `.ts` files in an in-repo addon's `app/` folder. 80 | 81 | -------------------------------------------------------------------------------- /docs/upgrade-notes.md: -------------------------------------------------------------------------------- 1 | # Upgrading from 1.x 2 | 3 | There are a number of important changes between ember-cli-typescript v1 and v2, which mean the upgrade process is _straightforward_ but _specific_: 4 | 5 | 1. Update ember-cli-babel. Fix any problems introduced during the upgrade. 6 | 2. Update ember-decorators. Fix any problems introduced during the upgrade. 7 | 3. Update ember-cli-typescript. Follow the detailed upgrade guide below to fix discrepancies between Babel and TypeScript's compiled output. 8 | 9 | If you deviate from this order, you are likely to have a _much_ more difficult time upgrading! 10 | 11 | ## Update ember-cli-babel 12 | 13 | ember-cli-typescript **requires** ember-cli-babel at version 7.1.0 or above, which requires ember-cli 2.13 or above. It also **requires** @babel/core 7.2.0 or higher. 14 | 15 | The recommended approach here is to deduplicate existing installations of the dependency, remove and reinstall ember-cli-babel to make sure that all its transitive dependencies are updated to the latest possible, and then to deduplicate _again_. 16 | 17 | If using yarn: 18 | 19 | ```bash 20 | npx yarn-deduplicate 21 | yarn remove ember-cli-babel 22 | yarn add --dev ember-cli-babel 23 | npx yarn-deduplicate 24 | ``` 25 | 26 | If using npm: 27 | 28 | ```bash 29 | npm dedupe 30 | npm uninstall ember-cli-babel 31 | npm install --save-dev ember-cli-babel 32 | npm dedupe 33 | ``` 34 | 35 | Note: If you are also using ember-decorators—and specifically the babel-transform that gets added with it—you will need update @ember-decorators/babel-transforms as well \(anything over 3.1.0 should work\): 36 | 37 | ```bash 38 | ember install ember-decorators@^3.1.0 @ember-decorators/babel-transforms@^3.1.0 39 | ``` 40 | 41 | ## Update ember-decorators 42 | 43 | If you're on a version of Ember before 3.10, follow the same process of deduplication, reinstallation, and re-deduplication as described for ember-cli-babel above for ember-decorators. This will get you the latest version of ember-decorators and, importantly, its @ember-decorators/babel-transforms dependency. 44 | 45 | ## Update ember-cli-typescript 46 | 47 | Now you can simply `ember install` the dependency like normal: 48 | 49 | ```bash 50 | ember install ember-cli-typescript@latest 51 | ``` 52 | 53 | _**Note:**_ **To work properly, starting from v2, ember-cli-typescript must be declared as a `dependency`, not a `devDependency` for addons. With `ember install` this migration would be automatically handled for you.** 54 | 55 | If you choose to make the upgrade manually with yarn or npm, here are the steps you need to follow: 56 | 57 | 1. Remove ember-cli-typescript from your `devDependencies`. 58 | 59 | With yarn: 60 | 61 | ```bash 62 | yarn remove ember-cli-typescript 63 | ``` 64 | 65 | With npm: 66 | 67 | ```bash 68 | npm uninstall ember-cli-typescript 69 | ``` 70 | 71 | 2. Install the latest of ember-cli-typescript as a `dependency`: 72 | 73 | With yarn: 74 | 75 | ```bash 76 | yarn add ember-cli-typescript@latest 77 | ``` 78 | 79 | With npm: 80 | 81 | ```bash 82 | npm install --save ember-cli-typescript@latest 83 | ``` 84 | 85 | 3. Run `ember generate`: 86 | 87 | ```bash 88 | ember generate ember-cli-typescript 89 | ``` 90 | 91 | ### Account for addon build pipeline changes 92 | 93 | Since we now integrate in a more traditional way into Ember CLI's build pipeline, there are two changes required for addons using TypeScript. 94 | 95 | * Addons can no longer use `.ts` in `app`, because an addon's `app` directory gets merged with and uses the _host's_ \(i.e. the other addon or app's\) preprocessors, and we cannot guarantee the host has TS support. Note that `.ts` will continue to work for in-repo addons because the app build works with the host's \(i.e. the app's, not the addon's\) preprocessors. 96 | * Similarly, apps must use `.js` to override addon defaults in `app`, since the different file extension means apps no longer consistently "win" over addon versions \(a limitation of how Babel + app merging interact\). 97 | 98 | ### Account for TS → Babel issues 99 | 100 | ember-cli-typescript v2 uses Babel to compile your code, and the TypeScript compiler only to _check_ your code. This makes for much faster builds, and eliminates the differences between Babel and TypeScript in the build output that could cause problems in v1. However, because of those differences, you’ll need to make a few changes in the process of upgrading. 101 | 102 | Any place where a type annotation overrides a _getter_ 103 | 104 | * Fields like `element`, `disabled`, etc. as annotated defined on a subclass of `Component` and \(correctly\) not initialized to anything, e.g.: 105 | 106 | ```typescript 107 | import Component from '@ember/component'; 108 | 109 | export default class Person extends Component { 110 | element!: HTMLImageElement; 111 | } 112 | ``` 113 | 114 | This breaks because `element` is a getter on `Component`. This declaration then shadows the getter declaration on the base class and stomps it to `undefined` \(effectively `Object.defineProperty(this, 'element', void 0)`. \(It would be nice to use `declare` here, but that doesn't work: you cannot use `declare` with a getter in a concrete subclass.\) 115 | 116 | Two solutions: 117 | 118 | 1. Annotate locally \(slightly more annoying, but less likely to troll you\): 119 | 120 | ```typescript 121 | class Image extends Component { 122 | useElement() { 123 | let element = this.element as HTMLImageElement; 124 | console.log(element.src); 125 | } 126 | } 127 | ``` 128 | 129 | 2. Use a local getter: 130 | 131 | ```typescript 132 | class Image extends Component { 133 | // We do this because... 134 | get _element(): HTMLImageElement { 135 | return this.element as HTMLImageElement; 136 | } 137 | 138 | useElement() { 139 | console.log(this._element.src); 140 | } 141 | } 142 | ``` 143 | 144 | Notably, this is not a problem for Glimmer components, so migrating to Octane will also help! 145 | 146 | * `const enum` is not supported at all. You will need to replace all uses of `const enum` with simply `enum` or constants. 147 | * Using ES5 getters or setters with `this` type annotations is not supported through at least Babel 7.3. However, they should also be unnecessary with ES6 classes, so you can simply _remove_ the `this` type annotation. 148 | * Trailing commas after rest function parameters \(`function foo(...bar[],) {}`\) are disallowed by the ECMAScript spec, so Babel also disallows them. 149 | * Re-exports of types have to be disambiguated to be _types_, rather than values. Neither of these will work: 150 | 151 | ```typescript 152 | export { FooType } from 'foo'; 153 | ``` 154 | 155 | ```typescript 156 | import { FooType } from 'foo'; 157 | export { FooType }; 158 | ``` 159 | 160 | In both cases, Babel attempts to emit a _value_ export, not just a _type_ export, and fails because there is no actual value to emit. You can do this instead as a workaround: 161 | 162 | ```typescript 163 | import * as Foo from 'foo'; 164 | export type FooType = Foo.FooType; 165 | ``` 166 | 167 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | 5 | module.exports = function (defaults) { 6 | let app = new EmberAddon(defaults, { 7 | 'ember-cli-babel': { 8 | throwUnlessParallelizable: true, 9 | }, 10 | babel: { 11 | sourceMaps: 'inline', 12 | }, 13 | }); 14 | 15 | /* 16 | This build file specifies the options for the dummy test app of this 17 | addon, located in `/tests/dummy` 18 | This build file does *not* influence how the addon or the app using it 19 | behave. You most likely want to be modifying `./index.js` or app's build file 20 | */ 21 | 22 | return app.toTree(); 23 | }; 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | 5 | // If transpiled output is present, always default to loading that first. 6 | // Otherwise, register ts-node if necessary and load from source. 7 | if (fs.existsSync(`${__dirname}/js/addon.js`)) { 8 | // eslint-disable-next-line node/no-missing-require 9 | module.exports = require('./js/addon').default; 10 | } else { 11 | require('./register-ts-node'); 12 | 13 | // eslint-disable-next-line node/no-unpublished-require 14 | module.exports = require('./ts/addon').default; 15 | } 16 | -------------------------------------------------------------------------------- /is_md_only.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | [[ -z $TRAVIS_COMMIT ]] && exit 1 6 | 7 | CHANGED_FILES=`git diff --name-only master...${TRAVIS_COMMIT}` 8 | 9 | [[ -z $CHANGED_FILES ]] && exit 1 10 | 11 | for CHANGED_FILE in $CHANGED_FILES; do 12 | if ! [[ $CHANGED_FILE =~ .md$ ]]; then 13 | exit 1 14 | fi 15 | done 16 | -------------------------------------------------------------------------------- /known-typings.md: -------------------------------------------------------------------------------- 1 | # Known Typings 2 | 3 | This is a list of all known typings specific to the Ember.js ecosystem. (You'll of 4 | course find many other modules with their own typings out there.) 5 | 6 | Don't see an addon you use listed here? You might be the one to write them! Check 7 | out [this blog post] or [this quest issue] for tips on how to do it, and feel free 8 | to ask for help in the `#topic-typescript` channel on the [Ember Community Discord server]. 9 | 10 | [this blog post]: http://www.chriskrycho.com/2017/typing-your-ember-part-5.html 11 | [this quest issue]: https://github.com/typed-ember/ember-typings/issues/14 12 | [Ember Community Discord server]: https://discord.gg/zT3asNS 13 | 14 | ## Integrated in the addon 15 | 16 | (Someday soon this list should get _long and awesome_! Help us make it happen!) 17 | 18 | * [ember-test-friendly-error-handler](https://github.com/rwjblue/ember-test-friendly-error-handler) 19 | * [True Myth](https://github.com/chriskrycho/true-myth) 20 | 21 | ## DefinitelyTyped (`@types`) 22 | 23 | For addons which do not have types shipped with the addon directly, you may be find them on DefinitelyTyped: 24 | 25 | * `@types/ember-data` 26 | * `@types/ember-mocha` 27 | * `@types/ember-qunit` 28 | * `@types/ember__test-helpers` 29 | * `@types/ember` 30 | * `@types/rsvp` 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-typescript", 3 | "version": "5.3.0", 4 | "description": "Allow Ember apps to use TypeScript files.", 5 | "keywords": [ 6 | "ember-addon", 7 | "typescript" 8 | ], 9 | "repository": "https://github.com/typed-ember/ember-cli-typescript.git", 10 | "license": "MIT", 11 | "author": "Chris Krycho (https://www.chriskrycho.com)", 12 | "directories": { 13 | "doc": "doc", 14 | "test": "tests" 15 | }, 16 | "contributors": [ 17 | "James C. Davis", 18 | "Dan Freeman", 19 | "Marius Seritan", 20 | "David Gardiner", 21 | "Philip Bjorge" 22 | ], 23 | "bugs": { 24 | "url": "https://github.com/typed-ember/ember-cli-typescript" 25 | }, 26 | "homepage": "https://github.com/typed-ember/ember-cli-typescript", 27 | "scripts": { 28 | "build": "ember build", 29 | "lint:js": "eslint --ext js,ts .", 30 | "start": "ember serve", 31 | "test": "yarn test:app && yarn test:node", 32 | "test:app": "ember test", 33 | "test:node": "mocha --loader=ts-node/esm --extension ts ts/tests/**/*.ts", 34 | "ci:prepare": "yarn prepublishOnly && rimraf ts", 35 | "ci:log-version-info": "echo '---- Ember CLI ----' && ember -v && echo '---- TypeScript ----' && tsc -v", 36 | "ci:test": "yarn ci:log-version-info && yarn ci:test:app && yarn ci:test:node", 37 | "ci:test:app": "ember test", 38 | "ci:test:node": "mocha --recursive js/tests", 39 | "prepublishOnly": "yarn tsc --noEmit false --project ts", 40 | "postpublish": "rimraf js" 41 | }, 42 | "dependencies": { 43 | "ansi-to-html": "^0.6.15", 44 | "broccoli-stew": "^3.0.0", 45 | "debug": "^4.0.0", 46 | "execa": "^4.0.0", 47 | "fs-extra": "^9.0.1", 48 | "resolve": "^1.5.0", 49 | "rsvp": "^4.8.1", 50 | "semver": "^7.3.2", 51 | "stagehand": "^1.0.0", 52 | "walk-sync": "^2.2.0" 53 | }, 54 | "devDependencies": { 55 | "@ember/optional-features": "2.0.0", 56 | "@glimmer/component": "^1.1.2", 57 | "@glimmer/tracking": "^1.1.2", 58 | "@release-it-plugins/lerna-changelog": "^6.1.0", 59 | "@tsconfig/ember": "^1.0.0", 60 | "@typed-ember/renovate-config": "1.2.1", 61 | "@types/capture-console": "1.0.1", 62 | "@types/chai": "4.3.0", 63 | "@types/chai-as-promised": "7.1.4", 64 | "@types/console-ui": "2.2.6", 65 | "@types/core-object": "3.0.1", 66 | "@types/debug": "4.1.7", 67 | "@types/ember": "4.0.1", 68 | "@types/ember-qunit": "5.0.0", 69 | "@types/esprima": "4.0.3", 70 | "@types/express": "4.17.13", 71 | "@types/fs-extra": "9.0.13", 72 | "@types/got": "9.6.12", 73 | "@types/mocha": "10.0.0", 74 | "@types/node": "14.14.31", 75 | "@types/qunit": "2.19.3", 76 | "@types/resolve": "1.20.1", 77 | "@types/semver": "7.3.9", 78 | "@typescript-eslint/eslint-plugin": "5.10.1", 79 | "@typescript-eslint/parser": "5.10.1", 80 | "broccoli-asset-rev": "3.0.0", 81 | "broccoli-node-api": "1.7.0", 82 | "broccoli-plugin": "4.0.3", 83 | "capture-console": "1.0.1", 84 | "co": "4.6.0", 85 | "ember-cli": "^4.8.0", 86 | "ember-cli-app-version": "4.0.0", 87 | "ember-cli-babel": "7.23.0", 88 | "ember-cli-blueprint-test-helpers": "0.19.2", 89 | "ember-cli-dependency-checker": "3.2.0", 90 | "ember-cli-htmlbars": "5.3.1", 91 | "ember-cli-inject-live-reload": "2.0.2", 92 | "ember-cli-sri": "2.1.1", 93 | "ember-cli-uglify": "3.0.0", 94 | "ember-cli-update": "0.54.6", 95 | "ember-disable-prototype-extensions": "1.1.3", 96 | "ember-export-application-global": "2.0.1", 97 | "ember-load-initializers": "2.1.2", 98 | "ember-maybe-import-regenerator": "0.1.6", 99 | "ember-qunit": "4.6.0", 100 | "ember-resolver": "8.0.2", 101 | "ember-source": "~3.28.0", 102 | "ember-try": "1.4.0", 103 | "eslint": "8.7.0", 104 | "eslint-config-prettier": "8.3.0", 105 | "eslint-plugin-ember": "10.5.8", 106 | "eslint-plugin-node": "11.1.0", 107 | "eslint-plugin-prettier": "4.0.0", 108 | "esprima": "4.0.1", 109 | "fixturify": "^2.1.1", 110 | "got": "12.5.2", 111 | "handlebars": "4.7.7", 112 | "in-repo-a": "link:tests/dummy/lib/in-repo-a", 113 | "in-repo-b": "link:tests/dummy/lib/in-repo-b", 114 | "loader.js": "4.7.0", 115 | "mocha": "10.1.0", 116 | "prettier": "2.5.1", 117 | "prettier-eslint": "13.0.0", 118 | "qunit-dom": "1.6.0", 119 | "release-it": "^17.1.1", 120 | "rimraf": "3.0.2", 121 | "testdouble": "3.16.1", 122 | "ts-node": "^10.9.1", 123 | "typescript": "4.8" 124 | }, 125 | "resolutions": { 126 | "hawk": "7", 127 | "ember-cli-typescript": "link:." 128 | }, 129 | "engines": { 130 | "node": ">= 12.*" 131 | }, 132 | "ember-addon": { 133 | "configPath": "tests/dummy/config", 134 | "before": [ 135 | "broccoli-watcher" 136 | ] 137 | }, 138 | "prettier": { 139 | "printWidth": 100, 140 | "semi": true, 141 | "singleQuote": true, 142 | "trailingComma": "es5", 143 | "tabWidth": 2, 144 | "proseWrap": "never" 145 | }, 146 | "changelog": { 147 | "labels": { 148 | "BREAKING": "Changed 💥", 149 | "enhancement": "Added ⭐️", 150 | "bug": "Fixed 🔧", 151 | "docs": "Documentation 📖", 152 | "internal": "Under the hood 🚗" 153 | } 154 | }, 155 | "release-it": { 156 | "git": { 157 | "tagName": "v${version}" 158 | }, 159 | "github": { 160 | "release": true 161 | }, 162 | "plugins": { 163 | "@release-it-plugins/lerna-changelog": { 164 | "infile": "CHANGELOG.md", 165 | "launchEditor": true 166 | } 167 | } 168 | }, 169 | "volta": { 170 | "node": "18.18.1", 171 | "yarn": "1.22.19" 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /register-ts-node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // eslint-disable-next-line node/no-deprecated-api 4 | if (!require.extensions['.ts']) { 5 | let options = { project: `${__dirname}/ts/tsconfig.json` }; 6 | 7 | // If we're operating in the context of another project, which might happen 8 | // if someone has installed ember-cli-typescript from git, only perform 9 | // transpilation. In this case, we also overwrite the default ignore glob 10 | // (which ignores everything in `node_modules`) to instead ignore anything 11 | // that doesn't end with `.ts`. 12 | if (process.cwd() !== __dirname) { 13 | options.ignore = [/\.(?!ts$)\w+$/]; 14 | options.transpileOnly = true; 15 | } 16 | 17 | // eslint-disable-next-line node/no-unpublished-require 18 | require('ts-node').register(options); 19 | } 20 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@typed-ember/renovate-config" 3 | } 4 | -------------------------------------------------------------------------------- /rfcs/0000-template.md: -------------------------------------------------------------------------------- 1 | - Start Date: (fill me in with today's date, YYYY-MM-DD) 2 | - Relevant Team(s): (fill this in with the [team(s)](README.md#relevant-teams) to which this RFC applies) 3 | - RFC PR: (after opening the RFC PR, update this with a link to it and update the file name) 4 | - Tracking: (leave this empty) 5 | 6 | # 7 | 8 | ## Summary 9 | 10 | > One paragraph explanation of the feature. 11 | 12 | ## Motivation 13 | 14 | > Why are we doing this? What use cases does it support? What is the expected 15 | outcome? 16 | 17 | ## Detailed design 18 | 19 | > This is the bulk of the RFC. 20 | 21 | > Explain the design in enough detail for somebody 22 | familiar with the framework to understand, and for somebody familiar with the 23 | implementation to implement. This should get into specifics and corner-cases, 24 | and include examples of how the feature is used. Any new terminology should be 25 | defined here. 26 | 27 | ## How we teach this 28 | 29 | > What names and terminology work best for these concepts and why? How is this 30 | idea best presented? As a continuation of existing Ember patterns, or as a 31 | wholly new one? 32 | 33 | > Would the acceptance of this proposal mean the Ember guides must be 34 | re-organized or altered? Does it change how Ember is taught to new users 35 | at any level? 36 | 37 | > How should this feature be introduced and taught to existing Ember 38 | users? 39 | 40 | ## Drawbacks 41 | 42 | > Why should we *not* do this? Please consider the impact on teaching Ember, 43 | on the integration of this feature with other existing and planned features, 44 | on the impact of the API churn on existing apps, etc. 45 | 46 | > There are tradeoffs to choosing any path, please attempt to identify them here. 47 | 48 | ## Alternatives 49 | 50 | > What other designs have been considered? What is the impact of not doing this? 51 | 52 | > This section could also include prior art, that is, how other frameworks in the same domain have solved this problem. 53 | 54 | ## Unresolved questions 55 | 56 | > Optional, but suggested for first drafts. What parts of the design are still 57 | TBD? 58 | -------------------------------------------------------------------------------- /rfcs/README.md: -------------------------------------------------------------------------------- 1 | # Typed Ember RFCs 2 | 3 | (This document is intentionally effectively identical with the README for 4 | [emberjs/rfcs](https://github.com/emberjs/rfcs/).) 5 | 6 | Many changes, including bug fixes and documentation improvements can be 7 | implemented and reviewed via the normal GitHub pull request workflow. 8 | 9 | Some changes though are "substantial", and we ask that these be put 10 | through a bit of a design process and produce a consensus among the Ember 11 | core teams. 12 | 13 | The "RFC" (request for comments) process is intended to provide a 14 | consistent and controlled path for new features to enter the framework. 15 | 16 | [Active RFC List](https://github.com/emberjs/rfcs/pulls) 17 | 18 | ## When you need to follow this process 19 | 20 | You need to follow this process if you intend to make "substantial" 21 | changes to Ember, Ember Data, Ember CLI, their documentation, or any other 22 | projects under the purview of the [Ember core teams](https://emberjs.com/team/). 23 | What constitutes a "substantial" change is evolving based on community norms, 24 | but may include the following: 25 | 26 | - A new feature that creates new API surface area, and would 27 | require a [feature flag] if introduced. 28 | - The removal of features that already shipped as part of the release 29 | channel. 30 | - The introduction of new idiomatic usage or conventions, even if they 31 | do not include code changes to Ember itself. 32 | 33 | Some changes do not require an RFC: 34 | 35 | - Rephrasing, reorganizing or refactoring 36 | - Addition or removal of warnings 37 | - Additions that strictly improve objective, numerical quality 38 | criteria (speedup, better browser support) 39 | - Additions only likely to be _noticed by_ other implementors-of-Ember, 40 | invisible to users-of-Ember. 41 | 42 | If you submit a pull request to implement a new feature without going 43 | through the RFC process, it may be closed with a polite request to 44 | submit an RFC first. 45 | 46 | ## Gathering feedback before submitting 47 | 48 | It's often helpful to get feedback on your concept before diving into the 49 | level of API design detail required for an RFC. **You may open an 50 | issue on this repo to start a high-level discussion**, with the goal of 51 | eventually formulating an RFC pull request with the specific implementation 52 | design. We also highly recommend sharing drafts of RFCs in `#dev-rfc` on 53 | the [Ember Discord](https://discord.gg/emberjs) for early feedback. 54 | 55 | ## The process 56 | 57 | In short, to get a major feature added to Ember, one must first get the 58 | RFC merged into the RFC repo as a markdown file. At that point the RFC 59 | is 'active' and may be implemented with the goal of eventual inclusion 60 | into Ember. 61 | 62 | * Fork the RFC repo http://github.com/emberjs/rfcs 63 | * Copy the appropriate template. For most RFCs, this is `0000-template.md`, 64 | for deprecation RFCs it is `deprecation-template.md`. 65 | Copy the template file to `text/0000-my-feature.md`, where 66 | 'my-feature' is descriptive. Don't assign an RFC number yet. 67 | * Fill in the RFC. Put care into the details: **RFCs that do not 68 | present convincing motivation, demonstrate understanding of the 69 | impact of the design, or are disingenuous about the drawbacks or 70 | alternatives tend to be poorly-received**. 71 | * Submit a pull request. As a pull request the RFC will receive design 72 | feedback from the larger community, and the author should be prepared 73 | to revise it in response. 74 | * Update the pull request to add the number of the PR to the filename and 75 | add a link to the PR in the header of the RFC. 76 | * Build consensus and integrate feedback. RFCs that have broad support 77 | are much more likely to make progress than those that don't receive any 78 | comments. 79 | * Eventually, the Typed Ember team will decide whether the RFC is a candidate 80 | for inclusion by ember-cli-typescript. 81 | * RFCs that are candidates for inclusion will enter a "final comment period" 82 | lasting 7 days. The beginning of this period will be signaled with a comment 83 | and tag on the RFC's pull request. Furthermore, the Typed Ember team will post 84 | in Discord in `#topic-typescript` and `#news-and-announcements`. 85 | * An RFC can be modified based upon feedback from the Typed Ember team and 86 | the community. Significant modifications may trigger a new final comment period. 87 | * An RFC may be rejected by the team after public discussion has settled and 88 | comments have been made summarizing the rationale for rejection. The RFC will 89 | enter a "final comment period to close" lasting 7 days. At the end of the 90 | "FCP to close" period, the PR will be closed. 91 | * An RFC may also be closed by the core teams if it is superseded by a merged 92 | RFC. In this case, a link to the new RFC should be added in a comment. 93 | * An RFC author may withdraw their own RFC by closing it themselves. 94 | * An RFC may be accepted at the close of its final comment period. A [core team] 95 | member will merge the RFC's associated pull request, at which point the RFC will 96 | become 'active'. 97 | 98 | ## The RFC life-cycle 99 | 100 | Once an RFC becomes active the relevant teams will plan the feature and create 101 | issues in the relevant repositories. 102 | Becoming 'active' is not a rubber stamp, and in particular still does not mean 103 | the feature will ultimately be merged; it does mean that the core team has agreed 104 | to it in principle and are amenable to merging it. 105 | 106 | Furthermore, the fact that a given RFC has been accepted and is 107 | 'active' implies nothing about what priority is assigned to its 108 | implementation, nor whether anybody is currently working on it. 109 | 110 | Modifications to active RFC's can be done in followup PR's. We strive 111 | to write each RFC in a manner that it will reflect the final design of 112 | the feature; but the nature of the process means that we cannot expect 113 | every merged RFC to actually reflect what the end result will be at 114 | the time of the next major release; therefore we try to keep each RFC 115 | document somewhat in sync with the feature as planned, 116 | tracking such changes via followup pull requests to the document. 117 | 118 | ## Implementing an RFC 119 | 120 | The author of an RFC is not obligated to implement it. Of course, the 121 | RFC author (like any other developer) is welcome to post an 122 | implementation for review after the RFC has been accepted. 123 | 124 | If you are interested in working on the implementation for an 'active' 125 | RFC, but cannot determine if someone else is already working on it, 126 | feel free to ask (e.g. by leaving a comment on the associated issue). 127 | 128 | ## For Core Team Members 129 | 130 | ### Reviewing RFCs 131 | 132 | Each core team is responsible for reviewing open RFCs. The team must ensure 133 | that if an RFC is relevant to their team's responsibilities the team is 134 | correctly specified in the 'Relevant Team(s)' section of the RFC front-matter. 135 | The team must also ensure that each RFC addresses any consequences, changes, or 136 | work required in the team's area of responsibility. 137 | 138 | As it is with the wider community, the RFC process is the time for 139 | teams and team members to push back on, encourage, refine, or otherwise comment 140 | on proposals. 141 | 142 | ### Referencing RFCs 143 | 144 | - When mentioning RFCs that have been merged, link to the merged version, 145 | not to the pull-request. 146 | -------------------------------------------------------------------------------- /test-fixtures/skeleton-app/app/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/test-fixtures/skeleton-app/app/index.html -------------------------------------------------------------------------------- /test-fixtures/skeleton-app/app/styles/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/test-fixtures/skeleton-app/app/styles/.gitkeep -------------------------------------------------------------------------------- /test-fixtures/skeleton-app/config/environment.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = function(environment) { 5 | return { 6 | environment, 7 | modulePrefix: 'skeleton-app', 8 | rootURL: '/' 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /test-fixtures/skeleton-app/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | module.exports = function(defaults) { 6 | return new EmberApp(defaults, {}).toTree(); 7 | }; 8 | -------------------------------------------------------------------------------- /test-fixtures/skeleton-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "skeleton-app", 3 | "devDependencies": { 4 | "ember-cli": "*", 5 | "ember-cli-htmlbars": "*", 6 | "ember-cli-babel": "*", 7 | "ember-source": "*", 8 | "ember-qunit": "*", 9 | "loader.js": "*", 10 | "typescript": "*" 11 | }, 12 | "ember-addon": { 13 | "paths": [".."] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-fixtures/skeleton-app/testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900' 20 | ].filter(Boolean) 21 | } 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /test-fixtures/skeleton-app/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /test-fixtures/skeleton-app/tests/test-helper.ts: -------------------------------------------------------------------------------- 1 | import { start } from 'ember-qunit'; 2 | 3 | start(); 4 | -------------------------------------------------------------------------------- /test-fixtures/skeleton-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "allowJs": false, 5 | "module": "Node16", 6 | "moduleResolution": "Node16", 7 | "noEmitOnError": true, 8 | "baseUrl": ".", 9 | "paths": { 10 | "skeleton-app/*": [ 11 | "app/*" 12 | ] 13 | }, 14 | // We *really* don't want this on, but our hand is forced somewhat at the 15 | // moment: the types for `console-ui` are correct for consuming `inquirer` 16 | // in the pre-Node16 world, but don't interoperate correctly when consumed 17 | // by a CJS-default package in the TS Node16+ world *and* consume an ESM 18 | // package. Our path forward there is to update `console-ui` to publish a 19 | // dual-mode package with types and runtime code to support it. 20 | // 21 | // NOTE TO READERS: this is *only* required because of a very specific bit 22 | // of weird wiring in our test harness; it will *not* affect normal apps. 23 | "skipLibCheck": true 24 | }, 25 | "include": [ 26 | "app" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test_page: 'tests/index.html?hidepassed', 3 | disable_watching: true, 4 | reporter: 'xunit', 5 | report_file: 'ember-tests.xml', 6 | xunit_exclude_stack: true, // we *probably* want this on to keep the xunit file clean 7 | xunit_intermediate_output: true, 8 | launch_in_ci: ['Chrome', 'Firefox'], 9 | launch_in_dev: ['Chrome'], 10 | browser_start_timeout: 60000, 11 | browser_args: { 12 | Firefox: { ci: ['-headless', '--window-size=1440,900'] }, 13 | Chrome: { 14 | mode: 'ci', 15 | // prettier-disable -- args are useful to have in a sane order 16 | args: [ 17 | '--disable-gpu', 18 | '--headless', 19 | '--remote-debugging-port=0', 20 | '--window-size=1440,900', 21 | ].concat( 22 | /* 23 | --no-sandbox is needed when running Chrome inside a container. 24 | See https://github.com/ember-cli/ember-cli-chai/pull/45/files. 25 | */ 26 | (process.env.TRAVIS || process.env.CI) ? '--no-sandbox' : [] 27 | ), 28 | }, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "define", 10 | "console", 11 | "visit", 12 | "exists", 13 | "fillIn", 14 | "click", 15 | "keyEvent", 16 | "triggerEvent", 17 | "find", 18 | "findWithAssert", 19 | "wait", 20 | "DS", 21 | "andThen", 22 | "currentURL", 23 | "currentPath", 24 | "currentRouteName" 25 | ], 26 | "node": false, 27 | "browser": false, 28 | "boss": true, 29 | "curly": true, 30 | "debug": false, 31 | "devel": false, 32 | "eqeqeq": true, 33 | "evil": true, 34 | "forin": false, 35 | "immed": false, 36 | "laxbreak": false, 37 | "newcap": true, 38 | "noarg": true, 39 | "noempty": false, 40 | "nonew": false, 41 | "nomen": false, 42 | "onevar": false, 43 | "plusplus": false, 44 | "regexp": false, 45 | "undef": true, 46 | "sub": true, 47 | "strict": false, 48 | "white": false, 49 | "eqnull": true, 50 | "esversion": 6, 51 | "unused": true 52 | } 53 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/components/js-importing-ts.hbs: -------------------------------------------------------------------------------- 1 |

2 |

js-importing-ts.hbs

3 | {{this.poke}} 4 |

Ts helper: {{typed-help}}

5 |

Js helper: {{js-help}}

6 |
7 | -------------------------------------------------------------------------------- /tests/dummy/app/components/js-importing-ts.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | import * as constants from '../lib/some-const'; 4 | 5 | export default class JsImportingTs extends Component { 6 | get poke() { 7 | return constants.CHANGE === 'ha'; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/dummy/app/components/test-one.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | export default class TestOne extends Component { 4 | someValue = 'from component'; 5 | } 6 | -------------------------------------------------------------------------------- /tests/dummy/app/components/ts-component.hbs: -------------------------------------------------------------------------------- 1 |
2 |

ts-component.hbs

3 | {{#if this.someValue}} 4 | Component defines someValue property as: {{this.someValue}} 5 | {{else}} 6 | Missing someValue property, expected to be defined by the component. 7 | {{/if}} 8 | 9 |

Ts helper: {{typed-help}}

10 |

Js helper: {{js-help}}

11 |
12 | -------------------------------------------------------------------------------- /tests/dummy/app/components/ts-component.ts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | function compute(): { value: string } { 4 | return { value: 'from component' }; 5 | } 6 | 7 | export default class TsComponent extends Component { 8 | someValue = compute().value; 9 | } 10 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/application.ts: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class ApplicationController extends Controller { 4 | // Just a very roundabout way of using some ES6 features 5 | value = ((test = 'Test') => `${test} ${'Value'}`)(); 6 | foo = 'hello'; 7 | } 8 | -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/helpers/js-help.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function jsHelp(/*params, hash*/) { 4 | return 'js please help me'; 5 | } 6 | 7 | export default helper(jsHelp); 8 | -------------------------------------------------------------------------------- /tests/dummy/app/helpers/typed-help.ts: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function typedHelp(/*params, hash*/) { 4 | return 'my type of help'; 5 | } 6 | 7 | export default helper(typedHelp); 8 | -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ember CLI TypeScript 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/dummy/app/lib/generate-avatar.ts: -------------------------------------------------------------------------------- 1 | /** This exists just to support example code. */ 2 | export function generateUrl(): string { 3 | return ''; 4 | } 5 | -------------------------------------------------------------------------------- /tests/dummy/app/lib/some-const.ts: -------------------------------------------------------------------------------- 1 | export const SPEED_OF_LIGHT = 299792458; 2 | export const CHANGE = true; 3 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from './config/environment'; 3 | 4 | class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () {}); 10 | 11 | export default Router; 12 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --brand-primary: rgb(41, 78, 128); 3 | } 4 | -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | let ENV = { 5 | modulePrefix: 'dummy', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | }, 14 | EXTEND_PROTOTYPES: { 15 | // Prevent Ember Data from overriding Date.parse. 16 | Date: false, 17 | }, 18 | }, 19 | 20 | APP: { 21 | // Here you can pass flags/options to your application instance 22 | // when it is created 23 | }, 24 | }; 25 | 26 | if (environment === 'development') { 27 | // ENV.APP.LOG_RESOLVER = true; 28 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 29 | // ENV.APP.LOG_TRANSITIONS = true; 30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 31 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 32 | } 33 | 34 | if (environment === 'test') { 35 | // Testem prefers this... 36 | ENV.locationType = 'none'; 37 | 38 | // keep test console output quieter 39 | ENV.APP.LOG_ACTIVE_GENERATION = false; 40 | ENV.APP.LOG_VIEW_LOOKUPS = false; 41 | 42 | ENV.APP.rootElement = '#ember-testing'; 43 | ENV.APP.autoboot = false; 44 | } 45 | 46 | if (environment === 'production') { 47 | // here you can enable a production-specific feature 48 | } 49 | 50 | return ENV; 51 | }; 52 | -------------------------------------------------------------------------------- /tests/dummy/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "jquery-integration": false 3 | } 4 | -------------------------------------------------------------------------------- /tests/dummy/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions']; 4 | 5 | const isCI = !!process.env.CI; 6 | const isProduction = process.env.EMBER_ENV === 'production'; 7 | 8 | if (isCI || isProduction) { 9 | browsers.push('ie 11'); 10 | } 11 | 12 | module.exports = { 13 | browsers, 14 | }; 15 | -------------------------------------------------------------------------------- /tests/dummy/lib/in-repo-a/addon-test-support/from-ats.ts: -------------------------------------------------------------------------------- 1 | export const description = 'From addon-test-support'; 2 | -------------------------------------------------------------------------------- /tests/dummy/lib/in-repo-a/addon/test-file.ts: -------------------------------------------------------------------------------- 1 | // This should wind up in the addon tree 2 | const value: string = 'in-repo-a/test-file'; 3 | 4 | export default value; 5 | -------------------------------------------------------------------------------- /tests/dummy/lib/in-repo-a/app/a.ts: -------------------------------------------------------------------------------- 1 | // This should wind up in the app tree 2 | const value: string = 'dummy/a'; 3 | 4 | export default value; 5 | -------------------------------------------------------------------------------- /tests/dummy/lib/in-repo-a/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | name: 'in-repo-a', 6 | 7 | isDevelopingAddon() { 8 | return true; 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /tests/dummy/lib/in-repo-a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "in-repo-a", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "ember-addon" 6 | ], 7 | "ember-addon": { 8 | "paths": [ 9 | "../../../..", 10 | "../../../../node_modules/ember-cli-babel" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/dummy/lib/in-repo-a/test-support/from-ts.ts: -------------------------------------------------------------------------------- 1 | export const description = 'From test-support'; 2 | -------------------------------------------------------------------------------- /tests/dummy/lib/in-repo-b/addon/test-file.ts: -------------------------------------------------------------------------------- 1 | // This should wind up in the addon tree 2 | const value: string = 'in-repo-b/test-file'; 3 | 4 | export default value; 5 | -------------------------------------------------------------------------------- /tests/dummy/lib/in-repo-b/app/b.ts: -------------------------------------------------------------------------------- 1 | // This should wind up in the app tree 2 | const value: string = 'dummy/b'; 3 | 4 | export default value; 5 | -------------------------------------------------------------------------------- /tests/dummy/lib/in-repo-b/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | name: 'in-repo-b', 6 | 7 | isDevelopingAddon() { 8 | return true; 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /tests/dummy/lib/in-repo-b/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "in-repo-b", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "ember-addon" 6 | ], 7 | "ember-addon": { 8 | "paths": [ 9 | "../../../..", 10 | "../../../../node_modules/ember-cli-babel" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/dummy/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/dummy/type-utils.ts: -------------------------------------------------------------------------------- 1 | export function is(val: unknown, predicate: boolean): val is T { 2 | return predicate; 3 | } 4 | -------------------------------------------------------------------------------- /tests/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/tests/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/tests/integration/.gitkeep -------------------------------------------------------------------------------- /tests/integration/components/dummy.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Need at least one ts file to avoid error 3 | "No inputs were found in config file 'tsconfig.json'." 4 | */ 5 | -------------------------------------------------------------------------------- /tests/integration/components/js-importing-ts-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | 6 | module('Integration | Component | js importing ts', function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it renders', async function (assert) { 10 | // Set any properties with this.set('myProperty', 'value'); 11 | // Handle any actions with this.on('myAction', function(val) { ... }); 12 | 13 | await render(hbs`{{js-importing-ts}}`); 14 | 15 | assert.equal( 16 | this.element.textContent.replace(/\s+/g, ' ').trim(), 17 | 'js-importing-ts.hbs false Ts helper: my type of help Js helper: js please help me' 18 | ); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/integration/components/ts-component-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | 6 | module('Integration | Component | ts component', function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it renders', async function (assert) { 10 | // Set any properties with this.set('myProperty', 'value'); 11 | // Handle any actions with this.on('myAction', function(val) { ... }); 12 | 13 | await render(hbs`{{ts-component}}`); 14 | 15 | assert.equal( 16 | this.element.textContent.replace(/\s+/g, ' ').trim(), 17 | 'ts-component.hbs Component defines someValue property as: from component Ts helper: my type of help Js helper: js please help me' 18 | ); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from '../app'; 2 | import config from '../config/environment'; 3 | import { setApplication } from '@ember/test-helpers'; 4 | import { start } from 'ember-qunit'; 5 | 6 | setApplication(Application.create(config.APP)); 7 | 8 | start(); 9 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/build-test.ts: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | 3 | import addonFileA from 'in-repo-a/test-file'; 4 | import addonFileB from 'in-repo-b/test-file'; 5 | import fileA from 'dummy/a'; 6 | import fileB from 'dummy/b'; 7 | import { description as fromAts } from 'in-repo-a/test-support/from-ats'; 8 | import { description as fromTs } from 'dummy/tests/from-ts'; 9 | 10 | module('Unit | Build', function () { 11 | test("in-repo addons' addon trees wind up in the right place", function (assert) { 12 | assert.equal(addonFileA, 'in-repo-a/test-file'); 13 | assert.equal(addonFileB, 'in-repo-b/test-file'); 14 | }); 15 | 16 | test("in-repo addons' app trees wind up in the right place", function (assert) { 17 | assert.equal(fileA, 'dummy/a'); 18 | assert.equal(fileB, 'dummy/b'); 19 | }); 20 | 21 | test("addon's addon-test-support files end up in /test-support/*", function (assert) { 22 | assert.ok(fromAts); 23 | assert.equal(fromAts, 'From addon-test-support'); 24 | }); 25 | 26 | test("addon's test-support files end up in dummy/tests/*", function (assert) { 27 | assert.ok(fromTs); 28 | assert.equal(fromTs, 'From test-support'); 29 | }); 30 | 31 | test('optional chaining and nullish coalescing are transpiled correctly', function (assert) { 32 | let value = { a: 'hello' } as { a?: string; b?: string }; 33 | assert.equal(value?.a, 'hello'); 34 | assert.equal(value?.b, undefined); 35 | assert.equal(value?.a ?? 'ok', 'hello'); 36 | assert.equal(value?.b ?? 'ok', 'ok'); 37 | }); 38 | 39 | test('class field declarations work', function (assert) { 40 | class MyClass { 41 | declare foo: string; 42 | } 43 | 44 | assert.notOk('foo' in new MyClass()); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /tests/unit/helpers/js-help-test.js: -------------------------------------------------------------------------------- 1 | import { jsHelp } from 'dummy/helpers/js-help'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | js help', function () { 5 | // Replace this with your real tests. 6 | test('it works', function (assert) { 7 | let result = jsHelp([42]); 8 | assert.ok(result); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /tests/unit/helpers/typed-help-test.js: -------------------------------------------------------------------------------- 1 | import { typedHelp } from 'dummy/helpers/typed-help'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Helper | typed help', function () { 5 | // Replace this with your real tests. 6 | test('it works', function (assert) { 7 | let result = typedHelp([42]); 8 | assert.ok(result); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /ts/addon.ts: -------------------------------------------------------------------------------- 1 | import semver from 'semver'; 2 | import { Remote } from 'stagehand'; 3 | import { connect } from 'stagehand/lib/adapters/child-process'; 4 | import Addon from 'ember-cli/lib/models/addon'; 5 | import PreprocessRegistry from 'ember-cli-preprocess-registry'; 6 | import { addon } from './lib/utilities/ember-cli-entities'; 7 | import fork from './lib/utilities/fork'; 8 | import TypecheckWorker from './lib/typechecking/worker'; 9 | import TypecheckMiddleware from './lib/typechecking/middleware'; 10 | import { Application } from 'express'; 11 | import walkSync from 'walk-sync'; 12 | import fs from 'fs-extra'; 13 | import logger from 'debug'; 14 | 15 | const debug = logger('ember-cli-typescript:addon'); 16 | 17 | export const ADDON_NAME = 'ember-cli-typescript'; 18 | 19 | export default addon({ 20 | name: ADDON_NAME, 21 | 22 | included() { 23 | this._super.included.apply(this, arguments); 24 | 25 | this._checkDevelopment(); 26 | this._checkAddonAppFiles(); 27 | this._checkBabelVersion(); 28 | 29 | // If we're a direct dependency of the host app, go ahead and start up the 30 | // typecheck worker so we don't wait until the end of the build to check 31 | if (this.parent === this.project) { 32 | this._getTypecheckWorker(); 33 | this._checkInstallationLocation(); 34 | this._checkEmberCLIVersion(); 35 | } 36 | }, 37 | 38 | includedCommands() { 39 | if (this.project.isEmberCLIAddon()) { 40 | return { 41 | 'ts:precompile': require('./lib/commands/precompile').default, 42 | 'ts:clean': require('./lib/commands/clean').default, 43 | }; 44 | } 45 | }, 46 | 47 | blueprintsPath() { 48 | return `${__dirname}/blueprints`; 49 | }, 50 | 51 | serverMiddleware({ app, options }) { 52 | if (!options || !options.path) { 53 | debug('Installing typecheck server middleware'); 54 | this._addTypecheckMiddleware(app); 55 | } else { 56 | debug('Skipping typecheck server middleware'); 57 | } 58 | }, 59 | 60 | testemMiddleware(app, options) { 61 | if (!options || !options.path) { 62 | debug('Installing typecheck testem middleware'); 63 | this._addTypecheckMiddleware(app); 64 | } else { 65 | debug('Skipping typecheck testem middleware'); 66 | } 67 | }, 68 | 69 | async postBuild() { 70 | // This code makes the fundamental assumption that the TS compiler's fs watcher 71 | // will notice a file change before the full Broccoli build completes. Otherwise 72 | // the `getStatus` call here might report the status of the previous check. In 73 | // practice, though, building takes much longer than the time to trigger the 74 | // compiler's "hey, a file changed" hook, and once the typecheck has begun, the 75 | // `getStatus` call will block until it's complete. 76 | let worker = await this._getTypecheckWorker(); 77 | let { failed } = await worker.getStatus(); 78 | 79 | if (failed) { 80 | // The actual details of the errors will already have been printed 81 | // with nice highlighting and formatting separately. 82 | throw new Error('Typechecking failed'); 83 | } 84 | }, 85 | 86 | setupPreprocessorRegistry(type, registry) { 87 | if (type !== 'parent') return; 88 | 89 | // If we're acting on behalf of the root app, issue a warning if we detect 90 | // a situation where a .js file from an addon has the same name as a .ts 91 | // file in the app, as which file wins is nondeterministic. 92 | if (this.parent === this.project) { 93 | this._registerCollisionDetectionPreprocessor(registry); 94 | } 95 | }, 96 | 97 | shouldIncludeChildAddon(addon) { 98 | // For testing, we have dummy in-repo addons set up, but e-c-ts doesn't depend on them; 99 | // its dummy app does. Otherwise we'd have a circular dependency. 100 | return !['in-repo-a', 'in-repo-b'].includes(addon.name); 101 | }, 102 | 103 | _registerCollisionDetectionPreprocessor(registry: PreprocessRegistry) { 104 | registry.add('js', { 105 | name: 'ember-cli-typescript-collision-check', 106 | toTree: (input, path) => { 107 | if (path !== '/') return input; 108 | 109 | let addon = this; 110 | let checked = false; 111 | let stew = require('broccoli-stew') as typeof import('broccoli-stew'); 112 | 113 | return stew.afterBuild(input, function () { 114 | if (!checked) { 115 | checked = true; 116 | addon._checkForFileCollisions(this.inputPaths[0]); 117 | } 118 | }); 119 | }, 120 | }); 121 | }, 122 | 123 | _checkForFileCollisions(directory: string) { 124 | let walkSync = require('walk-sync') as typeof import('walk-sync'); 125 | let files = new Set(walkSync(directory, ['**/*.{js,ts}'])); 126 | 127 | let collisions: string[] = []; 128 | for (let file of files) { 129 | if (file.endsWith('.js') && files.has(file.replace(/\.js$/, '.ts'))) { 130 | collisions.push(file.replace(/\.js$/, '.{js,ts}')); 131 | } 132 | } 133 | 134 | if (collisions.length) { 135 | this.ui.writeWarnLine( 136 | 'Detected collisions between .js and .ts files of the same name. ' + 137 | 'This can result in nondeterministic build output; ' + 138 | 'see https://git.io/JvIwo for more information.\n - ' + 139 | collisions.join('\n - ') 140 | ); 141 | } 142 | }, 143 | 144 | _checkBabelVersion() { 145 | let babel = this.parent.addons.find((addon) => addon.name === 'ember-cli-babel'); 146 | let version = babel && babel.pkg.version; 147 | 148 | if (!babel || !semver.gte(version!, '7.17.0')) { 149 | let versionString = babel 150 | ? `version ${babel.pkg.version} installed` 151 | : `no instance of ember-cli-babel installed in your dependencies (check if it's in devDependencies instead?)`; 152 | this.ui.writeWarnLine( 153 | `ember-cli-typescript requires ember-cli-babel ^7.17.0, but you have ${versionString}; ` + 154 | 'your TypeScript files may not be transpiled correctly.' 155 | ); 156 | } 157 | }, 158 | 159 | _checkEmberCLIVersion() { 160 | let cliPackage = this.project.require('ember-cli/package.json') as { 161 | version: string; 162 | }; 163 | if (semver.lt(cliPackage.version, '3.5.0')) { 164 | this.ui.writeWarnLine( 165 | 'ember-cli-typescript works best with ember-cli >= 3.5, which uses the system temporary directory ' + 166 | 'by default rather than a project-local one, minimizing file system events the TypeScript ' + 167 | 'compiler needs to keep track of.' 168 | ); 169 | } 170 | }, 171 | 172 | _checkDevelopment() { 173 | if (this.isDevelopingAddon() && !process.env.CI && __filename.endsWith('.js')) { 174 | this.ui.writeWarnLine( 175 | 'ember-cli-typescript is in development but not being loaded from `.ts` sources — ' + 176 | 'do you have compiled artifacts lingering in `/js`?' 177 | ); 178 | } 179 | }, 180 | 181 | _checkAddonAppFiles() { 182 | // Emit a warning for addons that are under active development... 183 | let isDevelopingAddon = !this.app && (this.parent as Addon).isDevelopingAddon(); 184 | 185 | // ...and are at the root of the project (i.e. not in-repo)... 186 | let isRootAddon = this.parent.root === this.project.root; 187 | 188 | // ...and have .ts files in their `app` directory. 189 | let appDir = `${this.parent.root}/app`; 190 | if (isDevelopingAddon && isRootAddon && fs.existsSync(appDir)) { 191 | let tsFilesInApp = walkSync(appDir, { globs: ['**/*.ts'] }); 192 | if (tsFilesInApp.length) { 193 | this.ui.writeWarnLine( 194 | `found .ts files in ${appDir}\n` + 195 | "ember-cli-typescript only compiles files in an addon's `addon` folder; " + 196 | 'see https://github.com/typed-ember/ember-cli-typescript/issues/562' 197 | ); 198 | } 199 | } 200 | }, 201 | 202 | _checkInstallationLocation() { 203 | if ( 204 | this.project.isEmberCLIAddon() && 205 | this.project.pkg.devDependencies && 206 | this.project.pkg.devDependencies[this.name] 207 | ) { 208 | this.ui.writeWarnLine( 209 | '`ember-cli-typescript` should be included in your `dependencies`, not `devDependencies`' 210 | ); 211 | } 212 | }, 213 | 214 | _addTypecheckMiddleware(app: Application) { 215 | let workerPromise = this._getTypecheckWorker(); 216 | let middleware = new TypecheckMiddleware(this.project, workerPromise); 217 | middleware.register(app); 218 | }, 219 | 220 | _typecheckWorker: undefined as Promise> | undefined, 221 | 222 | _getTypecheckWorker() { 223 | if (!this._typecheckWorker) { 224 | this._typecheckWorker = this._forkTypecheckWorker(); 225 | } 226 | 227 | return this._typecheckWorker; 228 | }, 229 | 230 | async _forkTypecheckWorker() { 231 | let childProcess = fork(`${__dirname}/lib/typechecking/worker/launch`); 232 | let worker = await connect(childProcess); 233 | 234 | await worker.onTypecheck((status) => { 235 | for (let error of status.errors) { 236 | this.ui.writeLine(error); 237 | } 238 | }); 239 | 240 | await worker.start(this.project.root); 241 | 242 | return worker; 243 | }, 244 | }); 245 | -------------------------------------------------------------------------------- /ts/blueprints/ember-cli-typescript/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const APP_DECLARATIONS = `import Ember from 'ember'; 7 | 8 | declare global { 9 | // Prevents ESLint from "fixing" this via its auto-fix to turn it into a type 10 | // alias (e.g. after running any Ember CLI generator) 11 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 12 | interface Array extends Ember.ArrayPrototypeExtensions {} 13 | // interface Function extends Ember.FunctionPrototypeExtensions {} 14 | } 15 | 16 | export {};`; 17 | 18 | /** 19 | * @param {string} projectName 20 | * @param {'classic' | 'pods'} layout 21 | */ 22 | function buildTemplateDeclarations(projectName, layout) { 23 | const comment = '// Types for compiled templates'; 24 | const moduleBody = ` 25 | import { TemplateFactory } from 'ember-cli-htmlbars'; 26 | 27 | const tmpl: TemplateFactory; 28 | export default tmpl; 29 | `; 30 | switch (layout) { 31 | case 'classic': 32 | return `${comment} 33 | declare module '${projectName}/templates/*' {${moduleBody}}`; 34 | case 'pods': 35 | return `${comment} 36 | declare module '${projectName}/*/template' {${moduleBody}}`; 37 | default: 38 | throw new Error(`Unexpected project layout type: "${layout}"`); 39 | } 40 | } 41 | 42 | const { ADDON_NAME } = require('../../addon'); 43 | 44 | module.exports = { 45 | APP_DECLARATIONS, 46 | 47 | description: 'Initialize files needed for typescript compilation', 48 | 49 | install(options) { 50 | if (options.project.isEmberCLIAddon()) { 51 | options.dummy = true; 52 | } 53 | 54 | return this._super.install.apply(this, arguments); 55 | }, 56 | 57 | locals() { 58 | let inRepoAddons = (this.project.pkg['ember-addon'] || {}).paths || []; 59 | let hasMirage = 'ember-cli-mirage' in (this.project.pkg.devDependencies || {}); 60 | let isAddon = this.project.isEmberCLIAddon(); 61 | let includes = ['app', isAddon && 'addon'].filter(Boolean); 62 | const isPods = this.pod; 63 | 64 | includes = includes.concat(['tests', 'types']).concat(inRepoAddons); 65 | 66 | if (isAddon) { 67 | includes.push('test-support', 'addon-test-support'); 68 | } 69 | // Mirage is already covered for addons because it's under `tests/` 70 | if (hasMirage && !isAddon) { 71 | includes.push('mirage'); 72 | } 73 | 74 | return { 75 | includes: JSON.stringify( 76 | includes.map((include) => `${include}/**/*`), 77 | null, 78 | 2 79 | ).replace(/\n/g, '\n '), 80 | pathsFor: (dasherizedName) => { 81 | let updatePathsForAddon = require('./update-paths-for-addon'); 82 | let appName = isAddon ? 'dummy' : dasherizedName; 83 | let paths = { 84 | [`${appName}/tests/*`]: ['tests/*'], 85 | }; 86 | 87 | if (hasMirage) { 88 | paths[`${appName}/mirage/*`] = [`${isAddon ? 'tests/dummy/' : ''}mirage/*`]; 89 | } 90 | 91 | if (isAddon) { 92 | paths[`${appName}/*`] = ['tests/dummy/app/*', 'app/*']; 93 | } else { 94 | paths[`${appName}/*`] = ['app/*']; 95 | } 96 | 97 | if (isAddon) { 98 | paths[dasherizedName] = ['addon']; 99 | paths[`${dasherizedName}/*`] = ['addon/*']; 100 | paths[`${dasherizedName}/test-support`] = ['addon-test-support']; 101 | paths[`${dasherizedName}/test-support/*`] = ['addon-test-support/*']; 102 | } 103 | 104 | for (let addon of inRepoAddons) { 105 | updatePathsForAddon(paths, path.basename(addon), appName); 106 | } 107 | 108 | paths['*'] = ['types/*']; 109 | 110 | return JSON.stringify(paths, null, 2).replace(/\n/g, '\n '); 111 | }, 112 | indexDeclarations: (dasherizedName) => { 113 | const isDummyApp = dasherizedName === 'dummy'; 114 | const useAppDeclarations = !(isAddon || isDummyApp); 115 | return useAppDeclarations ? APP_DECLARATIONS : ''; 116 | }, 117 | globalDeclarations: (dasherizedName) => { 118 | /** @type {'classic' | 'pods'} */ 119 | let projectLayout; 120 | if (isPods) projectLayout = 'pods'; 121 | else projectLayout = 'classic'; 122 | return buildTemplateDeclarations(dasherizedName, projectLayout); 123 | }, 124 | }; 125 | }, 126 | 127 | fileMapTokens(/*options*/) { 128 | // Return custom tokens to be replaced in your files. 129 | return { 130 | __app_name__(options) { 131 | return options.inAddon ? 'dummy' : options.dasherizedModuleName; 132 | }, 133 | 134 | __config_root__(options) { 135 | return options.inAddon ? 'tests/dummy/app' : 'app'; 136 | }, 137 | }; 138 | }, 139 | 140 | normalizeEntityName() { 141 | // Entity name is optional right now, creating this hook avoids an error. 142 | }, 143 | 144 | beforeInstall() { 145 | if (this.project.isEmberCLIAddon()) { 146 | this._transformAddonPackage(); 147 | } 148 | 149 | let packages = [ 150 | 'typescript', 151 | '@tsconfig/ember', 152 | '@types/ember', 153 | '@types/ember-resolver', 154 | '@types/ember__object', 155 | '@types/ember__service', 156 | '@types/ember__controller', 157 | '@types/ember__destroyable', 158 | '@types/ember__string', 159 | '@types/ember__template', 160 | '@types/ember__polyfills', 161 | '@types/ember__utils', 162 | '@types/ember__runloop', 163 | '@types/ember__debug', 164 | '@types/ember__engine', 165 | '@types/ember__application', 166 | '@types/ember__test', 167 | '@types/ember__array', 168 | '@types/ember__error', 169 | '@types/ember__component', 170 | '@types/ember__routing', 171 | '@types/rsvp', 172 | ]; 173 | 174 | if (this._has('@ember/jquery')) { 175 | packages.push('@types/jquery'); 176 | } 177 | 178 | if (this._has('ember-data')) { 179 | packages.push('@types/ember-data'); 180 | packages.push('@types/ember-data__adapter'); 181 | packages.push('@types/ember-data__model'); 182 | packages.push('@types/ember-data__serializer'); 183 | packages.push('@types/ember-data__store'); 184 | } 185 | 186 | if (this._has('ember-cli-qunit') || this._has('ember-qunit')) { 187 | packages.push('@types/ember-qunit'); 188 | packages.push('@types/qunit'); 189 | } 190 | 191 | if (this._has('ember-cli-mocha') || this._has('ember-mocha')) { 192 | packages.push('@types/ember-mocha'); 193 | packages.push('@types/mocha'); 194 | } 195 | 196 | return this.addPackagesToProject( 197 | packages.map((name) => { 198 | return { name, target: 'latest' }; 199 | }) 200 | ); 201 | }, 202 | 203 | filesPath() { 204 | return `${__dirname}/../../../blueprint-files/ember-cli-typescript`; 205 | }, 206 | 207 | files() { 208 | let files = this._super.files.apply(this, arguments); 209 | 210 | if (!this._has('ember-data')) { 211 | files = files.filter((file) => file !== 'types/ember-data/types/registries/model.d.ts'); 212 | } 213 | 214 | return files; 215 | }, 216 | 217 | _transformAddonPackage() { 218 | const pkgPath = `${this.project.root}/package.json`; 219 | 220 | let pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); 221 | 222 | // As of https://github.com/yarnpkg/yarn/pull/5712 yarn runs `prepack` and `postpack` when publishing 223 | this._addScript(pkg.scripts, 'prepack', 'ember ts:precompile'); 224 | this._addScript(pkg.scripts, 'postpack', 'ember ts:clean'); 225 | 226 | // avoid being placed in devDependencies 227 | if (pkg.devDependencies[ADDON_NAME]) { 228 | pkg.dependencies[ADDON_NAME] = pkg.devDependencies[ADDON_NAME]; 229 | delete pkg.devDependencies[ADDON_NAME]; 230 | } 231 | 232 | fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); 233 | }, 234 | 235 | _addScript(scripts, type, script) { 236 | if (scripts[type] && scripts[type] !== script) { 237 | this.ui.writeWarnLine( 238 | `Found a pre-existing \`${type}\` script in your package.json. ` + 239 | `By default, ember-cli-typescript expects to run \`${script}\` in this hook.` 240 | ); 241 | return; 242 | } 243 | 244 | scripts[type] = script; 245 | }, 246 | 247 | _has(pkg) { 248 | if (this.project) { 249 | return pkg in this.project.dependencies(); 250 | } 251 | }, 252 | }; 253 | -------------------------------------------------------------------------------- /ts/blueprints/ember-cli-typescript/update-paths-for-addon.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-prototype-builtins */ 2 | 3 | module.exports = function (paths, addonName, appName, options) { 4 | options = options || {}; 5 | const addonNameStar = [addonName, '*'].join('/'); 6 | const addonPath = [options.isLinked ? 'node_modules' : 'lib', addonName].join('/'); 7 | const addonAddonPath = [addonPath, 'addon'].join('/'); 8 | const addonAppPath = [addonPath, 'app'].join('/'); 9 | const appNameStar = [appName, '*'].join('/'); 10 | const addonTestSupportPath = [addonName, 'test-support'].join('/'); 11 | const addonTestSupportStarPath = `${addonTestSupportPath}/*`; 12 | let appStarPaths; 13 | paths = paths || {}; 14 | appStarPaths = paths[appNameStar] = paths[appNameStar] || []; 15 | 16 | if (options.removePaths) { 17 | if (paths.hasOwnProperty(addonName)) { 18 | delete paths[addonName]; 19 | } 20 | if (paths.hasOwnProperty(addonNameStar)) { 21 | delete paths[addonNameStar]; 22 | } 23 | let addonAppPathIndex = appStarPaths.indexOf([addonAppPath, '*'].join('/')); 24 | if (addonAppPathIndex > -1) { 25 | appStarPaths.splice(addonAppPathIndex, 1); 26 | paths[appNameStar] = appStarPaths; 27 | } 28 | } else { 29 | if (!paths.hasOwnProperty(addonName)) { 30 | paths[addonName] = [addonAddonPath]; 31 | } 32 | if (!paths.hasOwnProperty(addonNameStar)) { 33 | paths[addonNameStar] = [[addonAddonPath, '*'].join('/')]; 34 | } 35 | if (!paths.hasOwnProperty(addonTestSupportPath)) { 36 | paths[addonTestSupportPath] = [[addonPath, 'addon-test-support'].join('/')]; 37 | } 38 | if (!paths.hasOwnProperty(addonTestSupportStarPath)) { 39 | paths[addonTestSupportStarPath] = [[addonPath, 'addon-test-support', '*'].join('/')]; 40 | } 41 | if (appStarPaths.indexOf(addonAppPath) === -1) { 42 | appStarPaths.push([addonAppPath, '*'].join('/')); 43 | paths[appNameStar] = appStarPaths; 44 | } 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /ts/lib/commands/clean.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { command } from '../utilities/ember-cli-entities'; 3 | import { PRECOMPILE_MANIFEST } from './precompile'; 4 | 5 | export default command({ 6 | name: 'ts:clean', 7 | works: 'insideProject', 8 | description: 'Cleans up compiled JS and declaration files generated by `ember ts:precompile`.', 9 | 10 | availableOptions: [{ name: 'manifest-path', type: String, default: PRECOMPILE_MANIFEST }], 11 | 12 | run(options: { manifestPath: string }) { 13 | let manifestPath = options.manifestPath; 14 | 15 | if (!fs.existsSync(manifestPath)) { 16 | this.ui.writeWarnLine( 17 | 'No TS precompilation manifest found; you may need to clean up extraneous files yourself.' 18 | ); 19 | return; 20 | } 21 | 22 | let files = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')); 23 | for (let file of files) { 24 | if (fs.existsSync(file)) { 25 | if (file[file.length - 1] === '/') { 26 | fs.rmdirSync(file); 27 | } else { 28 | fs.unlinkSync(file); 29 | } 30 | } 31 | } 32 | fs.unlinkSync(manifestPath); 33 | }, 34 | }); 35 | -------------------------------------------------------------------------------- /ts/lib/commands/precompile.ts: -------------------------------------------------------------------------------- 1 | import execa from 'execa'; 2 | import fs from 'fs-extra'; 3 | import path from 'path'; 4 | import { command } from '../utilities/ember-cli-entities'; 5 | import copyDeclarations from '../utilities/copy-declarations'; 6 | 7 | export const PRECOMPILE_MANIFEST = 'dist/.ts-precompile-manifest'; 8 | 9 | export default command({ 10 | name: 'ts:precompile', 11 | works: 'insideProject', 12 | description: 'Generates declaration files from TypeScript sources in preparation for publishing.', 13 | 14 | availableOptions: [{ name: 'manifest-path', type: String, default: PRECOMPILE_MANIFEST }], 15 | 16 | async run(options: { manifestPath: string }) { 17 | let outDir = `${process.cwd()}/e-c-ts-precompile-${process.pid}`; 18 | let { paths, rootDir, pathRoots } = this._loadConfig(outDir); 19 | if (!paths) { 20 | this.ui.writeLine( 21 | 'No `paths` were found in your `tsconfig.json`, so `ts:precompile` is a no-op.' 22 | ); 23 | return; 24 | } 25 | 26 | try { 27 | // prettier-ignore 28 | await execa('tsc', [ 29 | '--allowJs', 'false', 30 | '--noEmit', 'false', 31 | '--rootDir', rootDir || this.project.root, 32 | '--isolatedModules', 'false', 33 | '--declaration', 34 | '--declarationDir', outDir, 35 | '--emitDeclarationOnly', 36 | '--pretty', 'true', 37 | ], { 38 | preferLocal: true, 39 | 40 | // Capture a string with stdout and stderr interleaved for error reporting 41 | all: true, 42 | }); 43 | } catch (e: any) { 44 | fs.removeSync(outDir); 45 | console.error(`\n${e.all}\n`); 46 | throw e; 47 | } 48 | 49 | let manifestPath = options.manifestPath; 50 | let packageName = this.project.pkg.name; 51 | 52 | // Ensure that if we are dealing with an addon that is using a different 53 | // addon name from its package name, we use the addon name, since that is 54 | // how it will be written for imports. 55 | let addon = this.project.addons.find((addon) => addon.root === this.project.root); 56 | if (addon) { 57 | let addonName = addon.moduleName ? addon.moduleName() : addon.name; 58 | packageName = addonName; 59 | } 60 | 61 | let createdFiles = copyDeclarations(pathRoots, paths, packageName, this.project.root); 62 | 63 | fs.mkdirsSync(path.dirname(manifestPath)); 64 | fs.writeFileSync(manifestPath, JSON.stringify(createdFiles.reverse())); 65 | fs.removeSync(outDir); 66 | }, 67 | 68 | _loadConfig(outDir: string) { 69 | let ts = this.project.require('typescript') as typeof import('typescript'); 70 | let configPath = ts.findConfigFile(this.project.root, ts.sys.fileExists); 71 | if (!configPath) { 72 | throw new Error('Unable to locate `tsconfig.json`'); 73 | } 74 | 75 | let configSource = ts.readJsonConfigFile(configPath, ts.sys.readFile); 76 | let config = ts.parseJsonSourceFileConfigFileContent( 77 | configSource, 78 | ts.sys, 79 | path.dirname(configPath) 80 | ); 81 | 82 | let { paths, rootDir, baseUrl } = config.options; 83 | let configDir = path.dirname(configPath); 84 | let relativeBaseDir = path.relative(configDir, baseUrl || configDir); 85 | 86 | let pathRoots = [ 87 | // Any declarations found in the actual source 88 | path.resolve(rootDir || configDir, relativeBaseDir), 89 | 90 | // Any declarations generated by `tsc` 91 | path.resolve(outDir, relativeBaseDir), 92 | ]; 93 | 94 | return { rootDir, paths, pathRoots }; 95 | }, 96 | }); 97 | -------------------------------------------------------------------------------- /ts/lib/typechecking/middleware/index.ts: -------------------------------------------------------------------------------- 1 | import { Remote } from 'stagehand'; 2 | import { Application, Request, Response, NextFunction } from 'express'; 3 | import Project from 'ember-cli/lib/models/project'; 4 | import TypecheckWorker from '../worker'; 5 | import renderErrorPage from './render-error-page'; 6 | 7 | export const LIVE_RELOAD_PATH = '/ember-cli-live-reload.js'; 8 | 9 | export default class TypecheckMiddleware { 10 | constructor(private project: Project, private workerPromise: Promise>) {} 11 | 12 | public register(app: Application): void { 13 | app.use((...params) => this.handleRequest(...params)); 14 | } 15 | 16 | private async handleRequest(request: Request, response: Response, next: NextFunction) { 17 | if (!request.accepts('html') || request.path === LIVE_RELOAD_PATH) { 18 | next(); 19 | return; 20 | } 21 | 22 | let worker = await this.workerPromise; 23 | let { errors, failed } = await worker.getStatus(); 24 | 25 | if (failed) { 26 | response.type('html'); 27 | response.end(renderErrorPage(errors, this.environmentInfo())); 28 | } else { 29 | next(); 30 | } 31 | } 32 | 33 | private environmentInfo(): string { 34 | let tsVersion = (this.project.require('typescript/package.json') as any).version; 35 | let ectsVersion = require(`${__dirname}/../../../../package`).version; 36 | 37 | return `typescript@${tsVersion}, ember-cli-typescript@${ectsVersion}`; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ts/lib/typechecking/worker/index.ts: -------------------------------------------------------------------------------- 1 | import resolve from 'resolve'; 2 | import { defer } from 'rsvp'; 3 | import logger from 'debug'; 4 | import { 5 | Diagnostic, 6 | FileWatcherEventKind, 7 | SemanticDiagnosticsBuilderProgram, 8 | CompilerOptions, 9 | WatchOfConfigFile, 10 | WatchCompilerHostOfConfigFile, 11 | } from 'typescript'; 12 | 13 | const debug = logger('ember-cli-typescript:typecheck-worker'); 14 | 15 | // The compiler has a hard-coded 250ms wait between when it last sees an FS event and when it actually 16 | // begins a new build. Since we can't know ahead of time whether a given file change will necessarily 17 | // trigger a new check, we assume it will and set a timer to go back to the previous resolution if 18 | // a new check doesn't actually start. 19 | // https://github.com/Microsoft/TypeScript/blob/c0587191fc536ca62b68748b0e47072e6f881968/src/compiler/watch.ts#L812-L825 20 | const TYPECHECK_TIMEOUT = 300; 21 | 22 | interface TypecheckStatus { 23 | errors: string[]; 24 | failed: boolean; 25 | } 26 | 27 | export default class TypecheckWorker { 28 | private typecheckListeners: Array<(status: TypecheckStatus) => void> = []; 29 | private isChecking = true; 30 | private status = defer(); 31 | private lastSettledStatus = this.status; 32 | private typecheckTimeout?: NodeJS.Timer; 33 | 34 | private projectRoot!: string; 35 | private ts!: typeof import('typescript'); 36 | private watch!: WatchOfConfigFile; 37 | private compilerOptions!: CompilerOptions; 38 | 39 | /** 40 | * Begin project typechecking, loading TypeScript from the given project root. 41 | */ 42 | public start(projectRoot: string) { 43 | this.projectRoot = projectRoot; 44 | this.ts = this.loadTypeScript(); 45 | this.watch = this.ts.createWatchProgram(this.buildWatchHost()); 46 | this.compilerOptions = this.watch.getProgram().getCompilerOptions(); 47 | } 48 | 49 | /** 50 | * Returns the current typechecking status, blocking until complete if a 51 | * check is currently in progress. 52 | */ 53 | public getStatus(): Promise { 54 | return this.status.promise; 55 | } 56 | 57 | /** 58 | * Accepts a callback that will be invoked any time a check completes, 59 | * receiving a `TypecheckStatus` payload describing the results. 60 | */ 61 | public onTypecheck(listener: (status: TypecheckStatus) => void): void { 62 | this.typecheckListeners.push(listener); 63 | } 64 | 65 | private loadTypeScript() { 66 | return require(resolve.sync('typescript', { basedir: this.projectRoot })); 67 | } 68 | 69 | private mayTypecheck(filePath: string) { 70 | debug('File change at %s; watching for new typecheck', filePath); 71 | 72 | this.beginCheck(); 73 | this.typecheckTimeout = setTimeout(() => { 74 | debug(`File change didn't result in a typecheck; resolving`); 75 | this.isChecking = false; 76 | this.status.resolve(this.lastSettledStatus.promise); 77 | }, TYPECHECK_TIMEOUT); 78 | } 79 | 80 | private willTypecheck() { 81 | debug('Typecheck starting'); 82 | 83 | this.beginCheck(); 84 | } 85 | 86 | private didTypecheck(diagnostics: ReadonlyArray) { 87 | if (this.isChecking) { 88 | debug('Typecheck complete (%d diagnostics)', diagnostics.length); 89 | 90 | let status = this.makeStatus(diagnostics); 91 | 92 | this.isChecking = false; 93 | this.status.resolve(status); 94 | this.lastSettledStatus = this.status; 95 | 96 | for (let listener of this.typecheckListeners) { 97 | listener(status); 98 | } 99 | } 100 | } 101 | 102 | private beginCheck() { 103 | if (this.typecheckTimeout !== undefined) { 104 | clearTimeout(this.typecheckTimeout); 105 | } 106 | 107 | if (!this.isChecking) { 108 | this.isChecking = true; 109 | this.status = defer(); 110 | } 111 | } 112 | 113 | private formatDiagnostic(diagnostic: Diagnostic) { 114 | return this.ts.formatDiagnosticsWithColorAndContext([diagnostic], { 115 | getCanonicalFileName: (path) => path, 116 | getCurrentDirectory: this.ts.sys.getCurrentDirectory, 117 | getNewLine: () => this.ts.sys.newLine, 118 | }); 119 | } 120 | 121 | private buildWatchHost() { 122 | let host = this.ts.createWatchCompilerHost( 123 | this.findConfigFile(), 124 | { noEmit: true }, 125 | this.ts.sys, 126 | this.ts.createSemanticDiagnosticsBuilderProgram, 127 | // Pass noop functions for reporters because we want to print our own output 128 | () => {}, 129 | () => {} 130 | ); 131 | 132 | return this.patchCompilerHostMethods(host); 133 | } 134 | 135 | // The preferred means of being notified when things happen in the compiler is 136 | // overriding methods and then calling the original. See the TypeScript wiki: 137 | // https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API 138 | private patchCompilerHostMethods( 139 | host: WatchCompilerHostOfConfigFile 140 | ) { 141 | let { watchFile, watchDirectory, afterProgramCreate = () => {} } = host; 142 | 143 | // Intercept tsc's `watchFile` to also invoke `mayTypecheck()` when a watched file changes 144 | host.watchFile = (path, callback, pollingInterval?) => { 145 | return watchFile.call( 146 | host, 147 | path, 148 | (filePath: string, eventKind: FileWatcherEventKind) => { 149 | this.mayTypecheck(filePath); 150 | return callback(filePath, eventKind); 151 | }, 152 | pollingInterval 153 | ); 154 | }; 155 | 156 | // Intercept tsc's `watchDirectory` callback to also invoke `mayTypecheck()` when a 157 | // file is added or removed in a watched directory. 158 | host.watchDirectory = (path, callback, recursive?) => { 159 | return watchDirectory.call( 160 | host, 161 | path, 162 | (filePath: string) => { 163 | this.mayTypecheck(filePath); 164 | return callback(filePath); 165 | }, 166 | recursive 167 | ); 168 | }; 169 | 170 | // Intercept `afterProgramCreate` to confirm when a suspected typecheck is happening 171 | // and schedule the new diagnostics to be emitted. 172 | host.afterProgramCreate = (program) => { 173 | this.willTypecheck(); 174 | 175 | // The `afterProgramCreate` callback will be invoked synchronously when we first call 176 | // `createWatchProgram`, meaning we can enter `didTypecheck` before we're fully set up 177 | // (e.g. before `compilerOptions` has been set). We use `nextTick` to ensure that 178 | // `didTypecheck` is only ever invoked after the worker is fully ready. 179 | process.nextTick(() => this.didTypecheck(program.getSemanticDiagnostics())); 180 | 181 | return afterProgramCreate.call(host, program); 182 | }; 183 | 184 | return host; 185 | } 186 | 187 | private makeStatus(diagnostics: ReadonlyArray): TypecheckStatus { 188 | let errors = diagnostics.map((d) => this.formatDiagnostic(d)); 189 | let failed = !!(this.compilerOptions.noEmitOnError && errors.length); 190 | return { errors, failed }; 191 | } 192 | 193 | private findConfigFile() { 194 | let configPath = this.ts.findConfigFile( 195 | this.projectRoot, 196 | this.ts.sys.fileExists, 197 | 'tsconfig.json' 198 | ); 199 | 200 | if (!configPath) { 201 | throw new Error(`Unable to locate tsconfig.json for project at ${this.projectRoot}`); 202 | } 203 | 204 | return configPath; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /ts/lib/typechecking/worker/launch.ts: -------------------------------------------------------------------------------- 1 | import TypecheckWorker from '.'; 2 | import { launch } from 'stagehand/lib/adapters/child-process'; 3 | 4 | launch(new TypecheckWorker()); 5 | -------------------------------------------------------------------------------- /ts/lib/utilities/copy-declarations.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | import walkSync from 'walk-sync'; 4 | 5 | export default function copyDeclarations( 6 | pathRoots: string[], 7 | paths: Record, 8 | packageName: string, 9 | destDir: string 10 | ) { 11 | let output: string[] = []; 12 | for (let logicalPath of Object.keys(paths)) { 13 | let physicalPaths = paths[logicalPath]; 14 | if ( 15 | logicalPath.startsWith(`${packageName}/`) && 16 | logicalPath.indexOf('/*') === logicalPath.length - 2 17 | ) { 18 | let subdirectory = logicalPath.replace(packageName, '').replace('/*', '').replace(/^\//, ''); 19 | 20 | copySubpathDeclarations(output, pathRoots, path.join(destDir, subdirectory), physicalPaths); 21 | } 22 | } 23 | return output; 24 | } 25 | 26 | function copySubpathDeclarations( 27 | output: string[], 28 | pathRoots: string[], 29 | destDir: string, 30 | physicalPaths: string[] 31 | ) { 32 | for (let pathRoot of pathRoots) { 33 | for (let physicalPath of physicalPaths) { 34 | if (!physicalPath.endsWith('/*')) { 35 | throw new Error(`Missing trailing '*' in path mapping: ${physicalPath}`); 36 | } 37 | 38 | let fullRoot = path.resolve(pathRoot, physicalPath.replace(/\/\*$/, '')); 39 | if (!fs.existsSync(fullRoot)) { 40 | continue; 41 | } 42 | 43 | for (let file of walkSync(fullRoot, { globs: ['**/*.d.ts'] })) { 44 | let destinationPath = path.join(destDir, file); 45 | if (!fs.existsSync(destinationPath)) { 46 | copyFile(output, path.join(fullRoot, file), destinationPath); 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | function copyFile(output: string[], source: string, dest: string) { 54 | let segments = dest.split(/\/|\\/); 55 | 56 | // Make (and record the making of) any missing directories 57 | for (let i = 1; i < segments.length; i++) { 58 | let dir = segments.slice(0, i).join('/'); 59 | if (dir && !fs.existsSync(dir)) { 60 | fs.mkdirSync(dir); 61 | output.push(`${dir}/`); 62 | } 63 | } 64 | 65 | fs.writeFileSync(dest, fs.readFileSync(source)); 66 | output.push(dest); 67 | } 68 | -------------------------------------------------------------------------------- /ts/lib/utilities/ember-cli-entities.ts: -------------------------------------------------------------------------------- 1 | import { ExtendOptions, ExtendThisType } from 'core-object'; 2 | import Addon from 'ember-cli/lib/models/addon'; 3 | import Command from 'ember-cli/lib/models/command'; 4 | 5 | /* 6 | * This module contains identity functions that accept and return config 7 | * hashes for various Ember CLI entities, ensuring that they're compatible 8 | * with the expected config signature and that any methods have the correct 9 | * `this` type. 10 | */ 11 | 12 | /** Configuration for defining an Ember CLI addon */ 13 | export function addon>(options: T & ExtendThisType): T { 14 | return options; 15 | } 16 | 17 | /** Configuration for defining an Ember CLI command */ 18 | export function command>( 19 | options: T & ExtendThisType 20 | ): T { 21 | return options; 22 | } 23 | -------------------------------------------------------------------------------- /ts/lib/utilities/fork.ts: -------------------------------------------------------------------------------- 1 | import * as ChildProcess from 'child_process'; 2 | 3 | const REGISTER_TS_NODE_PATH = `${__dirname}/../../../register-ts-node`; 4 | 5 | export default function fork(path: string) { 6 | let child = ChildProcess.fork(path, [], { 7 | execArgv: execArgs(), 8 | }); 9 | 10 | // Terminate the child when ember-cli shuts down 11 | process.on('exit', () => child.kill()); 12 | 13 | return child; 14 | } 15 | 16 | function execArgs() { 17 | // If we're running in a TypeScript file, we need to register ts-node for the child too 18 | if (isTypeScript()) { 19 | return ['-r', REGISTER_TS_NODE_PATH]; 20 | } else { 21 | return []; 22 | } 23 | } 24 | 25 | function isTypeScript() { 26 | return __filename.endsWith('.ts'); 27 | } 28 | -------------------------------------------------------------------------------- /ts/tests/acceptance/build-test.mts: -------------------------------------------------------------------------------- 1 | import SkeletonApp from '../helpers/skeleton-app.mjs'; 2 | // @ts-ignore -- we're not going to iterate on this, so there's no point in 3 | // working on adding types for it. 4 | import chai from 'ember-cli-blueprint-test-helpers/chai.js'; 5 | import * as esprima from 'esprima'; 6 | import { 7 | ExpressionStatement, 8 | Statement, 9 | ModuleDeclaration, 10 | CallExpression, 11 | Expression, 12 | ClassExpression, 13 | Literal, 14 | } from 'estree'; 15 | 16 | const { expect } = chai; 17 | 18 | describe('Acceptance: build', function () { 19 | this.timeout(60 * 1000); 20 | let app: SkeletonApp; 21 | beforeEach(function () { 22 | app = new SkeletonApp(); 23 | }); 24 | 25 | afterEach(function () { 26 | app.teardown(); 27 | }); 28 | 29 | it('builds and rebuilds files', async () => { 30 | app.writeFile( 31 | 'app/app.ts', 32 | ` 33 | export function add(a: number, b: number) { 34 | return a + b; 35 | } 36 | ` 37 | ); 38 | 39 | let server = app.serve(); 40 | 41 | await server.waitForBuild(); 42 | 43 | expectModuleBody( 44 | app, 45 | 'skeleton-app/app', 46 | ` 47 | _exports.add = add; 48 | function add(a, b) { 49 | return a + b; 50 | } 51 | ` 52 | ); 53 | 54 | app.writeFile( 55 | 'app/app.ts', 56 | ` 57 | export const foo: string = 'hello'; 58 | ` 59 | ); 60 | 61 | await server.waitForBuild(); 62 | 63 | expectModuleBody( 64 | app, 65 | 'skeleton-app/app', 66 | ` 67 | _exports.foo = void 0; 68 | const foo = 'hello'; 69 | _exports.foo = foo; 70 | ` 71 | ); 72 | }); 73 | 74 | it("doesn't launch type checking for `ember serve` when --path is used", async () => { 75 | await app.build(); 76 | 77 | let server = app.serve({ 78 | args: ['--path', 'dist'], 79 | env: { DEBUG: 'ember-cli-typescript:addon' }, 80 | }); 81 | 82 | let result = await server.waitForOutput('Serving on'); 83 | 84 | expect(result).to.include('ember-cli-typescript:addon Skipping typecheck server middleware'); 85 | }); 86 | 87 | it("doesn't launch type checking for `ember test` when --path is used", async () => { 88 | await app.build({ args: ['--environment', 'test'] }); 89 | 90 | let result = await app.test({ 91 | args: ['--path', 'dist'], 92 | env: { DEBUG: 'ember-cli-typescript:addon' }, 93 | }); 94 | 95 | expect(result.all).to.include( 96 | 'ember-cli-typescript:addon Skipping typecheck testem middleware' 97 | ); 98 | }); 99 | 100 | it('fails the build when noEmitOnError is set and an error is emitted', async () => { 101 | app.writeFile('app/app.ts', `import { foo } from 'nonexistent';`); 102 | 103 | try { 104 | await app.build(); 105 | expect.fail('Build should have failed'); 106 | } catch (error: any) { 107 | expect(error.all).to.include(`Cannot find module 'nonexistent'`); 108 | } 109 | }); 110 | 111 | it('serves a type error page when the build has failed', async () => { 112 | app.writeFile('app/index.html', 'plain index'); 113 | app.writeFile('app/app.ts', `import { foo } from 'nonexistent';`); 114 | 115 | let server = app.serve(); 116 | let output = await server.waitForOutput('Typechecking failed'); 117 | let response = await server.request('/'); 118 | 119 | expect(output).to.include(`Cannot find module 'nonexistent'`); 120 | expect(response.body).to.include(`Cannot find module 'nonexistent'`); 121 | }); 122 | 123 | it("doesn't block builds for file changes that don't result in a typecheck", async () => { 124 | let server = app.serve(); 125 | 126 | await server.waitForBuild(); 127 | 128 | app.writeFile('app/some-template.hbs', ''); 129 | 130 | await server.waitForBuild(); 131 | }); 132 | 133 | it('emits a warning when .js and .ts files conflict in the app/ tree', async () => { 134 | // Set up an in-repo addon 135 | app.updatePackageJSON((pkg) => { 136 | pkg['ember-addon'].paths.push('lib/in-repo-addon'); 137 | }); 138 | 139 | app.writeFile('lib/in-repo-addon/index.js', 'module.exports = { name: "in-repo-addon" };'); 140 | app.writeFile( 141 | 'lib/in-repo-addon/package.json', 142 | JSON.stringify({ 143 | name: 'in-repo-addon', 144 | keywords: ['ember-addon'], 145 | }) 146 | ); 147 | 148 | // Have it export a .js app file and attempt to overwrite it in the host with a .ts file 149 | app.writeFile('lib/in-repo-addon/app/foo.js', '// addon'); 150 | app.writeFile('app/foo.ts', '// app'); 151 | 152 | let output = await app.build(); 153 | 154 | expect(output.all).to.include('skeleton-app/foo.{js,ts}'); 155 | expect(output.all).to.include( 156 | 'WARNING: Detected collisions between .js and .ts files of the same name.' 157 | ); 158 | }); 159 | }); 160 | 161 | function isExpressionStatement(stmt: Statement | ModuleDeclaration): stmt is ExpressionStatement { 162 | return stmt.type === 'ExpressionStatement'; 163 | } 164 | 165 | function isSpecialCallExpression(expr: Expression): expr is CallExpression & { 166 | arguments: [Literal, Expression, ClassExpression]; 167 | } { 168 | return ( 169 | expr.type === 'CallExpression' && 170 | expr.callee.type === 'Identifier' && 171 | expr.callee.name === 'define' && 172 | expr.arguments && 173 | expr.arguments[0] && 174 | expr.arguments[0].type === 'Literal' 175 | ); 176 | } 177 | 178 | function extractModuleBody(script: string, moduleName: string) { 179 | let parsed = esprima.parseScript(script); 180 | let [definition] = parsed.body 181 | .filter(isExpressionStatement) 182 | .map((stmt) => stmt.expression) 183 | .filter(isSpecialCallExpression) 184 | .filter((expr) => expr.arguments[0].value === moduleName); 185 | if (!definition) throw new Error('Definition for call expression not found'); 186 | let moduleDef = definition.arguments[2].body; 187 | 188 | // Strip `'use strict'` 189 | moduleDef.body.shift(); 190 | 191 | // Strip `__esModule` definition 192 | moduleDef.body.shift(); 193 | 194 | return moduleDef; 195 | } 196 | 197 | function expectModuleBody(app: SkeletonApp, name: string, body: string) { 198 | let src = app.readFile('dist/assets/skeleton-app.js'); 199 | let actual = extractModuleBody(src, name); 200 | let expected = esprima.parseScript(body); 201 | expect(actual.body).to.deep.equal(expected.body); 202 | } 203 | -------------------------------------------------------------------------------- /ts/tests/commands/clean-test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import walkSync from 'walk-sync'; 3 | 4 | import ember from 'ember-cli-blueprint-test-helpers/lib/helpers/ember'; 5 | import blueprintHelpers from 'ember-cli-blueprint-test-helpers/helpers'; 6 | const setupTestHooks = blueprintHelpers.setupTestHooks; 7 | const emberNew = blueprintHelpers.emberNew; 8 | 9 | const chai = require('ember-cli-blueprint-test-helpers/chai'); 10 | const expect = chai.expect; 11 | 12 | describe('Acceptance: ts:clean command', function () { 13 | setupTestHooks(this); 14 | 15 | beforeEach(async () => { 16 | await emberNew({ target: 'addon' }); 17 | await ember(['generate', 'ember-cli-typescript']); 18 | }); 19 | 20 | it('removes all generated files', async () => { 21 | fs.ensureDirSync('dist'); 22 | fs.ensureDirSync('app'); 23 | fs.ensureDirSync('addon'); 24 | fs.writeFileSync('app/test-file.ts', `export const testString: string = 'app';`); 25 | fs.writeFileSync('addon/test-file.ts', `export const testString: string = 'addon';`); 26 | 27 | let before = walkSync(process.cwd()); 28 | await ember(['ts:precompile']); 29 | await ember(['ts:clean']); 30 | 31 | let after = walkSync(process.cwd()); 32 | expect(after).to.deep.equal(before); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /ts/tests/commands/precompile-test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import * as path from 'path'; 3 | import { EOL } from 'os'; 4 | import { hook } from 'capture-console'; 5 | import ember from 'ember-cli-blueprint-test-helpers/lib/helpers/ember'; 6 | import blueprintHelpers from 'ember-cli-blueprint-test-helpers/helpers'; 7 | import ts from 'typescript'; 8 | 9 | const setupTestHooks = blueprintHelpers.setupTestHooks; 10 | const emberNew = blueprintHelpers.emberNew; 11 | 12 | import * as chai from 'ember-cli-blueprint-test-helpers/chai'; 13 | const expect = chai.expect; 14 | const file = chai.file; 15 | 16 | describe('Acceptance: ts:precompile command', function () { 17 | setupTestHooks(this); 18 | 19 | beforeEach(async () => { 20 | await emberNew({ target: 'addon' }); 21 | await ember(['generate', 'ember-cli-typescript']); 22 | }); 23 | 24 | it('generates .d.ts files from the addon tree', async () => { 25 | fs.ensureDirSync('addon'); 26 | fs.writeFileSync('addon/test-file.ts', `export const testString: string = 'hello';`); 27 | 28 | await ember(['ts:precompile']); 29 | 30 | let declaration = file('test-file.d.ts'); 31 | expect(declaration).to.exist; 32 | expect(declaration.content.trim()).to.equal( 33 | `export declare const testString: string;${EOL}//# sourceMappingURL=test-file.d.ts.map` 34 | ); 35 | }); 36 | 37 | it('generates nothing from the app tree', async () => { 38 | fs.ensureDirSync('app'); 39 | fs.writeFileSync('app/test-file.ts', `export const testString: string = 'hello';`); 40 | 41 | await ember(['ts:precompile']); 42 | 43 | let declaration = file('test-file.d.ts'); 44 | expect(declaration).not.to.exist; 45 | }); 46 | 47 | it('emits errors to the console when precompilation fails', async () => { 48 | fs.ensureDirSync('app'); 49 | fs.writeFileSync('app/test-file.ts', `export const testString: string = {};`); 50 | 51 | let output = ''; 52 | let unhookStdout = hook(process.stdout, { quiet: true }, (chunk) => (output += chunk)); 53 | let unhookStderr = hook(process.stderr, { quiet: true }, (chunk) => (output += chunk)); 54 | try { 55 | await ember(['ts:precompile']); 56 | expect.fail('Precompilation should have failed'); 57 | } catch { 58 | expect(output).to.include(`Type '{}' is not assignable to type 'string'.`); 59 | } finally { 60 | unhookStdout(); 61 | unhookStderr(); 62 | } 63 | }); 64 | 65 | describe('custom project layout', function () { 66 | it('generates .d.ts files from the specified source tree', async () => { 67 | fs.ensureDirSync('src'); 68 | fs.writeFileSync('src/test-file.ts', `export const testString: string = 'hello';`); 69 | 70 | let pkg = fs.readJsonSync('package.json'); 71 | let tsconfig = ts.readConfigFile('tsconfig.json', ts.sys.readFile).config; 72 | tsconfig.include.push('src'); 73 | tsconfig.compilerOptions.paths[`${pkg.name}/src/*`] = ['src/*']; 74 | fs.writeJSONSync('tsconfig.json', tsconfig); 75 | 76 | await ember(['ts:precompile']); 77 | 78 | let declaration = file('src/test-file.d.ts'); 79 | expect(declaration).to.exist; 80 | expect(declaration.content.trim()).to.equal( 81 | `export declare const testString: string;${EOL}//# sourceMappingURL=test-file.d.ts.map` 82 | ); 83 | }); 84 | }); 85 | 86 | describe('remapped addon-test-support', function () { 87 | it('generates .d.ts files in the mapped location', async () => { 88 | fs.ensureDirSync('addon-test-support'); 89 | fs.writeFileSync( 90 | 'addon-test-support/test-file.ts', 91 | `export const testString: string = 'hello';` 92 | ); 93 | 94 | let pkg = fs.readJsonSync('package.json'); 95 | let tsconfig = ts.readConfigFile('tsconfig.json', ts.sys.readFile).config; 96 | tsconfig.include.push('src'); 97 | tsconfig.compilerOptions.paths[`${pkg.name}/*`] = ['addon-test-support/*']; 98 | fs.writeJSONSync('tsconfig.json', tsconfig); 99 | 100 | await ember(['ts:precompile']); 101 | 102 | let declaration = file('test-file.d.ts'); 103 | expect(declaration).to.exist; 104 | expect(declaration.content.trim()).to.equal( 105 | `export declare const testString: string;${EOL}//# sourceMappingURL=test-file.d.ts.map` 106 | ); 107 | }); 108 | }); 109 | 110 | it('generates .d.ts files when addon and package names do not match', function () { 111 | fs.ensureDirSync('addon-test-support'); 112 | fs.writeFileSync( 113 | 'addon-test-support/test-file.ts', 114 | `export const testString: string = 'hello';` 115 | ); 116 | fs.writeFileSync('index.js', `module.exports = { name: 'my-addon' };`); 117 | 118 | const pkg = fs.readJSONSync('package.json'); 119 | pkg.name = '@foo/my-addon'; // addon `name` is `my-addon` 120 | fs.writeJSONSync('package.json', pkg); 121 | 122 | // CAUTION! HACKY CODE AHEAD! 123 | // The ember blueprint helper stays in the same node process, so require 124 | // keeps any previously read files cached. We need to clear out these 125 | // caches so it picks up the changes properly. 126 | delete require.cache[path.join(process.cwd(), 'index.js')]; 127 | delete require.cache[path.join(process.cwd(), 'package.json')]; 128 | 129 | return ember(['ts:precompile']).then(() => { 130 | const declaration = file('test-support/test-file.d.ts'); 131 | expect(declaration).to.exist; 132 | expect(declaration.content.trim()).to.equal( 133 | `export declare const testString: string;${EOL}//# sourceMappingURL=test-file.d.ts.map` 134 | ); 135 | }); 136 | }); 137 | 138 | it('generates .d.ts files for components when addon and moduleName do not match', function () { 139 | fs.ensureDirSync('addon/components'); 140 | fs.writeFileSync( 141 | 'addon/components/my-component.ts', 142 | `export const testString: string = 'hello';` 143 | ); 144 | fs.ensureDirSync('addon-test-support'); 145 | fs.writeFileSync( 146 | 'addon-test-support/test-file.ts', 147 | `export const anotherTestString: string = 'hello';` 148 | ); 149 | fs.writeFileSync( 150 | 'index.js', 151 | `module.exports = { name: '@foo/my-addon', moduleName() { return 'my-addon'; } };` 152 | ); 153 | 154 | const pkg = fs.readJSONSync('package.json'); 155 | pkg.name = '@foo/my-addon'; // addon `moduleName()` is `my-addon` 156 | fs.writeJSONSync('package.json', pkg); 157 | 158 | // CAUTION! HACKY CODE AHEAD! 159 | // The ember blueprint helper stays in the same node process, so require 160 | // keeps any previously read files cached. We need to clear out these 161 | // caches so it picks up the changes properly. 162 | delete require.cache[path.join(process.cwd(), 'index.js')]; 163 | delete require.cache[path.join(process.cwd(), 'package.json')]; 164 | 165 | return ember(['ts:precompile']).then(() => { 166 | const componentDecl = file('components/my-component.d.ts'); 167 | expect(componentDecl).to.exist; 168 | expect(componentDecl.content.trim()).to.equal( 169 | `export declare const testString: string;${EOL}//# sourceMappingURL=my-component.d.ts.map` 170 | ); 171 | 172 | const testSupportDecl = file('test-support/test-file.d.ts'); 173 | expect(testSupportDecl).to.exist; 174 | expect(testSupportDecl.content.trim()).to.equal( 175 | `export declare const anotherTestString: string;${EOL}//# sourceMappingURL=test-file.d.ts.map` 176 | ); 177 | }); 178 | }); 179 | }); 180 | -------------------------------------------------------------------------------- /ts/tests/helpers/skeleton-app.mts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'node:path'; 3 | import got from 'got'; 4 | import execa from 'execa'; 5 | import { EventEmitter } from 'events'; 6 | 7 | import debugLib from 'debug'; 8 | import rimraf from 'rimraf'; 9 | 10 | const debug = debugLib('skeleton-app'); 11 | 12 | // Workaround for CSJ -> ESM transition. 13 | import { createRequire } from 'module'; 14 | import { fileURLToPath } from 'url'; 15 | 16 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 17 | const require = createRequire(import.meta.url); 18 | 19 | const getEmberPort = (() => { 20 | let lastPort = 4210; 21 | return () => lastPort++; 22 | })(); 23 | 24 | interface EmberCliOptions { 25 | args?: string[]; 26 | env?: Record | undefined; 27 | } 28 | 29 | export default class SkeletonApp { 30 | port = getEmberPort(); 31 | watched: WatchedEmberProcess | null = null; 32 | cleanupTempDir = () => rimraf(this.root, (error) => error && console.error(error)); 33 | root = path.join(process.cwd(), `test-skeleton-app-${Math.random().toString(36).slice(2)}`); 34 | 35 | constructor() { 36 | fs.mkdirSync(this.root); 37 | fs.copySync(`${__dirname}/../../../test-fixtures/skeleton-app`, this.root); 38 | process.on('beforeExit', this.cleanupTempDir); 39 | } 40 | 41 | build({ args = [], env }: EmberCliOptions = {}) { 42 | return this._ember({ args: ['build', ...args], env }); 43 | } 44 | 45 | test({ args = [], env }: EmberCliOptions = {}) { 46 | return this._ember({ args: ['test', '--test-port', `${this.port}`, ...args], env }); 47 | } 48 | 49 | serve({ args = [], env }: EmberCliOptions = {}) { 50 | if (this.watched) { 51 | throw new Error('Already serving'); 52 | } 53 | 54 | let childProcess = this._ember({ args: ['serve', '--port', `${this.port}`, ...args], env }); 55 | 56 | return (this.watched = new WatchedEmberProcess(childProcess, this.port)); 57 | } 58 | 59 | updatePackageJSON(callback: (arg: any) => any) { 60 | let pkgPath = `${this.root}/package.json`; 61 | let pkg = fs.readJSONSync(pkgPath); 62 | fs.writeJSONSync(pkgPath, callback(pkg) || pkg, { spaces: 2 }); 63 | } 64 | 65 | writeFile(filePath: string, contents: string) { 66 | let fullPath = `${this.root}/${filePath}`; 67 | fs.ensureDirSync(path.dirname(fullPath)); 68 | fs.writeFileSync(fullPath, contents, 'utf-8'); 69 | } 70 | 71 | readFile(path: string) { 72 | return fs.readFileSync(`${this.root}/${path}`, 'utf-8'); 73 | } 74 | 75 | removeFile(path: string) { 76 | return fs.unlinkSync(`${this.root}/${path}`); 77 | } 78 | 79 | teardown() { 80 | if (this.watched) { 81 | this.watched.kill(); 82 | } 83 | 84 | this.cleanupTempDir(); 85 | process.off('beforeExit', this.cleanupTempDir); 86 | } 87 | 88 | _ember({ args, env = {} }: EmberCliOptions) { 89 | let ember = require.resolve('ember-cli/bin/ember'); 90 | return execa.node(ember, args, { cwd: this.root, all: true, env }); 91 | } 92 | } 93 | 94 | class WatchedEmberProcess extends EventEmitter { 95 | constructor(protected ember: execa.ExecaChildProcess, protected port: number) { 96 | super(); 97 | this.ember.stdout?.on('data', (data) => { 98 | let output = data.toString(); 99 | if (output.includes('Build successful')) { 100 | this.emit('did-rebuild'); 101 | } 102 | 103 | debug(output); 104 | }); 105 | 106 | this.ember.stderr?.on('data', (data) => { 107 | debug(data.toString()); 108 | }); 109 | 110 | this.ember.catch((error) => { 111 | this.emit('did-error', error); 112 | }); 113 | } 114 | 115 | request(path: string) { 116 | return got(`http://localhost:${this.port}${path}`); 117 | } 118 | 119 | waitForOutput(target: string) { 120 | return new Promise((resolve) => { 121 | let output = ''; 122 | let listener = (data: string | Buffer) => { 123 | output += data.toString(); 124 | if (output.includes(target)) { 125 | this.ember.stdout?.removeListener('data', listener); 126 | this.ember.stderr?.removeListener('data', listener); 127 | resolve(output); 128 | } 129 | }; 130 | 131 | this.ember.stdout?.on('data', listener); 132 | this.ember.stderr?.on('data', listener); 133 | }); 134 | } 135 | 136 | waitForBuild() { 137 | return new Promise((resolve, reject) => { 138 | this.once('did-rebuild', resolve); 139 | this.once('did-error', reject); 140 | }); 141 | } 142 | 143 | kill() { 144 | this.ember.kill(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /ts/tests/helpers/stash-published-version.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | 3 | const PACKAGE_PATH = 'node_modules/ember-cli-typescript'; 4 | 5 | function isNodeError(e: unknown): e is NodeJS.ErrnoException { 6 | // Not full-proof but good enough for our purposes. 7 | return e instanceof Error && 'code' in e; 8 | } 9 | 10 | function handleError(e: unknown) { 11 | // Ignore the case where we just don't have the files to move. (And hope this 12 | // works correctly?) 13 | if (!isNodeError(e) || e.code !== 'ENOENT') { 14 | throw e; 15 | } 16 | } 17 | 18 | /** 19 | * We have assorted devDependencies that themselves depend on `ember-cli-typescript`. 20 | * This means we have a published copy of the addon present in `node_modules` normally, 21 | * though `ember-cli-blueprint-test-helpers` fiddles around in there as well. 22 | * 23 | * This helper ensures we move the published version of the addon out of the way into 24 | * an inert location during the enclosing `describe` block and put it back afterwards. 25 | */ 26 | export function setupPublishedVersionStashing(hooks: Mocha.Suite): void { 27 | hooks.beforeAll(async () => { 28 | fs.move(PACKAGE_PATH, `${PACKAGE_PATH}.published`).catch(); 29 | try { 30 | await fs.move(PACKAGE_PATH, `${PACKAGE_PATH}.published`); 31 | } catch (e: unknown) { 32 | handleError(e); 33 | } 34 | }); 35 | 36 | hooks.afterAll(async () => { 37 | try { 38 | await fs.move(`${PACKAGE_PATH}.published`, PACKAGE_PATH); 39 | } catch (e) { 40 | handleError(e); 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /ts/tests/unit/copy-declarations-test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | import { expect } from 'chai'; 3 | import os from 'os'; 4 | import fs from 'fs-extra'; 5 | import path from 'path'; 6 | import copyDeclarations from '../../lib/utilities/copy-declarations'; 7 | import fixturify from 'fixturify'; 8 | 9 | describe('Unit: copyDeclarations', function () { 10 | it('copies generated declarations for the correct package', function () { 11 | let { createdNodes, outputTree } = runCopy({ 12 | packageName: 'my-package', 13 | paths: { 14 | 'dummy/*': ['app/*', 'tests/dummy/app/*'], 15 | 'my-package/*': ['addon/*'], 16 | }, 17 | input: { 18 | addon: { 19 | 'index.d.ts': 'export declare const foo: string', 20 | }, 21 | app: { 22 | 'app.d.ts': 'ignore me please', 23 | }, 24 | }, 25 | }); 26 | 27 | expect(createdNodes).to.deep.equal(['index.d.ts']); 28 | expect(outputTree).to.deep.equal({ 29 | 'index.d.ts': 'export declare const foo: string', 30 | }); 31 | }); 32 | 33 | it('copies and merges generated declarations from subdirectories', function () { 34 | let { createdNodes, outputTree } = runCopy({ 35 | packageName: 'my-package', 36 | paths: { 37 | 'my-package/*': ['addon/*'], 38 | 'my-package/src/*': ['src/*'], 39 | }, 40 | input: { 41 | addon: { 42 | 'index.d.ts': 'export declare const foo: string', 43 | }, 44 | src: { 45 | 'file.d.ts': 'µ', 46 | }, 47 | }, 48 | }); 49 | 50 | expect(createdNodes).to.deep.equal(['index.d.ts', 'src', 'src/file.d.ts']); 51 | 52 | expect(outputTree).to.deep.equal({ 53 | 'index.d.ts': 'export declare const foo: string', 54 | src: { 55 | 'file.d.ts': 'µ', 56 | }, 57 | }); 58 | }); 59 | 60 | it('merges declarations from source with generated ones', function () { 61 | let { createdNodes, outputTree } = runCopy({ 62 | packageName: 'my-package', 63 | pathRoots: ['addon-files', 'generated-declarations'], 64 | paths: { 65 | 'my-package/*': ['addon/*'], 66 | }, 67 | input: { 68 | 'addon-files': { 69 | addon: { 70 | 'file.d.ts': 'declaration for file in addon', 71 | 'file.js': 'js source for file', 72 | 'addon-file.d.ts': 'declaration for addon-file', 73 | }, 74 | }, 75 | 'generated-declarations': { 76 | addon: { 77 | 'file.d.ts': 'declaration for file in generated declarations', 78 | 'generated-file.d.ts': 'declaration for generated-file', 79 | }, 80 | }, 81 | }, 82 | }); 83 | 84 | expect(createdNodes).to.deep.equal(['addon-file.d.ts', 'file.d.ts', 'generated-file.d.ts']); 85 | 86 | expect(outputTree).to.deep.equal({ 87 | 'addon-file.d.ts': 'declaration for addon-file', 88 | 'file.d.ts': 'declaration for file in addon', 89 | 'generated-file.d.ts': 'declaration for generated-file', 90 | }); 91 | }); 92 | 93 | it('ignores non-declaration files', function () { 94 | let { createdNodes, outputTree } = runCopy({ 95 | packageName: 'my-package', 96 | paths: { 97 | 'my-package/*': ['addon/*'], 98 | }, 99 | input: { 100 | addon: { 101 | 'file.d.ts': 'hello', 102 | 'style.css': 'ignore me please', 103 | }, 104 | }, 105 | }); 106 | 107 | expect(createdNodes).to.deep.equal(['file.d.ts']); 108 | expect(outputTree).to.deep.equal({ 109 | 'file.d.ts': 'hello', 110 | }); 111 | }); 112 | 113 | it('rejects invalid path mappings', function () { 114 | expect(() => 115 | runCopy({ 116 | packageName: 'my-package', 117 | input: {}, 118 | paths: { 119 | 'my-package/*': ['addon'], 120 | }, 121 | }) 122 | ).to.throw("Missing trailing '*' in path mapping: addon"); 123 | }); 124 | }); 125 | 126 | function runCopy(options: { 127 | packageName: string; 128 | pathRoots?: string[]; 129 | paths: Record; 130 | input: fixturify.DirJSON; 131 | }) { 132 | let tmpdir = `${os.tmpdir()}/e-c-tests`; 133 | let inputBaseDir = `${tmpdir}/compiled`; 134 | let outputBaseDir = `${tmpdir}/output`; 135 | let pathRoots = (options.pathRoots || ['.']).map((dir) => path.resolve(inputBaseDir, dir)); 136 | 137 | fs.ensureDirSync(inputBaseDir); 138 | fs.ensureDirSync(outputBaseDir); 139 | 140 | fixturify.writeSync(inputBaseDir, options.input); 141 | 142 | let absoluteCopiedFiles = copyDeclarations( 143 | pathRoots, 144 | options.paths, 145 | options.packageName, 146 | outputBaseDir 147 | ); 148 | let createdNodes = absoluteCopiedFiles.map((copiedFile) => 149 | path.relative(outputBaseDir, copiedFile).replace(/\\/g, '/') 150 | ); 151 | let outputTree = fixturify.readSync(outputBaseDir); 152 | 153 | fs.removeSync(tmpdir); 154 | 155 | return { createdNodes, outputTree }; 156 | } 157 | -------------------------------------------------------------------------------- /ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "module": "Node16", 5 | "moduleResolution": "node16", 6 | "allowJs": true, 7 | "noEmitOnError": true, 8 | "noEmit": true, 9 | "noImplicitAny": true, 10 | "noImplicitThis": true, 11 | "strict": true, 12 | "strictNullChecks": true, 13 | "strictPropertyInitialization": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "outDir": "../js", 17 | "baseUrl": ".", 18 | "paths": { 19 | "*": [ 20 | "./types/*" 21 | ] 22 | }, 23 | "esModuleInterop": true, 24 | // We *really* don't want this on, but our hand is forced somewhat at the 25 | // moment: the types for `console-ui` are correct for consuming `inquirer` 26 | // in the pre-Node16 world, but don't interoperate correctly when consumed 27 | // by a CJS-default package in the TS Node16+ world *and* consume an ESM 28 | // package. Our path forward there is to update `console-ui` to publish a 29 | // dual-mode package with types and runtime code to support it. 30 | "skipLibCheck": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ts/types/broccoli-stew/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'broccoli-stew' { 2 | import { Node as BroccoliNode } from 'broccoli-node-api'; 3 | import Plugin from 'broccoli-plugin'; 4 | 5 | export function afterBuild(node: BroccoliNode, callback: (this: Plugin) => void): BroccoliNode; 6 | } 7 | -------------------------------------------------------------------------------- /ts/types/ember-cli-blueprint-test-helpers/index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Chai { 2 | interface ChaiStatic { 3 | file(name: string): { content: string }; 4 | } 5 | } 6 | 7 | declare module 'ember-cli-blueprint-test-helpers/chai' { 8 | import 'chai-as-prsomised'; 9 | import * as chai from 'chai'; 10 | export = chai; 11 | } 12 | declare module 'ember-cli-blueprint-test-helpers/helpers'; 13 | declare module 'ember-cli-blueprint-test-helpers/lib/helpers/ember'; 14 | -------------------------------------------------------------------------------- /ts/types/ember-cli-preprocess-registry/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'ember-cli-preprocess-registry' { 2 | import { Node as BroccoliNode } from 'broccoli-node-api'; 3 | 4 | export = PreprocessRegistry; 5 | 6 | class PreprocessRegistry { 7 | add(type: string, plugin: PreprocessPlugin): void; 8 | load(type: string): Array; 9 | extensionsForType(type: string): Array; 10 | remove(type: string, plugin: PreprocessPlugin): void; 11 | } 12 | 13 | interface PreprocessPlugin { 14 | name: string; 15 | toTree(input: BroccoliNode, path: string): BroccoliNode; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ts/types/ember-cli/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'ember-cli/lib/broccoli/ember-app' { 2 | import CoreObject from 'core-object'; 3 | 4 | export default class EmberApp extends CoreObject { 5 | options: Record; 6 | } 7 | } 8 | 9 | declare module 'ember-cli/lib/models/addon' { 10 | import CoreObject, { ExtendOptions } from 'core-object'; 11 | import UI from 'console-ui'; 12 | import { Application } from 'express'; 13 | import Project from 'ember-cli/lib/models/project'; 14 | import { TaskOptions } from 'ember-cli/lib/models/task'; 15 | import Command from 'ember-cli/lib/models/command'; 16 | import EmberApp from 'ember-cli/lib/broccoli/ember-app'; 17 | import PreprocessRegistry from 'ember-cli-preprocess-registry'; 18 | 19 | export default class Addon extends CoreObject { 20 | name: string; 21 | root: string; 22 | app?: EmberApp; 23 | parent: Addon | Project; 24 | project: Project; 25 | addons: Addon[]; 26 | ui: UI; 27 | options?: Record; 28 | pkg: { 29 | name: string; 30 | version: string; 31 | dependencies?: Record; 32 | devDependencies?: Record; 33 | }; 34 | 35 | blueprintsPath(): string; 36 | included(includer: EmberApp | Project): void; 37 | includedCommands(): Record> | void; 38 | shouldIncludeChildAddon(addon: Addon): boolean; 39 | isDevelopingAddon(): boolean; 40 | serverMiddleware(options: { app: Application; options?: TaskOptions }): void | Promise; 41 | testemMiddleware(app: Application, options?: TaskOptions): void; 42 | setupPreprocessorRegistry(type: 'self' | 'parent', registry: PreprocessRegistry): void; 43 | moduleName(): string; 44 | } 45 | } 46 | 47 | declare module 'ember-cli/lib/models/blueprint' { 48 | class Blueprint { 49 | taskFor(taskName: string): void; 50 | } 51 | export = Blueprint; 52 | } 53 | 54 | declare module 'ember-cli/lib/models/task' { 55 | export interface TaskOptions { 56 | path?: string; 57 | } 58 | } 59 | 60 | declare module 'ember-cli/lib/models/command' { 61 | import CoreObject from 'core-object'; 62 | import UI from 'console-ui'; 63 | import Project from 'ember-cli/lib/models/project'; 64 | 65 | interface CommandOption { 66 | name: string; 67 | type: unknown; 68 | description?: string; 69 | required?: boolean; 70 | default?: unknown; 71 | aliases?: string[]; 72 | } 73 | 74 | export default class Command extends CoreObject { 75 | name: string; 76 | works: 'insideProject' | 'outsideProject' | 'everywhere'; 77 | description: string; 78 | availableOptions: CommandOption[]; 79 | anonymousOptions: string[]; 80 | 81 | ui: UI; 82 | project: Project; 83 | 84 | run(options: {}): void | Promise; 85 | } 86 | } 87 | 88 | declare module 'ember-cli/lib/models/project' { 89 | import CoreObject from 'core-object'; 90 | import UI from 'console-ui'; 91 | import Addon from 'ember-cli/lib/models/addon'; 92 | 93 | export default class Project extends CoreObject { 94 | ui: UI; 95 | root: string; 96 | addons: Addon[]; 97 | pkg: { 98 | name: string; 99 | version: string; 100 | dependencies?: Record; 101 | devDependencies?: Record; 102 | }; 103 | 104 | name(): string; 105 | isEmberCLIAddon(): boolean; 106 | require(module: string): unknown; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "allowJs": false, 5 | "module": "Node16", 6 | "moduleResolution": "Node16", 7 | "noEmitOnError": true, 8 | "inlineSourceMap": true, 9 | "inlineSources": true, 10 | "experimentalDecorators": true, 11 | "strictNullChecks": true, 12 | "baseUrl": ".", 13 | "paths": { 14 | "dummy/tests/*": [ 15 | "tests/*", 16 | "tests/dummy/lib/in-repo-a/test-support/*" 17 | ], 18 | "dummy/*": [ 19 | "tests/dummy/app/*", 20 | "tests/dummy/lib/in-repo-a/app/*", 21 | "tests/dummy/lib/in-repo-b/app/*" 22 | ], 23 | "in-repo-a/*": [ 24 | "tests/dummy/lib/in-repo-a/addon/*" 25 | ], 26 | "in-repo-a/test-support": [ 27 | "tests/dummy/lib/in-repo-a/addon-test-support/" 28 | ], 29 | "in-repo-a/test-support/*": [ 30 | "tests/dummy/lib/in-repo-a/addon-test-support/*" 31 | ], 32 | "in-repo-b/*": [ 33 | "tests/dummy/lib/in-repo-b/addon/*" 34 | ] 35 | }, 36 | // We *really* don't want this on, but our hand is forced somewhat at the 37 | // moment: the types for `console-ui` are correct for consuming `inquirer` 38 | // in the pre-Node16 world, but don't interoperate correctly when consumed 39 | // by a CJS-default package in the TS Node16+ world *and* consume an ESM 40 | // package. Our path forward there is to update `console-ui` to publish a 41 | // dual-mode package with types and runtime code to support it. 42 | "skipLibCheck": true 43 | }, 44 | "include": [ 45 | "tests" 46 | ], 47 | "exclude": [ 48 | "tests/dummy/app/snippets" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typed-ember/ember-cli-typescript/27637e095bac8e3fadea8a142977cd0212b30701/vendor/.gitkeep --------------------------------------------------------------------------------