├── .all-contributorsrc ├── .eslintignore ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── dependabot.yml ├── stale.yml └── workflows │ ├── documentation.yml.bak │ └── nodejs.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .npmrc ├── .vscode └── launch.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── commitlint.config.js ├── conf └── eslint-config-react-app │ ├── LICENSE │ ├── README.md │ ├── base.js │ ├── index.js │ ├── jest.js │ └── package.json ├── docs ├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .nextra │ └── search.js ├── components │ ├── features.js │ ├── features.module.css │ ├── logo.js │ └── release.js ├── next.config.js ├── package.json ├── pages │ ├── _app.jsx │ ├── api-reference.mdx │ ├── change-log.mdx │ ├── customization.mdx │ ├── index.mdx │ └── optimization.mdx ├── public │ └── favicon.ico ├── utils │ └── custom-mdx-patch.js └── yarn.lock ├── jest.config.js ├── package.json ├── renovate.json ├── src ├── babelPluginDts.ts ├── constants.ts ├── createBuildConfigs.ts ├── createEslintConfig.ts ├── createJestConfig.ts ├── createProgressEstimator.ts ├── createRollupConfig.ts ├── defaults.ts ├── deprecated.ts ├── env.d.ts ├── errors │ ├── evalToString.ts │ ├── extractErrors.ts │ ├── invertObject.ts │ └── transformErrorMessages.ts ├── getInstallArgs.ts ├── getInstallCmd.ts ├── index.ts ├── logError.ts ├── messages.ts ├── output.ts ├── rollupTypes.ts ├── templates │ ├── basic.ts │ ├── index.ts │ ├── react-with-storybook.ts │ ├── react.ts │ ├── template.d.ts │ └── utils │ │ └── index.ts ├── tsconfig.ts ├── types.ts └── utils.ts ├── templates ├── basic │ ├── .github │ │ └── workflows │ │ │ ├── main.yml │ │ │ └── size.yml │ ├── LICENSE │ ├── README.md │ ├── gitattributes │ ├── gitignore │ ├── src │ │ └── index.ts │ ├── test │ │ └── index.test.ts │ └── tsconfig.json ├── react-with-storybook │ ├── .github │ │ └── workflows │ │ │ ├── main.yml │ │ │ └── size.yml │ ├── .storybook │ │ ├── main.ts │ │ └── preview.ts │ ├── LICENSE │ ├── README.md │ ├── example │ │ ├── .gitignore │ │ ├── index.html │ │ ├── index.tsx │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── vite.config.js │ ├── gitattributes │ ├── gitignore │ ├── src │ │ └── index.tsx │ ├── stories │ │ └── Thing.stories.tsx │ ├── test │ │ └── index.test.tsx │ └── tsconfig.json └── react │ ├── .github │ └── workflows │ │ ├── main.yml │ │ └── size.yml │ ├── LICENSE │ ├── README.md │ ├── example │ ├── .gitignore │ ├── index.html │ ├── index.tsx │ ├── package.json │ ├── tsconfig.json │ └── vite.config.js │ ├── gitattributes │ ├── gitignore │ ├── src │ └── index.tsx │ ├── test │ └── index.test.tsx │ └── tsconfig.json ├── test ├── README.md ├── e2e │ ├── dts-build-default.test.ts │ ├── dts-build-invalid.test.ts │ ├── dts-build-multipleEntries.test.ts │ ├── dts-build-options.test.ts │ ├── dts-build-withTsconfig.test.ts │ ├── dts-build-withTypesRollup.test.ts │ ├── dts-lint.test.ts │ └── fixtures │ │ ├── README.md │ │ ├── build-default │ │ ├── package.json │ │ ├── package2.json │ │ ├── src │ │ │ ├── index.ts │ │ │ ├── returnsTrue.ts │ │ │ └── syntax │ │ │ │ ├── async.ts │ │ │ │ ├── generator.ts │ │ │ │ ├── jsx-import │ │ │ │ ├── JSX-A.jsx │ │ │ │ ├── JSX-B.jsx │ │ │ │ └── JSX-import-JSX.jsx │ │ │ │ ├── nullish-coalescing.ts │ │ │ │ └── optional-chaining.ts │ │ ├── test │ │ │ ├── some-test.test.ts │ │ │ └── testUtil.ts │ │ ├── tsconfig.json │ │ └── types │ │ │ └── blar.d.ts │ │ ├── build-invalid │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ └── tsconfig.json │ │ ├── build-multipleEntries │ │ ├── package.json │ │ ├── src │ │ │ ├── index.ts │ │ │ ├── returnsFalse.ts │ │ │ └── returnsTrue.ts │ │ └── tsconfig.json │ │ ├── build-withTsconfig │ │ ├── package.json │ │ ├── src │ │ │ ├── index.ts │ │ │ └── tsconfig.json │ │ ├── tsconfig.base.json │ │ └── tsconfig.json │ │ ├── build-withTypesRollup │ │ ├── package.json │ │ ├── src │ │ │ ├── bar │ │ │ │ └── bar.ts │ │ │ ├── foo │ │ │ │ └── foo.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ │ └── lint │ │ ├── file-with-lint-errors.ts │ │ ├── file-with-lint-warnings.ts │ │ ├── file-with-prettier-lint-errors.ts │ │ ├── file-without-lint-error.ts │ │ ├── react-file-with-lint-errors.tsx │ │ └── react-file-without-lint-error.tsx ├── integration │ ├── dts-build-options.test.ts │ ├── dts-build-withBabel.test.ts │ ├── dts-build-withConfig-defineConfig.test.ts │ ├── dts-build-withConfig.test.ts │ ├── dts-build-withConfigTs-defineConfig.test.ts │ ├── dts-build-withConfigTs-noRollup.test.ts │ ├── dts-build-withConfigTs.test.ts │ └── fixtures │ │ ├── README.md │ │ ├── build-options │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ └── tsconfig.json │ │ ├── build-withBabel │ │ ├── .babelrc.js │ │ ├── package.json │ │ ├── src │ │ │ ├── index.ts │ │ │ └── styled.tsx │ │ ├── test-babel-preset.js │ │ └── tsconfig.json │ │ ├── build-withConfig-defineConfig │ │ ├── dts.config.js │ │ ├── package.json │ │ ├── src │ │ │ ├── index.css │ │ │ └── index.ts │ │ └── tsconfig.json │ │ ├── build-withConfig │ │ ├── dts.config.js │ │ ├── package.json │ │ ├── src │ │ │ ├── index.css │ │ │ └── index.ts │ │ └── tsconfig.json │ │ ├── build-withConfigTs-defineConfig │ │ ├── dts.config.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.css │ │ │ └── index.ts │ │ └── tsconfig.json │ │ ├── build-withConfigTs-noRollup │ │ ├── dts.config.ts │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ └── tsconfig.json │ │ └── build-withConfigTs │ │ ├── dts.config.ts │ │ ├── package.json │ │ ├── src │ │ ├── index.css │ │ └── index.ts │ │ └── tsconfig.json ├── unit │ └── utils-safePackageName.test.ts └── utils │ ├── fixture.ts │ └── shell.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | dist/ 3 | /docs 4 | # fixtures/ 5 | /conf/eslint-config-react-app/ 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | /test/{e2e,integration}/fixtures/**/* -text -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Current Behavior 11 | 12 | 13 | 14 | ### Expected behavior 15 | 16 | 17 | 18 | ### Suggested solution(s) 19 | 20 | 21 | 22 | ### Additional context 23 | 24 | 25 | 26 | ### Your environment 27 | 28 | 35 | 36 | ```text 37 | 38 | ``` 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Current Behavior 11 | 12 | 13 | 14 | ### Desired Behavior 15 | 16 | 17 | 18 | ### Suggested Solution 19 | 20 | 21 | 22 | 23 | 24 | ### Who does this impact? Who is this for? 25 | 26 | 27 | 28 | ### Describe alternatives you've considered 29 | 30 | 31 | 32 | ### Additional context 33 | 34 | 35 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | # configuration for / 5 | - package-ecosystem: npm 6 | directory: '/' 7 | schedule: 8 | interval: weekly # don't spam daily 9 | commit-message: 10 | prefix: 'deps:' # prefix commit with deps: for consistency 11 | # only increase version when required, don't bump every patch or minor 12 | versioning-strategy: increase-if-necessary 13 | allow: 14 | # only upgrade prod deps (not devDeps) 15 | - dependency-name: '*' 16 | dependency-type: production 17 | ignore: 18 | # ignore eslint-config-react-app's peerDeps 19 | - dependency-name: '@typescript-eslint/eslint-plugin' 20 | - dependency-name: '@typescript-eslint/parser' 21 | - dependency-name: 'babel-eslint' 22 | - dependency-name: 'eslint-plugin-flowtype' 23 | - dependency-name: 'eslint-plugin-import' 24 | - dependency-name: 'eslint-plugin-jsx-a11y' 25 | - dependency-name: 'eslint-plugin-react' 26 | - dependency-name: 'eslint-plugin-react-hooks' 27 | # ignore Jest's "peers" that should be upgraded simultaneously with Jest 28 | - dependency-name: '@types/jest' 29 | - dependency-name: 'jest-watch-typeahead' 30 | - dependency-name: 'ts-jest' 31 | # temporarily disable dep upgrade PRs for / as they're being updated 32 | open-pull-requests-limit: 0 33 | 34 | # configuration for /website 35 | - package-ecosystem: npm 36 | directory: /website 37 | schedule: 38 | interval: weekly # don't spam daily 39 | commit-message: 40 | prefix: 'deps:' # prefix commit with deps: for consistency 41 | # only increase version when required, don't bump every patch or minor 42 | versioning-strategy: increase-if-necessary 43 | allow: 44 | # only upgrade prod deps (not devDeps) 45 | - dependency-name: '*' 46 | dependency-type: production 47 | # /website is not a published package and doesn't really have an attack 48 | # surface area, should only be updated as needed, not as soon as deps change 49 | ignore: 50 | # no security PRs for /website 51 | - dependency-name: '*' 52 | # disable dep upgrade PRs for /website 53 | open-pull-requests-limit: 0 54 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 3600 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - enhancement 8 | - pinned 9 | - RFC 10 | - bug 11 | - in progress 12 | - 2.0 13 | # Label to use when marking an issue as stale 14 | staleLabel: stale 15 | # Comment to post when marking an issue as stale. Set to `false` to disable 16 | markComment: false 17 | # Comment to post when closing a stale issue. Set to `false` to disable 18 | closeComment: false 19 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml.bak: -------------------------------------------------------------------------------- 1 | name: GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-20.04 12 | defaults: 13 | run: 14 | working-directory: docs 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.ref }} 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Setup Node 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: '14' 24 | 25 | - name: Cache dependencies 26 | uses: actions/cache@v3 27 | with: 28 | path: ~/.npm 29 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 30 | restore-keys: | 31 | ${{ runner.os }}-node- 32 | 33 | - run: npm install 34 | - run: npm run build 35 | - run: npm run export 36 | 37 | - name: Deploy 38 | uses: peaceiris/actions-gh-pages@v3 39 | if: ${{ github.ref == 'refs/heads/main' }} 40 | with: 41 | github_token: ${{ secrets.GITHUB_TOKEN }} 42 | publish_dir: ./docs/out 43 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | push: 7 | branches: [main] 8 | 9 | jobs: 10 | lint-and-dedupe: 11 | runs-on: ubuntu-latest 12 | 13 | name: Lint & Deduplicate deps on node 18.x and ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout repo 17 | uses: actions/checkout@v4 18 | - name: Use Node 18.x 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: 18.x 22 | 23 | - name: Install deps and build (with cache) 24 | run: npm install && npm run build 25 | 26 | - name: Lint codebase 27 | run: npm run lint:post-build 28 | 29 | test: 30 | name: Test on Node ${{ matrix.node }} and ${{ matrix.os }} 31 | 32 | runs-on: ${{ matrix.os }} 33 | strategy: 34 | matrix: 35 | node: ['18.x'] 36 | os: [ubuntu-latest, windows-latest, macOS-latest] 37 | include: 38 | - os: ubuntu-latest 39 | node: '16.x' 40 | - os: ubuntu-latest 41 | node: '19.x' 42 | steps: 43 | - name: Checkout repo 44 | uses: actions/checkout@v4 45 | - name: Use Node ${{ matrix.node }} 46 | uses: actions/setup-node@v4 47 | with: 48 | node-version: ${{ matrix.node }} 49 | 50 | - name: Install deps and build (with cache) 51 | run: npm install && npm run build 52 | 53 | - name: Test package 54 | run: npm run test:post-build 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | tester 6 | tester-react 7 | package-lock.json 8 | # Local Netlify folder 9 | .netlify 10 | 11 | # https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored 12 | .yarn/* 13 | !.yarn/patches 14 | !.yarn/releases 15 | !.yarn/plugins 16 | !.yarn/sdks 17 | !.yarn/versions 18 | .pnp.* 19 | 20 | # WebStorm project files 21 | .idea 22 | 23 | # tsc cache 24 | tsconfig.tsbuildinfo -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "" -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run build 5 | npx lint-staged -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /.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 | "name": "Launch via NPM", 9 | "request": "launch", 10 | "runtimeArgs": [ 11 | "run-script", 12 | "lint:post-build" 13 | ], 14 | "runtimeExecutable": "npm", 15 | "skipFiles": [ 16 | "/**" 17 | ], 18 | "type": "pwa-node" 19 | }, 20 | { 21 | "type": "pwa-node", 22 | "request": "launch", 23 | "name": "Launch Program", 24 | "skipFiles": [ 25 | "/**" 26 | ], 27 | "program": "${workspaceFolder}/node_modules/@rushstack/eslint-patch/lib/modern-module-resolution.js", 28 | "outFiles": [ 29 | "${workspaceFolder}/**/*.js" 30 | ] 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at hello@formium.io. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to DTS 2 | 3 | Thanks for your interest in DTS! You are very welcome to contribute. If you are proposing a new feature, make sure to [open an issue](https://github.com/weiran-zsd/dts-cli/issues/new/choose) to make sure it is inline with the project goals. 4 | 5 | ## Setup 6 | 7 | 0. First, remove any existing `dts-cli` global installations that may conflict. 8 | 9 | ``` 10 | yarn global remove dts-cli # or npm uninstall -g dts-cli 11 | ``` 12 | 13 | 1. Fork this repository to your own GitHub account and clone it to your local device: 14 | 15 | ``` 16 | git clone https://github.com/your-name/dts-cli.git 17 | cd dts-cli 18 | ``` 19 | 20 | 1. Install the dependencies and build the TypeScript files to JavaScript: 21 | 22 | ``` 23 | npm i && npm run build 24 | ``` 25 | 26 | > **Note:** you'll need to run `npm run build` any time you want to see your changes, or run `npm run watch` to leave it in watch mode. 27 | 28 | 1. Make it so running `dts` anywhere will run your local dev version: 29 | 30 | ``` 31 | yarn link 32 | ``` 33 | 34 | 4) To use your local version when running `npm run build`/`npm start`/`npm test` in a DTS project, run this in the project: 35 | 36 | ``` 37 | npm link dts-cli 38 | ``` 39 | 40 | You should see a success message: `success Using linked package for "dts".` The project will now use the locally linked version instead of a copy from `node_modules`. 41 | 42 | ## Submitting a PR 43 | 44 | Be sure to run `npm test` before you make your PR to make sure you haven't broken anything. 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jared Palmer https://jaredpalmer.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /conf/eslint-config-react-app/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013-present, Facebook, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /conf/eslint-config-react-app/README.md: -------------------------------------------------------------------------------- 1 | # eslint-config-react-app 2 | 3 | forked from eslint-config-react-app. the main changes: 4 | 5 | * removed @rushstack/eslint-patch, as it was breaking `dts lint`. 6 | * removed @babel/eslint-parser. -------------------------------------------------------------------------------- /conf/eslint-config-react-app/base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | 'use strict'; 9 | 10 | // removed it as it was breaking `dts lint` 11 | // Fix eslint shareable config (https://github.com/eslint/eslint/issues/3458) 12 | // require('@rushstack/eslint-patch/modern-module-resolution'); 13 | 14 | // This file contains the minimum ESLint configuration required for Create 15 | // React App support, and is used as the `baseConfig` for `eslint-loader` 16 | // to ensure that user-provided configs don't need this boilerplate. 17 | 18 | module.exports = { 19 | root: true, 20 | 21 | // parser: '@babel/eslint-parser', 22 | 23 | plugins: ['react'], 24 | 25 | env: { 26 | browser: true, 27 | commonjs: true, 28 | es6: true, 29 | jest: true, 30 | node: true, 31 | }, 32 | 33 | parserOptions: { 34 | sourceType: 'module', 35 | ecmaVersion: 'latest', 36 | ecmaFeatures: { 37 | jsx: true 38 | } 39 | }, 40 | 41 | settings: { 42 | react: { 43 | version: 'detect', 44 | }, 45 | }, 46 | 47 | rules: { 48 | 'react/jsx-uses-vars': 'warn', 49 | 'react/jsx-uses-react': 'warn', 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /conf/eslint-config-react-app/jest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | 'use strict'; 9 | 10 | // Fix eslint shareable config (https://github.com/eslint/eslint/issues/3458) 11 | // require('@rushstack/eslint-patch/modern-module-resolution'); 12 | 13 | // We use eslint-loader so even warnings are very visible. 14 | // This is why we prefer to use "WARNING" level for potential errors, 15 | // and we try not to use "ERROR" level at all. 16 | 17 | module.exports = { 18 | plugins: ['jest', 'testing-library'], 19 | overrides: [ 20 | { 21 | files: ['**/__tests__/**/*', '**/*.{spec,test}.*'], 22 | env: { 23 | 'jest/globals': true, 24 | }, 25 | // A subset of the recommended rules: 26 | rules: { 27 | // https://github.com/jest-community/eslint-plugin-jest 28 | 'jest/no-conditional-expect': 'error', 29 | 'jest/no-identical-title': 'error', 30 | 'jest/no-interpolation-in-snapshots': 'error', 31 | 'jest/no-jasmine-globals': 'error', 32 | 'jest/no-jest-import': 'error', 33 | 'jest/no-mocks-import': 'error', 34 | 'jest/valid-describe': 'error', 35 | 'jest/valid-expect': 'error', 36 | 'jest/valid-expect-in-promise': 'error', 37 | 'jest/valid-title': 'warn', 38 | 39 | // https://github.com/testing-library/eslint-plugin-testing-library 40 | 'testing-library/await-async-query': 'error', 41 | 'testing-library/await-async-utils': 'error', 42 | 'testing-library/no-await-sync-query': 'error', 43 | 'testing-library/no-container': 'error', 44 | 'testing-library/no-debugging-utils': 'error', 45 | 'testing-library/no-dom-import': ['error', 'react'], 46 | 'testing-library/no-node-access': 'error', 47 | 'testing-library/no-promise-in-fire-event': 'error', 48 | 'testing-library/no-render-in-setup': 'error', 49 | 'testing-library/no-unnecessary-act': 'error', 50 | 'testing-library/no-wait-for-empty-callback': 'error', 51 | 'testing-library/no-wait-for-multiple-assertions': 'error', 52 | 'testing-library/no-wait-for-side-effects': 'error', 53 | 'testing-library/no-wait-for-snapshot': 'error', 54 | 'testing-library/prefer-find-by': 'error', 55 | 'testing-library/prefer-presence-queries': 'error', 56 | 'testing-library/prefer-query-by-disappearance': 'error', 57 | 'testing-library/prefer-screen-queries': 'error', 58 | 'testing-library/render-result-naming-convention': 'error', 59 | }, 60 | }, 61 | ], 62 | }; 63 | -------------------------------------------------------------------------------- /conf/eslint-config-react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-react-app", 3 | "version": "6.0.0", 4 | "description": "ESLint configuration used by Create React App", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/facebook/create-react-app.git", 8 | "directory": "packages/eslint-config-react-app" 9 | }, 10 | "license": "MIT", 11 | "bugs": { 12 | "url": "https://github.com/facebook/create-react-app/issues" 13 | }, 14 | "files": [ 15 | "base.js", 16 | "index.js", 17 | "jest.js" 18 | ], 19 | "peerDependencies": { 20 | "eslint": "^8.0.0" 21 | }, 22 | "dependencies": { 23 | "@babel/core": "^7.24.3", 24 | "@babel/eslint-parser": "^7.24.1", 25 | "@rushstack/eslint-patch": "^1.10.1", 26 | "@typescript-eslint/eslint-plugin": "^5.62.0", 27 | "@typescript-eslint/parser": "^5.62.0", 28 | "babel-preset-react-app": "^10.0.1", 29 | "confusing-browser-globals": "^1.0.11", 30 | "eslint-plugin-flowtype": "^8.0.3", 31 | "eslint-plugin-import": "^2.29.1", 32 | "eslint-plugin-jest": "^26.9.0", 33 | "eslint-plugin-jsx-a11y": "^6.8.0", 34 | "eslint-plugin-react": "^7.34.1", 35 | "eslint-plugin-react-hooks": "^4.6.0", 36 | "eslint-plugin-testing-library": "^5.11.1" 37 | }, 38 | "engines": { 39 | "node": ">=14.0.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "plugins": [ "./utils/custom-mdx-patch"] 4 | } 5 | -------------------------------------------------------------------------------- /docs/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'plugin:@next/next/recommended', 4 | ], 5 | parserOptions: { 6 | ecmaVersion: 'latest', 7 | sourceType: 'module', 8 | ecmaFeatures: { 9 | jsx: true, 10 | } 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | sidebar.json 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | .env* 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | .vercel 30 | .yarn -------------------------------------------------------------------------------- /docs/.nextra/search.js: -------------------------------------------------------------------------------- 1 | import { useMemo, useCallback, useRef, useState, useEffect } from 'react'; 2 | import matchSorter from 'match-sorter'; 3 | import cn from 'classnames'; 4 | import { useRouter } from 'next/router'; 5 | import Link from 'next/link'; 6 | 7 | const Item = ({ title, active, href, onMouseOver, search }) => { 8 | const highlight = title.toLowerCase().indexOf(search.toLowerCase()); 9 | 10 | return ( 11 | 12 | 13 |
  • 18 | {title.substring(0, highlight)} 19 | 20 | {title.substring(highlight, highlight + search.length)} 21 | 22 | {title.substring(highlight + search.length)} 23 |
  • 24 |
    25 | 26 | ); 27 | }; 28 | 29 | const Search = ({ directories }) => { 30 | const router = useRouter(); 31 | const [show, setShow] = useState(false); 32 | const [search, setSearch] = useState(''); 33 | const [active, setActive] = useState(0); 34 | const input = useRef(null); 35 | 36 | const results = useMemo(() => { 37 | if (!search) return []; 38 | 39 | // Will need to scrape all the headers from each page and search through them here 40 | // (similar to what we already do to render the hash links in sidebar) 41 | // We could also try to search the entire string text from each page 42 | return matchSorter(directories, search, { keys: ['title'] }); 43 | }, [search]); 44 | 45 | const handleKeyDown = useCallback( 46 | (e) => { 47 | switch (e.key) { 48 | case 'ArrowDown': { 49 | e.preventDefault(); 50 | if (active + 1 < results.length) { 51 | setActive(active + 1); 52 | } 53 | break; 54 | } 55 | case 'ArrowUp': { 56 | e.preventDefault(); 57 | if (active - 1 >= 0) { 58 | setActive(active - 1); 59 | } 60 | break; 61 | } 62 | case 'Enter': { 63 | router.push(results[active].route); 64 | break; 65 | } 66 | } 67 | }, 68 | [active, results, router] 69 | ); 70 | 71 | useEffect(() => { 72 | setActive(0); 73 | }, [search]); 74 | 75 | useEffect(() => { 76 | const inputs = ['input', 'select', 'button', 'textarea']; 77 | 78 | const down = (e) => { 79 | if ( 80 | document.activeElement && 81 | inputs.indexOf(document.activeElement.tagName.toLowerCase() !== -1) 82 | ) { 83 | if (e.key === '/') { 84 | e.preventDefault(); 85 | input.current.focus(); 86 | } else if (e.key === 'Escape') { 87 | setShow(false); 88 | } 89 | } 90 | }; 91 | 92 | window.addEventListener('keydown', down); 93 | return () => window.removeEventListener('keydown', down); 94 | }, []); 95 | 96 | const renderList = show && results.length > 0; 97 | 98 | return ( 99 |
    100 | {renderList && ( 101 |
    setShow(false)} /> 102 | )} 103 |
    104 | 113 | 114 | 115 |
    116 | { 118 | setSearch(e.target.value); 119 | setShow(true); 120 | }} 121 | className="appearance-none pl-8 border rounded-md py-2 pr-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-full" 122 | type="search" 123 | placeholder='Search ("/" to focus)' 124 | onKeyDown={handleKeyDown} 125 | onFocus={() => setShow(true)} 126 | ref={input} 127 | aria-label="Search documentation" 128 | /> 129 | {renderList && ( 130 |
      131 | {results.map((res, i) => { 132 | return ( 133 | setActive(i)} 140 | /> 141 | ); 142 | })} 143 |
    144 | )} 145 |
    146 | ); 147 | }; 148 | 149 | export default Search; 150 | -------------------------------------------------------------------------------- /docs/components/features.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | flex-wrap: wrap; 4 | margin: 2.5rem -0.5rem 2rem; 5 | } 6 | 7 | .feature { 8 | flex: 0 0 33%; 9 | align-items: center; 10 | display: inline-flex; 11 | padding: 0 0.5rem 1.5rem; 12 | margin: 0 auto; 13 | } 14 | .feature h4 { 15 | margin: 0 0 0 0.5rem; 16 | font-weight: 700; 17 | font-size: 0.95rem; 18 | white-space: nowrap; 19 | } 20 | @media (max-width: 860px) { 21 | 22 | .feature h4 { 23 | font-size: 0.75rem; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/components/logo.js: -------------------------------------------------------------------------------- 1 | const Logo = ({ height }) => { 2 | return ( 3 | 9 | 10 | 14 | 18 | 22 | 23 | ); 24 | }; 25 | 26 | export default Logo; 27 | -------------------------------------------------------------------------------- /docs/components/release.js: -------------------------------------------------------------------------------- 1 | import { Box, Text } from '@chakra-ui/react'; 2 | import Markdown from 'markdown-to-jsx'; 3 | 4 | const Release = (props) => { 5 | const { url, name, date, body } = props; 6 | 7 | return ( 8 | 9 | 10 | #{' '} 11 | 12 | {name} 13 | 14 | 15 | 16 | Published on{' '} 17 | {`${new Date( 18 | date 19 | ).toDateString()}.`} 20 | 21 | {body} 22 | 23 | ); 24 | }; 25 | 26 | const ReleasesRenderer = ({ releases }) => { 27 | return releases.map((release) => ( 28 | 34 | )); 35 | }; 36 | 37 | export default ReleasesRenderer; 38 | -------------------------------------------------------------------------------- /docs/next.config.js: -------------------------------------------------------------------------------- 1 | const compose = require('compose-function'); 2 | const { withDokz } = require('dokz/dist/plugin'); 3 | const composed = compose(withDokz); 4 | 5 | const debug = process.env.NODE_ENV !== 'production'; 6 | 7 | module.exports = composed({ 8 | pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'], 9 | assetPrefix: !debug ? 'https://weiran-zsd.github.io/dts-cli/' : '', 10 | basePath: '/dts-cli', 11 | }); 12 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "export": "next export && touch out/.nojekyll" 10 | }, 11 | "dependencies": { 12 | "@chakra-ui/react": "1", 13 | "@emotion/react": "11", 14 | "@emotion/styled": "11", 15 | "@mdx-js/react": "^1.6.22", 16 | "dokz": "2", 17 | "framer-motion": "6", 18 | "markdown-to-jsx": "^7.1.5", 19 | "next": "^12.0.4", 20 | "react": "^17.0.2", 21 | "react-dom": "^17.0.2" 22 | }, 23 | "devDependencies": { 24 | "@next/eslint-plugin-next": "^12.0.4", 25 | "compose-function": "^3.0.3", 26 | "eslint-config-next": "^12.0.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /docs/pages/_app.jsx: -------------------------------------------------------------------------------- 1 | import { DokzProvider, GithubLink, ColorModeSwitch } from 'dokz'; 2 | import React, { Fragment } from 'react'; 3 | import Head from 'next/head'; 4 | import { ChakraProvider } from '@chakra-ui/react'; 5 | import Logo from '../components/logo'; 6 | 7 | export default function App(props) { 8 | const { Component, pageProps } = props; 9 | return ( 10 | 11 | 12 | 17 | 18 | } 20 | headerItems={[ 21 | , 22 | , 23 | ]} 24 | sidebarOrdering={{ 25 | 'index.mdx': true, 26 | 'api-reference.mdx': true, 27 | 'customization.mdx': true, 28 | 'change-log.mdx': true, 29 | }} 30 | > 31 | 32 | 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /docs/pages/api-reference.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: API Reference 3 | --- 4 | 5 | # API Reference 6 | 7 | ## dts watch 8 | > Rebuilds on any change 9 | 10 | ```sh 11 | Usage 12 | $ dts watch [options] 13 | 14 | Options 15 | -i, --entry Entry module(s) 16 | --target Specify your target environment (default web) 17 | --name Specify name exposed in UMD builds 18 | --format Specify module format(s) (default cjs,esm) 19 | --tsconfig Specify your custom tsconfig path (default /tsconfig.json) 20 | --verbose Keep outdated console output in watch mode instead of clearing the screen 21 | --onFirstSuccess Run a command on the first successful build 22 | --onSuccess Run a command on a successful build 23 | --onFailure Run a command on a failed build 24 | --noClean Don't clean the dist folder 25 | --transpileOnly Skip type checking 26 | -h, --help Displays this message 27 | 28 | Examples 29 | $ dts watch --entry src/foo.tsx 30 | $ dts watch --target node 31 | $ dts watch --name Foo 32 | $ dts watch --format cjs,esm,umd 33 | $ dts watch --tsconfig ./tsconfig.foo.json 34 | $ dts watch --noClean 35 | $ dts watch --onFirstSuccess "echo The first successful build!" 36 | $ dts watch --onSuccess "echo Successful build!" 37 | $ dts watch --onFailure "echo The build failed!" 38 | $ dts watch --transpileOnly 39 | ``` 40 | 41 | ## dts build 42 | > Build your project once and exit 43 | 44 | ```sh 45 | Usage 46 | $ dts build [options] 47 | 48 | Options 49 | -i, --entry Entry module(s) 50 | --target Specify your target environment (default web) 51 | --name Specify name exposed in UMD builds 52 | --format Specify module format(s) (default cjs,esm) 53 | --extractErrors Opt-in to extracting invariant error codes 54 | --tsconfig Specify your custom tsconfig path (default /tsconfig.json) 55 | --transpileOnly Skip type checking 56 | -h, --help Displays this message 57 | 58 | Examples 59 | $ dts build --entry src/foo.tsx 60 | $ dts build --target node 61 | $ dts build --name Foo 62 | $ dts build --format cjs,esm,umd 63 | $ dts build --extractErrors 64 | $ dts build --tsconfig ./tsconfig.foo.json 65 | $ dts build --transpileOnly 66 | ``` 67 | 68 | ## dts test 69 | > This runs Jest v24.x, forwarding all CLI flags to it. See [https://jestjs.io](https://jestjs.io) for options. For example, if you would like to run in watch mode, you can run `dts test --watch`. So you could set up your `package.json` `scripts` like: 70 | 71 | ```json 72 | { 73 | "scripts": { 74 | "test": "dts test", 75 | "test:watch": "dts test --watch", 76 | "test:coverage": "dts test --coverage" 77 | } 78 | } 79 | ``` 80 | 81 | ## dts lint 82 | > Run eslint with Prettier 83 | 84 | ```sh 85 | Usage 86 | $ dts lint [options] 87 | 88 | Options 89 | --fix Fixes fixable errors and warnings 90 | --ignore-pattern Ignore a pattern 91 | --write-file Write the config file locally 92 | --report-file Write JSON report to file locally 93 | -h, --help Displays this message 94 | 95 | Examples 96 | $ dts lint src 97 | $ dts lint src --fix 98 | $ dts lint src test --ignore-pattern test/foo.ts 99 | $ dts lint src --write-file 100 | $ dts lint src --report-file report.json 101 | ``` 102 | -------------------------------------------------------------------------------- /docs/pages/change-log.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Changelog 3 | --- 4 | 5 | import { useContext } from "react"; 6 | import ReleasesRenderer from "../components/release"; 7 | 8 | export const getStaticProps = ({ params }) => { 9 | return ( 10 | fetch("https://api.github.com/repos/weiran-zsd/dts-cli/releases") 11 | .then((res) => res.json()) 12 | // we keep the most recent 3 releases here 13 | .then((releases) => ({ props: { ssg: releases.slice(0, 10) } })) 14 | ); 15 | }; 16 | 17 | # Change Log 18 | 19 | Please visit the [DTS-cli release page](https://github.com/formium/dts-cli/releases) for all historical releases. 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/pages/customization.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Customization 3 | --- 4 | 5 | # Customization 6 | 7 | ## Rollup 8 | 9 | > **❗⚠️❗ Warning**:
    10 | > These modifications will override the default behavior and configuration of DTS. As such they can invalidate internal guarantees and assumptions. These types of changes can break internal behavior and can be very fragile against updates. Use with discretion! 11 | 12 | DTS uses Rollup under the hood. The defaults are solid for most packages (Formik uses the defaults!). However, if you do wish to alter the rollup configuration, you can do so by creating a file called `dts.config.js` (or `dts.config.ts`) at the root of your project like so: 13 | 14 | **dts.config.js** 15 | 16 | ```js 17 | // Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! 18 | /** 19 | * @type {import('dts-cli').DtsConfig} 20 | */ 21 | module.exports = { 22 | // This function will run for each entry/format/env combination 23 | rollup(config, options) { 24 | return config; // always return a config. 25 | }, 26 | }; 27 | ``` 28 | 29 | or 30 | 31 | ```js 32 | const defineConfig = require('dts-cli').defineConfig; 33 | 34 | module.exports = defineConfig({ 35 | // This function will run for each entry/format/env combination 36 | rollup: (config, options) => { 37 | return config; // always return a config. 38 | }, 39 | }); 40 | ``` 41 | 42 | **dts.config.ts** 43 | 44 | ```typescript 45 | import { defineConfig } from 'dts-cli'; 46 | 47 | export default defineConfig({ 48 | rollup: (config, options) => { 49 | return config; // always return a config. 50 | }, 51 | }); 52 | ``` 53 | 54 | The `options` object contains the following: 55 | 56 | ```tsx 57 | export interface DtsOptions { 58 | // path to file 59 | input: string; 60 | // Name of package 61 | name: string; 62 | // JS target 63 | target: 'node' | 'browser'; 64 | // Module format 65 | format: 'cjs' | 'umd' | 'esm' | 'system'; 66 | // Environment 67 | env: 'development' | 'production'; 68 | // Path to tsconfig file 69 | tsconfig?: string; 70 | // Is error extraction running? 71 | extractErrors?: boolean; 72 | // Is minifying? 73 | minify?: boolean; 74 | // Is this the very first rollup config (and thus should one-off metadata be extracted)? 75 | writeMeta?: boolean; 76 | // Only transpile, do not type check (makes compilation faster) 77 | transpileOnly?: boolean; 78 | } 79 | ``` 80 | 81 | ### Example: Adding Postcss 82 | 83 | ```js 84 | const postcss = require('rollup-plugin-postcss'); 85 | const autoprefixer = require('autoprefixer'); 86 | const cssnano = require('cssnano'); 87 | 88 | module.exports = { 89 | rollup(config, options) { 90 | config.plugins.push( 91 | postcss({ 92 | plugins: [ 93 | autoprefixer(), 94 | cssnano({ 95 | preset: 'default', 96 | }), 97 | ], 98 | inject: false, 99 | // only write out CSS for the first bundle (avoids pointless extra files): 100 | extract: !!options.writeMeta, 101 | }) 102 | ); 103 | return config; 104 | }, 105 | }; 106 | ``` 107 | 108 | ## Babel 109 | 110 | You can add your own `.babelrc` to the root of your project and DTS will **merge** it with [its own Babel transforms](https://github.com/weiran-zsd/dts-cli/blob/master/src/babelPluginTsdx.ts) (which are mostly for optimization), putting any new presets and plugins at the end of its list. 111 | 112 | ## Jest 113 | 114 | You can add your own `jest.config.js` to the root of your project and DTS will **shallow merge** it with [its own Jest config](https://github.com/weiran-zsd/dts-cli/blob/master/src/createJestConfig.ts). 115 | 116 | ## ESLint 117 | 118 | You can add your own `.eslintrc.js` to the root of your project and DTS will **deep merge** it with [its own ESLint config](https://github.com/weiran-zsd/dts-cli/blob/master/src/createEslintConfig.ts). 119 | -------------------------------------------------------------------------------- /docs/pages/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Getting started 3 | --- 4 | 5 | import Features from "../components/features"; 6 | 7 | # DTS 8 | 9 | 10 | 11 | Despite all the recent hype, setting up a new TypeScript (x React) 12 | library can be tough. Between Rollup, Jest, tsconfig, Yarn resolutions, 13 | TSLint, and getting VSCode to play nicely....there is just a whole lot 14 | of stuff to do (and things to screw up). 15 | 16 | **DTS is a zero-config CLI that helps you develop, test, and publish 17 | modern TypeScript packages** with ease--so you can focus on your awesome 18 | new library and not waste another afternoon on the configuration. 19 | 20 | ## Quick Start 21 | 22 | With DTS, you can quickly bootstrap a new TypeScript project in seconds, instead of hours. Open up Terminal and enter: 23 | 24 | ```bash 25 | npx dts-cli create mylib 26 | ``` 27 | 28 | You'll be prompted to choose from one of three project templates: 29 | 30 | | Template | Description | 31 | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 32 | | `basic` | A plain TypeScript project setup you can use for any kind of module. | 33 | | `react` | A React package with necessary development dependencies and `@types` installed. In addition, there is a [Vite](https://vitejs.dev)-powered React playground you can use while you develop. | 34 | | `react-with-storybook` | Same as the basic React template, but with [React Storybook](https://storybook.js.org/) already setup as well. | 35 | 36 | After you select one, DTS will create a folder with the project template in it and install all dependencies. Once that's done, you're ready-to-rock! TypeScript, Rollup, Jest, ESlint and all other plumbing is already setup with best practices. Just start editing `src/index.ts` (or `src/index.tsx` if you chose one of the React templates) and go! 37 | 38 | ## Useful Commands 39 | 40 | Below is a list of commands you will probably find useful: 41 | 42 | `npm start` or `yarn start` 43 | 44 | Runs the project in development/watch mode. Your project will be rebuilt upon changes. DTS has a special logger for your convenience. Error messages are pretty printed and formatted for compatibility VS Code's Problems tab. 45 | 46 | 50 | 51 | Your library will be rebuilt if you make edits. 52 | 53 | `npm run build` or `yarn build` 54 | 55 | Bundles the package to the `dist` folder. 56 | The package is optimized and bundled with Rollup into multiple formats (CommonJS, UMD, and ES Module). 57 | 58 | 62 | 63 | - `npm test` or `yarn test` 64 | 65 | Runs your tests using Jest. 66 | 67 | - `npm run lint` or `yarn lint` 68 | 69 | Runs Eslint with Prettier on .ts and .tsx files. 70 | If you want to customize eslint you can add an `eslint` block to your package.json, or you can run `yarn lint --write-file` and edit the generated `.eslintrc.js` file. 71 | 72 | - `prepare` script 73 | 74 | Bundles and packages to the `dist` folder. 75 | Runs automatically when you run either `npm publish` or `yarn publish`. The `prepare` script will run the equivalent of `npm run build` or `yarn build`. It will also be run if your module is installed as a git dependency (ie: `"mymodule": "github:myuser/mymodule#some-branch"`) so it can be depended on without checking the transpiled code into git. 76 | 77 | ## Best Practices 78 | 79 | DTS includes best-practices and optimizations for modern NPM packages. These include things like the ability to have different development and production builds, multiple bundle formats, proper lodash-optimizations, treeshaking, and minification to name a few. All of these come out-of-the-box with DTS. While you probably won't need to configure anything, [you totally can do so with dts.config.js](customization). 80 | 81 | Before you start extending DTS though, you'll want to fully understand what _exactly_ DTS does. In the next section, we'll go over all of these optimizations in finer detail. 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiran-zsd/dts-cli/5a0172f7543f4ff00b59ac3f6fd246888d59c49c/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/utils/custom-mdx-patch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Currently it's not possible to export data fetching functions from MDX pages 3 | * because MDX includes them in `layoutProps`, and Next.js removes them at some 4 | * point, causing a `ReferenceError`. 5 | * 6 | * https://github.com/mdx-js/mdx/issues/742#issuecomment-612652071 7 | * 8 | * This plugin can be removed once MDX removes `layoutProps`, at least that 9 | * seems to be the current plan. 10 | */ 11 | 12 | // https://nextjs.org/docs/basic-features/data-fetching 13 | const DATA_FETCH_FNS = ['getStaticPaths', 'getStaticProps', 'getServerProps'] 14 | 15 | module.exports = () => { 16 | return { 17 | visitor: { 18 | ObjectProperty(path) { 19 | if ( 20 | DATA_FETCH_FNS.includes(path.node.value.name) && 21 | path.findParent( 22 | (path) => 23 | path.isVariableDeclarator() && 24 | path.node.id.name === 'layoutProps', 25 | ) 26 | ) { 27 | path.remove() 28 | } 29 | }, 30 | }, 31 | } 32 | } 33 | 34 | // https://github.com/vercel/next.js/issues/12053#issuecomment-622939046 35 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | testMatch: ['/**/*(*.)@(test).[tj]s?(x)'], 4 | testPathIgnorePatterns: [ 5 | '/node_modules/', // default 6 | '/templates/', // don't run tests in the templates 7 | '/test/.*/fixtures/', // don't run tests in fixtures 8 | '/stage-.*/', // don't run tests in auto-generated (and auto-removed) test dirs 9 | ], 10 | }; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dts-cli", 3 | "version": "2.0.5", 4 | "author": "唯然", 5 | "description": "Zero-config TypeScript package development", 6 | "license": "MIT", 7 | "homepage": "https://github.com/weiran-zsd/dts-cli", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/weiran-zsd/dts-cli.git" 11 | }, 12 | "keywords": [ 13 | "react", 14 | "typescript", 15 | "bundle", 16 | "rollup" 17 | ], 18 | "bugs": { 19 | "url": "https://github.com/weiran-zsd/dts-cli/issues" 20 | }, 21 | "bin": { 22 | "dts": "./dist/index.js" 23 | }, 24 | "main": "./dist/index.js", 25 | "typings": "./dist/index.d.ts", 26 | "scripts": { 27 | "build": "tsc -p tsconfig.json", 28 | "lint": "npm run build && npm run lint:post-build", 29 | "lint:post-build": "node dist/index.js lint ./ --ignore-pattern 'test/e2e/fixtures/'", 30 | "test": "npm run build && npm run test:post-build", 31 | "test:post-build": "node dist/index.js test", 32 | "start": "tsc -p tsconfig.json --watch", 33 | "release": "npm run test && release-it", 34 | "release:beta": "npm run test && release-it major --preRelease=beta", 35 | "doctoc": "doctoc README.md", 36 | "prepare": "husky install" 37 | }, 38 | "files": [ 39 | "dist", 40 | "templates", 41 | "conf" 42 | ], 43 | "engines": { 44 | "node": "^16.0.0 || >=18.0.0" 45 | }, 46 | "dependencies": { 47 | "@babel/core": "^7.24.3", 48 | "@babel/helper-module-imports": "^7.24.3", 49 | "@babel/parser": "^7.24.1", 50 | "@babel/plugin-proposal-class-properties": "^7.18.6", 51 | "@babel/preset-env": "^7.24.3", 52 | "@babel/traverse": "^7.24.1", 53 | "@rollup/plugin-babel": "^6.0.4", 54 | "@rollup/plugin-commonjs": "^24.1.0", 55 | "@rollup/plugin-json": "^6.1.0", 56 | "@rollup/plugin-node-resolve": "^15.2.3", 57 | "@rollup/plugin-replace": "^5.0.5", 58 | "@rollup/plugin-terser": "^0.4.4", 59 | "@types/jest": "^29.5.12", 60 | "@typescript-eslint/eslint-plugin": "^5.62.0", 61 | "@typescript-eslint/parser": "^5.62.0", 62 | "ansi-escapes": "^4.3.2", 63 | "asyncro": "^3.0.0", 64 | "babel-jest": "^29.7.0", 65 | "babel-plugin-annotate-pure-calls": "^0.4.0", 66 | "babel-plugin-dev-expression": "^0.2.3", 67 | "babel-plugin-macros": "^3.1.0", 68 | "babel-plugin-polyfill-regenerator": "^0.6.1", 69 | "babel-plugin-transform-rename-import": "^2.3.0", 70 | "camelcase": "^6.3.0", 71 | "chalk": "^4.1.2", 72 | "confusing-browser-globals": "^1.0.11", 73 | "enquirer": "^2.4.1", 74 | "eslint": "^8.37.0", 75 | "eslint-config-prettier": "^8.10.0", 76 | "eslint-plugin-flowtype": "^8.0.3", 77 | "eslint-plugin-import": "^2.29.1", 78 | "eslint-plugin-jest": "^27.9.0", 79 | "eslint-plugin-jsx-a11y": "^6.8.0", 80 | "eslint-plugin-prettier": "^4.2.1", 81 | "eslint-plugin-react": "^7.34.1", 82 | "eslint-plugin-react-hooks": "^4.6.0", 83 | "eslint-plugin-testing-library": "^5.11.1", 84 | "execa": "^4.1.0", 85 | "figlet": "^1.7.0", 86 | "fs-extra": "^10.1.0", 87 | "jest": "^29.5.0", 88 | "jest-environment-jsdom": "^29.7.0", 89 | "jest-watch-typeahead": "^2.2.2", 90 | "jpjs": "^1.2.1", 91 | "lodash.merge": "^4.6.2", 92 | "ora": "^5.4.1", 93 | "pascal-case": "^3.1.2", 94 | "postcss": "^8.4.38", 95 | "prettier": "^2.8.1", 96 | "progress-estimator": "^0.3.1", 97 | "regenerator-runtime": "^0.14.1", 98 | "rollup": "^3.20.0", 99 | "rollup-plugin-delete": "^2.0.0", 100 | "rollup-plugin-dts": "^5.3.1", 101 | "rollup-plugin-typescript2": "^0.36.0", 102 | "sade": "^1.8.1", 103 | "semver": "^7.6.0", 104 | "shelljs": "^0.8.5", 105 | "sort-package-json": "^1.57.0", 106 | "tiny-glob": "^0.2.9", 107 | "ts-jest": "^29.1.2", 108 | "ts-node": "^10.9.2", 109 | "tslib": "^2.6.2", 110 | "type-fest": "^2.19.0", 111 | "typescript": "^5.0.2" 112 | }, 113 | "devDependencies": { 114 | "@commitlint/cli": "^16.3.0", 115 | "@commitlint/config-conventional": "^16.2.4", 116 | "@release-it/conventional-changelog": "^5.1.1", 117 | "@types/cssnano": "^5.1.0", 118 | "@types/eslint": "^8.56.7", 119 | "@types/figlet": "^1.5.8", 120 | "@types/fs-extra": "^9.0.13", 121 | "@types/lodash": "^4.17.0", 122 | "@types/node": "^16.18.94", 123 | "@types/react": "^18.2.74", 124 | "@types/rollup-plugin-json": "^3.0.7", 125 | "@types/semver": "^7.5.8", 126 | "@types/shelljs": "^0.8.15", 127 | "@types/styled-components": "^5.1.34", 128 | "auto-changelog": "^2.4.0", 129 | "autoprefixer": "^10.4.19", 130 | "babel-plugin-replace-identifiers": "^0.1.1", 131 | "cssnano": "^5.1.15", 132 | "doctoc": "^2.2.1", 133 | "dts-cli": "file:.", 134 | "husky": "^8.0.3", 135 | "lint-staged": "^15.2.2", 136 | "react": "^16.14.0", 137 | "react-dom": "^16.14.0", 138 | "react-is": "^16.13.1", 139 | "release-it": "^15.11.0", 140 | "rollup-plugin-postcss": "^4.0.2", 141 | "styled-components": "^5.3.11", 142 | "tiny-invariant": "^1.3.3", 143 | "tiny-warning": "^1.0.3" 144 | }, 145 | "prettier": { 146 | "printWidth": 80, 147 | "semi": true, 148 | "singleQuote": true, 149 | "trailingComma": "es5" 150 | }, 151 | "publishConfig": { 152 | "access": "public", 153 | "registry": "https://registry.npmjs.org/" 154 | }, 155 | "release-it": { 156 | "git": { 157 | "commitMessage": "chore: release v${version}" 158 | }, 159 | "plugins": { 160 | "@release-it/conventional-changelog": { 161 | "preset": "conventionalcommits", 162 | "infile": "CHANGELOG.md" 163 | } 164 | }, 165 | "github": { 166 | "release": true 167 | }, 168 | "npm": { 169 | "skipChecks": true 170 | } 171 | }, 172 | "lint-staged": { 173 | "*.{js,jsx,ts,tsx,md,json,yml,yaml}": [ 174 | "prettier -w" 175 | ], 176 | "*.{js,jsx,ts,tsx}": [ 177 | "node dist/index.js lint --fix" 178 | ] 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base", ":dependencyDashboard", "group:allNonMajor"], 3 | "ignorePaths": [ 4 | "website/*", 5 | "docs/*", 6 | "./stage-*", 7 | "./test/*", 8 | "example/*", 9 | "templates/*", 10 | "conf/*" 11 | ], 12 | "pin": false, 13 | "rangeStrategy": "bump", 14 | "ignoreDeps": [ 15 | "node", 16 | "typescript", 17 | "prettier", 18 | "jest", 19 | "eslint", 20 | "rollup", 21 | 22 | "react", 23 | "react-dom", 24 | "react-is", 25 | "ansi-escapes", 26 | "ansi-skip", 27 | "ora" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /src/babelPluginDts.ts: -------------------------------------------------------------------------------- 1 | import { createConfigItem } from '@babel/core'; 2 | import { createBabelInputPluginFactory } from '@rollup/plugin-babel'; 3 | import merge from 'lodash.merge'; 4 | import { resolve } from './utils'; 5 | 6 | export const isTruthy = (obj?: any) => { 7 | if (!obj) { 8 | return false; 9 | } 10 | 11 | return obj.constructor !== Object || Object.keys(obj).length > 0; 12 | }; 13 | 14 | // replace lodash with lodash-es, but not lodash/fp 15 | const replacements = [{ original: 'lodash(?!/fp)', replacement: 'lodash-es' }]; 16 | 17 | export const mergeConfigItems = (type: any, ...configItemsToMerge: any[]) => { 18 | const mergedItems: any[] = []; 19 | 20 | configItemsToMerge.forEach((configItemToMerge) => { 21 | configItemToMerge.forEach((item: any) => { 22 | const itemToMergeWithIndex = mergedItems.findIndex( 23 | (mergedItem) => mergedItem.file.resolved === item.file.resolved 24 | ); 25 | 26 | if (itemToMergeWithIndex === -1) { 27 | mergedItems.push(item); 28 | return; 29 | } 30 | 31 | mergedItems[itemToMergeWithIndex] = createConfigItem( 32 | [ 33 | mergedItems[itemToMergeWithIndex].file.resolved, 34 | merge(mergedItems[itemToMergeWithIndex].options, item.options), 35 | ], 36 | { 37 | type, 38 | } 39 | ); 40 | }); 41 | }); 42 | 43 | return mergedItems; 44 | }; 45 | 46 | export const createConfigItems = (type: any, items: any[]) => { 47 | return items.map(({ name, ...options }) => { 48 | return createConfigItem([require.resolve(name), options], { type }); 49 | }); 50 | }; 51 | 52 | export const babelPluginDts = createBabelInputPluginFactory(() => ({ 53 | // Passed the plugin options. 54 | options({ custom: customOptions, ...pluginOptions }: any) { 55 | return { 56 | // Pull out any custom options that the plugin might have. 57 | customOptions, 58 | 59 | // Pass the options back with the two custom options removed. 60 | pluginOptions, 61 | }; 62 | }, 63 | config(config: any, { customOptions }: any) { 64 | const defaultPlugins = createConfigItems( 65 | 'plugin', 66 | [ 67 | // { 68 | // name: '@babel/plugin-transform-react-jsx', 69 | // pragma: customOptions.jsx || 'h', 70 | // pragmaFrag: customOptions.jsxFragment || 'Fragment', 71 | // }, 72 | { name: resolve('babel-plugin-macros') }, 73 | { name: resolve('babel-plugin-annotate-pure-calls') }, 74 | { name: resolve('babel-plugin-dev-expression') }, 75 | customOptions.format !== 'cjs' && { 76 | name: resolve('babel-plugin-transform-rename-import'), 77 | replacements, 78 | }, 79 | { 80 | name: resolve('babel-plugin-polyfill-regenerator'), 81 | // don't pollute global env as this is being used in a library 82 | method: 'usage-pure', 83 | }, 84 | { 85 | name: resolve('@babel/plugin-proposal-class-properties'), 86 | loose: true, 87 | }, 88 | isTruthy(customOptions.extractErrors) && { 89 | name: './errors/transformErrorMessages', 90 | }, 91 | ].filter(Boolean) 92 | ); 93 | 94 | const babelOptions = config.options || {}; 95 | babelOptions.presets = babelOptions.presets || []; 96 | 97 | const presetEnvIdx = babelOptions.presets.findIndex((preset: any) => 98 | preset.file.request.includes('@babel/preset-env') 99 | ); 100 | 101 | // if they use preset-env, merge their options with ours 102 | if (presetEnvIdx !== -1) { 103 | const presetEnv = babelOptions.presets[presetEnvIdx]; 104 | babelOptions.presets[presetEnvIdx] = createConfigItem( 105 | [ 106 | presetEnv.file.resolved, 107 | merge( 108 | { 109 | loose: true, 110 | targets: customOptions.targets, 111 | }, 112 | presetEnv.options, 113 | { 114 | modules: false, 115 | } 116 | ), 117 | ], 118 | { 119 | type: `preset`, 120 | } 121 | ); 122 | } else { 123 | // if no preset-env, add it & merge with their presets 124 | const defaultPresets = createConfigItems('preset', [ 125 | { 126 | name: resolve('@babel/preset-env'), 127 | targets: customOptions.targets, 128 | modules: false, 129 | loose: true, 130 | }, 131 | ]); 132 | 133 | babelOptions.presets = mergeConfigItems( 134 | 'preset', 135 | defaultPresets, 136 | babelOptions.presets 137 | ); 138 | } 139 | 140 | // Merge babelrc & our plugins together 141 | babelOptions.plugins = mergeConfigItems( 142 | 'plugin', 143 | defaultPlugins, 144 | babelOptions.plugins || [] 145 | ); 146 | 147 | return babelOptions; 148 | }, 149 | })); 150 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { resolveApp } from './utils'; 2 | 3 | export const paths = { 4 | appPackageJson: resolveApp('package.json'), 5 | tsconfigJson: resolveApp('tsconfig.json'), 6 | testsSetup: resolveApp('test/setupTests.ts'), 7 | appRoot: resolveApp('.'), 8 | appSrc: resolveApp('src'), 9 | appErrorsJson: resolveApp('errors/codes.json'), 10 | appErrors: resolveApp('errors'), 11 | appDist: resolveApp('dist'), 12 | appConfigJs: resolveApp('dts.config.js'), 13 | appConfigTs: resolveApp('dts.config.ts'), 14 | appConfigCjs: resolveApp('dts.config.cjs'), 15 | jestConfig: resolveApp('jest.config.js'), 16 | progressEstimatorCache: resolveApp('node_modules/.cache/.progress-estimator'), 17 | }; 18 | -------------------------------------------------------------------------------- /src/createBuildConfigs.ts: -------------------------------------------------------------------------------- 1 | import { RollupOptions } from 'rollup'; 2 | import * as fs from 'fs-extra'; 3 | import chalk from 'chalk'; 4 | 5 | import { paths } from './constants'; 6 | import { 7 | DtsConfig, 8 | DtsOptions, 9 | DtsOptionsInput, 10 | NormalizedDtsConfig, 11 | NormalizedOpts, 12 | PackageJson, 13 | } from './types'; 14 | 15 | import { createRollupConfig } from './createRollupConfig'; 16 | import logError from './logError'; 17 | import { interopRequireDefault } from './utils'; 18 | import { configDefaults } from './defaults'; 19 | 20 | export async function createBuildConfigs( 21 | opts: NormalizedOpts, 22 | appPackageJson: PackageJson 23 | ): Promise> { 24 | const allInputs = createAllFormats(opts).map( 25 | (options: DtsOptions, index: number) => ({ 26 | ...options, 27 | // We want to know if this is the first run for each entryfile 28 | // for certain plugins (e.g. css) 29 | writeMeta: index === 0, 30 | }) 31 | ); 32 | 33 | // check for custom dts.config.ts/dts.config.js 34 | const dtsBuildConfig: NormalizedDtsConfig = getNormalizedDtsConfig(); 35 | 36 | return await Promise.all( 37 | allInputs.map(async (options: DtsOptions, index: number) => { 38 | // pass the full rollup config to dts-cli.config.js override 39 | const config = await createRollupConfig(appPackageJson, options, index); 40 | return dtsBuildConfig.rollup(config, options); 41 | }) 42 | ); 43 | } 44 | 45 | function createAllFormats(opts: NormalizedOpts): [DtsOptions, ...DtsOptions[]] { 46 | const sharedOpts: Omit = { 47 | ...opts, 48 | // for multi-entry, we use an input object to specify where to put each 49 | // file instead of output.file 50 | input: opts.input.reduce((dict: DtsOptionsInput, input, index) => { 51 | dict[`${opts.output.file[index]}`] = input; 52 | return dict; 53 | }, {}), 54 | // multiple UMD names aren't currently supported for multi-entry 55 | // (can't code-split UMD anyway) 56 | name: Array.isArray(opts.name) ? opts.name[0] : opts.name, 57 | }; 58 | 59 | return [ 60 | opts.format.includes('cjs') && { 61 | ...sharedOpts, 62 | format: 'cjs', 63 | env: 'development', 64 | }, 65 | opts.format.includes('cjs') && { 66 | ...sharedOpts, 67 | format: 'cjs', 68 | env: 'production', 69 | }, 70 | opts.format.includes('esm') && { ...sharedOpts, format: 'esm' }, 71 | opts.format.includes('umd') && { 72 | ...sharedOpts, 73 | format: 'umd', 74 | env: 'development', 75 | }, 76 | opts.format.includes('umd') && { 77 | ...sharedOpts, 78 | format: 'umd', 79 | env: 'production', 80 | }, 81 | opts.format.includes('system') && { 82 | ...sharedOpts, 83 | format: 'system', 84 | env: 'development', 85 | }, 86 | opts.format.includes('system') && { 87 | ...sharedOpts, 88 | format: 'system', 89 | env: 'production', 90 | }, 91 | ].filter(Boolean) as [DtsOptions, ...DtsOptions[]]; 92 | } 93 | 94 | function getNormalizedDtsConfig(): NormalizedDtsConfig { 95 | const dtsConfig = getDtsConfig(); 96 | 97 | if (!dtsConfig.rollup) { 98 | console.log( 99 | chalk.yellow( 100 | 'rollup configuration not provided. Using default no-op configuration.' 101 | ) 102 | ); 103 | } 104 | 105 | return { 106 | ...dtsConfig, 107 | rollup: dtsConfig.rollup ?? configDefaults.rollup, 108 | }; 109 | } 110 | 111 | function getDtsConfig(): DtsConfig { 112 | // check for custom dts.config.js 113 | let dtsConfig: any = configDefaults; 114 | 115 | if (fs.existsSync(paths.appConfigTs)) { 116 | dtsConfig = loadDtsConfigTs(); 117 | } else if (fs.existsSync(paths.appConfigJs)) { 118 | dtsConfig = loadDtsConfigJs(); 119 | } else if (fs.existsSync(paths.appConfigCjs)) { 120 | dtsConfig = loadDtsConfigCjs(); 121 | } 122 | 123 | return isDtsConfig(dtsConfig) ? dtsConfig : configDefaults; 124 | } 125 | 126 | // This can return undefined if they don't export anything in 127 | // dts.config.ts 128 | function loadDtsConfigTs(): DtsConfig | undefined { 129 | try { 130 | require('ts-node').register({ 131 | compilerOptions: { 132 | module: 'CommonJS', 133 | }, 134 | transpileOnly: true, // skip type checking 135 | }); 136 | return interopRequireDefault(require(paths.appConfigTs)).default; 137 | } catch (error) { 138 | logError(error); 139 | process.exit(1); 140 | } 141 | } 142 | 143 | // This can return undefined if they don't export anything in 144 | // dts.config.js 145 | function loadDtsConfigJs(): DtsConfig | undefined { 146 | // babel-node could easily be injected here if so desired. 147 | return require(paths.appConfigJs); 148 | } 149 | 150 | function loadDtsConfigCjs(): DtsConfig | undefined { 151 | return require(paths.appConfigCjs); 152 | } 153 | 154 | function isDtsConfig(required: any): required is DtsConfig { 155 | return isDefined(required) && isDefined(required); 156 | } 157 | 158 | function isDefined(required: T | undefined | null): required is T { 159 | return required !== null && required !== undefined; 160 | } 161 | -------------------------------------------------------------------------------- /src/createEslintConfig.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | import { Linter } from 'eslint'; 4 | import { PackageJson } from './types'; 5 | import { getReactVersion, resolve } from './utils'; 6 | 7 | interface CreateEslintConfigArgs { 8 | pkg: PackageJson; 9 | rootDir: string; 10 | writeFile: boolean; 11 | } 12 | export async function createEslintConfig({ 13 | pkg, 14 | rootDir, 15 | writeFile, 16 | }: CreateEslintConfigArgs): Promise { 17 | const isReactLibrary = Boolean(getReactVersion(pkg)); 18 | 19 | const config = { 20 | extends: [ 21 | path.join(__dirname, '../conf/eslint-config-react-app/index.js'), 22 | resolve('eslint-config-prettier'), 23 | 'plugin:prettier/recommended', 24 | ], 25 | settings: { 26 | react: { 27 | // Fix for https://github.com/jaredpalmer/tsdx/issues/279 28 | version: isReactLibrary ? 'detect' : '999.999.999', 29 | }, 30 | }, 31 | }; 32 | 33 | if (!writeFile) { 34 | return config; 35 | } 36 | 37 | const file = path.join(rootDir, '.eslintrc.js'); 38 | // if the path is an abs path(e.g. "/Users/user/my-project/.eslintrc.js"), 39 | // need to convert a rel path to app root. 40 | config.extends[0] = './' + path.relative(rootDir, config.extends[0]); 41 | config.extends[1] = './' + path.relative(rootDir, config.extends[1]); 42 | try { 43 | await fs.writeFile( 44 | file, 45 | `module.exports = ${JSON.stringify(config, null, 2)}`, 46 | { flag: 'wx' } 47 | ); 48 | } catch (e) { 49 | if ((e as NodeJS.ErrnoException).code === 'EEXIST') { 50 | console.error( 51 | 'Error trying to save the Eslint configuration file:', 52 | `${file} already exists.` 53 | ); 54 | } else { 55 | console.error(e); 56 | } 57 | 58 | return config; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/createJestConfig.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '@jest/types'; 2 | import { resolve } from './utils'; 3 | 4 | export type JestConfigOptions = Partial; 5 | 6 | export function createJestConfig( 7 | _: (relativePath: string) => void, 8 | rootDir: string 9 | ): JestConfigOptions { 10 | const config: JestConfigOptions = { 11 | transform: { 12 | '.(ts|tsx)$': resolve('ts-jest'), 13 | '.(js|jsx)$': resolve('babel-jest'), // jest's default 14 | }, 15 | transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'], 16 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 17 | collectCoverageFrom: ['src/**/*.{ts,tsx,js,jsx}'], 18 | testMatch: ['/**/*.(spec|test).{ts,tsx,js,jsx}'], 19 | testEnvironmentOptions: { testURL: 'http://localhost' }, 20 | rootDir, 21 | watchPlugins: [ 22 | 'jest-watch-typeahead/filename', 23 | 'jest-watch-typeahead/testname', 24 | ], 25 | }; 26 | 27 | return config; 28 | } 29 | -------------------------------------------------------------------------------- /src/createProgressEstimator.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | 3 | import { paths } from './constants'; 4 | 5 | const progressEstimator = require('progress-estimator'); 6 | 7 | export async function createProgressEstimator() { 8 | await fs.ensureDir(paths.progressEstimatorCache); 9 | return progressEstimator({ 10 | // All configuration keys are optional, but it's recommended to specify a storage location. 11 | storagePath: paths.progressEstimatorCache, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/defaults.ts: -------------------------------------------------------------------------------- 1 | import { NormalizedDtsConfig } from './types'; 2 | 3 | export const configDefaults: NormalizedDtsConfig = Object.freeze({ 4 | rollup: (config: any) => config, 5 | }); 6 | -------------------------------------------------------------------------------- /src/deprecated.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | 3 | import { paths } from './constants'; 4 | 5 | /* 6 | This was originally needed because the default 7 | tsconfig.compilerOptions.rootDir was set to './' instead of './src'. 8 | Now that it's set to './src', this is now deprecated. 9 | To ensure a stable upgrade path for users, leave the warning in for 10 | 6 months - 1 year, then change it to an error in a breaking bump and leave 11 | that in for some time too. 12 | */ 13 | export async function moveTypes() { 14 | const appDistSrc = paths.appDist + '/src'; 15 | 16 | const pathExists = await fs.pathExists(appDistSrc); 17 | if (!pathExists) return; 18 | 19 | // see note above about deprecation window 20 | console.warn( 21 | '[dts]: Your rootDir is currently set to "./". Please change your ' + 22 | 'rootDir to "./src".\n' + 23 | 'DTS has deprecated setting tsconfig.compilerOptions.rootDir to ' + 24 | '"./" as it caused buggy output for declarationMaps and more.\n' + 25 | 'You may also need to change your include to remove "test", which also ' + 26 | 'caused declarations to be unnecessarily created for test files.' 27 | ); 28 | 29 | // Move the type declarations to the base of the ./dist folder 30 | await fs.copy(appDistSrc, paths.appDist, { 31 | overwrite: true, 32 | }); 33 | await fs.remove(appDistSrc); 34 | } 35 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'asyncro'; // doesn't have types (unmerged 2+ year old PR: https://github.com/developit/asyncro/pull/10) 2 | declare module 'enquirer'; // doesn't have types for Input or Select 3 | declare module 'jpjs'; // doesn't ship types (written in TS though) 4 | declare module 'tiny-glob/sync'; // /sync isn't typed (but maybe we can use async?) 5 | 6 | // Patch Babel 7 | // @see line 226 of https://unpkg.com/@babel/core@7.4.4/lib/index.js 8 | declare module '@babel/core' { 9 | export const DEFAULT_EXTENSIONS: string[]; 10 | export function createConfigItem(boop: any[], options: any): any[]; 11 | } 12 | 13 | // Rollup plugins 14 | // declare module 'rollup-plugin-terser'; 15 | declare module '@babel/traverse'; 16 | declare module '@babel/helper-module-imports'; 17 | 18 | declare module 'lodash.merge'; 19 | -------------------------------------------------------------------------------- /src/errors/evalToString.ts: -------------------------------------------------------------------------------- 1 | // largely borrowed from https://github.com/facebook/react/blob/8b2d3783e58d1acea53428a10d2035a8399060fe/scripts/shared/evalToString.js 2 | /** 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | export function evalToString(ast: any): string { 10 | switch (ast.type) { 11 | case 'StringLiteral': 12 | case 'Literal': // ESLint 13 | return ast.value; 14 | case 'BinaryExpression': // `+` 15 | if (ast.operator !== '+') { 16 | throw new Error('Unsupported binary operator ' + ast.operator); 17 | } 18 | return evalToString(ast.left) + evalToString(ast.right); 19 | default: 20 | throw new Error('Unsupported type ' + ast.type); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/errors/extractErrors.ts: -------------------------------------------------------------------------------- 1 | // largely borrowed from https://github.com/facebook/react/blob/8b2d3783e58d1acea53428a10d2035a8399060fe/scripts/error-codes/extract-errors.js 2 | /** 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | import fs from 'fs-extra'; 9 | import { parse, ParserOptions } from '@babel/parser'; 10 | import traverse from '@babel/traverse'; 11 | import { invertObject } from './invertObject'; 12 | import { evalToString } from './evalToString'; 13 | import { paths } from '../constants'; 14 | import { safeVariableName } from '../utils'; 15 | import { pascalCase } from 'pascal-case'; 16 | 17 | const babelParserOptions: ParserOptions = { 18 | sourceType: 'module', 19 | // As a parser, @babel/parser has its own options and we can't directly 20 | // import/require a babel preset. It should be kept **the same** as 21 | // the `babel-plugin-syntax-*` ones specified in 22 | // https://github.com/facebook/fbjs/blob/master/packages/babel-preset-fbjs/configure.js 23 | plugins: [ 24 | 'classProperties', 25 | 'flow', 26 | 'jsx', 27 | 'trailingFunctionCommas', 28 | 'objectRestSpread', 29 | ], 30 | } as ParserOptions; // workaround for trailingFunctionCommas syntax 31 | 32 | export async function extractErrors(opts: any) { 33 | if (!opts || !('errorMapFilePath' in opts)) { 34 | throw new Error( 35 | 'Missing options. Ensure you pass an object with `errorMapFilePath`.' 36 | ); 37 | } 38 | 39 | if (!opts.name || !('name' in opts)) { 40 | throw new Error('Missing options. Ensure you pass --name flag to dts'); 41 | } 42 | 43 | const errorMapFilePath = opts.errorMapFilePath; 44 | let existingErrorMap: any; 45 | try { 46 | // Using `fs.readFile` instead of `require` here, because `require()` 47 | // calls are cached, and the cache map is not properly invalidated after 48 | // file changes. 49 | existingErrorMap = JSON.parse(await fs.readFile(errorMapFilePath, 'utf8')); 50 | } catch (e) { 51 | existingErrorMap = {}; 52 | } 53 | 54 | const allErrorIDs = Object.keys(existingErrorMap); 55 | let currentID: any; 56 | 57 | if (allErrorIDs.length === 0) { 58 | // Map is empty 59 | currentID = 0; 60 | } else { 61 | currentID = Math.max.apply(null, allErrorIDs as any) + 1; 62 | } 63 | 64 | // Here we invert the map object in memory for faster error code lookup 65 | existingErrorMap = invertObject(existingErrorMap); 66 | 67 | function transform(source: string) { 68 | const ast = parse(source, babelParserOptions); 69 | 70 | traverse(ast, { 71 | CallExpression: { 72 | exit(astPath: any) { 73 | if (astPath.get('callee').isIdentifier({ name: 'invariant' })) { 74 | const node = astPath.node; 75 | 76 | // error messages can be concatenated (`+`) at runtime, so here's a 77 | // trivial partial evaluator that interprets the literal value 78 | const errorMsgLiteral = evalToString(node.arguments[1]); 79 | addToErrorMap(errorMsgLiteral); 80 | } 81 | }, 82 | }, 83 | }); 84 | } 85 | 86 | function addToErrorMap(errorMsgLiteral: any) { 87 | if (existingErrorMap.hasOwnProperty(errorMsgLiteral)) { 88 | return; 89 | } 90 | existingErrorMap[errorMsgLiteral] = '' + currentID++; 91 | } 92 | 93 | async function flush() { 94 | const prettyName = pascalCase(safeVariableName(opts.name)); 95 | // Ensure that the ./src/errors directory exists or create it 96 | await fs.ensureDir(paths.appErrors); 97 | 98 | // Output messages to ./errors/codes.json 99 | await fs.writeFile( 100 | errorMapFilePath, 101 | JSON.stringify(invertObject(existingErrorMap), null, 2) + '\n', 102 | 'utf-8' 103 | ); 104 | 105 | // Write the error files, unless they already exist 106 | await fs.writeFile( 107 | paths.appErrors + '/ErrorDev.js', 108 | ` 109 | function ErrorDev(message) { 110 | const error = new Error(message); 111 | error.name = 'Invariant Violation'; 112 | return error; 113 | } 114 | 115 | export default ErrorDev; 116 | `, 117 | 'utf-8' 118 | ); 119 | 120 | await fs.writeFile( 121 | paths.appErrors + '/ErrorProd.js', 122 | ` 123 | function ErrorProd(code) { 124 | // TODO: replace this URL with yours 125 | let url = 'https://reactjs.org/docs/error-decoder.html?invariant=' + code; 126 | for (let i = 1; i < arguments.length; i++) { 127 | url += '&args[]=' + encodeURIComponent(arguments[i]); 128 | } 129 | return new Error( 130 | \`Minified ${prettyName} error #$\{code}; visit $\{url} for the full message or \` + 131 | 'use the non-minified dev environment for full errors and additional ' + 132 | 'helpful warnings. ' 133 | ); 134 | } 135 | 136 | export default ErrorProd; 137 | `, 138 | 'utf-8' 139 | ); 140 | } 141 | 142 | return async function extractErrors(source: any) { 143 | transform(source); 144 | await flush(); 145 | }; 146 | } 147 | -------------------------------------------------------------------------------- /src/errors/invertObject.ts: -------------------------------------------------------------------------------- 1 | // largely borrowed from https://github.com/facebook/react/blob/8b2d3783e58d1acea53428a10d2035a8399060fe/scripts/error-codes/invertObject.js 2 | 3 | /** 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | */ 9 | 10 | /** 11 | * turns 12 | * { 'MUCH ERROR': '0', 'SUCH WRONG': '1' } 13 | * into 14 | * { 0: 'MUCH ERROR', 1: 'SUCH WRONG' } 15 | */ 16 | 17 | type Dict = { [key: string]: any }; 18 | 19 | export function invertObject(targetObj: Dict) { 20 | const result: Dict = {}; 21 | const mapKeys = Object.keys(targetObj); 22 | 23 | for (const originalKey of mapKeys) { 24 | const originalVal = targetObj[originalKey]; 25 | 26 | result[originalVal] = originalKey; 27 | } 28 | 29 | return result; 30 | } 31 | -------------------------------------------------------------------------------- /src/errors/transformErrorMessages.ts: -------------------------------------------------------------------------------- 1 | // largely borrowed from https://github.com/facebook/react/blob/2c8832075b05009bd261df02171bf9888ac76350/scripts/error-codes/transform-error-messages.js 2 | /** 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import fs from 'fs'; 10 | import { invertObject } from './invertObject'; 11 | import { evalToString } from './evalToString'; 12 | import { addDefault } from '@babel/helper-module-imports'; 13 | import { paths } from '../constants'; 14 | 15 | export default function transformErrorMessages(babel: any) { 16 | const t = babel.types; 17 | 18 | const DEV_EXPRESSION = t.identifier('__DEV__'); 19 | 20 | return { 21 | visitor: { 22 | CallExpression(path: any, file: any) { 23 | const node = path.node; 24 | const noMinify = file.opts.noMinify; 25 | if (path.get('callee').isIdentifier({ name: 'invariant' })) { 26 | // Turns this code: 27 | // 28 | // invariant(condition, 'A %s message that contains %s', adj, noun); 29 | // 30 | // into this: 31 | // 32 | // if (!condition) { 33 | // if (__DEV__) { 34 | // throw ReactError(`A ${adj} message that contains ${noun}`); 35 | // } else { 36 | // throw ReactErrorProd(ERR_CODE, adj, noun); 37 | // } 38 | // } 39 | // 40 | // where ERR_CODE is an error code: a unique identifier (a number 41 | // string) that references a verbose error message. The mapping is 42 | // stored in `paths.appErrorsJson`. 43 | const condition = node.arguments[0]; 44 | const errorMsgLiteral = evalToString(node.arguments[1]); 45 | const errorMsgExpressions = Array.from(node.arguments.slice(2)); 46 | const errorMsgQuasis = errorMsgLiteral 47 | .split('%s') 48 | .map((raw: any) => 49 | t.templateElement({ raw, cooked: String.raw({ raw } as any) }) 50 | ); 51 | 52 | // Import ReactError 53 | const reactErrorIdentfier = addDefault( 54 | path, 55 | paths.appRoot + '/errors/ErrorDev.js', 56 | { 57 | nameHint: 'InvariantError', 58 | } 59 | ); 60 | 61 | // Outputs: 62 | // throw ReactError(`A ${adj} message that contains ${noun}`); 63 | const devThrow = t.throwStatement( 64 | t.callExpression(reactErrorIdentfier, [ 65 | t.templateLiteral(errorMsgQuasis, errorMsgExpressions), 66 | ]) 67 | ); 68 | 69 | if (noMinify) { 70 | // Error minification is disabled for this build. 71 | // 72 | // Outputs: 73 | // if (!condition) { 74 | // throw ReactError(`A ${adj} message that contains ${noun}`); 75 | // } 76 | path.replaceWith( 77 | t.ifStatement( 78 | t.unaryExpression('!', condition), 79 | t.blockStatement([devThrow]) 80 | ) 81 | ); 82 | return; 83 | } 84 | 85 | // Avoid caching because we write it as we go. 86 | const existingErrorMap = JSON.parse( 87 | fs.readFileSync(paths.appErrorsJson, 'utf-8') 88 | ); 89 | const errorMap = invertObject(existingErrorMap); 90 | 91 | let prodErrorId = errorMap[errorMsgLiteral]; 92 | 93 | if (prodErrorId === undefined) { 94 | // There is no error code for this message. Add an inline comment 95 | // that flags this as an unminified error. This allows the build 96 | // to proceed, while also allowing a post-build linter to detect it. 97 | // 98 | // Outputs: 99 | // /* FIXME (minify-errors-in-prod): Unminified error message in production build! */ 100 | // if (!condition) { 101 | // throw ReactError(`A ${adj} message that contains ${noun}`); 102 | // } 103 | path.replaceWith( 104 | t.ifStatement( 105 | t.unaryExpression('!', condition), 106 | t.blockStatement([devThrow]) 107 | ) 108 | ); 109 | path.addComment( 110 | 'leading', 111 | 'FIXME (minify-errors-in-prod): Unminified error message in production build!' 112 | ); 113 | return; 114 | } 115 | prodErrorId = parseInt(prodErrorId, 10); 116 | 117 | // Import ReactErrorProd 118 | const reactErrorProdIdentfier = addDefault( 119 | path, 120 | paths.appRoot + '/errors/ErrorProd.js', 121 | { 122 | nameHint: 'InvariantErrorProd', 123 | } 124 | ); 125 | 126 | // Outputs: 127 | // throw ReactErrorProd(ERR_CODE, adj, noun); 128 | const prodThrow = t.throwStatement( 129 | t.callExpression(reactErrorProdIdentfier, [ 130 | t.numericLiteral(prodErrorId), 131 | ...errorMsgExpressions, 132 | ]) 133 | ); 134 | 135 | // Outputs: 136 | // if (!condition) { 137 | // if (__DEV__) { 138 | // throw ReactError(`A ${adj} message that contains ${noun}`); 139 | // } else { 140 | // throw ReactErrorProd(ERR_CODE, adj, noun); 141 | // } 142 | // } 143 | path.replaceWith( 144 | t.ifStatement( 145 | t.unaryExpression('!', condition), 146 | t.blockStatement([ 147 | t.ifStatement( 148 | DEV_EXPRESSION, 149 | t.blockStatement([devThrow]), 150 | t.blockStatement([prodThrow]) 151 | ), 152 | ]) 153 | ) 154 | ); 155 | } 156 | }, 157 | }, 158 | }; 159 | } 160 | -------------------------------------------------------------------------------- /src/getInstallArgs.ts: -------------------------------------------------------------------------------- 1 | import { InstallCommand } from './getInstallCmd'; 2 | 3 | export default function getInstallArgs( 4 | cmd: InstallCommand, 5 | packages: string[] 6 | ) { 7 | // replace 'package#version' with 'package@version' 8 | packages = packages.map((pkg) => pkg.replace(/#/, '@')); 9 | 10 | switch (cmd) { 11 | case 'npm': 12 | return ['install', ...packages, '--save-dev']; 13 | case 'yarn': 14 | return ['add', ...packages, '--dev']; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/getInstallCmd.ts: -------------------------------------------------------------------------------- 1 | import execa from 'execa'; 2 | 3 | let cmd: InstallCommand; 4 | 5 | export type InstallCommand = 'yarn' | 'npm'; 6 | 7 | export default async function getInstallCmd(): Promise { 8 | if (cmd) { 9 | return cmd; 10 | } 11 | 12 | try { 13 | await execa('yarnpkg', ['--version']); 14 | cmd = 'yarn'; 15 | } catch (e) { 16 | cmd = 'npm'; 17 | } 18 | 19 | return cmd; 20 | } 21 | -------------------------------------------------------------------------------- /src/logError.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | const stderr = console.error.bind(console); 4 | 5 | export default function logError(err: any) { 6 | const error = err.error || err; 7 | const description = `${error.name ? error.name + ': ' : ''}${ 8 | error.message || error 9 | }`; 10 | const message = error.plugin 11 | ? error.plugin === 'rpt2' 12 | ? `(typescript) ${description}` 13 | : `(${error.plugin} plugin) ${description}` 14 | : description; 15 | 16 | stderr(chalk.bold.red(message)); 17 | 18 | if (error.loc) { 19 | stderr(); 20 | stderr(`at ${error.loc.file}:${error.loc.line}:${error.loc.column}`); 21 | } 22 | 23 | if (error.frame) { 24 | stderr(); 25 | stderr(chalk.dim(error.frame)); 26 | } else if (err.stack) { 27 | const headlessStack = error.stack.replace(message, ''); 28 | stderr(chalk.dim(headlessStack)); 29 | } 30 | 31 | stderr(); 32 | } 33 | -------------------------------------------------------------------------------- /src/messages.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import getInstallCmd from './getInstallCmd'; 3 | import * as Output from './output'; 4 | 5 | // using import will report: 'rootDir' is expected to contain all source files. 6 | const pkg = require('../package.json'); 7 | 8 | // This was copied from Razzle. Lots of unused stuff. 9 | const program = { 10 | name: 'dts', 11 | }; 12 | 13 | export const help = function () { 14 | return ` 15 | Only ${chalk.green('')} is required. 16 | If you have any problems, do not hesitate to file an issue: 17 | ${chalk.cyan(`${pkg.bugs.url}/new`)} 18 | `; 19 | }; 20 | 21 | export const missingProjectName = function () { 22 | return ` 23 | Please specify the project directory: 24 | ${chalk.cyan(program.name)} ${chalk.green('')} 25 | For example: 26 | ${chalk.cyan(program.name)} ${chalk.green('my-dts-lib')} 27 | Run ${chalk.cyan(`${program.name} --help`)} to see all options. 28 | `; 29 | }; 30 | 31 | export const alreadyExists = function (projectName: string) { 32 | return ` 33 | Uh oh! Looks like there's already a directory called ${chalk.red( 34 | projectName 35 | )}. Please try a different name or delete that folder.`; 36 | }; 37 | 38 | export const installing = function (packages: string[]) { 39 | const pkgText = packages 40 | .map(function (pkg) { 41 | return ` ${chalk.cyan(chalk.bold(pkg))}`; 42 | }) 43 | .join('\n'); 44 | 45 | return `Installing npm modules: 46 | ${pkgText} 47 | `; 48 | }; 49 | 50 | export const installError = function (packages: string[]) { 51 | const pkgText = packages 52 | .map(function (pkg) { 53 | return `${chalk.cyan(chalk.bold(pkg))}`; 54 | }) 55 | .join(', '); 56 | 57 | Output.error(`Failed to install ${pkgText}, try again.`); 58 | }; 59 | 60 | export const copying = function (projectName: string) { 61 | return ` 62 | Creating ${chalk.bold(chalk.green(projectName))}... 63 | `; 64 | }; 65 | 66 | export const start = async function (projectName: string) { 67 | const cmd = await getInstallCmd(); 68 | 69 | const commands = { 70 | install: cmd === 'npm' ? 'npm install' : 'yarn install', 71 | build: cmd === 'npm' ? 'npm run build' : 'yarn build', 72 | start: cmd === 'npm' ? 'npm run start' : 'yarn start', 73 | test: cmd === 'npm' ? 'npm test' : 'yarn test', 74 | }; 75 | 76 | return ` 77 | ${chalk.green('Awesome!')} You're now ready to start coding. 78 | 79 | I already ran ${Output.cmd(commands.install)} for you, so your next steps are: 80 | ${Output.cmd(`cd ${projectName}`)} 81 | 82 | To start developing (rebuilds on changes): 83 | ${Output.cmd(commands.start)} 84 | 85 | To build for production: 86 | ${Output.cmd(commands.build)} 87 | 88 | To test your library with Jest: 89 | ${Output.cmd(commands.test)} 90 | 91 | Questions? Feedback? Please let me know! 92 | ${chalk.green(pkg.bugs.url)} 93 | `; 94 | }; 95 | 96 | export const incorrectNodeVersion = function (requiredVersion: string) { 97 | return `Unsupported Node version! Your current Node version (${chalk.red( 98 | process.version 99 | )}) does not satisfy the requirement of Node ${chalk.cyan(requiredVersion)}.`; 100 | }; 101 | -------------------------------------------------------------------------------- /src/output.ts: -------------------------------------------------------------------------------- 1 | import { eraseLine } from 'ansi-escapes'; 2 | import chalk from 'chalk'; 3 | import ora from 'ora'; 4 | 5 | // This was copied from Razzle. Lots of unused stuff. 6 | export const info = (msg: string) => { 7 | console.log(`${chalk.gray('>')} ${msg}`); 8 | }; 9 | 10 | export const error = (msg: string | Error) => { 11 | if (msg instanceof Error) { 12 | msg = msg.message; 13 | } 14 | 15 | console.error(`${chalk.red('> Error!')} ${msg}`); 16 | }; 17 | 18 | export const success = (msg: string) => { 19 | console.log(`${chalk.green('> Success!')} ${msg}`); 20 | }; 21 | 22 | export const wait = (msg: string) => { 23 | const spinner = ora(chalk.green(msg)); 24 | spinner.color = 'blue'; 25 | spinner.start(); 26 | 27 | return () => { 28 | spinner.stop(); 29 | process.stdout.write(eraseLine); 30 | }; 31 | }; 32 | 33 | export const cmd = (cmd: string) => { 34 | return chalk.bold(chalk.cyan(cmd)); 35 | }; 36 | 37 | export const code = (cmd: string) => { 38 | return `${chalk.gray('`')}${chalk.bold(cmd)}${chalk.gray('`')}`; 39 | }; 40 | 41 | export const param = (param: string) => { 42 | return chalk.bold(`${chalk.gray('{')}${chalk.bold(param)}${chalk.gray('}')}`); 43 | }; 44 | -------------------------------------------------------------------------------- /src/rollupTypes.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import dts from 'rollup-plugin-dts'; 3 | import del from 'rollup-plugin-delete'; 4 | import { OutputOptions, rollup, RollupOptions } from 'rollup'; 5 | import { typescriptCompilerOptions } from './tsconfig'; 6 | import { PackageJson } from './types'; 7 | import { paths } from './constants'; 8 | import { resolveApp } from './utils'; 9 | 10 | function descendantOfDist(declarationDir: string): boolean { 11 | const relative = path.relative(paths.appDist, resolveApp(declarationDir)); 12 | return ( 13 | Boolean(relative) && 14 | !relative.startsWith('..') && 15 | !path.isAbsolute(relative) 16 | ); 17 | } 18 | 19 | function getTypesEntryPoint(appPackageJson: PackageJson): string | undefined { 20 | // https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#including-declarations-in-your-npm-package 21 | return appPackageJson.types || appPackageJson.typings; 22 | } 23 | 24 | export function isTypesRollupEnabled(appPackageJson: PackageJson): boolean { 25 | return Boolean(getTypesEntryPoint(appPackageJson)); 26 | } 27 | 28 | export async function rollupTypes( 29 | tsconfig: string | undefined, 30 | appPackageJson: PackageJson 31 | ) { 32 | const tsCompilerOptions = typescriptCompilerOptions(tsconfig); 33 | const declarationDir = 34 | tsCompilerOptions.declarationDir || path.join('dist', 'types'); 35 | 36 | // define bailout conditions 37 | // - when no 'typings' or 'types' entrypoint is defined in package.json 38 | // - when tsconfig.json `declaration` is explicitly set to false 39 | // - when `declarationDir` is not a descendant of `dist` (this must be a configuration error, but bailing out just to be safe) 40 | if ( 41 | !isTypesRollupEnabled(appPackageJson) || 42 | tsCompilerOptions.declaration === false || 43 | !descendantOfDist(declarationDir) 44 | ) { 45 | return; 46 | } 47 | 48 | const config = { 49 | input: path.join(declarationDir, 'index.d.ts'), 50 | output: { file: getTypesEntryPoint(appPackageJson), format: 'es' }, 51 | plugins: [dts(), del({ hook: 'buildEnd', targets: declarationDir })], 52 | } as RollupOptions & { output: OutputOptions }; 53 | 54 | try { 55 | const bundle = await rollup(config); 56 | await bundle.write(config.output); 57 | } catch (e) { 58 | console.log('Failed to rollup types:', e); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/templates/basic.ts: -------------------------------------------------------------------------------- 1 | import { Template } from './template'; 2 | 3 | const basicTemplate: Template = { 4 | name: 'basic', 5 | dependencies: [ 6 | 'husky', 7 | 'dts-cli', 8 | 'tslib', 9 | 'typescript', 10 | 'size-limit', 11 | '@size-limit/preset-small-lib', 12 | '@tsconfig/recommended', 13 | ], 14 | packageJson: { 15 | // name: safeName, 16 | version: '0.1.0', 17 | license: 'MIT', 18 | // author: author, 19 | main: 'dist/index.js', 20 | // module: `dist/${safeName}.esm.js`, 21 | typings: `dist/index.d.ts`, 22 | files: ['dist', 'src'], 23 | engines: { 24 | node: '>=12', 25 | }, 26 | scripts: { 27 | start: 'dts watch', 28 | build: 'dts build', 29 | test: 'dts test', 30 | lint: 'dts lint', 31 | prepare: 'dts build', 32 | size: 'size-limit', 33 | analyze: 'size-limit --why', 34 | }, 35 | peerDependencies: {}, 36 | /* 37 | 'size-limit': [ 38 | { 39 | path: `dist/${safeName}.cjs.production.min.js`, 40 | limit: '10 KB', 41 | }, 42 | { 43 | path: `dist/${safeName}.esm.js`, 44 | limit: '10 KB', 45 | }, 46 | ], 47 | */ 48 | husky: { 49 | hooks: { 50 | 'pre-commit': 'dts lint', 51 | }, 52 | }, 53 | prettier: { 54 | printWidth: 80, 55 | semi: true, 56 | singleQuote: true, 57 | trailingComma: 'es5', 58 | }, 59 | jest: { 60 | testEnvironment: 'node', 61 | }, 62 | }, 63 | }; 64 | 65 | export default basicTemplate; 66 | -------------------------------------------------------------------------------- /src/templates/index.ts: -------------------------------------------------------------------------------- 1 | import reactTemplate from './react'; 2 | import basicTemplate from './basic'; 3 | import storybookTemplate from './react-with-storybook'; 4 | 5 | export const templates = { 6 | basic: basicTemplate, 7 | react: reactTemplate, 8 | 'react-with-storybook': storybookTemplate, 9 | }; 10 | -------------------------------------------------------------------------------- /src/templates/react-with-storybook.ts: -------------------------------------------------------------------------------- 1 | import { Template } from './template'; 2 | import reactTemplate from './react'; 3 | import { PackageJson } from 'type-fest'; 4 | 5 | const storybookTemplate: Template = { 6 | dependencies: [ 7 | ...reactTemplate.dependencies, 8 | '@babel/core', 9 | 'storybook#next', 10 | '@storybook/addon-essentials#next', 11 | '@storybook/addon-info#next', 12 | '@storybook/addon-links#next', 13 | '@storybook/addons#next', 14 | '@storybook/react#next', 15 | '@storybook/react-vite#next', 16 | 'vite', 17 | 'react-is', 18 | 'babel-loader', 19 | ], 20 | name: 'react-with-storybook', 21 | packageJson: { 22 | ...reactTemplate.packageJson, 23 | scripts: { 24 | ...reactTemplate.packageJson.scripts, 25 | storybook: 'storybook dev -p 6006', 26 | 'build-storybook': 'storybook build', 27 | } as PackageJson['scripts'], 28 | jest: { 29 | testEnvironment: 'jsdom', 30 | }, 31 | }, 32 | }; 33 | 34 | export default storybookTemplate; 35 | -------------------------------------------------------------------------------- /src/templates/react.ts: -------------------------------------------------------------------------------- 1 | import { Template } from './template'; 2 | 3 | import basicTemplate from './basic'; 4 | import { PackageJson } from 'type-fest'; 5 | 6 | const reactTemplate: Template = { 7 | name: 'react', 8 | dependencies: [ 9 | ...basicTemplate.dependencies, 10 | '@types/react', 11 | '@types/react-dom', 12 | 'react', 13 | 'react-dom', 14 | '@tsconfig/vite-react', 15 | ], 16 | packageJson: { 17 | ...basicTemplate.packageJson, 18 | peerDependencies: { 19 | react: '>=16', 20 | }, 21 | scripts: { 22 | ...basicTemplate.packageJson.scripts, 23 | test: 'dts test --passWithNoTests', 24 | } as PackageJson['scripts'], 25 | jest: { 26 | testEnvironment: 'jsdom', 27 | }, 28 | }, 29 | }; 30 | 31 | export default reactTemplate; 32 | -------------------------------------------------------------------------------- /src/templates/template.d.ts: -------------------------------------------------------------------------------- 1 | import { PackageJson } from 'type-fest'; 2 | 3 | interface Template { 4 | dependencies: string[]; 5 | name: string; 6 | packageJson: PackageJson & { 7 | husky?: any; 8 | prettier?: any; 9 | jest?: any; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/templates/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { Template } from '../template'; 2 | 3 | interface ProjectArgs { 4 | name: string; 5 | author: string; 6 | includeHuskyConfig: boolean; 7 | } 8 | export const composePackageJson = 9 | (template: Template) => 10 | ({ name, author, includeHuskyConfig }: ProjectArgs) => { 11 | const pkgJson = { 12 | ...template.packageJson, 13 | name, 14 | author, 15 | module: `dist/${name}.esm.js`, 16 | 'size-limit': [ 17 | { 18 | path: `dist/${name}.cjs.production.min.js`, 19 | limit: '10 KB', 20 | }, 21 | { 22 | path: `dist/${name}.esm.js`, 23 | limit: '10 KB', 24 | }, 25 | ], 26 | }; 27 | if (!includeHuskyConfig) { 28 | delete pkgJson.husky; 29 | } 30 | return pkgJson; 31 | }; 32 | 33 | interface DependencyArgs { 34 | includeHusky: boolean; 35 | } 36 | export const composeDependencies = 37 | (template: Template) => 38 | ({ includeHusky }: DependencyArgs) => { 39 | return template.dependencies.filter( 40 | (dep) => dep !== 'husky' || includeHusky 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /src/tsconfig.ts: -------------------------------------------------------------------------------- 1 | import ts, { CompilerOptions } from 'typescript'; 2 | import { paths } from './constants'; 3 | 4 | export function typescriptCompilerOptions( 5 | tsconfig: string | undefined 6 | ): CompilerOptions { 7 | const tsconfigPath = tsconfig || paths.tsconfigJson; 8 | 9 | // borrowed from https://github.com/facebook/create-react-app/pull/7248 10 | const tsconfigJSON = ts.readConfigFile(tsconfigPath, ts.sys.readFile).config; 11 | 12 | // borrowed from https://github.com/ezolenko/rollup-plugin-typescript2/blob/42173460541b0c444326bf14f2c8c27269c4cb11/src/parse-tsconfig.ts#L48 13 | return ts.parseJsonConfigFileContent(tsconfigJSON, ts.sys, './').options; 14 | } 15 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { RollupOptions } from 'rollup'; 2 | 3 | interface SharedOpts { 4 | // JS target 5 | target: 'node' | 'browser'; 6 | // Path to tsconfig file 7 | tsconfig?: string; 8 | // Is error extraction running? 9 | extractErrors?: boolean; 10 | } 11 | 12 | export type ModuleFormat = 'cjs' | 'umd' | 'esm' | 'system'; 13 | 14 | export interface BuildOpts extends SharedOpts { 15 | name?: string; 16 | entry?: string | string[]; 17 | format: 'cjs,esm'; 18 | target: 'browser'; 19 | noClean?: boolean; 20 | rollupTypes?: boolean; 21 | } 22 | 23 | export interface WatchOpts extends BuildOpts { 24 | verbose?: boolean; 25 | // callback hooks 26 | onFirstSuccess?: string; 27 | onSuccess?: string; 28 | onFailure?: string; 29 | } 30 | 31 | export interface NormalizedOpts 32 | extends Omit { 33 | name: string | string[]; 34 | input: string[]; 35 | format: [ModuleFormat, ...ModuleFormat[]]; 36 | output: { 37 | file: string[]; 38 | }; 39 | } 40 | 41 | export type DtsOptionsInput = { [entryAlias: string]: string }; 42 | 43 | export interface DtsOptions extends SharedOpts { 44 | // Name of package 45 | name: string; 46 | // path to file 47 | input: string | DtsOptionsInput; 48 | // Environment 49 | env: 'development' | 'production'; 50 | // Module format 51 | format: ModuleFormat; 52 | // Is minifying? 53 | minify?: boolean; 54 | // Is this the very first rollup config (and thus should one-off metadata be extracted)? 55 | writeMeta?: boolean; 56 | // Only transpile, do not type check (makes compilation faster) 57 | transpileOnly?: boolean; 58 | // Is rolling up types? 59 | rollupTypes?: boolean; 60 | } 61 | 62 | export interface PackageJson { 63 | name: string; 64 | source?: string; 65 | jest?: any; 66 | eslint?: any; 67 | dependencies?: { [packageName: string]: string | undefined }; 68 | devDependencies?: { [packageName: string]: string | undefined }; 69 | engines?: { 70 | node?: string; 71 | }; 72 | types?: string; 73 | typings?: string; 74 | } 75 | 76 | export interface DtsConfig { 77 | rollup?: (config: RollupOptions, options: DtsOptions) => RollupOptions; 78 | } 79 | 80 | export interface NormalizedDtsConfig extends DtsConfig { 81 | rollup: (config: RollupOptions, options: DtsOptions) => RollupOptions; 82 | } 83 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | import camelCase from 'camelcase'; 4 | 5 | import { PackageJson } from './types'; 6 | 7 | // Remove the package name scope if it exists 8 | export const removeScope = (name: string) => name.replace(/^@.*\//, ''); 9 | 10 | // UMD-safe package name 11 | export const safeVariableName = (name: string) => 12 | camelCase( 13 | removeScope(name) 14 | .toLowerCase() 15 | .replace(/((^[^a-zA-Z]+)|[^\w.-])|([^a-zA-Z0-9]+$)/g, '') 16 | ); 17 | 18 | export const safePackageName = (name: string) => 19 | name 20 | .toLowerCase() 21 | .replace(/(^@.*\/)|((^[^a-zA-Z]+)|[^\w.-])|([^a-zA-Z0-9]+$)/g, ''); 22 | 23 | export const external = (id: string) => 24 | !id.startsWith('.') && !path.isAbsolute(id); 25 | 26 | // Make sure any symlinks in the project folder are resolved: 27 | // https://github.com/facebookincubator/create-react-app/issues/637 28 | export const appDirectory = fs.realpathSync(process.cwd()); 29 | export const resolveApp = function (relativePath: string) { 30 | return path.resolve(appDirectory, relativePath); 31 | }; 32 | 33 | // Taken from Create React App, react-dev-utils/clearConsole 34 | // @see https://github.com/facebook/create-react-app/blob/master/packages/react-dev-utils/clearConsole.js 35 | export function clearConsole() { 36 | process.stdout.write( 37 | process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H' 38 | ); 39 | } 40 | 41 | export function getReactVersion({ 42 | dependencies, 43 | devDependencies, 44 | }: PackageJson) { 45 | return ( 46 | (dependencies && dependencies.react) || 47 | (devDependencies && devDependencies.react) 48 | ); 49 | } 50 | 51 | export function getNodeEngineRequirement({ engines }: PackageJson) { 52 | return engines && engines.node; 53 | } 54 | 55 | // copied from https://github.com/facebook/jest/blob/5b14366bf3726d48c67b1c6609764556052d909f/packages/jest-util/src/interopRequireDefault.ts#L10 56 | export function interopRequireDefault(obj: any): any { 57 | return obj && obj.__esModule ? obj : { default: obj }; 58 | } 59 | 60 | // resolve a module from where dts was installed 61 | // this is needed as some package managers failed to hoist the modules 62 | // e.g. https://github.com/weiran-zsd/dts-cli/issues/186 63 | export function resolve(mod: string) { 64 | return require.resolve(mod, { paths: [__dirname] }); 65 | } 66 | -------------------------------------------------------------------------------- /templates/basic/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ['14.x', '16.x'] 11 | os: [ubuntu-latest, windows-latest, macOS-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v4 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Lint 26 | run: yarn lint 27 | 28 | - name: Test 29 | run: yarn test --ci --coverage --maxWorkers=2 30 | 31 | - name: Build 32 | run: yarn build 33 | -------------------------------------------------------------------------------- /templates/basic/.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | env: 7 | CI_JOB_NUMBER: 1 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: andresz1/size-limit-action@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /templates/basic/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /templates/basic/README.md: -------------------------------------------------------------------------------- 1 | # DTS User Guide 2 | 3 | Congrats! You just saved yourself hours of work by bootstrapping this project with DTS. Let’s get you oriented with what’s here and how to use it. 4 | 5 | > This DTS setup is meant for developing libraries (not apps!) that can be published to NPM. If you’re looking to build a Node app, you could use `ts-node-dev`, plain `ts-node`, or simple `tsc`. 6 | 7 | > If you’re new to TypeScript, checkout [this handy cheatsheet](https://devhints.io/typescript) 8 | 9 | ## Commands 10 | 11 | DTS scaffolds your new library inside `/src`. 12 | 13 | To run DTS, use: 14 | 15 | ```bash 16 | npm start # or yarn start 17 | ``` 18 | 19 | This builds to `/dist` and runs the project in watch mode so any edits you save inside `src` causes a rebuild to `/dist`. 20 | 21 | To do a one-off build, use `npm run build` or `yarn build`. 22 | 23 | To run tests, use `npm test` or `yarn test`. 24 | 25 | ## Configuration 26 | 27 | Code quality is set up for you with `prettier`, `husky`, and `lint-staged`. Adjust the respective fields in `package.json` accordingly. 28 | 29 | ### Jest 30 | 31 | Jest tests are set up to run with `npm test` or `yarn test`. 32 | 33 | ### Bundle Analysis 34 | 35 | [`size-limit`](https://github.com/ai/size-limit) is set up to calculate the real cost of your library with `npm run size` and visualize the bundle with `npm run analyze`. 36 | 37 | #### Setup Files 38 | 39 | This is the folder structure we set up for you: 40 | 41 | ```txt 42 | /src 43 | index.ts # EDIT THIS 44 | /test 45 | index.test.ts # EDIT THIS 46 | .gitignore 47 | package.json 48 | README.md # EDIT THIS 49 | tsconfig.json 50 | ``` 51 | 52 | ### Rollup 53 | 54 | DTS uses [Rollup](https://rollupjs.org) as a bundler and generates multiple rollup configs for various module formats and build settings. See [Optimizations](#optimizations) for details. 55 | 56 | ### TypeScript 57 | 58 | `tsconfig.json` is set up to interpret `dom` and `esnext` types, as well as `react` for `jsx`. Adjust according to your needs. 59 | 60 | ## Continuous Integration 61 | 62 | ### GitHub Actions 63 | 64 | Two actions are added by default: 65 | 66 | - `main` which installs deps w/ cache, lints, tests, and builds on all pushes against a Node and OS matrix 67 | - `size` which comments cost comparison of your library on every pull request using [`size-limit`](https://github.com/ai/size-limit) 68 | 69 | ## Optimizations 70 | 71 | Please see the main `dts` [optimizations docs](https://github.com/weiran-zsd/dts-cli#optimizations). In particular, know that you can take advantage of development-only optimizations: 72 | 73 | ```js 74 | // ./types/index.d.ts 75 | declare var __DEV__: boolean; 76 | 77 | // inside your code... 78 | if (__DEV__) { 79 | console.log('foo'); 80 | } 81 | ``` 82 | 83 | You can also choose to install and use [invariant](https://github.com/weiran-zsd/dts-cli#invariant) and [warning](https://github.com/weiran-zsd/dts-cli#warning) functions. 84 | 85 | ## Module Formats 86 | 87 | CJS, ESModules, and UMD module formats are supported. 88 | 89 | The appropriate paths are configured in `package.json` and `dist/index.js` accordingly. Please report if any issues are found. 90 | 91 | ## Named Exports 92 | 93 | Per Palmer Group guidelines, [always use named exports.](https://github.com/palmerhq/typescript#exports) Code split inside your React app instead of your React library. 94 | 95 | ## Including Styles 96 | 97 | There are many ways to ship styles, including with CSS-in-JS. DTS has no opinion on this, configure how you like. 98 | 99 | For vanilla CSS, you can include it at the root directory and add it to the `files` section in your `package.json`, so that it can be imported separately by your users and run through their bundler's loader. 100 | 101 | ## Publishing to NPM 102 | 103 | We recommend using [np](https://github.com/sindresorhus/np). 104 | -------------------------------------------------------------------------------- /templates/basic/gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /templates/basic/gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /templates/basic/src/index.ts: -------------------------------------------------------------------------------- 1 | export const sum = (a: number, b: number) => { 2 | if ('development' === process.env.NODE_ENV) { 3 | console.log('dev only output'); 4 | } 5 | return a + b; 6 | }; 7 | -------------------------------------------------------------------------------- /templates/basic/test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { sum } from '../src/index'; 2 | 3 | describe('sum', () => { 4 | it('adds two numbers together', () => { 5 | expect(sum(1, 1)).toEqual(2); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /templates/basic/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/recommended/tsconfig.json", 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "allowImportingTsExtensions": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /templates/react-with-storybook/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ['14.x', '16.x'] 11 | os: [ubuntu-latest, windows-latest, macOS-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v4 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Lint 26 | run: yarn lint 27 | 28 | - name: Test 29 | run: yarn test --ci --coverage --maxWorkers=2 30 | 31 | - name: Build 32 | run: yarn build 33 | -------------------------------------------------------------------------------- /templates/react-with-storybook/.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | env: 7 | CI_JOB_NUMBER: 1 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: andresz1/size-limit-action@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /templates/react-with-storybook/.storybook/main.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../stories/**/*.stories.@(ts|tsx|js|jsx)'], 3 | addons: ['@storybook/addon-links', '@storybook/addon-essentials'], 4 | // https://storybook.js.org/docs/react/configure/typescript#mainjs-configuration 5 | typescript: { 6 | check: true // type-check stories during Storybook build 7 | }, 8 | 9 | framework: { 10 | name: '@storybook/react-vite', 11 | options: {} 12 | }, 13 | docs: { 14 | autodocs: true 15 | } 16 | }; -------------------------------------------------------------------------------- /templates/react-with-storybook/.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | 2 | const preview = { 3 | parameters: { 4 | actions: { argTypesRegex: '^on[A-Z].*' }, 5 | controls: { 6 | matchers: { 7 | color: /(background|color)$/i, 8 | date: /Date$/, 9 | }, 10 | }, 11 | }, 12 | }; 13 | 14 | export default preview; 15 | -------------------------------------------------------------------------------- /templates/react-with-storybook/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /templates/react-with-storybook/README.md: -------------------------------------------------------------------------------- 1 | # DTS React w/ Storybook User Guide 2 | 3 | Congrats! You just saved yourself hours of work by bootstrapping this project with DTS. Let’s get you oriented with what’s here and how to use it. 4 | 5 | > This DTS setup is meant for developing React component libraries (not apps!) that can be published to NPM. If you’re looking to build a React-based app, you should use `create-react-app`, `razzle`, `nextjs`, `gatsby`, or `react-static`. 6 | 7 | > If you’re new to TypeScript and React, checkout [this handy cheatsheet](https://github.com/sw-yx/react-typescript-cheatsheet/) 8 | 9 | ## Commands 10 | 11 | DTS scaffolds your new library inside `/src`, and also sets up a [Vite-based](https://vitejs.dev) playground for it inside `/example`. 12 | 13 | The recommended workflow is to run DTS in one terminal: 14 | 15 | ```bash 16 | npm start # or yarn start 17 | ``` 18 | 19 | This builds to `/dist` and runs the project in watch mode so any edits you save inside `src` causes a rebuild to `/dist`. 20 | 21 | Then run either Storybook or the example playground: 22 | 23 | ### Storybook 24 | 25 | Run inside another terminal: 26 | 27 | ```bash 28 | yarn storybook 29 | ``` 30 | 31 | This loads the stories from `./stories`. 32 | 33 | > NOTE: Stories should reference the components as if using the library, similar to the example playground. This means importing from the root project directory. This has been aliased in the tsconfig and the storybook webpack config as a helper. 34 | 35 | ### Example 36 | 37 | Then run the example inside another: 38 | 39 | ```bash 40 | cd example 41 | npm i # or yarn to install dependencies 42 | npm start # or yarn start 43 | ``` 44 | 45 | The default example imports and live reloads whatever is in `/dist`, so if you are seeing an out of date component, make sure DTS is running in watch mode like we recommend above. 46 | 47 | To do a one-off build, use `npm run build` or `yarn build`. 48 | 49 | To run tests, use `npm test` or `yarn test`. 50 | 51 | ## Configuration 52 | 53 | Code quality is set up for you with `prettier`, `husky`, and `lint-staged`. Adjust the respective fields in `package.json` accordingly. 54 | 55 | ### Jest 56 | 57 | Jest tests are set up to run with `npm test` or `yarn test`. 58 | 59 | ### Bundle analysis 60 | 61 | Calculates the real cost of your library using [size-limit](https://github.com/ai/size-limit) with `npm run size` and visulize it with `npm run analyze`. 62 | 63 | #### Setup Files 64 | 65 | This is the folder structure we set up for you: 66 | 67 | ```txt 68 | /example 69 | index.html 70 | index.tsx # test your component here in a demo app 71 | package.json 72 | tsconfig.json 73 | /src 74 | index.tsx # EDIT THIS 75 | /test 76 | index.test.tsx # EDIT THIS 77 | /stories 78 | Thing.stories.tsx # EDIT THIS 79 | /.storybook 80 | main.js 81 | preview.js 82 | .gitignore 83 | package.json 84 | README.md # EDIT THIS 85 | tsconfig.json 86 | ``` 87 | 88 | #### React Testing Library 89 | 90 | We do not set up `react-testing-library` for you yet, we welcome contributions and documentation on this. 91 | 92 | ### Rollup 93 | 94 | DTS uses [Rollup](https://rollupjs.org) as a bundler and generates multiple rollup configs for various module formats and build settings. See [Optimizations](#optimizations) for details. 95 | 96 | ### TypeScript 97 | 98 | `tsconfig.json` is set up to interpret `dom` and `esnext` types, as well as `react` for `jsx`. Adjust according to your needs. 99 | 100 | ## Continuous Integration 101 | 102 | ### GitHub Actions 103 | 104 | Two actions are added by default: 105 | 106 | - `main` which installs deps w/ cache, lints, tests, and builds on all pushes against a Node and OS matrix 107 | - `size` which comments cost comparison of your library on every pull request using [size-limit](https://github.com/ai/size-limit) 108 | 109 | ## Optimizations 110 | 111 | Please see the main `dts` [optimizations docs](https://github.com/weiran-zsd/dts-cli#optimizations). In particular, know that you can take advantage of development-only optimizations: 112 | 113 | ```js 114 | // ./types/index.d.ts 115 | declare var __DEV__: boolean; 116 | 117 | // inside your code... 118 | if (__DEV__) { 119 | console.log('foo'); 120 | } 121 | ``` 122 | 123 | You can also choose to install and use [invariant](https://github.com/weiran-zsd/dts-cli#invariant) and [warning](https://github.com/weiran-zsd/dts-cli#warning) functions. 124 | 125 | ## Module Formats 126 | 127 | CJS, ESModules, and UMD module formats are supported. 128 | 129 | The appropriate paths are configured in `package.json` and `dist/index.js` accordingly. Please report if any issues are found. 130 | 131 | ## Deploying the Example Playground 132 | 133 | The Playground is just a simple [Vite](https://vitejs.dev) app, you can deploy it anywhere you would normally deploy that. Here are some guidelines for **manually** deploying with the Netlify CLI (`npm i -g netlify-cli`): 134 | 135 | ```bash 136 | cd example # if not already in the example folder 137 | npm run build # builds to dist 138 | netlify deploy # deploy the dist folder 139 | ``` 140 | 141 | Alternatively, if you already have a git repo connected, you can set up continuous deployment with Netlify: 142 | 143 | ```bash 144 | netlify init 145 | # build command: yarn build && cd example && yarn && yarn build 146 | # directory to deploy: example/dist 147 | # pick yes for netlify.toml 148 | ``` 149 | 150 | ## Named Exports 151 | 152 | Per Palmer Group guidelines, [always use named exports.](https://github.com/palmerhq/typescript#exports) Code split inside your React app instead of your React library. 153 | 154 | ## Including Styles 155 | 156 | There are many ways to ship styles, including with CSS-in-JS. DTS has no opinion on this, configure how you like. 157 | 158 | For vanilla CSS, you can include it at the root directory and add it to the `files` section in your `package.json`, so that it can be imported separately by your users and run through their bundler's loader. 159 | 160 | ## Publishing to NPM 161 | 162 | We recommend using [np](https://github.com/sindresorhus/np). 163 | 164 | ## Usage with Lerna 165 | 166 | When creating a new package with DTS within a project set up with Lerna, you might encounter a `Cannot resolve dependency` error when trying to run the `example` project. To fix that you will need to make changes to the `package.json` file _inside the `example` directory_. 167 | 168 | The problem is that due to the nature of how dependencies are installed in Lerna projects, the aliases in the example project's `package.json` might not point to the right place, as those dependencies might have been installed in the root of your Lerna project. 169 | 170 | Change the `alias` to point to where those packages are actually installed. This depends on the directory structure of your Lerna project, so the actual path might be different from the diff below. 171 | 172 | ```diff 173 | "alias": { 174 | - "react": "../node_modules/react", 175 | - "react-dom": "../node_modules/react-dom" 176 | + "react": "../../../node_modules/react", 177 | + "react-dom": "../../../node_modules/react-dom" 178 | }, 179 | ``` 180 | 181 | An alternative to fixing this problem would be to remove aliases altogether and define the dependencies referenced as aliases as dev dependencies instead. [However, that might cause other problems.](https://github.com/palmerhq/tsdx/issues/64) 182 | -------------------------------------------------------------------------------- /templates/react-with-storybook/example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | dist -------------------------------------------------------------------------------- /templates/react-with-storybook/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Playground 8 | 9 | 10 | 11 |
    12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /templates/react-with-storybook/example/index.tsx: -------------------------------------------------------------------------------- 1 | import 'react-app-polyfill/ie11'; 2 | import * as React from 'react'; 3 | import * as ReactDOM from 'react-dom'; 4 | import { Thing } from '../.'; 5 | 6 | const App = () => { 7 | return ( 8 |
    9 | 10 |
    11 | ); 12 | }; 13 | 14 | ReactDOM.render(, document.getElementById('root')); 15 | -------------------------------------------------------------------------------- /templates/react-with-storybook/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "private": true, 4 | "version": "1.0.0", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "vite", 9 | "build": "vite build" 10 | }, 11 | "dependencies": { 12 | "react-app-polyfill": "^3.0.0" 13 | }, 14 | "alias": { 15 | "react": "../node_modules/react", 16 | "react-dom": "../node_modules/react-dom/profiling", 17 | "scheduler/tracing": "../node_modules/scheduler/tracing-profiling" 18 | }, 19 | "devDependencies": { 20 | "@types/react": "^18.2.74", 21 | "@types/react-dom": "^18.2.23", 22 | "vite": "latest", 23 | "@vitejs/plugin-react": "latest", 24 | "typescript": "^4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /templates/react-with-storybook/example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/vite-react/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /templates/react-with-storybook/example/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import ReactPlugin from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [ReactPlugin({})], 7 | }); 8 | -------------------------------------------------------------------------------- /templates/react-with-storybook/gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /templates/react-with-storybook/gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | -------------------------------------------------------------------------------- /templates/react-with-storybook/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes, ReactChild } from 'react'; 2 | 3 | export interface Props extends HTMLAttributes { 4 | /** custom content, defaults to 'the snozzberries taste like snozzberries' */ 5 | children?: ReactChild; 6 | } 7 | 8 | // Please do not use types off of a default export module or else Storybook Docs will suffer. 9 | // see: https://github.com/storybookjs/storybook/issues/9556 10 | /** 11 | * A custom Thing component. Neat! 12 | */ 13 | export const Thing: FC = ({ children }) => { 14 | return
    {children || `the snozzberries taste like snozzberries`}
    ; 15 | }; 16 | -------------------------------------------------------------------------------- /templates/react-with-storybook/stories/Thing.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Meta, Story } from '@storybook/react'; 3 | import { Thing, Props } from '../src/index'; 4 | 5 | const meta: Meta = { 6 | title: 'Welcome', 7 | component: Thing, 8 | argTypes: { 9 | children: { 10 | control: { 11 | type: 'text', 12 | }, 13 | }, 14 | }, 15 | parameters: { 16 | controls: { expanded: true }, 17 | }, 18 | }; 19 | 20 | export default meta; 21 | 22 | const Template: Story = (args) => ; 23 | 24 | // By passing using the Args format for exported stories, you can control the props for a component for reuse in a test 25 | // https://storybook.js.org/docs/react/workflows/unit-testing 26 | export const Default = Template.bind({}); 27 | 28 | Default.args = {}; 29 | -------------------------------------------------------------------------------- /templates/react-with-storybook/test/index.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { Default as Thing } from '../stories/Thing.stories'; 4 | 5 | describe('Thing', () => { 6 | it('renders without crashing', () => { 7 | const div = document.createElement('div'); 8 | ReactDOM.render(, div); 9 | ReactDOM.unmountComponentAtNode(div); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /templates/react-with-storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "extends": "@tsconfig/vite-react/tsconfig.json", 4 | "compilerOptions": { 5 | "allowImportingTsExtensions": false 6 | }, 7 | "include": ["src", "types"] 8 | } 9 | -------------------------------------------------------------------------------- /templates/react/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ['14.x', '16.x'] 11 | os: [ubuntu-latest, windows-latest, macOS-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v4 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Lint 26 | run: yarn lint 27 | 28 | - name: Test 29 | run: yarn test --ci --coverage --maxWorkers=2 30 | 31 | - name: Build 32 | run: yarn build 33 | -------------------------------------------------------------------------------- /templates/react/.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | env: 7 | CI_JOB_NUMBER: 1 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: andresz1/size-limit-action@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /templates/react/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /templates/react/README.md: -------------------------------------------------------------------------------- 1 | # DTS React User Guide 2 | 3 | Congrats! You just saved yourself hours of work by bootstrapping this project with DTS. Let’s get you oriented with what’s here and how to use it. 4 | 5 | > This DTS setup is meant for developing React component libraries (not apps!) that can be published to NPM. If you’re looking to build a React-based app, you should use `create-react-app`, `razzle`, `nextjs`, `gatsby`, or `react-static`. 6 | 7 | > If you’re new to TypeScript and React, checkout [this handy cheatsheet](https://github.com/sw-yx/react-typescript-cheatsheet/) 8 | 9 | ## Commands 10 | 11 | DTS scaffolds your new library inside `/src`, and also sets up a [Vite-based](https://vitejs.dev) playground for it inside `/example`. 12 | 13 | The recommended workflow is to run DTS in one terminal: 14 | 15 | ```bash 16 | npm start # or yarn start 17 | ``` 18 | 19 | This builds to `/dist` and runs the project in watch mode so any edits you save inside `src` causes a rebuild to `/dist`. 20 | 21 | Then run the example inside another: 22 | 23 | ```bash 24 | cd example 25 | npm i # or yarn to install dependencies 26 | npm start # or yarn start 27 | ``` 28 | 29 | The default example imports and live reloads whatever is in `/dist`, so if you are seeing an out of date component, make sure DTS is running in watch mode like we recommend above. 30 | 31 | To do a one-off build, use `npm run build` or `yarn build`. 32 | 33 | To run tests, use `npm test` or `yarn test`. 34 | 35 | ## Configuration 36 | 37 | Code quality is set up for you with `prettier`, `husky`, and `lint-staged`. Adjust the respective fields in `package.json` accordingly. 38 | 39 | ### Jest 40 | 41 | Jest tests are set up to run with `npm test` or `yarn test`. 42 | 43 | ### Bundle analysis 44 | 45 | Calculates the real cost of your library using [size-limit](https://github.com/ai/size-limit) with `npm run size` and visulize it with `npm run analyze`. 46 | 47 | #### Setup Files 48 | 49 | This is the folder structure we set up for you: 50 | 51 | ```txt 52 | /example 53 | index.html 54 | index.tsx # test your component here in a demo app 55 | package.json 56 | tsconfig.json 57 | /src 58 | index.tsx # EDIT THIS 59 | /test 60 | index.test.tsx # EDIT THIS 61 | .gitignore 62 | package.json 63 | README.md # EDIT THIS 64 | tsconfig.json 65 | ``` 66 | 67 | #### React Testing Library 68 | 69 | We do not set up `react-testing-library` for you yet, we welcome contributions and documentation on this. 70 | 71 | ### Rollup 72 | 73 | DTS uses [Rollup](https://rollupjs.org) as a bundler and generates multiple rollup configs for various module formats and build settings. See [Optimizations](#optimizations) for details. 74 | 75 | ### TypeScript 76 | 77 | `tsconfig.json` is set up to interpret `dom` and `esnext` types, as well as `react` for `jsx`. Adjust according to your needs. 78 | 79 | ## Continuous Integration 80 | 81 | ### GitHub Actions 82 | 83 | Two actions are added by default: 84 | 85 | - `main` which installs deps w/ cache, lints, tests, and builds on all pushes against a Node and OS matrix 86 | - `size` which comments cost comparison of your library on every pull request using [`size-limit`](https://github.com/ai/size-limit) 87 | 88 | ## Optimizations 89 | 90 | Please see the main `dts` [optimizations docs](https://github.com/weiran-zsd/dts-cli#optimizations). In particular, know that you can take advantage of development-only optimizations: 91 | 92 | ```js 93 | // ./types/index.d.ts 94 | declare var __DEV__: boolean; 95 | 96 | // inside your code... 97 | if (__DEV__) { 98 | console.log('foo'); 99 | } 100 | ``` 101 | 102 | You can also choose to install and use [invariant](https://github.com/weiran-zsd/dts-cli#invariant) and [warning](https://github.com/weiran-zsd/dts-cli#warning) functions. 103 | 104 | ## Module Formats 105 | 106 | CJS, ESModules, and UMD module formats are supported. 107 | 108 | The appropriate paths are configured in `package.json` and `dist/index.js` accordingly. Please report if any issues are found. 109 | 110 | ## Deploying the Example Playground 111 | 112 | The Playground is just a simple [Vite](https://vitejs.dev) app, you can deploy it anywhere you would normally deploy that. Here are some guidelines for **manually** deploying with the Netlify CLI (`npm i -g netlify-cli`): 113 | 114 | ```bash 115 | cd example # if not already in the example folder 116 | npm run build # builds to dist 117 | netlify deploy # deploy the dist folder 118 | ``` 119 | 120 | Alternatively, if you already have a git repo connected, you can set up continuous deployment with Netlify: 121 | 122 | ```bash 123 | netlify init 124 | # build command: yarn build && cd example && yarn && yarn build 125 | # directory to deploy: example/dist 126 | # pick yes for netlify.toml 127 | ``` 128 | 129 | ## Named Exports 130 | 131 | Per Palmer Group guidelines, [always use named exports.](https://github.com/palmerhq/typescript#exports) Code split inside your React app instead of your React library. 132 | 133 | ## Including Styles 134 | 135 | There are many ways to ship styles, including with CSS-in-JS. DTS has no opinion on this, configure how you like. 136 | 137 | For vanilla CSS, you can include it at the root directory and add it to the `files` section in your `package.json`, so that it can be imported separately by your users and run through their bundler's loader. 138 | 139 | ## Publishing to NPM 140 | 141 | We recommend using [np](https://github.com/sindresorhus/np). 142 | 143 | ## Usage with Lerna 144 | 145 | When creating a new package with DTS within a project set up with Lerna, you might encounter a `Cannot resolve dependency` error when trying to run the `example` project. To fix that you will need to make changes to the `package.json` file _inside the `example` directory_. 146 | 147 | The problem is that due to the nature of how dependencies are installed in Lerna projects, the aliases in the example project's `package.json` might not point to the right place, as those dependencies might have been installed in the root of your Lerna project. 148 | 149 | Change the `alias` to point to where those packages are actually installed. This depends on the directory structure of your Lerna project, so the actual path might be different from the diff below. 150 | 151 | ```diff 152 | "alias": { 153 | - "react": "../node_modules/react", 154 | - "react-dom": "../node_modules/react-dom" 155 | + "react": "../../../node_modules/react", 156 | + "react-dom": "../../../node_modules/react-dom" 157 | }, 158 | ``` 159 | 160 | An alternative to fixing this problem would be to remove aliases altogether and define the dependencies referenced as aliases as dev dependencies instead. [However, that might cause other problems.](https://github.com/formium/tsdx/issues/64) 161 | -------------------------------------------------------------------------------- /templates/react/example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | dist -------------------------------------------------------------------------------- /templates/react/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Playground 8 | 9 | 10 | 11 |
    12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /templates/react/example/index.tsx: -------------------------------------------------------------------------------- 1 | import 'react-app-polyfill/ie11'; 2 | import * as React from 'react'; 3 | import * as ReactDOM from 'react-dom'; 4 | import { Thing } from '../.'; 5 | 6 | const App = () => { 7 | return ( 8 |
    9 | 10 |
    11 | ); 12 | }; 13 | 14 | ReactDOM.render(, document.getElementById('root')); 15 | -------------------------------------------------------------------------------- /templates/react/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "private": true, 4 | "version": "1.0.0", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "vite", 9 | "build": "vite build" 10 | }, 11 | "dependencies": { 12 | "react-app-polyfill": "^3.0.0" 13 | }, 14 | "alias": { 15 | "react": "../node_modules/react", 16 | "react-dom": "../node_modules/react-dom/profiling", 17 | "scheduler/tracing": "../node_modules/scheduler/tracing-profiling" 18 | }, 19 | "devDependencies": { 20 | "@types/react": "^18.2.74", 21 | "@types/react-dom": "^18.2.23", 22 | "vite": "latest", 23 | "@vitejs/plugin-react": "latest", 24 | "typescript": "^4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /templates/react/example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/vite-react/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /templates/react/example/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import ReactPlugin from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [ReactPlugin({})], 7 | }); 8 | -------------------------------------------------------------------------------- /templates/react/gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /templates/react/gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | -------------------------------------------------------------------------------- /templates/react/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | // Delete me 4 | export const Thing = () => { 5 | return
    Welcome to your first test package.
    ; 6 | }; 7 | -------------------------------------------------------------------------------- /templates/react/test/index.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { Thing } from '../src/index'; 4 | 5 | describe('Thing', () => { 6 | it('renders without crashing', () => { 7 | const div = document.createElement('div'); 8 | ReactDOM.render(, div); 9 | ReactDOM.unmountComponentAtNode(div); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /templates/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "extends": "@tsconfig/vite-react/tsconfig.json", 4 | "compilerOptions": { 5 | "allowImportingTsExtensions": false 6 | }, 7 | "include": ["src", "types"] 8 | } 9 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | - `unit` contains unit tests of internals 4 | - `e2e` contains end-to-end (E2E) tests of the CLI 5 | - `integration` contains tests ensuring that common or recommended plugins work properly together with DTS 6 | -------------------------------------------------------------------------------- /test/e2e/dts-build-default.test.ts: -------------------------------------------------------------------------------- 1 | import * as shell from 'shelljs'; 2 | 3 | import * as util from '../utils/fixture'; 4 | import { execWithCache, grep } from '../utils/shell'; 5 | 6 | shell.config.silent = false; 7 | 8 | const testDir = 'e2e'; 9 | const fixtureName = 'build-default'; 10 | const stageName = `stage-${fixtureName}`; 11 | 12 | describe('dts build :: zero-config defaults', () => { 13 | beforeAll(() => { 14 | util.teardownStage(stageName); 15 | util.setupStageWithFixture(testDir, stageName, fixtureName); 16 | }); 17 | 18 | it('should compile files into a dist directory', () => { 19 | const output = execWithCache('node ../dist/index.js build'); 20 | 21 | expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); 22 | expect( 23 | shell.test('-f', 'dist/build-default.cjs.development.js') 24 | ).toBeTruthy(); 25 | expect( 26 | shell.test('-f', 'dist/build-default.cjs.production.min.js') 27 | ).toBeTruthy(); 28 | expect(shell.test('-f', 'dist/build-default.esm.js')).toBeTruthy(); 29 | 30 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 31 | 32 | expect(output.code).toBe(0); 33 | }); 34 | 35 | it("shouldn't compile files in test/ or types/", () => { 36 | const output = execWithCache('node ../dist/index.js build'); 37 | 38 | expect(shell.test('-d', 'dist/test/')).toBeFalsy(); 39 | expect(shell.test('-d', 'dist/types/')).toBeFalsy(); 40 | 41 | expect(output.code).toBe(0); 42 | }); 43 | 44 | it('should create the library correctly', async () => { 45 | const output = execWithCache('node ../dist/index.js build'); 46 | 47 | const lib = require(`../../${stageName}/dist`); 48 | expect(lib.returnsTrue()).toBe(true); 49 | expect(lib.__esModule).toBe(true); // test that ESM -> CJS interop was output 50 | 51 | // syntax tests 52 | expect(lib.testNullishCoalescing()).toBe(true); 53 | expect(lib.testOptionalChaining()).toBe(true); 54 | // can't use an async generator in Jest yet, so use next().value instead of yield 55 | expect(lib.testGenerator().next().value).toBe(true); 56 | expect(await lib.testAsync()).toBe(true); 57 | 58 | expect(output.code).toBe(0); 59 | }); 60 | 61 | it('should bundle regeneratorRuntime', () => { 62 | const output = execWithCache('node ../dist/index.js build'); 63 | expect(output.code).toBe(0); 64 | 65 | const matched = grep(/regeneratorRuntime = /, ['dist/build-default.*.js']); 66 | expect(matched).toBeTruthy(); 67 | }); 68 | 69 | it('should use lodash for the CJS build', () => { 70 | const output = execWithCache('node ../dist/index.js build'); 71 | expect(output.code).toBe(0); 72 | 73 | const matched = grep(/lodash/, ['dist/build-default.cjs.*.js']); 74 | expect(matched).toBeTruthy(); 75 | }); 76 | 77 | it('should use lodash-es for the ESM build', () => { 78 | const output = execWithCache('node ../dist/index.js build'); 79 | expect(output.code).toBe(0); 80 | 81 | const matched = grep(/lodash-es/, ['dist/build-default.esm.js']); 82 | expect(matched).toBeTruthy(); 83 | }); 84 | 85 | it("shouldn't replace lodash/fp", () => { 86 | const output = execWithCache('node ../dist/index.js build'); 87 | expect(output.code).toBe(0); 88 | 89 | const matched = grep(/lodash\/fp/, ['dist/build-default.*.js']); 90 | expect(matched).toBeTruthy(); 91 | }); 92 | 93 | it('should clean the dist directory before rebuilding', () => { 94 | let output = execWithCache('node ../dist/index.js build'); 95 | expect(output.code).toBe(0); 96 | 97 | shell.mv('package.json', 'package-og.json'); 98 | shell.mv('package2.json', 'package.json'); 99 | 100 | // cache bust because we want to re-run this command with new package.json 101 | output = execWithCache('node ../dist/index.js build', { noCache: true }); 102 | expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); 103 | 104 | // build-default files have been cleaned out 105 | expect( 106 | shell.test('-f', 'dist/build-default.cjs.development.js') 107 | ).toBeFalsy(); 108 | expect( 109 | shell.test('-f', 'dist/build-default.cjs.production.min.js') 110 | ).toBeFalsy(); 111 | expect(shell.test('-f', 'dist/build-default.esm.js')).toBeFalsy(); 112 | 113 | // build-default-2 files have been added 114 | expect( 115 | shell.test('-f', 'dist/build-default-2.cjs.development.js') 116 | ).toBeTruthy(); 117 | expect( 118 | shell.test('-f', 'dist/build-default-2.cjs.production.min.js') 119 | ).toBeTruthy(); 120 | expect(shell.test('-f', 'dist/build-default-2.esm.js')).toBeTruthy(); 121 | 122 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 123 | 124 | expect(output.code).toBe(0); 125 | 126 | // reset package.json files 127 | shell.mv('package.json', 'package2.json'); 128 | shell.mv('package-og.json', 'package.json'); 129 | }); 130 | 131 | afterAll(() => { 132 | util.teardownStage(stageName); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /test/e2e/dts-build-invalid.test.ts: -------------------------------------------------------------------------------- 1 | import * as shell from 'shelljs'; 2 | 3 | import * as util from '../utils/fixture'; 4 | import { execWithCache } from '../utils/shell'; 5 | 6 | shell.config.silent = false; 7 | 8 | const testDir = 'e2e'; 9 | const fixtureName = 'build-invalid'; 10 | const stageName = `stage-${fixtureName}`; 11 | 12 | describe('dts build :: invalid build', () => { 13 | beforeAll(() => { 14 | util.teardownStage(stageName); 15 | util.setupStageWithFixture(testDir, stageName, fixtureName); 16 | }); 17 | 18 | it('should fail gracefully with exit code 1 when build failed', () => { 19 | const output = execWithCache('node ../dist/index.js build'); 20 | expect(output.code).toBe(1); 21 | }); 22 | 23 | it('should only transpile and not type check', () => { 24 | const output = execWithCache('node ../dist/index.js build --transpileOnly'); 25 | 26 | expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); 27 | expect( 28 | shell.test('-f', 'dist/build-invalid.cjs.development.js') 29 | ).toBeTruthy(); 30 | expect( 31 | shell.test('-f', 'dist/build-invalid.cjs.production.min.js') 32 | ).toBeTruthy(); 33 | expect(shell.test('-f', 'dist/build-invalid.esm.js')).toBeTruthy(); 34 | 35 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 36 | 37 | expect(output.code).toBe(0); 38 | }); 39 | 40 | afterAll(() => { 41 | util.teardownStage(stageName); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/e2e/dts-build-multipleEntries.test.ts: -------------------------------------------------------------------------------- 1 | import * as shell from 'shelljs'; 2 | 3 | import * as util from '../utils/fixture'; 4 | import { execWithCache } from '../utils/shell'; 5 | 6 | shell.config.silent = false; 7 | 8 | const testDir = 'e2e'; 9 | const fixtureName = 'build-multipleEntries'; 10 | const stageName = `stage-${fixtureName}`; 11 | 12 | describe('dts build :: multiple entries', () => { 13 | beforeAll(() => { 14 | util.teardownStage(stageName); 15 | util.setupStageWithFixture(testDir, stageName, fixtureName); 16 | }); 17 | 18 | it('should compile files into a dist directory', () => { 19 | const output = execWithCache( 20 | [ 21 | 'node ../dist/index.js build', 22 | '--entry ./src/index.ts', 23 | '--entry ./src/returnsFalse.ts', 24 | '--entry ./src/returnsTrue.ts', 25 | ].join(' ') 26 | ); 27 | 28 | expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); 29 | expect(shell.test('-f', 'dist/index.cjs.development.js')).toBeTruthy(); 30 | expect(shell.test('-f', 'dist/index.cjs.production.min.js')).toBeTruthy(); 31 | expect(shell.test('-f', 'dist/index.esm.js')).toBeTruthy(); 32 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 33 | 34 | expect(shell.test('-f', 'dist/returnsFalse.js')).toBeTruthy(); 35 | expect( 36 | shell.test('-f', 'dist/returnsFalse.cjs.development.js') 37 | ).toBeTruthy(); 38 | expect( 39 | shell.test('-f', 'dist/returnsFalse.cjs.production.min.js') 40 | ).toBeTruthy(); 41 | expect(shell.test('-f', 'dist/returnsFalse.esm.js')).toBeTruthy(); 42 | expect(shell.test('-f', 'dist/returnsFalse.d.ts')).toBeTruthy(); 43 | 44 | expect(shell.test('-f', 'dist/returnsTrue.js')).toBeTruthy(); 45 | expect( 46 | shell.test('-f', 'dist/returnsTrue.cjs.development.js') 47 | ).toBeTruthy(); 48 | expect( 49 | shell.test('-f', 'dist/returnsTrue.cjs.production.min.js') 50 | ).toBeTruthy(); 51 | expect(shell.test('-f', 'dist/returnsTrue.esm.js')).toBeTruthy(); 52 | expect(shell.test('-f', 'dist/returnsTrue.d.ts')).toBeTruthy(); 53 | 54 | expect(output.code).toBe(0); 55 | }); 56 | 57 | afterAll(() => { 58 | util.teardownStage(stageName); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/e2e/dts-build-options.test.ts: -------------------------------------------------------------------------------- 1 | import * as shell from 'shelljs'; 2 | 3 | import * as util from '../utils/fixture'; 4 | import { execWithCache, grep } from '../utils/shell'; 5 | 6 | shell.config.silent = false; 7 | 8 | const testDir = 'e2e'; 9 | const fixtureName = 'build-default'; 10 | // create a second version of build-default's stage for concurrent testing 11 | const stageName = 'stage-build-options'; 12 | 13 | describe('dts build :: options', () => { 14 | beforeAll(() => { 15 | util.teardownStage(stageName); 16 | util.setupStageWithFixture(testDir, stageName, fixtureName); 17 | }); 18 | 19 | it('should compile all formats', () => { 20 | const output = execWithCache( 21 | 'node ../dist/index.js build --format cjs,esm,umd,system' 22 | ); 23 | 24 | expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); 25 | expect( 26 | shell.test('-f', 'dist/build-default.cjs.development.js') 27 | ).toBeTruthy(); 28 | expect( 29 | shell.test('-f', 'dist/build-default.cjs.production.min.js') 30 | ).toBeTruthy(); 31 | expect(shell.test('-f', 'dist/build-default.esm.js')).toBeTruthy(); 32 | expect( 33 | shell.test('-f', 'dist/build-default.umd.development.js') 34 | ).toBeTruthy(); 35 | expect( 36 | shell.test('-f', 'dist/build-default.umd.production.min.js') 37 | ).toBeTruthy(); 38 | expect( 39 | shell.test('-f', 'dist/build-default.system.development.js') 40 | ).toBeTruthy(); 41 | expect( 42 | shell.test('-f', 'dist/build-default.system.production.min.js') 43 | ).toBeTruthy(); 44 | 45 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 46 | 47 | expect(output.code).toBe(0); 48 | }); 49 | 50 | it('should not bundle regeneratorRuntime when targeting Node', () => { 51 | const output = execWithCache('node ../dist/index.js build --target node'); 52 | expect(output.code).toBe(0); 53 | 54 | const matched = grep(/regeneratorRuntime = r/, ['dist/build-default.*.js']); 55 | expect(matched).toBeFalsy(); 56 | }); 57 | 58 | afterAll(() => { 59 | util.teardownStage(stageName); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/e2e/dts-build-withTsconfig.test.ts: -------------------------------------------------------------------------------- 1 | import * as shell from 'shelljs'; 2 | 3 | import * as util from '../utils/fixture'; 4 | import { execWithCache } from '../utils/shell'; 5 | 6 | shell.config.silent = false; 7 | 8 | const testDir = 'e2e'; 9 | const fixtureName = 'build-withTsconfig'; 10 | const stageName = `stage-${fixtureName}`; 11 | 12 | describe('dts build :: build with custom tsconfig.json options', () => { 13 | beforeAll(() => { 14 | util.teardownStage(stageName); 15 | util.setupStageWithFixture(testDir, stageName, fixtureName); 16 | }); 17 | 18 | it('should use the declarationDir when set', () => { 19 | const output = execWithCache('node ../dist/index.js build'); 20 | 21 | expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); 22 | expect( 23 | shell.test('-f', 'dist/build-withtsconfig.cjs.development.js') 24 | ).toBeTruthy(); 25 | expect( 26 | shell.test('-f', 'dist/build-withtsconfig.cjs.production.min.js') 27 | ).toBeTruthy(); 28 | expect(shell.test('-f', 'dist/build-withtsconfig.esm.js')).toBeTruthy(); 29 | 30 | expect(shell.test('-f', 'dist/index.d.ts')).toBeFalsy(); 31 | expect(shell.test('-f', 'typings/index.d.ts')).toBeTruthy(); 32 | expect(shell.test('-f', 'typings/index.d.ts.map')).toBeTruthy(); 33 | 34 | expect(output.code).toBe(0); 35 | }); 36 | 37 | it('should set __esModule according to esModuleInterop', () => { 38 | const output = execWithCache('node ../dist/index.js build'); 39 | 40 | const lib = require(`../../${stageName}/dist/build-withtsconfig.cjs.production.min.js`); 41 | // if esModuleInterop: false, no __esModule is added, therefore undefined 42 | expect(lib.__esModule).toBe(undefined); 43 | 44 | expect(output.code).toBe(0); 45 | }); 46 | 47 | it('should read custom --tsconfig path', () => { 48 | const output = execWithCache( 49 | 'node ../dist/index.js build --format cjs --tsconfig ./src/tsconfig.json' 50 | ); 51 | 52 | expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); 53 | expect( 54 | shell.test('-f', 'dist/build-withtsconfig.cjs.development.js') 55 | ).toBeTruthy(); 56 | expect( 57 | shell.test('-f', 'dist/build-withtsconfig.cjs.production.min.js') 58 | ).toBeTruthy(); 59 | 60 | expect(shell.test('-f', 'dist/index.d.ts')).toBeFalsy(); 61 | expect(shell.test('-f', 'typingsCustom/index.d.ts')).toBeTruthy(); 62 | expect(shell.test('-f', 'typingsCustom/index.d.ts.map')).toBeTruthy(); 63 | 64 | expect(output.code).toBe(0); 65 | }); 66 | 67 | afterAll(() => { 68 | util.teardownStage(stageName); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/e2e/dts-build-withTypesRollup.test.ts: -------------------------------------------------------------------------------- 1 | import * as shell from 'shelljs'; 2 | 3 | import * as util from '../utils/fixture'; 4 | import { execWithCache } from '../utils/shell'; 5 | 6 | shell.config.silent = false; 7 | 8 | const testDir = 'e2e'; 9 | const fixtureName = 'build-withTypesRollup'; 10 | const stageName = `stage-${fixtureName}`; 11 | 12 | describe('dts build :: types rollup', () => { 13 | beforeAll(() => { 14 | util.teardownStage(stageName); 15 | util.setupStageWithFixture(testDir, stageName, fixtureName); 16 | }); 17 | 18 | it('should rollup types into index.d.ts', () => { 19 | const output = execWithCache('node ../dist/index.js build --rollupTypes'); 20 | 21 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 22 | 23 | expect(shell.test('-d', 'dist/types')).toBeFalsy(); 24 | expect(shell.test('-d', 'dist/foo')).toBeFalsy(); 25 | expect(shell.test('-d', 'dist/bar')).toBeFalsy(); 26 | 27 | expect(output.code).toBe(0); 28 | }); 29 | 30 | it('should not run by default and types should be output into the dist root', () => { 31 | const output = execWithCache('node ../dist/index.js build'); 32 | 33 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 34 | 35 | expect(shell.test('-f', 'dist/foo/foo.d.ts')).toBeTruthy(); 36 | expect(shell.test('-f', 'dist/bar/bar.d.ts')).toBeTruthy(); 37 | 38 | expect(output.code).toBe(0); 39 | }); 40 | 41 | it('should honor custom declarationDir', () => { 42 | shell.sed( 43 | '-i', 44 | '"declaration": true,', 45 | '"declaration": true, "declarationDir": "dist/my-types",', 46 | 'tsconfig.json' 47 | ); 48 | 49 | try { 50 | const output = execWithCache( 51 | 'node ../dist/index.js build --rollupTypes', 52 | { 53 | noCache: true, 54 | } 55 | ); 56 | 57 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 58 | 59 | expect(shell.test('-d', 'dist/my-types')).toBeFalsy(); 60 | 61 | expect(output.code).toBe(0); 62 | } finally { 63 | shell.sed( 64 | '-i', 65 | '"declaration": true, "declarationDir": "dist/my-types",', 66 | '"declaration": true,', 67 | 'tsconfig.json' 68 | ); 69 | } 70 | }); 71 | 72 | it('should not rollup types when there are no types or typings definition in package.json', () => { 73 | shell.sed('-i', '"types"', '"types-disabled"', 'package.json'); 74 | 75 | try { 76 | const output = execWithCache( 77 | 'node ../dist/index.js build --rollupTypes', 78 | { 79 | noCache: true, 80 | } 81 | ); 82 | 83 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 84 | expect(shell.test('-f', 'dist/foo/foo.d.ts')).toBeTruthy(); 85 | expect(shell.test('-f', 'dist/bar/bar.d.ts')).toBeTruthy(); 86 | 87 | expect(output.code).toBe(0); 88 | } finally { 89 | shell.sed('-i', '"types-disabled"', '"types"', 'package.json'); 90 | } 91 | }); 92 | 93 | afterAll(() => { 94 | util.teardownStage(stageName); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /test/e2e/dts-lint.test.ts: -------------------------------------------------------------------------------- 1 | import shell from 'shelljs'; 2 | import path from 'path'; 3 | import * as util from '../utils/fixture'; 4 | 5 | shell.config.silent = true; 6 | 7 | const testDir = 'e2e'; 8 | const stageName = 'stage-lint'; 9 | 10 | const lintDir = `test/${testDir}/fixtures/lint`; 11 | 12 | describe('dts lint', () => { 13 | it('should fail to lint a ts file with errors', () => { 14 | const testFile = `${lintDir}/file-with-lint-errors.ts`; 15 | const output = shell.exec(`node dist/index.js lint ${testFile}`); 16 | expect(output.code).toBe(1); 17 | expect(output.stdout.includes('Parsing error:')).toBe(true); 18 | }); 19 | 20 | it('should succeed linting a ts file without errors', () => { 21 | const testFile = `${lintDir}/file-without-lint-error.ts`; 22 | const output = shell.exec(`node dist/index.js lint ${testFile}`); 23 | expect(output.code).toBe(0); 24 | }); 25 | 26 | it('should fail to lint a ts file with prettier errors', () => { 27 | const testFile = `${lintDir}/file-with-prettier-lint-errors.ts`; 28 | const output = shell.exec(`node dist/index.js lint ${testFile}`); 29 | expect(output.code).toBe(1); 30 | expect(output.stdout.includes('prettier/prettier')).toBe(true); 31 | }); 32 | 33 | it('should fail to lint a tsx file with errors', () => { 34 | const testFile = `${lintDir}/react-file-with-lint-errors.tsx`; 35 | const output = shell.exec(`node dist/index.js lint ${testFile}`); 36 | expect(output.code).toBe(1); 37 | expect(output.stdout.includes('Parsing error:')).toBe(true); 38 | }); 39 | 40 | it('should succeed linting a tsx file without errors', () => { 41 | const testFile = `${lintDir}/react-file-without-lint-error.tsx`; 42 | const output = shell.exec(`node dist/index.js lint ${testFile}`); 43 | expect(output.code).toBe(0); 44 | }); 45 | 46 | it('should succeed linting a ts file with warnings when --max-warnings is not used', () => { 47 | const testFile = `${lintDir}/file-with-lint-warnings.ts`; 48 | const output = shell.exec(`node dist/index.js lint ${testFile}`); 49 | expect(output.code).toBe(0); 50 | expect(output.stdout.includes('@typescript-eslint/no-unused-vars')).toBe( 51 | true 52 | ); 53 | }); 54 | 55 | it('should succeed linting a ts file with fewer warnings than --max-warnings', () => { 56 | const testFile = `${lintDir}/file-with-lint-warnings.ts`; 57 | const output = shell.exec( 58 | `node dist/index.js lint ${testFile} --max-warnings 4` 59 | ); 60 | expect(output.code).toBe(0); 61 | expect(output.stdout.includes('@typescript-eslint/no-unused-vars')).toBe( 62 | true 63 | ); 64 | }); 65 | 66 | it('should succeed linting a ts file with same number of warnings as --max-warnings', () => { 67 | const testFile = `${lintDir}/file-with-lint-warnings.ts`; 68 | const output = shell.exec( 69 | `node dist/index.js lint ${testFile} --max-warnings 3` 70 | ); 71 | expect(output.code).toBe(0); 72 | expect(output.stdout.includes('@typescript-eslint/no-unused-vars')).toBe( 73 | true 74 | ); 75 | }); 76 | 77 | it('should fail to lint a ts file with more warnings than --max-warnings', () => { 78 | const testFile = `${lintDir}/file-with-lint-warnings.ts`; 79 | const output = shell.exec( 80 | `node dist/index.js lint ${testFile} --max-warnings 2` 81 | ); 82 | expect(output.code).toBe(1); 83 | expect(output.stdout.includes('@typescript-eslint/no-unused-vars')).toBe( 84 | true 85 | ); 86 | }); 87 | 88 | it('should not lint', () => { 89 | const output = shell.exec(`node dist/index.js lint`); 90 | expect(output.code).toBe(1); 91 | expect(output.toString()).toContain('Defaulting to "dts lint src test"'); 92 | expect(output.toString()).toContain( 93 | 'You can override this in the package.json scripts, like "lint": "dts lint src otherDir"' 94 | ); 95 | }); 96 | 97 | describe('when --write-file is used', () => { 98 | beforeEach(() => { 99 | util.teardownStage(stageName); 100 | util.setupStageWithFixture(testDir, stageName, 'build-default'); 101 | }); 102 | 103 | it('should create a valid eslint config file', () => { 104 | const cwd = shell.pwd().stdout; 105 | 106 | const output = shell.exec(`node ../dist/index.js lint --write-file`); 107 | expect(shell.test('-f', '.eslintrc.js')).toBeTruthy(); 108 | expect(output.code).toBe(0); 109 | 110 | // https://github.com/weiran-zsd/dts-cli/issues/118 111 | const eslintrc: { extends: any[] } = require(path.join( 112 | cwd, 113 | '.eslintrc.js' 114 | )); 115 | const configs: string[] = eslintrc.extends; 116 | configs.map((item) => 117 | expect(typeof item === 'string' && !item.startsWith('/')).toBe(true) 118 | ); 119 | }); 120 | 121 | afterAll(() => { 122 | util.teardownStage(stageName); 123 | }); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /test/e2e/fixtures/README.md: -------------------------------------------------------------------------------- 1 | # E2E Test Fixtures Directory 2 | 3 | - `build-default` focuses on our zero config defaults 4 | - `build-invalid` lets us check what happens when we have invalid builds due to type errors 5 | - `build-withTsconfig` lets us check that `tsconfig.json` options are correctly used 6 | - `lint` lets us check that lint errors are correctly detected 7 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "dts build" 4 | }, 5 | "name": "build-default", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/package2.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "dts build" 4 | }, 5 | "name": "build-default-2", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/src/index.ts: -------------------------------------------------------------------------------- 1 | import './syntax/jsx-import/JSX-import-JSX'; 2 | 3 | export { testNullishCoalescing } from './syntax/nullish-coalescing'; 4 | export { testOptionalChaining } from './syntax/optional-chaining'; 5 | 6 | export { testGenerator } from './syntax/generator'; 7 | export { testAsync } from './syntax/async'; 8 | 9 | export { kebabCase } from 'lodash'; 10 | export { merge, mergeAll } from 'lodash/fp'; 11 | 12 | export { returnsTrue } from './returnsTrue'; 13 | 14 | export const sum = (a: number, b: number) => { 15 | if ('development' === process.env.NODE_ENV) { 16 | console.log('dev only output'); 17 | } 18 | return a + b; 19 | }; 20 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/src/returnsTrue.ts: -------------------------------------------------------------------------------- 1 | // this just ensure a simple import works 2 | export const returnsTrue = () => true; 3 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/src/syntax/async.ts: -------------------------------------------------------------------------------- 1 | // regression test for async/await 2 | // code inspired by https://github.com/formium/tsdx/issues/869 3 | let shouldBeTrue = false; 4 | (async () => { 5 | shouldBeTrue = true; // a side effect to make sure this is output 6 | await Promise.resolve(); 7 | })(); 8 | 9 | export async function testAsync() { 10 | return await Promise.resolve(shouldBeTrue); 11 | } 12 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/src/syntax/generator.ts: -------------------------------------------------------------------------------- 1 | // regression test for generators 2 | export function* testGenerator(): IterableIterator { 3 | return yield true; 4 | } 5 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/src/syntax/jsx-import/JSX-A.jsx: -------------------------------------------------------------------------------- 1 | // DO NOT IMPORT THIS FILE DIRECTLY FROM index.ts 2 | // THIS FILE IS INTENTIONALLY TO TEST JSX CHAINING IMPORT 3 | // SEE https://github.com/jaredpalmer/tsdx/issues/523 4 | 5 | import JSXB from './JSX-B'; 6 | 7 | export default JSXB; 8 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/src/syntax/jsx-import/JSX-B.jsx: -------------------------------------------------------------------------------- 1 | // DO NOT IMPORT THIS FILE DIRECTLY FROM index.ts 2 | // THIS FILE IS INTENTIONALLY TO TEST JSX CHAINING IMPORT 3 | // SEE https://github.com/jaredpalmer/tsdx/issues/523 4 | 5 | export default function JSXComponent() { 6 | return 'JSXC'; 7 | } 8 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/src/syntax/jsx-import/JSX-import-JSX.jsx: -------------------------------------------------------------------------------- 1 | // Testing for jsx chaining import 2 | // https://github.com/jaredpalmer/tsdx/issues/523 3 | 4 | export * from './JSX-A'; 5 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/src/syntax/nullish-coalescing.ts: -------------------------------------------------------------------------------- 1 | // regression test for nullish coalescing syntax 2 | // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#nullish-coalescing 3 | 4 | export function testNullishCoalescing() { 5 | const someFunc = () => 'some string'; 6 | const someFalse = false; 7 | const shouldBeTrue = !(someFalse ?? someFunc()); 8 | return shouldBeTrue; 9 | } 10 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/src/syntax/optional-chaining.ts: -------------------------------------------------------------------------------- 1 | // regression test for optional chaining syntax 2 | // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining 3 | 4 | export function testOptionalChaining() { 5 | const someObj: { someOptionalString?: string } = {}; 6 | const shouldBeTrue = someObj?.someOptionalString || true; 7 | return shouldBeTrue; 8 | } 9 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/test/some-test.test.ts: -------------------------------------------------------------------------------- 1 | // this is to test that .test/.spec files in the test/ dir are excluded 2 | 3 | // and that rootDir: './src' doesn't error with test/ files 4 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/test/testUtil.ts: -------------------------------------------------------------------------------- 1 | // this is to test that test helper files in the test/ dir are excluded 2 | // i.e. files in test/ that don't have a .spec/.test suffix 3 | 4 | // and that rootDir: './src' doesn't error with test/ files 5 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "lib": ["dom", "esnext"], 5 | "declaration": true, 6 | "sourceMap": true, 7 | "rootDir": "./src", 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "moduleResolution": "node", 14 | "jsx": "react", 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noEmit": true 19 | }, 20 | "include": ["src", "types"] 21 | } 22 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-default/types/blar.d.ts: -------------------------------------------------------------------------------- 1 | // this is to test that rootDir: './src' doesn't error with types/ files 2 | 3 | // and that declaration files aren't re-output in dist/ 4 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-invalid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "dts build" 4 | }, 5 | "name": "build-invalid", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-invalid/src/index.ts: -------------------------------------------------------------------------------- 1 | export const inconsistentType: number = '123'; 2 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-invalid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "lib": ["dom", "esnext"], 5 | "declaration": true, 6 | "sourceMap": true, 7 | "rootDir": "./src", 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "moduleResolution": "node", 14 | "jsx": "react", 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noEmit": true 19 | }, 20 | "include": ["src", "types"] 21 | } 22 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-multipleEntries/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "dts build" 4 | }, 5 | "name": "build-multipleentries", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-multipleEntries/src/index.ts: -------------------------------------------------------------------------------- 1 | export { kebabCase } from 'lodash'; 2 | export { merge, mergeAll } from 'lodash/fp'; 3 | 4 | export { returnsTrue } from './returnsTrue'; 5 | 6 | export const sum = (a: number, b: number) => { 7 | if ('development' === process.env.NODE_ENV) { 8 | console.log('dev only output'); 9 | } 10 | return a + b; 11 | }; 12 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-multipleEntries/src/returnsFalse.ts: -------------------------------------------------------------------------------- 1 | // this just ensure a simple import works 2 | export const returnsFalse = () => false; 3 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-multipleEntries/src/returnsTrue.ts: -------------------------------------------------------------------------------- 1 | // this just ensure a simple import works 2 | export const returnsTrue = () => true; 3 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-multipleEntries/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "lib": ["dom", "esnext"], 5 | "declaration": true, 6 | "sourceMap": true, 7 | "rootDir": "./src", 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "moduleResolution": "node", 14 | "jsx": "react", 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noEmit": true 19 | }, 20 | "include": ["src", "types"] 21 | } 22 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-withTsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "dts build" 4 | }, 5 | "name": "build-withtsconfig", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-withTsconfig/src/index.ts: -------------------------------------------------------------------------------- 1 | export const sum = (a: number, b: number) => { 2 | if ('development' === process.env.NODE_ENV) { 3 | console.log('dev only output'); 4 | } 5 | return a + b; 6 | }; 7 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-withTsconfig/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // ensure that extends works (trailing comma & comment too) 3 | "extends": "../tsconfig.base.json", 4 | "compilerOptions": { 5 | "declarationDir": "../typingsCustom/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-withTsconfig/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "lib": ["dom", "esnext"], 5 | "declaration": true, 6 | "declarationDir": "typings", 7 | "declarationMap": true, 8 | "sourceMap": true, 9 | "rootDir": "./src", 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "moduleResolution": "node", 16 | "jsx": "react", 17 | "esModuleInterop": false, 18 | "skipLibCheck": true, 19 | "forceConsistentCasingInFileNames": true, 20 | "noEmit": true 21 | }, 22 | "include": ["src", "types"], // test parsing of trailing comma & comment 23 | } 24 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-withTsconfig/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // ensure that extends works (trailing comma & comment too) 3 | "extends": "./tsconfig.base.json", 4 | } 5 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-withTypesRollup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "dts build" 4 | }, 5 | "name": "build-with-types-rollup", 6 | "license": "MIT", 7 | "types": "dist/index.d.ts" 8 | } 9 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-withTypesRollup/src/bar/bar.ts: -------------------------------------------------------------------------------- 1 | export function bar(x: number, y: number): number { 2 | return x - y; 3 | } 4 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-withTypesRollup/src/foo/foo.ts: -------------------------------------------------------------------------------- 1 | export function foo(x: number, y: number): number { 2 | return x + y; 3 | } 4 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-withTypesRollup/src/index.ts: -------------------------------------------------------------------------------- 1 | import { foo } from './foo/foo'; 2 | import { bar } from './bar/bar'; 3 | import { FooBar } from './types'; 4 | 5 | export default function fooBar(x: number, y: number): FooBar { 6 | return { foo: foo(x, y), bar: bar(x, y) }; 7 | } 8 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-withTypesRollup/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface FooBar { 2 | foo: number; 3 | bar: number; 4 | } 5 | -------------------------------------------------------------------------------- /test/e2e/fixtures/build-withTypesRollup/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "lib": ["dom", "esnext"], 5 | "declaration": true, 6 | "sourceMap": true, 7 | "rootDir": "./src", 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "moduleResolution": "node", 14 | "jsx": "react", 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noEmit": true 19 | }, 20 | "include": ["src", "types"] 21 | } 22 | -------------------------------------------------------------------------------- /test/e2e/fixtures/lint/file-with-lint-errors.ts: -------------------------------------------------------------------------------- 1 | export const foo () => !!'bar'; 2 | -------------------------------------------------------------------------------- /test/e2e/fixtures/lint/file-with-lint-warnings.ts: -------------------------------------------------------------------------------- 1 | // this file should have 3 "unused var" lint warnings 2 | const unusedVar1 = () => { 3 | const unusedVar2 = 'baz'; 4 | const unusedVar3 = ''; 5 | }; 6 | -------------------------------------------------------------------------------- /test/e2e/fixtures/lint/file-with-prettier-lint-errors.ts: -------------------------------------------------------------------------------- 1 | export const foo = ( ) => 2 | !! ('bar') 3 | ; 4 | 5 | -------------------------------------------------------------------------------- /test/e2e/fixtures/lint/file-without-lint-error.ts: -------------------------------------------------------------------------------- 1 | export const foo = () => !!'bar'; 2 | -------------------------------------------------------------------------------- /test/e2e/fixtures/lint/react-file-with-lint-errors.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Foobar = (props: any) => { 4 | return <
    foobar
    ; 5 | }; 6 | -------------------------------------------------------------------------------- /test/e2e/fixtures/lint/react-file-without-lint-error.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Foobar = (props: any) => { 4 | return
    foobar
    ; 5 | }; 6 | -------------------------------------------------------------------------------- /test/integration/dts-build-options.test.ts: -------------------------------------------------------------------------------- 1 | import * as shell from 'shelljs'; 2 | 3 | import * as util from '../utils/fixture'; 4 | import { execWithCache } from '../utils/shell'; 5 | 6 | shell.config.silent = false; 7 | 8 | const testDir = 'integration'; 9 | const fixtureName = 'build-options'; 10 | const stageName = `stage-integration-${fixtureName}`; 11 | 12 | describe('integration :: dts build :: options', () => { 13 | beforeAll(() => { 14 | util.teardownStage(stageName); 15 | util.setupStageWithFixture(testDir, stageName, fixtureName); 16 | }); 17 | 18 | it('should create errors/ dir with --extractErrors', () => { 19 | const output = execWithCache('node ../dist/index.js build --extractErrors'); 20 | 21 | expect(shell.test('-f', 'errors/ErrorDev.js')).toBeTruthy(); 22 | expect(shell.test('-f', 'errors/ErrorProd.js')).toBeTruthy(); 23 | expect(shell.test('-f', 'errors/codes.json')).toBeTruthy(); 24 | 25 | expect(output.code).toBe(0); 26 | }); 27 | 28 | it('should have correct errors/codes.json', () => { 29 | const output = execWithCache('node ../dist/index.js build --extractErrors'); 30 | 31 | const errors = require(`../../${stageName}/errors/codes.json`); 32 | expect(errors['0']).toBe('error occurred! o no'); 33 | // TODO: warning is actually not extracted, only invariant 34 | // expect(errors['1']).toBe('warning - water is wet'); 35 | 36 | expect(output.code).toBe(0); 37 | }); 38 | 39 | it('should compile files into a dist directory', () => { 40 | const output = execWithCache('node ../dist/index.js build --extractErrors'); 41 | 42 | expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); 43 | expect( 44 | shell.test('-f', 'dist/build-options.cjs.development.js') 45 | ).toBeTruthy(); 46 | expect( 47 | shell.test('-f', 'dist/build-options.cjs.production.min.js') 48 | ).toBeTruthy(); 49 | expect(shell.test('-f', 'dist/build-options.esm.js')).toBeTruthy(); 50 | 51 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 52 | 53 | expect(output.code).toBe(0); 54 | }); 55 | 56 | afterAll(() => { 57 | util.teardownStage(stageName); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/integration/dts-build-withBabel.test.ts: -------------------------------------------------------------------------------- 1 | import * as shell from 'shelljs'; 2 | 3 | import * as util from '../utils/fixture'; 4 | import { execWithCache, grep } from '../utils/shell'; 5 | 6 | shell.config.silent = false; 7 | 8 | const testDir = 'integration'; 9 | const fixtureName = 'build-withBabel'; 10 | const stageName = `stage-integration-${fixtureName}`; 11 | 12 | describe('integration :: dts build :: .babelrc.js', () => { 13 | beforeAll(() => { 14 | util.teardownStage(stageName); 15 | util.setupStageWithFixture(testDir, stageName, fixtureName); 16 | }); 17 | 18 | // not working somehow, to be fixed later. 19 | it.skip('should convert styled-components template tags', () => { 20 | const output = execWithCache('node ../dist/index.js build'); 21 | expect(output.code).toBe(0); 22 | 23 | // from styled.h1` to styled.h1.withConfig( 24 | const matched = grep(/styled.h1.withConfig\(/, [ 25 | 'dist/build-withbabel.*.js', 26 | ]); 27 | expect(matched).toBeTruthy(); 28 | }); 29 | 30 | // TODO: make styled-components work with its Babel plugin and not just its 31 | // macro by allowing customization of plugin order 32 | // not working somehow, to be fixed later. 33 | it.skip('should remove comments in the CSS', () => { 34 | const output = execWithCache('node ../dist/index.js build'); 35 | expect(output.code).toBe(0); 36 | 37 | // the comment "should be removed" should no longer be there 38 | const matched = grep(/should be removed/, ['dist/build-withbabel.*.js']); 39 | expect(matched).toBeFalsy(); 40 | }); 41 | 42 | it('should merge and apply presets', () => { 43 | const output = execWithCache('node ../dist/index.js build'); 44 | expect(output.code).toBe(0); 45 | 46 | // ensures replace-identifiers was used 47 | const matched = grep(/replacedSum/, ['dist/build-withbabel.*.js']); 48 | expect(matched).toBeTruthy(); 49 | }); 50 | 51 | it('should compile files into a dist directory', () => { 52 | const output = execWithCache('node ../dist/index.js build'); 53 | 54 | expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); 55 | expect( 56 | shell.test('-f', 'dist/build-withbabel.cjs.development.js') 57 | ).toBeTruthy(); 58 | expect( 59 | shell.test('-f', 'dist/build-withbabel.cjs.production.min.js') 60 | ).toBeTruthy(); 61 | expect(shell.test('-f', 'dist/build-withbabel.esm.js')).toBeTruthy(); 62 | 63 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 64 | 65 | expect(output.code).toBe(0); 66 | }); 67 | 68 | afterAll(() => { 69 | util.teardownStage(stageName); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/integration/dts-build-withConfig-defineConfig.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import * as shell from 'shelljs'; 3 | 4 | import * as util from '../utils/fixture'; 5 | import { execWithCache } from '../utils/shell'; 6 | 7 | shell.config.silent = false; 8 | 9 | const testDir = 'integration'; 10 | const fixtureName = 'build-withConfig-defineConfig'; 11 | const stageName = `stage-integration-${fixtureName}`; 12 | 13 | describe('integration :: dts build :: dts-config.js :: defineConfig', () => { 14 | beforeAll(() => { 15 | util.teardownStage(stageName); 16 | util.setupStageWithFixture(testDir, stageName, fixtureName); 17 | }); 18 | 19 | it('should create a CSS file in the dist/ directory', () => { 20 | const output = execWithCache('node ../dist/index.js build'); 21 | 22 | // TODO: this is kind of subpar naming, rollup-plugin-postcss just names it 23 | // the same as the output file, but with the .css extension 24 | expect( 25 | shell.test('-f', 'dist/build-withconfig-defineconfig.cjs.development.css') 26 | ); 27 | 28 | expect(output.code).toBe(0); 29 | }); 30 | 31 | it('should autoprefix and minify the CSS file', async () => { 32 | const output = execWithCache('node ../dist/index.js build'); 33 | 34 | const cssText = await fs.readFile( 35 | './dist/build-withconfig-defineconfig.cjs.development.css' 36 | ); 37 | 38 | // autoprefixed and minifed output 39 | expect( 40 | cssText.includes('.test::-moz-placeholder{color:"blue"}') 41 | ).toBeTruthy(); 42 | 43 | expect(output.code).toBe(0); 44 | }); 45 | 46 | it('should compile files into a dist directory', () => { 47 | const output = execWithCache('node ../dist/index.js build'); 48 | 49 | expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); 50 | expect( 51 | shell.test('-f', 'dist/build-withconfig-defineconfig.cjs.development.js') 52 | ).toBeTruthy(); 53 | expect( 54 | shell.test( 55 | '-f', 56 | 'dist/build-withconfig-defineconfig.cjs.production.min.js' 57 | ) 58 | ).toBeTruthy(); 59 | expect( 60 | shell.test('-f', 'dist/build-withconfig-defineconfig.esm.js') 61 | ).toBeTruthy(); 62 | 63 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 64 | 65 | expect(output.code).toBe(0); 66 | }); 67 | 68 | afterAll(() => { 69 | util.teardownStage(stageName); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/integration/dts-build-withConfig.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import * as shell from 'shelljs'; 3 | 4 | import * as util from '../utils/fixture'; 5 | import { execWithCache } from '../utils/shell'; 6 | 7 | shell.config.silent = false; 8 | 9 | const testDir = 'integration'; 10 | const fixtureName = 'build-withConfig'; 11 | const stageName = `stage-integration-${fixtureName}`; 12 | 13 | describe('integration :: dts build :: dts-config.js', () => { 14 | beforeAll(() => { 15 | util.teardownStage(stageName); 16 | util.setupStageWithFixture(testDir, stageName, fixtureName); 17 | }); 18 | 19 | it('should create a CSS file in the dist/ directory', () => { 20 | const output = execWithCache('node ../dist/index.js build'); 21 | 22 | // TODO: this is kind of subpar naming, rollup-plugin-postcss just names it 23 | // the same as the output file, but with the .css extension 24 | expect(shell.test('-f', 'dist/build-withconfig.cjs.development.css')); 25 | 26 | expect(output.code).toBe(0); 27 | }); 28 | 29 | it('should autoprefix and minify the CSS file', async () => { 30 | const output = execWithCache('node ../dist/index.js build'); 31 | 32 | const cssText = await fs.readFile( 33 | './dist/build-withconfig.cjs.development.css' 34 | ); 35 | 36 | // autoprefixed and minifed output 37 | expect( 38 | cssText.includes('.test::-moz-placeholder{color:"blue"}') 39 | ).toBeTruthy(); 40 | 41 | expect(output.code).toBe(0); 42 | }); 43 | 44 | it('should compile files into a dist directory', () => { 45 | const output = execWithCache('node ../dist/index.js build'); 46 | 47 | expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); 48 | expect( 49 | shell.test('-f', 'dist/build-withconfig.cjs.development.js') 50 | ).toBeTruthy(); 51 | expect( 52 | shell.test('-f', 'dist/build-withconfig.cjs.production.min.js') 53 | ).toBeTruthy(); 54 | expect(shell.test('-f', 'dist/build-withconfig.esm.js')).toBeTruthy(); 55 | 56 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 57 | 58 | expect(output.code).toBe(0); 59 | }); 60 | 61 | afterAll(() => { 62 | util.teardownStage(stageName); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/integration/dts-build-withConfigTs-defineConfig.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import * as shell from 'shelljs'; 3 | 4 | import * as util from '../utils/fixture'; 5 | import { execWithCache } from '../utils/shell'; 6 | 7 | shell.config.silent = false; 8 | 9 | const testDir = 'integration'; 10 | const fixtureName = 'build-withConfigTs-defineConfig'; 11 | const stageName = `stage-integration-${fixtureName}`; 12 | 13 | describe('integration :: dts build :: dts-config.ts :: defineConfig', () => { 14 | beforeAll(() => { 15 | util.teardownStage(stageName); 16 | util.setupStageWithFixture(testDir, stageName, fixtureName); 17 | }); 18 | 19 | it('should create a CSS file in the dist/ directory', () => { 20 | const output = execWithCache('node ../dist/index.js build'); 21 | 22 | // TODO: this is kind of subpar naming, rollup-plugin-postcss just names it 23 | // the same as the output file, but with the .css extension 24 | expect( 25 | shell.test( 26 | '-f', 27 | 'dist/build-withconfigts-defineconfig.cjs.development.css' 28 | ) 29 | ); 30 | 31 | expect(output.code).toBe(0); 32 | }); 33 | 34 | it('should autoprefix and minify the CSS file', async () => { 35 | const output = execWithCache('node ../dist/index.js build'); 36 | 37 | const cssText = await fs.readFile( 38 | './dist/build-withconfigts-defineconfig.cjs.development.css' 39 | ); 40 | 41 | // autoprefixed and minifed output 42 | expect( 43 | cssText.includes('.test::-moz-placeholder{color:"blue"}') 44 | ).toBeTruthy(); 45 | 46 | expect(output.code).toBe(0); 47 | }); 48 | 49 | it('should compile files into a dist directory', () => { 50 | const output = execWithCache('node ../dist/index.js build'); 51 | 52 | expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); 53 | expect( 54 | shell.test( 55 | '-f', 56 | 'dist/build-withconfigts-defineconfig.cjs.development.js' 57 | ) 58 | ).toBeTruthy(); 59 | expect( 60 | shell.test( 61 | '-f', 62 | 'dist/build-withconfigts-defineconfig.cjs.production.min.js' 63 | ) 64 | ).toBeTruthy(); 65 | expect( 66 | shell.test('-f', 'dist/build-withconfigts-defineconfig.esm.js') 67 | ).toBeTruthy(); 68 | 69 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 70 | 71 | expect(output.code).toBe(0); 72 | }); 73 | 74 | afterAll(() => { 75 | util.teardownStage(stageName); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/integration/dts-build-withConfigTs-noRollup.test.ts: -------------------------------------------------------------------------------- 1 | import * as shell from 'shelljs'; 2 | 3 | import * as util from '../utils/fixture'; 4 | import { execWithCache } from '../utils/shell'; 5 | 6 | shell.config.silent = false; 7 | 8 | const testDir = 'integration'; 9 | const fixtureName = 'build-withConfigTs-noRollup'; 10 | const stageName = `stage-integration-${fixtureName}`; 11 | 12 | describe('integration :: dts build :: dts-config.ts :: noRollup', () => { 13 | beforeAll(() => { 14 | util.teardownStage(stageName); 15 | util.setupStageWithFixture(testDir, stageName, fixtureName); 16 | }); 17 | 18 | it('should compile files into a dist directory', () => { 19 | const output = execWithCache('node ../dist/index.js build'); 20 | 21 | expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); 22 | expect( 23 | shell.test('-f', 'dist/build-withconfigts-norollup.cjs.development.js') 24 | ).toBeTruthy(); 25 | expect( 26 | shell.test('-f', 'dist/build-withconfigts-norollup.cjs.production.min.js') 27 | ).toBeTruthy(); 28 | expect( 29 | shell.test('-f', 'dist/build-withconfigts-norollup.esm.js') 30 | ).toBeTruthy(); 31 | 32 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 33 | 34 | expect(output.code).toBe(0); 35 | }); 36 | 37 | afterAll(() => { 38 | util.teardownStage(stageName); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/integration/dts-build-withConfigTs.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import * as shell from 'shelljs'; 3 | 4 | import * as util from '../utils/fixture'; 5 | import { execWithCache } from '../utils/shell'; 6 | 7 | shell.config.silent = false; 8 | 9 | const testDir = 'integration'; 10 | const fixtureName = 'build-withConfigTs'; 11 | const stageName = `stage-integration-${fixtureName}`; 12 | 13 | describe('integration :: dts build :: dts-config.ts', () => { 14 | beforeAll(() => { 15 | util.teardownStage(stageName); 16 | util.setupStageWithFixture(testDir, stageName, fixtureName); 17 | }); 18 | 19 | it('should create a CSS file in the dist/ directory', () => { 20 | const output = execWithCache('node ../dist/index.js build'); 21 | 22 | // TODO: this is kind of subpar naming, rollup-plugin-postcss just names it 23 | // the same as the output file, but with the .css extension 24 | expect(shell.test('-f', 'dist/build-withconfigts.cjs.development.css')); 25 | 26 | expect(output.code).toBe(0); 27 | }); 28 | 29 | it('should autoprefix and minify the CSS file', async () => { 30 | const output = execWithCache('node ../dist/index.js build'); 31 | 32 | const cssText = await fs.readFile( 33 | './dist/build-withconfigts.cjs.development.css' 34 | ); 35 | 36 | // autoprefixed and minifed output 37 | expect( 38 | cssText.includes('.test::-moz-placeholder{color:"blue"}') 39 | ).toBeTruthy(); 40 | 41 | expect(output.code).toBe(0); 42 | }); 43 | 44 | it('should compile files into a dist directory', () => { 45 | const output = execWithCache('node ../dist/index.js build'); 46 | 47 | expect(shell.test('-f', 'dist/index.js')).toBeTruthy(); 48 | expect( 49 | shell.test('-f', 'dist/build-withconfigts.cjs.development.js') 50 | ).toBeTruthy(); 51 | expect( 52 | shell.test('-f', 'dist/build-withconfigts.cjs.production.min.js') 53 | ).toBeTruthy(); 54 | expect(shell.test('-f', 'dist/build-withconfigts.esm.js')).toBeTruthy(); 55 | 56 | expect(shell.test('-f', 'dist/index.d.ts')).toBeTruthy(); 57 | 58 | expect(output.code).toBe(0); 59 | }); 60 | 61 | afterAll(() => { 62 | util.teardownStage(stageName); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/integration/fixtures/README.md: -------------------------------------------------------------------------------- 1 | # Integration Test Fixtures Directory 2 | 3 | - `build-options` lets us check that DTS's flags work as expected 4 | - `build-withConfig` lets us check that `dts.config.js` works as expected 5 | - `build-withBabel` lets us check that `.babelrc` works as expected 6 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-options/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "dts build --extractErrors" 4 | }, 5 | "name": "build-options", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-options/src/index.ts: -------------------------------------------------------------------------------- 1 | import invariant from 'tiny-invariant'; 2 | import warning from 'tiny-warning'; 3 | 4 | invariant(true, 'error occurred! o no'); 5 | warning(true, 'warning - water is wet'); 6 | 7 | export const sum = (a: number, b: number) => { 8 | if ('development' === process.env.NODE_ENV) { 9 | console.log('dev only output'); 10 | } 11 | return a + b; 12 | }; 13 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-options/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "lib": ["dom", "esnext"], 5 | "declaration": true, 6 | "sourceMap": true, 7 | "rootDir": "./src", 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "moduleResolution": "node", 14 | "jsx": "react", 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noEmit": true 19 | }, 20 | "include": ["src", "types"], 21 | } 22 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withBabel/.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | // ensure Babel presets are merged and applied 4 | './test-babel-preset' 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withBabel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "dts build" 4 | }, 5 | "name": "build-withbabel", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withBabel/src/index.ts: -------------------------------------------------------------------------------- 1 | export { Title } from './styled'; 2 | 3 | export const sum = (a: number, b: number) => { 4 | if ('development' === process.env.NODE_ENV) { 5 | console.log('dev only output'); 6 | } 7 | return a + b; 8 | }; 9 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withBabel/src/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/macro'; 2 | 3 | export const Title = styled.h1` 4 | /* this comment should be removed */ 5 | font-size: 1.5em; 6 | text-align: center; 7 | color: palevioletred; 8 | `; 9 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withBabel/test-babel-preset.js: -------------------------------------------------------------------------------- 1 | // a simple babel preset to ensure presets are merged and applied 2 | module.exports = () => ({ 3 | plugins: [['replace-identifiers', { sum: 'replacedSum' }]], 4 | }); 5 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withBabel/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "lib": ["dom", "esnext"], 5 | "declaration": true, 6 | "sourceMap": true, 7 | "rootDir": "./src", 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "moduleResolution": "node", 14 | "jsx": "react", 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noEmit": false 19 | }, 20 | "include": ["src", "types"], 21 | } 22 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfig-defineConfig/dts.config.js: -------------------------------------------------------------------------------- 1 | const defineConfig = require('../dist').defineConfig; 2 | const postcss = require('rollup-plugin-postcss'); 3 | const autoprefixer = require('autoprefixer'); 4 | const cssnano = require('cssnano'); 5 | 6 | module.exports = defineConfig({ 7 | rollup: (config, options) => { 8 | config.plugins.push( 9 | postcss({ 10 | plugins: [ 11 | autoprefixer(), 12 | cssnano({ 13 | preset: 'default', 14 | }), 15 | ], 16 | inject: false, 17 | // only write out CSS for the first bundle (avoids pointless extra files): 18 | extract: !!options.writeMeta, 19 | }) 20 | ); 21 | return config; 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfig-defineConfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "dts build" 4 | }, 5 | "name": "build-withconfig-defineconfig", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfig-defineConfig/src/index.css: -------------------------------------------------------------------------------- 1 | /* ::placeholder should be autoprefixed, and everything minified */ 2 | .test::placeholder { 3 | color: 'blue'; 4 | } 5 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfig-defineConfig/src/index.ts: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | 3 | export const sum = (a: number, b: number) => { 4 | if ('development' === process.env.NODE_ENV) { 5 | console.log('dev only output'); 6 | } 7 | return a + b; 8 | }; 9 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfig-defineConfig/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "lib": ["dom", "esnext"], 5 | "declaration": true, 6 | "sourceMap": true, 7 | "rootDir": "./src", 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "moduleResolution": "node", 14 | "jsx": "react", 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noEmit": true 19 | }, 20 | "include": ["src", "types"] 21 | } 22 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfig/dts.config.js: -------------------------------------------------------------------------------- 1 | const postcss = require('rollup-plugin-postcss'); 2 | const autoprefixer = require('autoprefixer'); 3 | const cssnano = require('cssnano'); 4 | 5 | module.exports = { 6 | rollup(config, options) { 7 | config.plugins.push( 8 | postcss({ 9 | plugins: [ 10 | autoprefixer(), 11 | cssnano({ 12 | preset: 'default', 13 | }), 14 | ], 15 | inject: false, 16 | // only write out CSS for the first bundle (avoids pointless extra files): 17 | extract: !!options.writeMeta, 18 | }) 19 | ); 20 | return config; 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "dts build" 4 | }, 5 | "name": "build-withconfig", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfig/src/index.css: -------------------------------------------------------------------------------- 1 | /* ::placeholder should be autoprefixed, and everything minified */ 2 | .test::placeholder { 3 | color: 'blue'; 4 | } 5 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfig/src/index.ts: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | 3 | export const sum = (a: number, b: number) => { 4 | if ('development' === process.env.NODE_ENV) { 5 | console.log('dev only output'); 6 | } 7 | return a + b; 8 | }; 9 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfig/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "lib": ["dom", "esnext"], 5 | "declaration": true, 6 | "sourceMap": true, 7 | "rootDir": "./src", 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "moduleResolution": "node", 14 | "jsx": "react", 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noEmit": true 19 | }, 20 | "include": ["src", "types"], 21 | } 22 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfigTs-defineConfig/dts.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '../dist'; 2 | import autoprefixer from 'autoprefixer'; 3 | import cssnano from 'cssnano'; 4 | import postcss from 'rollup-plugin-postcss'; 5 | 6 | // This is necessary due to how typechecking works with the @types/cssnano 7 | // package. If you remove the `if` check below and attempt to add the cssnano 8 | // processor directly, you run into issues with type stack depth. 9 | const getPlugins = () => { 10 | const plugins: any[] = [autoprefixer()]; 11 | const cssnanoProcessor = cssnano({ preset: 'default' }); 12 | if ('version' in cssnanoProcessor) { 13 | plugins.push(cssnanoProcessor); 14 | } 15 | return plugins; 16 | }; 17 | 18 | export default defineConfig({ 19 | rollup: (config, options) => { 20 | config?.plugins?.push( 21 | postcss({ 22 | plugins: getPlugins(), 23 | inject: false, 24 | // only write out CSS for the first bundle (avoids pointless extra files): 25 | extract: !!options.writeMeta, 26 | }) 27 | ); 28 | return config; 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfigTs-defineConfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "dts build" 4 | }, 5 | "name": "build-withconfigts-defineconfig", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfigTs-defineConfig/src/index.css: -------------------------------------------------------------------------------- 1 | /* ::placeholder should be autoprefixed, and everything minified */ 2 | .test::placeholder { 3 | color: 'blue'; 4 | } 5 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfigTs-defineConfig/src/index.ts: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | 3 | export const sum = (a: number, b: number) => { 4 | if ('development' === process.env.NODE_ENV) { 5 | console.log('dev only output'); 6 | } 7 | return a + b; 8 | }; 9 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfigTs-defineConfig/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "ESNext", 5 | "lib": ["dom", "esnext"], 6 | "declaration": true, 7 | "sourceMap": true, 8 | "rootDir": "./src", 9 | "strict": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "moduleResolution": "node", 15 | "jsx": "react", 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "forceConsistentCasingInFileNames": true, 19 | "noEmit": true, 20 | "paths": { 21 | "dts-cli": ["../src"] 22 | } 23 | }, 24 | "include": ["src", "types"] 25 | } 26 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfigTs-noRollup/dts.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '../dist'; 2 | 3 | export default defineConfig({}); 4 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfigTs-noRollup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "dts build" 4 | }, 5 | "name": "build-withconfigts-norollup", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfigTs-noRollup/src/index.ts: -------------------------------------------------------------------------------- 1 | export const sum = (a: number, b: number) => { 2 | if ('development' === process.env.NODE_ENV) { 3 | console.log('dev only output'); 4 | } 5 | return a + b; 6 | }; 7 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfigTs-noRollup/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "ESNext", 5 | "lib": ["dom", "esnext"], 6 | "declaration": true, 7 | "sourceMap": true, 8 | "rootDir": "./src", 9 | "strict": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "moduleResolution": "node", 15 | "jsx": "react", 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "forceConsistentCasingInFileNames": true, 19 | "noEmit": true, 20 | "paths": { 21 | "dts-cli": ["../src"] 22 | } 23 | }, 24 | "include": ["src", "types"] 25 | } 26 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfigTs/dts.config.ts: -------------------------------------------------------------------------------- 1 | import { DtsOptions, RollupOptions } from '../dist'; 2 | import autoprefixer from 'autoprefixer'; 3 | import cssnano from 'cssnano'; 4 | import postcss from 'rollup-plugin-postcss'; 5 | 6 | // This is necessary due to how typechecking works with the @types/cssnano 7 | // package. If you remove the `if` check below and attempt to add the cssnano 8 | // processor directly, you run into issues with type stack depth. 9 | const getPlugins = () => { 10 | const plugins: any[] = [autoprefixer()]; 11 | const cssnanoProcessor = cssnano({ preset: 'default' }); 12 | if ('version' in cssnanoProcessor) { 13 | plugins.push(cssnanoProcessor); 14 | } 15 | return plugins; 16 | }; 17 | 18 | export default { 19 | rollup(config: RollupOptions, options: DtsOptions) { 20 | config?.plugins?.push( 21 | postcss({ 22 | plugins: getPlugins(), 23 | inject: false, 24 | // only write out CSS for the first bundle (avoids pointless extra files): 25 | extract: !!options.writeMeta, 26 | }) 27 | ); 28 | return config; 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfigTs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "dts build" 4 | }, 5 | "name": "build-withconfigts", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfigTs/src/index.css: -------------------------------------------------------------------------------- 1 | /* ::placeholder should be autoprefixed, and everything minified */ 2 | .test::placeholder { 3 | color: 'blue'; 4 | } 5 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfigTs/src/index.ts: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | 3 | export const sum = (a: number, b: number) => { 4 | if ('development' === process.env.NODE_ENV) { 5 | console.log('dev only output'); 6 | } 7 | return a + b; 8 | }; 9 | -------------------------------------------------------------------------------- /test/integration/fixtures/build-withConfigTs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "ESNext", 5 | "lib": ["dom", "esnext"], 6 | "declaration": true, 7 | "sourceMap": true, 8 | "rootDir": "./src", 9 | "strict": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "moduleResolution": "node", 15 | "jsx": "react", 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "forceConsistentCasingInFileNames": true, 19 | "noEmit": true, 20 | "paths": { 21 | "dts-cli": ["../src"] 22 | } 23 | }, 24 | "include": ["src", "types"] 25 | } 26 | -------------------------------------------------------------------------------- /test/unit/utils-safePackageName.test.ts: -------------------------------------------------------------------------------- 1 | const { safePackageName } = require('../../src/utils'); 2 | 3 | describe('utils | safePackageName', () => { 4 | it('should generate safe package name', () => { 5 | expect(safePackageName('@babel/core')).toBe('core'); 6 | expect(safePackageName('react')).toBe('react'); 7 | expect(safePackageName('react-dom')).toBe('react-dom'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/utils/fixture.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as shell from 'shelljs'; 3 | 4 | export const rootDir = process.cwd(); 5 | 6 | shell.config.silent = true; 7 | 8 | export function setupStageWithFixture( 9 | testDir: string, 10 | stageName: string, 11 | fixtureName: string 12 | ): void { 13 | const stagePath = path.join(rootDir, stageName); 14 | shell.mkdir(stagePath); 15 | shell.exec( 16 | `cp -a ${rootDir}/test/${testDir}/fixtures/${fixtureName}/. ${stagePath}/` 17 | ); 18 | shell.ln( 19 | '-s', 20 | path.join(rootDir, 'node_modules'), 21 | path.join(stagePath, 'node_modules') 22 | ); 23 | shell.cd(stagePath); 24 | } 25 | 26 | export function teardownStage(stageName: string): void { 27 | shell.cd(rootDir); 28 | shell.rm('-rf', path.join(rootDir, stageName)); 29 | } 30 | -------------------------------------------------------------------------------- /test/utils/shell.ts: -------------------------------------------------------------------------------- 1 | // this file contains helper utils for working with shell.js functions 2 | import * as shell from 'shelljs'; 3 | 4 | shell.config.silent = true; 5 | 6 | // simple shell.exec "cache" that doesn't re-run the same command twice in a row 7 | let prevCommand = ''; 8 | let prevCommandOutput = {} as shell.ShellReturnValue; 9 | export function execWithCache( 10 | command: string, 11 | { noCache = false } = {} 12 | ): shell.ShellReturnValue { 13 | // return the old output 14 | if (!noCache && prevCommand === command) return prevCommandOutput; 15 | 16 | const output = shell.exec(command); 17 | 18 | // reset if command is not to be cached 19 | if (noCache) { 20 | prevCommand = ''; 21 | prevCommandOutput = {} as shell.ShellReturnValue; 22 | } else { 23 | prevCommand = command; 24 | prevCommandOutput = output; 25 | } 26 | 27 | return output; 28 | } 29 | 30 | // shell.js grep wrapper returns true if pattern has matches in file 31 | export function grep(pattern: RegExp, fileName: string[]): boolean { 32 | const output = shell.grep(pattern, fileName); 33 | // output.code is always 0 regardless of matched/unmatched patterns 34 | // so need to test output.stdout 35 | // https://github.com/jaredpalmer/tsdx/pull/525#discussion_r395571779 36 | return Boolean(output.stdout.match(pattern)); 37 | } 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "exclude": ["src/template/**/*"], 4 | "compilerOptions": { 5 | "allowJs": true, 6 | "jsx": "react", 7 | "importHelpers": true, 8 | "esModuleInterop": true, 9 | "outDir": "dist", 10 | "declaration": true, 11 | "module": "commonjs", 12 | "rootDir": "src", 13 | "strict": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "skipLibCheck": true, 19 | "target": "es2017", 20 | "resolveJsonModule": true, 21 | "incremental": true 22 | } 23 | } 24 | --------------------------------------------------------------------------------