diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 84a2ebde..4b433eda 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
- node: [10.x, 11.x, 12.x]
+ node: [10.x, 11.x, 12.x, 14.x]
steps:
- uses: actions/checkout@v1
@@ -22,13 +22,6 @@ jobs:
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- - name: lint commits
- run:
- npx commitlint --config="./src/config/commitlint.config.js" --from="origin/${{
- github.base_ref }}" --to="origin/${{ github.head_ref }}"
- env:
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
-
- run: npm run build
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 09048d22..8e0c70cb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,7 @@
node_modules
coverage
dist
-.opt-in
-.opt-out
.DS_Store
-.eslintcache
# these cause more harm than good
# when working with contributors
diff --git a/.huskyrc.js b/.huskyrc.js
new file mode 100644
index 00000000..ae7e9373
--- /dev/null
+++ b/.huskyrc.js
@@ -0,0 +1 @@
+module.exports = require('./src/config/huskyrc')
diff --git a/.prettierrc.js b/.prettierrc.js
index 698d14b8..2cda2dfc 100644
--- a/.prettierrc.js
+++ b/.prettierrc.js
@@ -1 +1,7 @@
-module.exports = require('./src/config/prettierrc')
+const prettierConfig = require('./src/config/prettierrc')
+
+module.exports = Object.assign({}, prettierConfig, {
+ // to prevent additional conflicts with update kcd-scripts, making
+ // backmerging a little easier.
+ printWidth: 80,
+})
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e719b93a..2a675299 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,5 @@
# CHANGELOG
The changelog is automatically updated using
-[semantic-release](https://github.com/semantic-release/semantic-release). You can see it on the
-[releases page](../../releases).
+[semantic-release](https://github.com/semantic-release/semantic-release). You
+can see it on the [releases page](../../releases).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 88b0cd6c..57d0dda4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,18 +2,18 @@
Thanks for being willing to contribute!
-**Working on your first Pull Request?** You can learn how from this _free_ series [How to Contribute
-to an Open Source Project on GitHub][egghead]
+**Working on your first Pull Request?** You can learn how from this _free_
+series [How to Contribute to an Open Source Project on GitHub][egghead]
## Project setup
-1. Fork and clone the repo
-2. `$ npm install` to install dependencies
-3. `$ npm run validate` to validate you've got it working
-4. Create a branch for your PR
+1. Fork and clone the repo
+2. `$ npm install` to install dependencies
+3. `$ npm run validate` to validate you've got it working
+4. Create a branch for your PR
-> Tip: Keep your `master` branch pointing at the original repository and make pull requests from
-> branches on your fork. To do this, run:
+> Tip: Keep your `master` branch pointing at the original repository and make
+> pull requests from branches on your fork. To do this, run:
>
> ```
> git remote add upstream https://github.com/codfish/cod-scripts.git
@@ -21,51 +21,29 @@ to an Open Source Project on GitHub][egghead]
> git branch --set-upstream-to=upstream/master master
> ```
>
-> This will add the original repository as a "remote" called "upstream," Then fetch the git
-> information from that remote, then set your local `master` branch to use the upstream master
-> branch whenever you run `git pull`. Then you can make all of your pull request branches based on
-> this `master` branch. Whenever you want to update your version of `master`, do a regular
-> `git pull`.
+> This will add the original repository as a "remote" called "upstream," Then
+> fetch the git information from that remote, then set your local `master`
+> branch to use the upstream master branch whenever you run `git pull`. Then you
+> can make all of your pull request branches based on this `master` branch.
+> Whenever you want to update your version of `master`, do a regular `git pull`.
## Committing and Pushing changes
-This project uses [`semantic-release`][semantic-release] to do automatic releases and generate a
-changelog based on the commit history. So we follow [a convention][convention] for commit messages.
-You don't have to follow this convention if you don't want to. Just know that when we merge your
-commit, we'll probably use "Squash and Merge" so we can change the commit message :)
-
-Please make sure to run the tests before you commit your changes. You can run `npm run test:update`
-which will update any snapshots that need updating. Make sure to include those changes (if they
-exist) in your commit.
-
-### opt in/out of git hooks
-
-There are git hooks set up with this project that are automatically installed when you install
-dependencies. They're really handy, but are turned off by default (so as to not hinder new
-contributors). You can opt into these by creating a file called `.opt-in` at the root of the project
-and putting this inside:
-
-```
-pre-commit
-```
-
-One of the things that the git hooks does is automatically format the files you change. It does this
-by reformating the entire file and running `git add` on the file after. This breaks workflows where
-you're trying to commit portions of the file only. You can always run your commit with
-`--no-verify`, but if this is a bummer to your workflow, you can add an `.opt-out` file with the
-contents:
-
-```
-autoformat
-```
+Please make sure to run the tests before you commit your changes. You can run
+`npm run test:update` which will update any snapshots that need updating. Make
+sure to include those changes (if they exist) in your commit.
## Help needed
Please checkout the [the open issues][issues]
-Also, please watch the repo and respond to questions/bug reports/feature requests! Thanks!
+Also, please watch the repo and respond to questions/bug reports/feature
+requests! Thanks!
+
+
-[egghead]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github
+[egghead]:
+ https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github
[semantic-release]: https://npmjs.com/package/semantic-release
[convention]:
https://github.com/conventional-changelog/conventional-changelog-angular/blob/ed32559941719a130bb0327f886d6a32a8cbc2ba/convention.md
diff --git a/README.md b/README.md
index 4a56a8fd..95c12c2d 100644
--- a/README.md
+++ b/README.md
@@ -4,17 +4,23 @@
CLI toolbox for common scripts for my projects
-
-
-[![version][version-badge]][package] [![downloads][downloads-badge]][npmcharts]
-[![GitHub Workflow Status][actions-badge]][actions-badge] [![MIT License][license-badge]][license]
-[![PRs Welcome][prs-badge]][prs] [![Semantic Release][semantic-release-badge]][semantic-release]
+---
+
+
+[![version][version-badge]][package]
+[![downloads][downloads-badge]][npmcharts]
+[![GitHub Workflow Status][actions-badge]][actions-badge]
+[![MIT License][license-badge]][license]
+[![PRs Welcome][prs-badge]][prs]
+[![Semantic Release][semantic-release-badge]][semantic-release]
[![Commitizen friendly][commitizen-badge]][commitizen]
+
## Motivation
-This helps me maintain personal & work projects without duplication. This is a CLI that abstracts
-away all configuration for my open source projects for linting, testing, building, and more.
+This helps me maintain personal & work projects without duplication. This is a
+CLI that abstracts away all configuration for my open source projects for
+linting, testing, building, and more.
## Table of Contents
@@ -24,16 +30,15 @@ away all configuration for my open source projects for linting, testing, buildin
- [Installation](#installation)
- [Usage](#usage)
- [Overriding Config](#overriding-config)
- - [Flow support](#flow-support)
-- [Inspiration](#inspiration)
+ - [TypeScript Support](#typescript-support)
- [LICENSE](#license)
## Installation
-This module is distributed via [npm][npm] which is bundled with [node][node] and should be installed
-as one of your project's `devDependencies`:
+This module is distributed via [npm][npm] which is bundled with [node][node] and
+should be installed as one of your project's `devDependencies`:
```sh
npm install --save-dev cod-scripts
@@ -42,26 +47,29 @@ npm install --save @babel/runtime
## Usage
-This is a CLI and exposes a bin called `cod-scripts`. I don't really plan on documenting or testing
-it super duper well because it's really specific to my needs. You'll find all available scripts in
-`src/scripts`.
+This is a CLI and exposes a bin called `cod-scripts`. I don't really plan on
+documenting or testing it super duper well because it's really specific to my
+needs. You'll find all available scripts in `src/scripts`.
-This project actually dogfoods itself. If you look in the `package.json`, you'll find scripts with
-`node src {scriptName}`. This serves as an example of some of the things you can do with
-`cod-scripts`.
+This project actually dogfoods itself. If you look in the `package.json`, you'll
+find scripts with `node src {scriptName}`. This serves as an example of some of
+the things you can do with `cod-scripts`.
### Overriding Config
-Unlike `react-scripts`, `cod-scripts` allows you to specify your own configuration for things and
-have that plug directly into the way things work with `cod-scripts`. There are various ways that it
-works, but basically if you want to have your own config for something, just add the configuration
-and `cod-scripts` will use that instead of it's own internal config. In addition, `cod-scripts`
-exposes its configuration so you can use it and override only the parts of the config you need to.
+Unlike `react-scripts`, `cod-scripts` allows you to specify your own
+configuration for things and have that plug directly into the way things work
+with `cod-scripts`. There are various ways that it works, but basically if you
+want to have your own config for something, just add the configuration and
+`cod-scripts` will use that instead of it's own internal config. In addition,
+`cod-scripts` exposes its configuration so you can use it and override only the
+parts of the config you need to.
-This can be a very helpful way to make editor integration work for tools like ESLint which require
-project-based ESLint configuration to be present to work.
+This can be a very helpful way to make editor integration work for tools like
+ESLint which require project-based ESLint configuration to be present to work.
-So, if we were to do this for ESLint, you could create an `.eslintrc` with the contents of:
+So, if we were to do this for ESLint, you could create an `.eslintrc` with the
+contents of:
```
{"extends": "./node_modules/cod-scripts/eslint.js"}
@@ -91,7 +99,8 @@ module.exports = Object.assign(jestConfig, {
});
```
-Or, for `commitlint`, a `commitlint.config.js` file or `commitlint` prop in package.json:
+Or, for `commitlint`, a `commitlint.config.js` file or `commitlint` prop in
+package.json:
```js
// commitlint.config.js or .commitlintrc.js
@@ -118,30 +127,37 @@ module.exports = {
}
```
-> Note: `cod-scripts` intentionally does not merge things for you when you start configuring things
-> to make it less magical and more straightforward. Extending can take place on your terms. I think
-> this is actually a great way to do this.
+> Note: `cod-scripts` intentionally does not merge things for you when you start
+> configuring things to make it less magical and more straightforward. Extending
+> can take place on your terms. I think this is actually a great way to do this.
+
+### TypeScript Support
-### Flow support
+If the `tsconfig.json`-file is present in the project root directory and
+`typescript` is a dependency the `@babel/preset-typescript` will automatically
+get loaded when you use the default babel config that comes with `cod-scripts`.
+If you customised your `.babelrc`-file you might need to manually add
+`@babel/preset-typescript` to the `presets`-section.
-If the `flow-bin` is a dependency on the project the `@babel/preset-flow` will automatically get
-loaded when you use the default babel config that comes with `cod-scripts`. If you customised your
-`.babelrc`-file you might need to manually add `@babel/preset-flow` to the `presets`-section.
+`cod-scripts` will automatically load any `.ts` and `.tsx` files, including the
+default entry point, so you don't have to worry about any rollup configuration.
-## Inspiration
+If you have a `typecheck` script (normally set to `kcd-scripts typecheck`) that
+will be run as part of the `validate` script (which is run as part of the
+`pre-commit` script as well).
-This was forked from `kentcdodds/kcd-scripts`. This is
-[inspired by `react-scripts`](https://github.com/kentcdodds/kcd-scripts#inspiration).
+TypeScript definition files will also automatically be generated during the
+`build` script.
## LICENSE
MIT
-[npm]: https://www.npmjs.com/
+
+[npm]: https://www.npmjs.com
[node]: https://nodejs.org
[semantic-release]: https://github.com/semantic-release/semantic-release
-[semantic-release-badge]:
- https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
+[semantic-release-badge]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
[prs]: http://makeapullrequest.com
[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square
[commitizen]: http://commitizen.github.io/cz-cli/
@@ -154,3 +170,4 @@ MIT
[license]: https://github.com/codfish/cod-scripts/blob/master/LICENSE
[actions]: https://github.com/codfish/cod-scripts/actions
[actions-badge]: https://img.shields.io/github/workflow/status/codfish/cod-scripts/Release/master
+
diff --git a/husky.js b/husky.js
new file mode 100644
index 00000000..8fa198b2
--- /dev/null
+++ b/husky.js
@@ -0,0 +1 @@
+module.exports = require('./dist/config/huskyrc');
diff --git a/other/MAINTAINING.md b/other/MAINTAINING.md
new file mode 100644
index 00000000..87d1f70b
--- /dev/null
+++ b/other/MAINTAINING.md
@@ -0,0 +1,84 @@
+# Maintaining
+
+
+
+
+**Table of Contents**
+
+- [Code of Conduct](#code-of-conduct)
+- [Issues](#issues)
+- [Pull Requests](#pull-requests)
+- [Release](#release)
+- [Thanks!](#thanks)
+
+
+
+This is documentation for maintainers of this project.
+
+## Code of Conduct
+
+Please review, understand, and be an example of it. Violations of the code of
+conduct are taken seriously, even (especially) for maintainers.
+
+## Issues
+
+We want to support and build the community. We do that best by helping people
+learn to solve their own problems. We have an issue template and hopefully most
+folks follow it. If it's not clear what the issue is, invite them to create a
+minimal reproduction of what they're trying to accomplish or the bug they think
+they've found.
+
+Once it's determined that a code change is necessary, point people to
+[makeapullrequest.com](http://makeapullrequest.com) and invite them to make a
+pull request. If they're the one who needs the feature, they're the one who can
+build it. If they need some hand holding and you have time to lend a hand,
+please do so. It's an investment into another human being, and an investment
+into a potential maintainer.
+
+Remember that this is open source, so the code is not yours, it's ours. If
+someone needs a change in the codebase, you don't have to make it happen
+yourself. Commit as much time to the project as you want/need to. Nobody can ask
+any more of you than that.
+
+## Pull Requests
+
+As a maintainer, you're fine to make your branches on the main repo or on your
+own fork. Either way is fine.
+
+When we receive a pull request, a GitHub Action is kicked off automatically (see
+the `.github/workflows/validate.yml` for what runs in the Action). We avoid
+merging anything that breaks the GitHub Action.
+
+Please review PRs and focus on the code rather than the individual. You never
+know when this is someone's first ever PR and we want their experience to be as
+positive as possible, so be uplifting and constructive.
+
+When you merge the pull request, 99% of the time you should use the
+[Squash and merge](https://help.github.com/articles/merging-a-pull-request/)
+feature. This keeps our git history clean, but more importantly, this allows us
+to make any necessary changes to the commit message so we release what we want
+to release. See the next section on Releases for more about that.
+
+## Release
+
+Our releases are automatic. They happen whenever code lands into `master`. A
+GitHub Action gets kicked off and if it's successful, a tool called
+[`semantic-release`](https://github.com/semantic-release/semantic-release) is
+used to automatically publish a new release to npm as well as a changelog to
+GitHub. It is only able to determine the version and whether a release is
+necessary by the git commit messages. With this in mind, **please brush up on
+[the commit message convention][commit] which drives our releases.**
+
+> One important note about this: Please make sure that commit messages do NOT
+> contain the words "BREAKING CHANGE" in them unless we want to push a major
+> version. I've been burned by this more than once where someone will include
+> "BREAKING CHANGE: None" and it will end up releasing a new major version. Not
+> a huge deal honestly, but kind of annoying...
+
+## Thanks!
+
+Thank you so much for helping to maintain this project!
+
+
+[commit]: https://github.com/conventional-changelog-archived-repos/conventional-changelog-angular/blob/ed32559941719a130bb0327f886d6a32a8cbc2ba/convention.md
+
diff --git a/package.json b/package.json
index 9d958761..aa4ad755 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"version": "0.0.0-semantically-released",
"description": "CLI for common scripts for my projects",
"engines": {
- "node": ">=10.13",
+ "node": ">=10.18",
"npm": ">=6",
"yarn": ">=1"
},
@@ -25,67 +25,74 @@
"commitlint.js",
"eslint.js",
"config.js",
+ "eslint.js",
+ "husky.js",
+ "jest.js",
"prettier.js",
- "jest.js"
+ "shared-tsconfig.json"
],
"keywords": [],
"author": "Chris O'Donnell (http://codfish.io/)",
"license": "MIT",
"dependencies": {
- "@babel/cli": "^7.11.6",
- "@babel/core": "^7.11.6",
- "@babel/plugin-proposal-class-properties": "^7.10.4",
- "@babel/plugin-transform-modules-commonjs": "^7.10.4",
- "@babel/plugin-transform-runtime": "^7.11.5",
- "@babel/preset-env": "^7.11.5",
- "@babel/preset-flow": "^7.10.4",
- "@babel/preset-react": "^7.10.4",
- "@babel/runtime": "^7.11.2",
- "@commitlint/cli": "^9.1.2",
+ "@babel/cli": "^7.12.8",
+ "@babel/core": "^7.12.9",
+ "@babel/plugin-proposal-class-properties": "^7.12.1",
+ "@babel/plugin-transform-modules-commonjs": "^7.12.1",
+ "@babel/plugin-transform-runtime": "^7.12.1",
+ "@babel/preset-env": "^7.12.7",
+ "@babel/preset-react": "^7.12.7",
+ "@babel/preset-typescript": "^7.12.7",
+ "@babel/runtime": "^7.12.5",
+ "@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
- "@rollup/plugin-babel": "^5.2.1",
- "@rollup/plugin-commonjs": "^15.0.0",
+ "@rollup/plugin-babel": "^5.2.2",
+ "@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-json": "^4.1.0",
- "@rollup/plugin-node-resolve": "^9.0.0",
- "@rollup/plugin-replace": "^2.3.3",
+ "@rollup/plugin-node-resolve": "^11.0.0",
+ "@rollup/plugin-replace": "^2.3.4",
+ "@types/jest": "^26.0.17",
"arrify": "^2.0.1",
- "babel-jest": "^26.3.0",
- "babel-plugin-macros": "^2.6.1",
- "babel-plugin-minify-dead-code-elimination": "^0.5.0",
+ "babel-jest": "^26.6.3",
+ "babel-plugin-macros": "^3.0.0",
+ "babel-plugin-minify-dead-code-elimination": "^0.5.1",
"babel-plugin-module-resolver": "^4.0.0",
- "babel-plugin-transform-inline-environment-variables": "^0.4.0",
- "babel-plugin-transform-react-remove-prop-types": "^0.4.21",
- "browserslist": "^4.14.2",
+ "babel-plugin-transform-inline-environment-variables": "^0.4.3",
+ "babel-plugin-transform-react-remove-prop-types": "^0.4.24",
+ "browserslist": "^4.14.7",
+ "chalk": "^4.1.0",
"concurrently": "^5.3.0",
"cosmiconfig": "^7.0.0",
- "cross-env": "^7.0.0",
+ "cross-env": "^7.0.3",
"cross-spawn": "^7.0.3",
- "doctoc": "^1.4.0",
- "eslint": "^7.8.1",
- "eslint-config-codfish": "^6.0.1",
+ "doctoc": "^2.0.0",
+ "eslint": "^7.15.0",
+ "eslint-config-codfish": "^8.2.0",
+ "eslint-config-prettier": "^7.0.0",
"glob": "^7.1.5",
- "husky": "^4.3.0",
+ "husky": "^4.3.5",
"is-ci": "^2.0.0",
- "jest": "^26.4.2",
+ "jest": "^26.6.1",
+ "jest-serializer-path": "^0.1.15",
"jest-watch-typeahead": "^0.6.1",
- "lint-staged": "^10.3.0",
+ "lint-staged": "^10.5.3",
"lodash.camelcase": "^4.3.0",
"lodash.has": "^4.5.2",
"lodash.omit": "^4.5.0",
"mkdirp": "^1.0.4",
- "prettier": "^2.1.1",
- "react-app-polyfill": "^1.0.3",
+ "prettier": "2.2.1",
+ "react-app-polyfill": "^2.0.0",
"read-pkg-up": "^7.0.1",
- "resolve": "^1.17.0",
- "rimraf": "^3.0.0",
- "rollup": "^2.26.11",
+ "resolve": "^1.19.0",
+ "rimraf": "^3.0.2",
+ "rollup": "^2.34.2",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-globals": "^1.4.0",
"rollup-plugin-size-snapshot": "^0.12.0",
"rollup-plugin-terser": "^7.0.2",
- "semver": "^7.3.2",
+ "semver": "^7.3.4",
"which": "^2.0.2",
- "yargs-parser": "^20.0.0"
+ "yargs-parser": "^20.2.4"
},
"eslintConfig": {
"extends": [
@@ -98,13 +105,10 @@
"no-useless-catch": "off",
"import/no-dynamic-require": "off",
"import/no-unassigned-import": "off",
- "no-nested-ternary": "off"
- }
- },
- "husky": {
- "hooks": {
- "pre-commit": "node src pre-commit",
- "commit-msg": "node src commitlint -E HUSKY_GIT_PARAMS"
+ "no-nested-ternary": "off",
+ "no-use-before-define": "off",
+ "no-restricted-syntax": "off",
+ "no-param-reassign": "off"
}
},
"commitlint": {
@@ -112,26 +116,17 @@
"./src/config/commitlint.config"
],
"rules": {
- "scope-enum": [
- 2,
- "always",
- [
- "eslint",
- "babel",
- "rollup",
- "commitlint",
- "jest",
- "lintstaged",
- "lint-staged",
- "husky",
- "prettier"
- ]
+ "scope-case": [
+ 0
+ ],
+ "subject-case": [
+ 0
]
}
},
"repository": {
"type": "git",
- "url": "https://github.com/codfish/cod-scripts.git"
+ "url": "https://github.com/codfish/cod-scripts"
},
"bugs": {
"url": "https://github.com/codfish/cod-scripts/issues"
diff --git a/shared-tsconfig.json b/shared-tsconfig.json
new file mode 100644
index 00000000..4211a822
--- /dev/null
+++ b/shared-tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "exclude": ["node_modules"],
+ "include": ["../../src/**/*"],
+ "compilerOptions": {
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "moduleResolution": "node",
+ "noEmit": true,
+ "strict": true,
+ "jsx": "react",
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "baseUrl": "../../src",
+ "paths": {
+ "*": ["*", "../tests/*"]
+ },
+ "preserveWatchOutput": true
+ }
+}
diff --git a/src/__tests__/__snapshots__/index.js.snap b/src/__tests__/__snapshots__/index.js.snap
index cced864d..e11a482a 100644
--- a/src/__tests__/__snapshots__/index.js.snap
+++ b/src/__tests__/__snapshots__/index.js.snap
@@ -35,6 +35,7 @@ Available Scripts:
lint
pre-commit
test
+ typecheck
validate
Options:
diff --git a/src/__tests__/__snapshots__/utils.js.snap b/src/__tests__/__snapshots__/utils.js.snap
index 3b250307..e5a9478d 100644
--- a/src/__tests__/__snapshots__/utils.js.snap
+++ b/src/__tests__/__snapshots__/utils.js.snap
@@ -8,7 +8,7 @@ Array [
"--names",
"build,lint,test,validate,a,b,c,d,e,f,g,h,i,j",
"--prefix-colors",
- "bgBlue.bold.reset,bgGreen.bold.reset,bgMagenta.bold.reset,bgCyan.bold.reset,bgWhite.bold.reset,bgRed.bold.reset,bgBlack.bold.reset,bgYellow.bold.reset,bgBlue.bold.reset,bgGreen.bold.reset,bgMagenta.bold.reset,bgCyan.bold.reset,bgWhite.bold.reset,bgRed.bold.reset",
+ "bgBlue.bold.white,bgGreen.bold.white,bgMagenta.bold.white,bgCyan.bold.white,bgWhite.bold.white,bgRed.bold.white,bgBlack.bold.white,bgYellow.bold.white,bgBlue.bold.white,bgGreen.bold.white,bgMagenta.bold.white,bgCyan.bold.white,bgWhite.bold.white,bgRed.bold.white",
"\\"echo build\\"",
"\\"echo lint\\"",
"\\"echo test\\"",
diff --git a/src/config/babelrc.js b/src/config/babelrc.js
index 71dcd7b0..1be78a6b 100644
--- a/src/config/babelrc.js
+++ b/src/config/babelrc.js
@@ -1,7 +1,7 @@
const browserslist = require('browserslist');
const semver = require('semver');
-const { ifAnyDep, parseEnv, appDirectory, pkg } = require('../utils');
+const { ifDep, ifAnyDep, ifTypescript, parseEnv, appDirectory, pkg } = require('../utils');
const { BABEL_ENV, NODE_ENV, BUILD_FORMAT } = process.env;
const isTest = (BABEL_ENV || NODE_ENV) === 'test';
@@ -39,14 +39,13 @@ function getNodeVersion({ engines: { node: nodeVersion = '10.13' } = {} }) {
}
/**
- * Use the strategy declared by browserslist to load browsers configuration.
+ * use the strategy declared by browserslist to load browsers configuration.
* fallback to the default if don't found custom configuration
*
* @see https://github.com/browserslist/browserslist/blob/master/node.js#L139
*/
const browsersConfig = browserslist.loadConfig({ path: appDirectory }) || ['ie 10', 'ios 7'];
-// eslint-disable-next-line
const envTargets = isTest
? { node: 'current' }
: isWebpack || isRollup
@@ -59,9 +58,12 @@ module.exports = () => ({
[require.resolve('@babel/preset-env'), envOptions],
ifAnyDep(
['react', 'preact'],
- [require.resolve('@babel/preset-react'), { pragma: isPreact ? 'React.h' : undefined }],
+ [
+ require.resolve('@babel/preset-react'),
+ { pragma: isPreact ? ifDep('react', 'React.h', 'h') : undefined },
+ ],
),
- ifAnyDep(['flow-bin'], [require.resolve('@babel/preset-flow')]),
+ ifTypescript([require.resolve('@babel/preset-typescript')]),
].filter(Boolean),
plugins: [
[require.resolve('@babel/plugin-transform-runtime'), { useESModules: treeshake && !isCJS }],
diff --git a/src/config/huskyrc.js b/src/config/huskyrc.js
new file mode 100644
index 00000000..0afde656
--- /dev/null
+++ b/src/config/huskyrc.js
@@ -0,0 +1,10 @@
+const { resolveCodScripts } = require('../utils');
+
+const codScripts = resolveCodScripts();
+
+module.exports = {
+ hooks: {
+ 'pre-commit': `"${codScripts}" pre-commit`,
+ 'commit-msg': `"${codScripts}" commitlint -E HUSKY_GIT_PARAMS`,
+ },
+};
diff --git a/src/config/index.js b/src/config/index.js
index d9b7d4a3..5ae6f9c5 100644
--- a/src/config/index.js
+++ b/src/config/index.js
@@ -2,6 +2,7 @@ module.exports = {
babel: require('./babelrc'),
commitlint: require('./commitlint.config'),
eslint: require('./eslintrc'),
+ husky: require('./huskyrc'),
jest: require('./jest.config'),
lintStaged: require('./lintstagedrc'),
prettier: require('./prettierrc'),
diff --git a/src/config/jest.config.js b/src/config/jest.config.js
index aac38a79..8a140091 100644
--- a/src/config/jest.config.js
+++ b/src/config/jest.config.js
@@ -15,13 +15,12 @@ const ignores = [
'__mocks__',
];
-const hasSrcDir = hasFile('src');
-
const jestConfig = {
- roots: [fromRoot(hasSrcDir ? 'src' : '')],
+ roots: [fromRoot(hasFile('src') ? 'src' : '')],
testEnvironment: ifAnyDep(['webpack', 'rollup', 'react'], 'jsdom', 'node'),
testURL: 'http://localhost',
moduleFileExtensions: ['js', 'jsx', 'json', 'ts', 'tsx'],
+ moduleDirectories: ['node_modules', fromRoot('src'), 'shared', fromRoot('tests')],
collectCoverageFrom: ['src/**/*.+(js|jsx|ts|tsx)'],
testMatch: ['**/__tests__/**/*.+(js|jsx|ts|tsx)', '**/?(*.)+(spec|test).[jt]s?(x)'],
testPathIgnorePatterns: [...ignores],
@@ -39,16 +38,24 @@ const jestConfig = {
require.resolve('jest-watch-typeahead/filename'),
require.resolve('jest-watch-typeahead/testname'),
],
- setupFilesAfterEnv: [
- ifAnyDep('@testing-library/jest-dom', '@testing-library/jest-dom/extend-expect'),
- ifFile('jest.setup.js', fromRoot('jest.setup.js')),
- ifFile('setupTests.js', fromRoot('setupTests.js')),
- ifFile('tests/setup-env.js', fromRoot('tests/setup-env.js')),
- ].filter(Boolean),
};
+const setupFilesAfterEnv = [
+ ifAnyDep('@testing-library/jest-dom', '@testing-library/jest-dom/extend-expect'),
+ ifFile('jest.setup.js', fromRoot('jest.setup.js')),
+ ifFile('setupTests.js', fromRoot('setupTests.js')),
+ ifFile('setupTests.js', fromRoot('setupTests.js')),
+ ifFile('tests/setup-env.js', fromRoot('tests/setup-env.js')),
+ ifFile('tests/setup-env.ts', fromRoot('tests/setup-env.ts')),
+ ifFile('tests/setup-env.tsx', fromRoot('tests/setup-env.tsx')),
+].filter(Boolean);
+
+if (setupFilesAfterEnv.length) {
+ jestConfig.setupFilesAfterEnv = setupFilesAfterEnv;
+}
+
if (useBuiltInBabelConfig) {
- jestConfig.transform = { '^.+\\.js$': here('./babel-transform') };
+ jestConfig.transform = { '^.+\\.(js|jsx|ts|tsx)$': here('./babel-transform') };
}
if (jestConfig.testEnvironment === 'jsdom') {
diff --git a/src/config/prettierrc.js b/src/config/prettierrc.js
index 309694a5..b1bb8359 100644
--- a/src/config/prettierrc.js
+++ b/src/config/prettierrc.js
@@ -4,6 +4,8 @@ module.exports = {
useTabs: false,
semi: true,
arrowParens: 'avoid',
+ bracketSpacing: true,
+ embeddedLanguageFormatting: 'auto',
endOfLine: 'lf',
htmlWhitespaceSensitivity: 'css',
insertPragma: false,
@@ -12,7 +14,6 @@ module.exports = {
requirePragma: false,
singleQuote: true,
trailingComma: 'all',
- bracketSpacing: true,
jsxBracketSameLine: false,
proseWrap: 'always',
vueIndentScriptAndStyle: false,
diff --git a/src/index.js b/src/index.js
index dc17c418..5a8ba7f1 100755
--- a/src/index.js
+++ b/src/index.js
@@ -1,20 +1,19 @@
#!/usr/bin/env node
-
-let shouldThrow;
+let shouldThrow = false;
try {
+ const [major, minor] = process.version.slice(1).split('.').map(Number);
shouldThrow =
// eslint-disable-next-line global-require,import/no-dynamic-require
require(`${process.cwd()}/package.json`).name === 'cod-scripts' &&
- Number(process.version.slice(1).split('.')[0]) < 8;
+ (major < 10 || (major === 10 && minor < 18));
} catch (error) {
// ignore
}
if (shouldThrow) {
throw new Error(
- 'You must use Node version 8 or greater to run the scripts within cod-scripts ' +
- 'because we dogfood the untranspiled version of the scripts.',
+ 'You must use Node version 10.18 or greater to run the scripts within cod-scripts, because we dogfood the untranspiled version of the scripts.',
);
}
diff --git a/src/run-script.js b/src/run-script.js
index e0801405..f9d657a4 100755
--- a/src/run-script.js
+++ b/src/run-script.js
@@ -4,6 +4,38 @@ const glob = require('glob');
const [executor, ignoredBin, script] = process.argv;
+if (script && script !== '--help' && script !== 'help') {
+ spawnScript();
+} else {
+ const scriptsPath = path.join(__dirname, 'scripts/');
+ const scriptsAvailable = glob.sync(path.join(__dirname, 'scripts', '*'));
+ // `glob.sync` returns paths with unix style path separators even on Windows.
+ // So we normalize it before attempting to strip out the scripts path.
+ const scriptsAvailableMessage = scriptsAvailable
+ .map(path.normalize)
+ .map(s =>
+ s
+ .replace(scriptsPath, '')
+ .replace(/__tests__/, '')
+ .replace(/\.js$/, ''),
+ )
+ .filter(Boolean)
+ .join('\n ')
+ .trim();
+ const fullMessage = `
+Usage: ${ignoredBin} [script] [--flags]
+
+Available Scripts:
+ ${scriptsAvailableMessage}
+
+Options:
+ All options depend on the script. Docs will be improved eventually, but for most scripts you can assume that the args you pass will be forwarded to the respective tool that's being run under the hood.
+
+May the force be with you.
+ `.trim();
+ console.log(`\n${fullMessage}\n`);
+}
+
function getEnv() {
// this is required to address an issue in cross-spawn
// https://github.com/kentcdodds/kcd-scripts/issues/4
@@ -42,15 +74,17 @@ function attemptResolve(...resolveArgs) {
function spawnScript() {
// get all the arguments of the script and find the position of our script commands
const args = process.argv.slice(2);
- const scriptIndex = args.findIndex(
- x =>
- x === 'format' ||
- x === 'lint' ||
- x === 'commitlint' ||
- x === 'pre-commit' ||
- x === 'test' ||
- x === 'validate' ||
- x === 'build',
+ const scriptIndex = args.findIndex(x =>
+ [
+ 'build',
+ 'format',
+ 'lint',
+ 'pre-commit',
+ 'test',
+ 'validate',
+ 'commitlint',
+ 'typecheck',
+ ].includes(x),
);
// Extract the node arguments so we can pass them to node later on
@@ -83,35 +117,3 @@ function spawnScript() {
process.exit(result.status);
}
}
-
-if (script) {
- spawnScript();
-} else {
- const scriptsPath = path.join(__dirname, 'scripts/');
- const scriptsAvailable = glob.sync(path.join(__dirname, 'scripts', '*'));
- // `glob.sync` returns paths with unix style path separators even on Windows.
- // So we normalize it before attempting to strip out the scripts path.
- const scriptsAvailableMessage = scriptsAvailable
- .map(path.normalize)
- .map(s =>
- s
- .replace(scriptsPath, '')
- .replace(/__tests__/, '')
- .replace(/\.js$/, ''),
- )
- .filter(Boolean)
- .join('\n ')
- .trim();
- const fullMessage = `
-Usage: ${ignoredBin} [script] [--flags]
-
-Available Scripts:
- ${scriptsAvailableMessage}
-
-Options:
- All options depend on the script. Docs will be improved eventually, but for most scripts you can assume that the args you pass will be forwarded to the respective tool that's being run under the hood.
-
-May the force be with you.
- `.trim();
- console.log(`\n${fullMessage}\n`);
-}
diff --git a/src/scripts/__tests__/__snapshots__/lint.js.snap b/src/scripts/__tests__/__snapshots__/lint.js.snap
index 211f9d07..03ae938d 100644
--- a/src/scripts/__tests__/__snapshots__/lint.js.snap
+++ b/src/scripts/__tests__/__snapshots__/lint.js.snap
@@ -1,21 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`lint --no-cache will disable caching 1`] = `eslint --config ./src/config/eslintrc.js --ignore-path ./src/config/eslintignore --no-cache .`;
+exports[`lint --no-cache will disable caching 1`] = `eslint --config ./src/config/eslintrc.js --ext js,ts,tsx --ignore-path ./src/config/eslintignore --no-cache .`;
-exports[`lint calls eslint CLI with default args 1`] = `eslint --config ./src/config/eslintrc.js --ignore-path ./src/config/eslintignore --cache --cache-location /node_modules/.cache/.eslintcache .`;
+exports[`lint calls eslint CLI with default args 1`] = `eslint --config ./src/config/eslintrc.js --ext js,ts,tsx --ignore-path ./src/config/eslintignore --cache --cache-location /node_modules/.cache/.eslintcache .`;
-exports[`lint does not use built-in config with .eslintrc file 1`] = `eslint --ignore-path ./src/config/eslintignore --cache --cache-location /node_modules/.cache/.eslintcache .`;
+exports[`lint does not use built-in config with .eslintrc file 1`] = `eslint --ext js,ts,tsx --ignore-path ./src/config/eslintignore --cache --cache-location /node_modules/.cache/.eslintcache .`;
-exports[`lint does not use built-in config with .eslintrc.js file 1`] = `eslint --ignore-path ./src/config/eslintignore --cache --cache-location /node_modules/.cache/.eslintcache .`;
+exports[`lint does not use built-in config with .eslintrc.js file 1`] = `eslint --ext js,ts,tsx --ignore-path ./src/config/eslintignore --cache --cache-location /node_modules/.cache/.eslintcache .`;
-exports[`lint does not use built-in config with --config 1`] = `eslint --ignore-path ./src/config/eslintignore --cache --cache-location /node_modules/.cache/.eslintcache --config ./custom-config.js .`;
+exports[`lint does not use built-in config with --config 1`] = `eslint --ext js,ts,tsx --ignore-path ./src/config/eslintignore --cache --cache-location /node_modules/.cache/.eslintcache --config ./custom-config.js .`;
-exports[`lint does not use built-in config with eslintConfig pkg prop 1`] = `eslint --ignore-path ./src/config/eslintignore --cache --cache-location /node_modules/.cache/.eslintcache .`;
+exports[`lint does not use built-in config with eslintConfig pkg prop 1`] = `eslint --ext js,ts,tsx --ignore-path ./src/config/eslintignore --cache --cache-location /node_modules/.cache/.eslintcache .`;
-exports[`lint does not use built-in ignore with .eslintignore file 1`] = `eslint --config ./src/config/eslintrc.js --cache --cache-location /node_modules/.cache/.eslintcache .`;
+exports[`lint does not use built-in ignore with .eslintignore file 1`] = `eslint --config ./src/config/eslintrc.js --ext js,ts,tsx --cache --cache-location /node_modules/.cache/.eslintcache .`;
-exports[`lint does not use built-in ignore with --ignore-path 1`] = `eslint --config ./src/config/eslintrc.js --cache --cache-location /node_modules/.cache/.eslintcache --ignore-path ./my-ignore .`;
+exports[`lint does not use built-in ignore with --ignore-path 1`] = `eslint --config ./src/config/eslintrc.js --ext js,ts,tsx --cache --cache-location /node_modules/.cache/.eslintcache --ignore-path ./my-ignore .`;
-exports[`lint does not use built-in ignore with eslintIgnore pkg prop 1`] = `eslint --config ./src/config/eslintrc.js --cache --cache-location /node_modules/.cache/.eslintcache .`;
+exports[`lint does not use built-in ignore with eslintIgnore pkg prop 1`] = `eslint --config ./src/config/eslintrc.js --ext js,ts,tsx --cache --cache-location /node_modules/.cache/.eslintcache .`;
-exports[`lint runs on given files, but only js files 1`] = `eslint --config ./src/config/eslintrc.js --ignore-path ./src/config/eslintignore --cache --cache-location /node_modules/.cache/.eslintcache ./src/index.js ./src/component.js`;
+exports[`lint runs on given files, but only js files 1`] = `eslint --config ./src/config/eslintrc.js --ext js,ts,tsx --ignore-path ./src/config/eslintignore --cache --cache-location /node_modules/.cache/.eslintcache ./src/index.js ./src/thing.ts ./src/lib.tsx ./src/component.js`;
+
+exports[`lint supports custom --ext 1`] = `eslint --config ./src/config/eslintrc.js --ignore-path ./src/config/eslintignore --cache --cache-location /node_modules/.cache/.eslintcache --ext js .`;
diff --git a/src/scripts/__tests__/__snapshots__/validate.js.snap b/src/scripts/__tests__/__snapshots__/validate.js.snap
index eb051cbd..bebec96f 100644
--- a/src/scripts/__tests__/__snapshots__/validate.js.snap
+++ b/src/scripts/__tests__/__snapshots__/validate.js.snap
@@ -1,15 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`validate allows you to specify your own npm scripts 1`] = `concurrently --kill-others-on-fail --prefix [{name}] --names specialbuild,specialtest,speciallint --prefix-colors bgBlue.bold.reset,bgGreen.bold.reset,bgMagenta.bold.reset "npm run specialbuild --silent" "npm run specialtest --silent" "npm run speciallint --silent"`;
+exports[`validate allows you to specify your own npm scripts 1`] = `concurrently --kill-others-on-fail --prefix [{name}] --names specialbuild,specialtest,speciallint --prefix-colors bgBlue.bold.white,bgGreen.bold.white,bgMagenta.bold.white "npm run specialbuild --silent" "npm run specialtest --silent" "npm run speciallint --silent"`;
-exports[`validate calls concurrently with all scripts 1`] = `concurrently --kill-others-on-fail --prefix [{name}] --names build,lint,test,flow --prefix-colors bgBlue.bold.reset,bgGreen.bold.reset,bgMagenta.bold.reset,bgCyan.bold.reset "npm run build --silent" "npm run lint --silent" "npm run test --silent -- --coverage" "npm run flow --silent"`;
+exports[`validate calls concurrently with all scripts 1`] = `concurrently --kill-others-on-fail --prefix [{name}] --names build,lint,test,typecheck --prefix-colors bgBlue.bold.white,bgGreen.bold.white,bgMagenta.bold.white,bgCyan.bold.white "npm run build --silent" "npm run lint --silent" "npm run test --silent -- --coverage" "npm run typecheck --silent"`;
-exports[`validate does not include "build" if it doesn't have that script 1`] = `concurrently --kill-others-on-fail --prefix [{name}] --names lint,test,flow --prefix-colors bgBlue.bold.reset,bgGreen.bold.reset,bgMagenta.bold.reset "npm run lint --silent" "npm run test --silent -- --coverage" "npm run flow --silent"`;
+exports[`validate does not include "build" if it doesn't have that script 1`] = `concurrently --kill-others-on-fail --prefix [{name}] --names lint,test,typecheck --prefix-colors bgBlue.bold.white,bgGreen.bold.white,bgMagenta.bold.white "npm run lint --silent" "npm run test --silent -- --coverage" "npm run typecheck --silent"`;
-exports[`validate does not include "flow" if it doesn't have that script 1`] = `concurrently --kill-others-on-fail --prefix [{name}] --names build,lint,test --prefix-colors bgBlue.bold.reset,bgGreen.bold.reset,bgMagenta.bold.reset "npm run build --silent" "npm run lint --silent" "npm run test --silent -- --coverage"`;
+exports[`validate does not include "lint" if it doesn't have that script 1`] = `concurrently --kill-others-on-fail --prefix [{name}] --names build,test,typecheck --prefix-colors bgBlue.bold.white,bgGreen.bold.white,bgMagenta.bold.white "npm run build --silent" "npm run test --silent -- --coverage" "npm run typecheck --silent"`;
-exports[`validate does not include "lint" if it doesn't have that script 1`] = `concurrently --kill-others-on-fail --prefix [{name}] --names build,test,flow --prefix-colors bgBlue.bold.reset,bgGreen.bold.reset,bgMagenta.bold.reset "npm run build --silent" "npm run test --silent -- --coverage" "npm run flow --silent"`;
+exports[`validate does not include "test" if it doesn't have that script 1`] = `concurrently --kill-others-on-fail --prefix [{name}] --names build,lint,typecheck --prefix-colors bgBlue.bold.white,bgGreen.bold.white,bgMagenta.bold.white "npm run build --silent" "npm run lint --silent" "npm run typecheck --silent"`;
-exports[`validate does not include "test" if it doesn't have that script 1`] = `concurrently --kill-others-on-fail --prefix [{name}] --names build,lint,flow --prefix-colors bgBlue.bold.reset,bgGreen.bold.reset,bgMagenta.bold.reset "npm run build --silent" "npm run lint --silent" "npm run flow --silent"`;
+exports[`validate does not include "typecheck" if it doesn't have that script 1`] = `concurrently --kill-others-on-fail --prefix [{name}] --names build,lint,test --prefix-colors bgBlue.bold.white,bgGreen.bold.white,bgMagenta.bold.white "npm run build --silent" "npm run lint --silent" "npm run test --silent -- --coverage"`;
-exports[`validate doesn't use test or lint if it's in pre-commit 1`] = `concurrently --kill-others-on-fail --prefix [{name}] --names build,flow --prefix-colors bgBlue.bold.reset,bgGreen.bold.reset "npm run build --silent" "npm run flow --silent"`;
+exports[`validate doesn't use test or lint if it's in pre-commit 1`] = `concurrently --kill-others-on-fail --prefix [{name}] --names build,typecheck --prefix-colors bgBlue.bold.white,bgGreen.bold.white "npm run build --silent" "npm run typecheck --silent"`;
+
+exports[`validate exits if there are no scripts to be run 1`] = ``;
diff --git a/src/scripts/__tests__/lint.js b/src/scripts/__tests__/lint.js
index c347a605..3a3094d0 100644
--- a/src/scripts/__tests__/lint.js
+++ b/src/scripts/__tests__/lint.js
@@ -75,8 +75,18 @@ cases(
'--no-cache will disable caching': {
args: ['--no-cache'],
},
+ 'supports custom --ext': {
+ args: ['--ext', 'js'],
+ },
'runs on given files, but only js files': {
- args: ['./src/index.js', './package.json', './src/index.css', './src/component.js'],
+ args: [
+ './src/index.js',
+ './package.json',
+ './src/index.css',
+ './src/thing.ts',
+ './src/lib.tsx',
+ './src/component.js',
+ ],
},
},
);
diff --git a/src/scripts/__tests__/validate.js b/src/scripts/__tests__/validate.js
index 52727382..4e7b50df 100644
--- a/src/scripts/__tests__/validate.js
+++ b/src/scripts/__tests__/validate.js
@@ -3,7 +3,7 @@ import { unquoteSerializer } from './helpers/serializers';
expect.addSnapshotSerializer(unquoteSerializer);
-function setupWithScripts(scripts = ['test', 'lint', 'build', 'flow']) {
+function setupWithScripts(scripts = ['test', 'lint', 'build', 'typecheck']) {
return function setup() {
const utils = require('../../utils');
const originalIfScript = utils.ifScript;
@@ -56,9 +56,8 @@ cases(
try {
// tests
require('../validate');
- expect(crossSpawnSyncMock).toHaveBeenCalledTimes(1);
const [firstCall] = crossSpawnSyncMock.mock.calls;
- const [script, calledArgs] = firstCall;
+ const [script, calledArgs] = firstCall || ['', []];
expect([script, ...calledArgs].join(' ')).toMatchSnapshot();
} catch (error) {
throw error;
@@ -75,15 +74,15 @@ cases(
setup: withDefaultSetup(setupWithScripts()),
},
[`does not include "lint" if it doesn't have that script`]: {
- setup: withDefaultSetup(setupWithScripts(['test', 'build', 'flow'])),
+ setup: withDefaultSetup(setupWithScripts(['test', 'build', 'typecheck'])),
},
[`does not include "test" if it doesn't have that script`]: {
- setup: withDefaultSetup(setupWithScripts(['lint', 'build', 'flow'])),
+ setup: withDefaultSetup(setupWithScripts(['lint', 'build', 'typecheck'])),
},
[`does not include "build" if it doesn't have that script`]: {
- setup: withDefaultSetup(setupWithScripts(['test', 'lint', 'flow'])),
+ setup: withDefaultSetup(setupWithScripts(['test', 'lint', 'typecheck'])),
},
- [`does not include "flow" if it doesn't have that script`]: {
+ [`does not include "typecheck" if it doesn't have that script`]: {
setup: withDefaultSetup(setupWithScripts(['test', 'build', 'lint'])),
},
'allows you to specify your own npm scripts': {
@@ -98,5 +97,8 @@ cases(
};
}),
},
+ 'exits if there are no scripts to be run': {
+ setup: withDefaultSetup(setupWithScripts([])),
+ },
},
);
diff --git a/src/scripts/build/babel.js b/src/scripts/build/babel.js
index 4a162c12..856802d8 100644
--- a/src/scripts/build/babel.js
+++ b/src/scripts/build/babel.js
@@ -1,9 +1,17 @@
const path = require('path');
+const { DEFAULT_EXTENSIONS } = require('@babel/core');
const spawn = require('cross-spawn');
const yargsParser = require('yargs-parser');
const rimraf = require('rimraf');
const glob = require('glob');
-const { hasPkgProp, fromRoot, resolveBin, hasFile } = require('../../utils');
+const {
+ hasPkgProp,
+ fromRoot,
+ resolveBin,
+ hasFile,
+ hasTypescript,
+ generateTypeDefs,
+} = require('../../utils');
let args = process.argv.slice(2);
const here = p => path.join(__dirname, p);
@@ -18,6 +26,11 @@ const useBuiltinConfig =
!hasPkgProp('babel');
const config = useBuiltinConfig ? ['--presets', here('../../config/babelrc.js')] : [];
+const extensions =
+ args.includes('--extensions') || args.includes('--x')
+ ? []
+ : ['--extensions', [...DEFAULT_EXTENSIONS, '.ts', '.tsx']];
+
const builtInIgnore = '**/__tests__/**,**/__mocks__/**';
const ignore = args.includes('--ignore') ? [] : ['--ignore', builtInIgnore];
@@ -34,20 +47,35 @@ if (!useSpecifiedOutDir && !args.includes('--no-clean')) {
args = args.filter(a => a !== '--no-clean');
}
-const result = spawn.sync(
- resolveBin('@babel/cli', { executable: 'babel' }),
- [...outDir, ...copyFiles, ...ignore, ...config, 'src'].concat(args),
- { stdio: 'inherit' },
-);
-
-// because babel will copy even ignored files, we need to remove the ignored files
-const pathToOutDir = fromRoot(parsedArgs.outDir || builtInOutDir);
-const ignoredPatterns = (parsedArgs.ignore || builtInIgnore)
- .split(',')
- .map(pattern => path.join(pathToOutDir, pattern));
-const ignoredFiles = ignoredPatterns.reduce((all, pattern) => [...all, ...glob.sync(pattern)], []);
-ignoredFiles.forEach(ignoredFile => {
- rimraf.sync(ignoredFile);
-});
-
-process.exit(result.status);
+function go() {
+ let result = spawn.sync(
+ resolveBin('@babel/cli', { executable: 'babel' }),
+ [...outDir, ...copyFiles, ...ignore, ...extensions, ...config, 'src'].concat(args),
+ { stdio: 'inherit' },
+ );
+ if (result.status !== 0) return result.status;
+
+ if (hasTypescript && !args.includes('--no-ts-defs')) {
+ console.log('Generating TypeScript definitions');
+ result = generateTypeDefs();
+ console.log('TypeScript definitions generated');
+ if (result.status !== 0) return result.status;
+ }
+
+ // because babel will copy even ignored files, we need to remove the ignored files
+ const pathToOutDir = fromRoot(parsedArgs.outDir || builtInOutDir);
+ const ignoredPatterns = (parsedArgs.ignore || builtInIgnore)
+ .split(',')
+ .map(pattern => path.join(pathToOutDir, pattern));
+ const ignoredFiles = ignoredPatterns.reduce(
+ (all, pattern) => [...all, ...glob.sync(pattern)],
+ [],
+ );
+ ignoredFiles.forEach(ignoredFile => {
+ rimraf.sync(ignoredFile);
+ });
+
+ return result.status;
+}
+
+process.exit(go());
diff --git a/src/scripts/build/rollup.js b/src/scripts/build/rollup.js
index 29617dcb..c55dcf9d 100644
--- a/src/scripts/build/rollup.js
+++ b/src/scripts/build/rollup.js
@@ -1,4 +1,5 @@
const path = require('path');
+const fs = require('fs');
const spawn = require('cross-spawn');
const glob = require('glob');
const rimraf = require('rimraf');
@@ -9,6 +10,8 @@ const {
fromRoot,
getConcurrentlyArgs,
writeExtraEntry,
+ hasTypescript,
+ generateTypeDefs,
} = require('../../utils');
const crossEnv = resolveBin('cross-env');
@@ -38,50 +41,13 @@ if (typeof parsedArgs.bundle === 'string') {
const defaultEnv = 'BUILD_ROLLUP=true';
-function prefixKeys(prefix, object) {
- return Object.entries(object).reduce((cmds, [key, value]) => {
- return { ...cmds, [`${prefix}${key}`]: value };
- }, {});
-}
-
const getCommand = (env, ...flags) =>
[crossEnv, defaultEnv, env, rollup, config, environment, watch, ...flags]
.filter(Boolean)
.join(' ');
-function getCommands({ preact = false } = {}) {
- return formats.reduce((cmds, format) => {
- const [formatName, minify = false] = format.split('.');
- const nodeEnv = minify ? 'production' : 'development';
- const sourceMap = formatName === 'umd' ? '--sourcemap' : '';
- const buildMinify = Boolean(minify);
-
- return {
- ...cmds,
- [format]: getCommand(
- [
- `BUILD_FORMAT=${formatName}`,
- `BUILD_MINIFY=${buildMinify}`,
- `NODE_ENV=${nodeEnv}`,
- `BUILD_PREACT=${preact}`,
- `BUILD_SIZE_SNAPSHOT=${sizeSnapshot}`,
- `BUILD_NODE=${process.env.BUILD_NODE || false}`,
- `BUILD_REACT_NATIVE=${process.env.BUILD_REACT_NATIVE || false}`,
- ].join(' '),
- sourceMap,
- ),
- };
- }, {});
-}
-
-function getPReactScripts() {
- const reactCommands = prefixKeys('react.', getCommands());
- const preactCommands = prefixKeys('preact.', getCommands({ preact: true }));
- return getConcurrentlyArgs(Object.assign(reactCommands, preactCommands));
-}
-
const buildPreact = args.includes('--p-react');
-const scripts = buildPreact ? getPReactScripts() : getConcurrentlyArgs(getCommands());
+const scripts = getConcurrentlyArgs(buildPreact ? getPReactCommands() : getCommands());
const cleanBuildDirs = !args.includes('--no-clean');
@@ -93,19 +59,86 @@ if (cleanBuildDirs) {
}
}
-const result = spawn.sync(resolveBin('concurrently'), scripts, {
- stdio: 'inherit',
-});
-
-if (result.status === 0 && buildPreact && !args.includes('--no-package-json')) {
- writeExtraEntry(
- 'preact',
- {
- cjs: glob.sync(fromRoot('preact/**/*.cjs.js'))[0],
- esm: glob.sync(fromRoot('preact/**/*.esm.js'))[0],
- },
- false,
- );
+function go() {
+ let result = spawn.sync(resolveBin('concurrently'), scripts, {
+ stdio: 'inherit',
+ });
+
+ if (result.status !== 0) return result.status;
+
+ if (buildPreact && !args.includes('--no-package-json')) {
+ writeExtraEntry(
+ 'preact',
+ {
+ cjs: glob.sync(fromRoot('preact/**/*.cjs.js'))[0],
+ esm: glob.sync(fromRoot('preact/**/*.esm.js'))[0],
+ },
+ false,
+ );
+ }
+
+ if (hasTypescript && !args.includes('--no-ts-defs')) {
+ console.log('Generating TypeScript definitions');
+ result = generateTypeDefs();
+ if (result.status !== 0) return result.status;
+
+ for (const format of formats) {
+ const [formatFile] = glob.sync(fromRoot(`dist/*.${format}.js`));
+ const { name } = path.parse(formatFile);
+ // make a .d.ts file for every generated file that re-exports index.d.ts
+ fs.writeFileSync(fromRoot('dist', `${name}.d.ts`), 'export * from ".";\n');
+ }
+
+ // because typescript generates type defs for ignored files, we need to
+ // remove the ignored files
+ const ignoredFiles = [
+ ...glob.sync(fromRoot('dist', '**/__tests__/**')),
+ ...glob.sync(fromRoot('dist', '**/__mocks__/**')),
+ ];
+ ignoredFiles.forEach(ignoredFile => {
+ rimraf.sync(ignoredFile);
+ });
+ console.log('TypeScript definitions generated');
+ }
+
+ return result.status;
+}
+
+function getPReactCommands() {
+ return {
+ ...prefixKeys('react.', getCommands()),
+ ...prefixKeys('preact.', getCommands({ preact: true })),
+ };
+}
+
+function prefixKeys(prefix, object) {
+ return Object.entries(object).reduce((cmds, [key, value]) => {
+ cmds[`${prefix}${key}`] = value;
+ return cmds;
+ }, {});
+}
+
+function getCommands({ preact = false } = {}) {
+ return formats.reduce((cmds, format) => {
+ const [formatName, minify = false] = format.split('.');
+ const nodeEnv = minify ? 'production' : 'development';
+ const sourceMap = formatName === 'umd' ? '--sourcemap' : '';
+ const buildMinify = Boolean(minify);
+
+ cmds[format] = getCommand(
+ [
+ `BUILD_FORMAT=${formatName}`,
+ `BUILD_MINIFY=${buildMinify}`,
+ `NODE_ENV=${nodeEnv}`,
+ `BUILD_PREACT=${preact}`,
+ `BUILD_SIZE_SNAPSHOT=${sizeSnapshot}`,
+ `BUILD_NODE=${process.env.BUILD_NODE || false}`,
+ `BUILD_REACT_NATIVE=${process.env.BUILD_REACT_NATIVE || false}`,
+ ].join(' '),
+ sourceMap,
+ );
+ return cmds;
+ }, {});
}
-process.exit(result.status);
+process.exit(go());
diff --git a/src/scripts/lint.js b/src/scripts/lint.js
index 18cb817f..bb148a91 100644
--- a/src/scripts/lint.js
+++ b/src/scripts/lint.js
@@ -16,6 +16,10 @@ const useBuiltinConfig =
const config = useBuiltinConfig ? ['--config', hereRelative('../config/eslintrc.js')] : [];
+const defaultExtensions = 'js,ts,tsx';
+const ext = args.includes('--ext') ? [] : ['--ext', defaultExtensions];
+const extensions = (parsedArgs.ext || defaultExtensions).split(',');
+
const useBuiltinIgnore =
!args.includes('--ignore-path') && !hasFile('.eslintignore') && !hasPkgProp('eslintIgnore');
@@ -32,12 +36,12 @@ if (filesGiven) {
// we need to take all the flag-less arguments (the files that should be linted)
// and filter out the ones that aren't js files. Otherwise json or css files
// may be passed through
- args = args.filter(a => !parsedArgs._.includes(a) || /\.(jsx|js)?$/.test(a));
+ args = args.filter(a => !parsedArgs._.includes(a) || extensions.some(e => a.endsWith(e)));
}
const result = spawn.sync(
resolveBin('eslint'),
- [...config, ...ignore, ...cache, ...args, ...filesToApply],
+ [...config, ...ext, ...ignore, ...cache, ...args, ...filesToApply],
{ stdio: 'inherit' },
);
diff --git a/src/scripts/pre-commit.js b/src/scripts/pre-commit.js
index f6ae85fe..9ba7545f 100644
--- a/src/scripts/pre-commit.js
+++ b/src/scripts/pre-commit.js
@@ -1,6 +1,6 @@
const path = require('path');
const spawn = require('cross-spawn');
-const { hasPkgProp, hasFile, ifScript, resolveBin } = require('../utils');
+const { hasPkgProp, hasFile, resolveBin, ifScript } = require('../utils');
const here = p => path.join(__dirname, p);
const hereRelative = p => here(p).replace(process.cwd(), '.');
@@ -15,16 +15,24 @@ const useBuiltInConfig =
const config = useBuiltInConfig ? ['--config', hereRelative('../config/lintstagedrc.js')] : [];
-const lintStagedResult = spawn.sync(resolveBin('lint-staged'), [...config, ...args], {
- stdio: 'inherit',
-});
+function go() {
+ let result;
-if (lintStagedResult.status === 0 && ifScript('validate', true)) {
- const validateResult = spawn.sync('npm', ['run', 'validate'], {
+ result = spawn.sync(resolveBin('lint-staged'), [...config, ...args], {
stdio: 'inherit',
});
- process.exit(validateResult.status);
-} else {
- process.exit(lintStagedResult.status);
+ if (result.status !== 0) return result.status;
+
+ if (ifScript('validate', true)) {
+ result = spawn.sync('npm', ['run', 'validate'], {
+ stdio: 'inherit',
+ });
+
+ return result.status;
+ }
+
+ return 0;
}
+
+process.exit(go());
diff --git a/src/scripts/typecheck.js b/src/scripts/typecheck.js
new file mode 100644
index 00000000..4f096064
--- /dev/null
+++ b/src/scripts/typecheck.js
@@ -0,0 +1,31 @@
+const spawn = require('cross-spawn');
+const yargsParser = require('yargs-parser');
+const { hasAnyDep, resolveBin, hasFile } = require('../utils');
+
+let args = process.argv.slice(2);
+const parsedArgs = yargsParser(args);
+
+if (!hasAnyDep('typescript')) {
+ throw new Error(
+ 'Cannot use the "typecheck" script in a project that does not have typescript listed as a dependency (or devDependency).',
+ );
+}
+
+if (!parsedArgs.project && !parsedArgs.build && !hasFile('tsconfig.json')) {
+ throw new Error(
+ 'Cannot use the "typecheck" script without --project or --build in a project that does not have a tsconfig.json file.',
+ );
+}
+
+// if --project is provided, we can't pass --build
+// if --build is provided, we don't need to add it
+// if --no-build is passed, we'll just trust they know what they're doing
+if (!parsedArgs.project && !parsedArgs.build && !parsedArgs.noBuild) {
+ args = ['--build', ...args];
+}
+
+const result = spawn.sync(resolveBin('typescript', { executable: 'tsc' }), args, {
+ stdio: 'inherit',
+});
+
+process.exit(result.status);
diff --git a/src/scripts/validate.js b/src/scripts/validate.js
index 5f15ecba..67941d50 100644
--- a/src/scripts/validate.js
+++ b/src/scripts/validate.js
@@ -15,14 +15,24 @@ const scripts = useDefaultScripts
build: ifScript('build', 'npm run build --silent'),
lint: preCommit ? null : ifScript('lint', 'npm run lint --silent'),
test: preCommit ? null : ifScript('test', 'npm run test --silent -- --coverage'),
- flow: ifScript('flow', 'npm run flow --silent'),
+ typecheck: ifScript('typecheck', 'npm run typecheck --silent'),
}
- : validateScripts.split(',').reduce((scriptsToRun, name) => {
- return { ...scriptsToRun, [name]: `npm run ${name} --silent` };
- }, {});
+ : validateScripts.split(',').reduce(
+ (scriptsToRun, name) => ({
+ ...scriptsToRun,
+ [name]: `npm run ${name} --silent`,
+ }),
+ {},
+ );
-const result = spawn.sync(resolveBin('concurrently'), getConcurrentlyArgs(scripts), {
- stdio: 'inherit',
-});
+const scriptCount = Object.values(scripts).filter(Boolean).length;
-process.exit(result.status);
+if (scriptCount > 0) {
+ const result = spawn.sync(resolveBin('concurrently'), getConcurrentlyArgs(scripts), {
+ stdio: 'inherit',
+ });
+
+ process.exit(result.status);
+} else {
+ process.exit(0);
+}
diff --git a/src/utils.js b/src/utils.js
index 0fba0857..78b6b75f 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -1,5 +1,6 @@
const fs = require('fs');
const path = require('path');
+const spawn = require('cross-spawn');
const rimraf = require('rimraf');
const mkdirp = require('mkdirp');
const arrify = require('arrify');
@@ -8,7 +9,7 @@ const readPkgUp = require('read-pkg-up');
const which = require('which');
const { cosmiconfigSync } = require('cosmiconfig');
-const { packageJson, path: pkgPath } = readPkgUp.sync({
+const { packageJson: pkg, path: pkgPath } = readPkgUp.sync({
cwd: fs.realpathSync(process.cwd()),
});
const appDirectory = path.dirname(pkgPath);
@@ -40,7 +41,11 @@ function resolveBin(modName, { executable = modName, cwd = process.cwd() } = {})
}
function resolveCodScripts() {
- if (packageJson.name === 'cod-scripts') {
+ if (
+ pkg.name === 'cod-scripts' ||
+ // this happens on install of husky within cod-scripts locally
+ appDirectory.includes(path.join(__dirname, '..'))
+ ) {
return require.resolve('./').replace(process.cwd(), '.');
}
return resolveBin('cod-scripts');
@@ -50,7 +55,7 @@ const fromRoot = (...p) => path.join(appDirectory, ...p);
const hasFile = (...p) => fs.existsSync(fromRoot(...p));
const ifFile = (files, t, f) => (arrify(files).some(file => hasFile(file)) ? t : f);
-const hasPkgProp = props => arrify(props).some(prop => has(packageJson, prop));
+const hasPkgProp = props => arrify(props).some(prop => has(pkg, prop));
const hasPkgSubProp = pkgProp => props => hasPkgProp(arrify(props).map(p => `${pkgProp}.${p}`));
@@ -73,6 +78,9 @@ function envIsSet(name) {
return process.env.hasOwnProperty(name) && process.env[name] && process.env[name] !== 'undefined';
}
+const hasTypescript = hasAnyDep('typescript') && hasFile('tsconfig.json');
+const ifTypescript = (t, f) => (hasTypescript ? t : f);
+
function parseEnv(name, def) {
if (envIsSet(name)) {
try {
@@ -104,7 +112,7 @@ function getConcurrentlyArgs(scripts, { killOthers = true } = {}) {
return all;
}, {});
const prefixColors = Object.keys(scripts)
- .reduce((pColors, _s, i) => pColors.concat([`${colors[i % colors.length]}.bold.reset`]), [])
+ .reduce((pColors, _s, i) => pColors.concat([`${colors[i % colors.length]}.bold.white`]), [])
.join(',');
// prettier-ignore
@@ -151,6 +159,20 @@ function hasLocalConfig(moduleName, searchOptions = {}) {
return result !== null;
}
+function generateTypeDefs() {
+ return spawn.sync(
+ resolveBin('typescript', { executable: 'tsc' }),
+ // prettier-ignore
+ [
+ '--declaration',
+ '--emitDeclarationOnly',
+ '--noEmit', 'false',
+ '--outDir', fromRoot('dist'),
+ ],
+ { stdio: 'inherit' },
+ );
+}
+
module.exports = {
appDirectory,
fromRoot,
@@ -159,6 +181,7 @@ module.exports = {
hasLocalConfig,
hasPkgProp,
hasScript,
+ hasAnyDep,
hasDep,
ifAnyDep,
ifDep,
@@ -166,10 +189,13 @@ module.exports = {
ifFile,
ifPeerDep,
ifScript,
+ hasTypescript,
+ ifTypescript,
parseEnv,
- pkg: packageJson,
+ pkg,
resolveBin,
resolveCodScripts,
uniq,
writeExtraEntry,
+ generateTypeDefs,
};