chore(merge): v2
This commit is contained in:
23
.circleci/config.yml
Normal file
23
.circleci/config.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
# https://circleci.com/docs/2.0/language-javascript/
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
working_directory: ~/typescript-starter
|
||||
steps:
|
||||
- checkout
|
||||
# Download and cache dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "package.json" }}
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- v1-dependencies-
|
||||
- run: npm install
|
||||
- save_cache:
|
||||
paths:
|
||||
- node_modules
|
||||
key: v1-dependencies-{{ checksum "package.json" }}
|
||||
- run: npm test
|
||||
- run: npx nyc report --reporter=lcov | npx codecov
|
||||
- run: npm run cov:check
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,3 +6,5 @@ src/**.js
|
||||
coverage
|
||||
.nyc_output
|
||||
*.log
|
||||
|
||||
yarn.lock
|
||||
@@ -1,14 +1,14 @@
|
||||
src
|
||||
config
|
||||
examples
|
||||
test
|
||||
tsconfig.json
|
||||
tsconfig.module.json
|
||||
tslint.json
|
||||
.travis.yml
|
||||
.github
|
||||
build/temp
|
||||
.prettierignore
|
||||
build/docs
|
||||
|
||||
**/*.spec.*
|
||||
coverage
|
||||
.nyc_output
|
||||
*.log
|
||||
examples
|
||||
|
||||
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
package.json
|
||||
@@ -1,8 +0,0 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- 8
|
||||
- 6
|
||||
- 4
|
||||
after_success:
|
||||
- yarn send-coverage
|
||||
96
.vscode/launch.json
vendored
Normal file
96
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Project",
|
||||
// we test in `build` to make cleanup fast and easy
|
||||
"cwd": "${workspaceFolder}/build",
|
||||
// Replace this with your project root. If there are multiple, you can
|
||||
// automatically run the currently visible file with: "program": ${file}"
|
||||
"program": "${workspaceFolder}/src/cli/cli.ts",
|
||||
// "args": ["--no-install"],
|
||||
"outFiles": ["${workspaceFolder}/build/main/**/*.js"],
|
||||
"skipFiles": [
|
||||
"<node_internals>/**/*.js",
|
||||
"${workspaceFolder}/node_modules/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "npm: build",
|
||||
"stopOnEntry": true,
|
||||
"smartStep": true,
|
||||
"runtimeArgs": ["--nolazy"],
|
||||
"env": {
|
||||
"TYPESCRIPT_STARTER_REPO_URL": "${workspaceFolder}"
|
||||
},
|
||||
"console": "externalTerminal"
|
||||
},
|
||||
{
|
||||
/// Usage: set appropriate breakpoints in a *.spec.ts file, then open the
|
||||
// respective *.spec.js file to run this task. Once a breakpoint is hit,
|
||||
// the debugger will open the source *.spec.ts file for debugging.
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Visible Compiled Spec",
|
||||
"program": "${workspaceFolder}/node_modules/ava/profile.js",
|
||||
"args": [
|
||||
"${file}"
|
||||
// TODO: VSCode's launch.json variable substitution
|
||||
// (https://code.visualstudio.com/docs/editor/variables-reference)
|
||||
// doesn't quite allow us to go from:
|
||||
// `./src/path/to/file.ts` to `./build/main/path/to/file.js`
|
||||
// so the user has to navigate to the compiled file manually. (Close:)
|
||||
// "${workspaceFolder}/build/main/lib/${fileBasenameNoExtension}.js"
|
||||
],
|
||||
"skipFiles": ["<node_internals>/**/*.js"],
|
||||
// Consider using `npm run watch` or `yarn watch` for faster debugging
|
||||
// "preLaunchTask": "npm: build",
|
||||
// "smartStep": true,
|
||||
"runtimeArgs": ["--nolazy"]
|
||||
},
|
||||
// --- cut here ---
|
||||
// TODO: Simpler test debugging option. Discussion:
|
||||
// https://github.com/avajs/ava/issues/1505#issuecomment-370654427
|
||||
// {
|
||||
// "type": "node",
|
||||
// "request": "launch",
|
||||
// "name": "Debug Visible Compiled Spec",
|
||||
// "program": "${file}",
|
||||
// "outFiles": ["${workspaceFolder}/build/main/**/*.js"],
|
||||
// "skipFiles": ["<node_internals>/**/*.js"],
|
||||
// // Consider using `npm run watch` or `yarn watch` for faster debugging
|
||||
// // "preLaunchTask": "npm: build",
|
||||
// // "stopOnEntry": true,
|
||||
// // "smartStep": true,
|
||||
// "runtimeArgs": ["--nolazy"],
|
||||
// "env": {
|
||||
// "AVA_DEBUG_MODE": "1"
|
||||
// }
|
||||
// }
|
||||
// CLI:
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug CLI Unit Tests",
|
||||
"program": "${workspaceFolder}/node_modules/ava/profile.js",
|
||||
"args": ["${workspaceFolder}/build/main/cli/tests/cli.unit.spec.js"],
|
||||
"skipFiles": ["<node_internals>/**/*.js"],
|
||||
// "preLaunchTask": "npm: build",
|
||||
// "smartStep": true,
|
||||
"runtimeArgs": ["--nolazy"]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug CLI Integration Tests",
|
||||
"program": "${workspaceFolder}/node_modules/ava/profile.js",
|
||||
"args": [
|
||||
"${workspaceFolder}/build/main/cli/tests/cli.integration.spec.js"
|
||||
],
|
||||
"skipFiles": ["<node_internals>/**/*.js"],
|
||||
// "preLaunchTask": "npm: build",
|
||||
// "smartStep": true,
|
||||
"runtimeArgs": ["--nolazy"]
|
||||
}
|
||||
]
|
||||
}
|
||||
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
// "typescript.implementationsCodeLens.enabled": true
|
||||
// "typescript.referencesCodeLens.enabled": true
|
||||
}
|
||||
23
CHANGELOG.md
23
CHANGELOG.md
@@ -2,6 +2,29 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
<a name="2.0.0"></a>
|
||||
# [2.0.0](https://github.com/bitjson/typescript-starter/compare/v1.4.1...v2.0.0) (2018-03-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **CLI:** automatically remove a CLI-related part of tsconfig.module.json ([a8e33af](https://github.com/bitjson/typescript-starter/commit/a8e33af))
|
||||
* **CLI:** automatically remove the CLI's dependencies ([11509cf](https://github.com/bitjson/typescript-starter/commit/11509cf))
|
||||
* **CLI:** if git config user.name/email fails, use a placeholder rather than failing ([41ac315](https://github.com/bitjson/typescript-starter/commit/41ac315))
|
||||
* **CLI:** use placeholders for email, name, and GitHub username ([abef54a](https://github.com/bitjson/typescript-starter/commit/abef54a))
|
||||
* **codecov:** prefix with npx ([c93c9db](https://github.com/bitjson/typescript-starter/commit/c93c9db))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **CLI:** begin adding extra configuration options ([ee930cc](https://github.com/bitjson/typescript-starter/commit/ee930cc))
|
||||
* **CLI:** create CLI, begin work on v2 ([76336c8](https://github.com/bitjson/typescript-starter/commit/76336c8))
|
||||
* **CLI:** don't attempt to commit if user.name/email is not set ([58688fc](https://github.com/bitjson/typescript-starter/commit/58688fc))
|
||||
* **CLI:** implement strict, immutable, and vscode options ([3bf7a7e](https://github.com/bitjson/typescript-starter/commit/3bf7a7e))
|
||||
* **CLI:** v2 ([260a7d3](https://github.com/bitjson/typescript-starter/commit/260a7d3))
|
||||
|
||||
|
||||
|
||||
<a name="1.4.1"></a>
|
||||
## [1.4.1](https://github.com/bitjson/typescript-starter/compare/v1.4.0...v1.4.1) (2017-06-27)
|
||||
|
||||
|
||||
3
README-starter.md
Normal file
3
README-starter.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# [package-name]
|
||||
|
||||
[description]
|
||||
321
README.md
321
README.md
@@ -1,71 +1,97 @@
|
||||
<img height="0" width="0" alt="Typescript Starter Dark" src="https://cloud.githubusercontent.com/assets/904007/23006840/4e2b0c6c-f3d2-11e6-8f32-11384ee0cc4b.png"><img alt="typescript-starter" src="https://cloud.githubusercontent.com/assets/904007/23006836/4c67a3b8-f3d2-11e6-8784-12f0a34284d1.png">
|
||||
|
||||
[](https://www.npmjs.com/package/typescript-starter)
|
||||
[](https://travis-ci.org/bitjson/typescript-starter)
|
||||
[](https://codecov.io/gh/bitjson/typescript-starter)
|
||||
[](https://www.npmjs.com/package/typescript-starter)
|
||||
[](https://github.com/conventional-changelog/standard-version)
|
||||
[](https://david-dm.org/bitjson/typescript-starter)
|
||||
[](https://david-dm.org/bitjson/typescript-starter?type=dev)
|
||||
[](https://github.com/bitjson/typescript-starter)
|
||||
|
||||
# typescript-starter
|
||||
|
||||
A [typescript](https://www.typescriptlang.org/) starter for building javascript libraries and projects:
|
||||
### A clean, simple [typescript](https://www.typescriptlang.org/) starter for building javascript libraries and Node.js applications.
|
||||
|
||||
* Write **standard, future javascript** – with stable es7 features – today ([stage 3](https://github.com/tc39/proposals) or [finished](https://github.com/tc39/proposals/blob/master/finished-proposals.md) features)
|
||||
* [Optionally use typescript](https://basarat.gitbooks.io/typescript/content/docs/why-typescript.html) to improve tooling, linting, and documentation generation
|
||||
* Export as a [javascript module](http://jsmodules.io/), making your work **fully tree-shakable** for consumers using [es6 imports](https://github.com/rollup/rollup/wiki/pkg.module) (like [Rollup](http://rollupjs.org/) or [Webpack 2](https://webpack.js.org/))
|
||||
<p align="center">
|
||||
<img alt="demo of the typescript-starter command-line interface" src="">
|
||||
</p>
|
||||
|
||||
## Start Now
|
||||
|
||||
Run one simple command to install and use the interactive project generator. You'll need [Node](https://nodejs.org/) `v8.9` (the current LTS release) or later.
|
||||
|
||||
```bash
|
||||
npx typescript-starter
|
||||
```
|
||||
|
||||
The interactive CLI will help you create and configure your project automatically.
|
||||
|
||||
> Since this repo includes [the CLI and it's tests](./src/cli), you'll only need to fork or clone this project if you want to contribute. If you find this project useful, please consider [leaving a star](https://github.com/bitjson/typescript-starter/stargazers) so others can find it. Thanks!
|
||||
|
||||
# Features
|
||||
|
||||
* Write **standard, future javascript** – with stable ESNext features – today ([stage 3](https://github.com/tc39/proposals) or [finished](https://github.com/tc39/proposals/blob/master/finished-proposals.md) features)
|
||||
* [Optionally use typescript](https://medium.freecodecamp.org/its-time-to-give-typescript-another-chance-2caaf7fabe61) to improve tooling, linting, and documentation generation
|
||||
* Export as a [javascript module](http://jsmodules.io/), making your work **fully tree-shakable** for consumers capable of using [es6 imports](https://github.com/rollup/rollup/wiki/pkg.module) (like [Rollup](http://rollupjs.org/), [Webpack](https://webpack.js.org/), or [Parcel](https://parceljs.org/))
|
||||
* Export type declarations to improve your downstream development experience
|
||||
* Backwards compatibility for Node.js-style (CommonJS) imports
|
||||
* Both [strict](config/tsconfig.strict.json) and [flexible](config/tsconfig.flexible.json) typescript configurations available
|
||||
* Both strict and flexible [typescript configurations](config/tsconfig.json) available
|
||||
|
||||
So we can have nice things:
|
||||
|
||||
* Generate API documentation (HTML or JSON) [without a mess of JSDoc tags](https://blog.cloudflare.com/generating-documentation-for-typescript-projects/) to maintain
|
||||
* Collocated, atomic, concurrent unit tests with [AVA](https://github.com/avajs/ava)
|
||||
* Source-mapped code coverage reports with [nyc](https://github.com/istanbuljs/nyc)
|
||||
* Configurable code coverage testing (for continuous integration)
|
||||
* Automatic linting and formatting using [TSLint](https://github.com/palantir/tslint) and [Prettier](https://prettier.io/)
|
||||
* Automatically check for known vulnerabilities in your dependencies with [`nsp`](https://github.com/nodesecurity/nsp)
|
||||
|
||||
## Get started
|
||||
## But first, a good editor
|
||||
|
||||
Before you start, consider using an [editor with good typescript support](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Editor-Support).
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) (below) is a popular option. Editors with typescript support can provide helpful autocomplete, inline documentation, and code refactoring features.
|
||||
|
||||
Also consider installing editor extensions for [TSLint](https://github.com/Microsoft/vscode-tslint) and [Prettier](https://github.com/prettier/prettier-vscode). These extensions automatically format your code each time you save, and may quickly become invaluable.
|
||||
|
||||
<p align="center">
|
||||
<img alt="Typescript Editor Support – vscode" width="600" src="https://cloud.githubusercontent.com/assets/904007/23042221/ccebd534-f465-11e6-838d-e2449899282c.png">
|
||||
</p>
|
||||
|
||||
To see how this starter can be used as a dependency in other projects, check out the [`examples`](./examples) folder. The example above is from [`examples/node-typescript`](./examples/node-typescript).
|
||||
## View usage examples
|
||||
|
||||
To see how this starter can be used as a dependency in other projects, check out the [`examples`](./examples) folder. The example in the VSCode screenshot above is from [`examples/node-typescript`](./examples/node-typescript).
|
||||
|
||||
# Developing with typescript-starter
|
||||
|
||||
## Development zen
|
||||
|
||||
To start working, run the `watch` task using [`npm`](https://docs.npmjs.com/getting-started/what-is-npm) or [`yarn`](https://yarnpkg.com/).
|
||||
|
||||
```bash
|
||||
npm run watch
|
||||
```
|
||||
|
||||
This starter includes a watch task which makes development faster and more interactive. It's particularly helpful for [TDD](https://en.wikipedia.org/wiki/Test-driven_development)/[BDD](https://en.wikipedia.org/wiki/Behavior-driven_development) workflows.
|
||||
|
||||
To start working, [install Yarn](https://yarnpkg.com/en/docs/getting-started) and run:
|
||||
|
||||
```
|
||||
yarn watch
|
||||
```
|
||||
|
||||
which will build and watch the entire project for changes (to both the library source files and test source files). As you develop, you can add tests for new functionality – which will initially fail – before developing the new functionality. Each time you save, any changes will be rebuilt and retested.
|
||||
The watch task will build and watch the entire project for changes (to both the library source files and test source files). As you develop, you can add tests for new functionality – which will initially fail – before developing the new functionality. Each time you save, any changes will be rebuilt and retested.
|
||||
|
||||
<p align="center">
|
||||
<img alt="Typescript and AVA watch task" src="https://cloud.githubusercontent.com/assets/904007/23443884/c625d562-fdff-11e6-8f26-77bf75add240.png">
|
||||
<img alt="demo of typescript-starter's watch task" src="">
|
||||
</p>
|
||||
|
||||
Since only changed files are rebuilt and retested, this workflow remains fast even for large projects.
|
||||
|
||||
## Enable stronger type checking (recommended)
|
||||
|
||||
To make getting started easier, the default `tsconfig.json` is using the `config/tsconfig.flexible` configuration. This will allow you to get started without many warnings from Typescript.
|
||||
To make getting started easier, the default `tsconfig.json` is using a very flexible configuration. This will allow you to get started without many warnings from Typescript.
|
||||
|
||||
To enable additional Typescript type checking features (a good idea for mission-critical or large projects), change the `extends` value in `tsconfig.json` to `./config/tsconfig.strict`.
|
||||
To enable additional Typescript type checking features (a good idea for mission-critical or large projects), review the commented-out lines in your [typescript compiler options](./tsconfig.json).
|
||||
|
||||
## View test coverage
|
||||
|
||||
To generate and view test coverage, run:
|
||||
|
||||
```bash
|
||||
yarn cov
|
||||
npm run cov
|
||||
```
|
||||
|
||||
This will create an HTML report of test coverage – source-mapped back to Typescript – and open it in your default browser.
|
||||
@@ -76,141 +102,250 @@ This will create an HTML report of test coverage – source-mapped back to Types
|
||||
|
||||
## Generate your API docs
|
||||
|
||||
The src folder is analyzed and documentation is automatically generated using [typedoc](https://github.com/TypeStrong/typedoc).
|
||||
The src folder is analyzed and documentation is automatically generated using [TypeDoc](https://github.com/TypeStrong/typedoc).
|
||||
|
||||
```bash
|
||||
yarn docs
|
||||
npm run docs
|
||||
```
|
||||
|
||||
This command generates API documentation for your library in HTML format and opens it in a browser.
|
||||
|
||||
Since types are tracked by Typescript, there's no need to indicate types in JSDoc format. For more information, see the [typedoc documentation](http://typedoc.org/guides/doccomments/).
|
||||
Since types are tracked by Typescript, there's no need to indicate types in JSDoc format. For more information, see the [TypeDoc documentation](http://typedoc.org/guides/doccomments/).
|
||||
|
||||
To generate and publish your documentation to [GitHub Pages](https://pages.github.com/) use the following command:
|
||||
|
||||
```bash
|
||||
yarn docs:publish
|
||||
npm run docs:publish
|
||||
```
|
||||
|
||||
Once published, your documentation should be available at the proper GitHub Pages URL for your repo. See [this repo's GitHub Pages](https://bitjson.github.io/typescript-starter/) for an example.
|
||||
Once published, your documentation should be available at the proper GitHub Pages URL for your repo. See [`typescript-starter`'s GitHub Pages](https://bitjson.github.io/typescript-starter/) for an example.
|
||||
|
||||
<p align="center">
|
||||
<img height="500" alt="typedoc documentation example" src="https://cloud.githubusercontent.com/assets/904007/22909419/085b9e38-f222-11e6-996e-c7a86390478c.png">
|
||||
<img height="500" alt="TypeDoc documentation example" src="https://cloud.githubusercontent.com/assets/904007/22909419/085b9e38-f222-11e6-996e-c7a86390478c.png">
|
||||
</p>
|
||||
|
||||
For more advanced documentation generation, you can provide your own [typedoc theme](http://typedoc.org/guides/themes/), or [build your own documentation](https://blog.cloudflare.com/generating-documentation-for-typescript-projects/) using the JSON typedoc export:
|
||||
For more advanced documentation generation, you can provide your own [TypeDoc theme](http://typedoc.org/guides/themes/), or [build your own documentation](https://blog.cloudflare.com/generating-documentation-for-typescript-projects/) using the JSON TypeDoc export:
|
||||
|
||||
```bash
|
||||
yarn docs:json
|
||||
npm run docs:json
|
||||
```
|
||||
|
||||
## Generate/update changelog & tag release
|
||||
## Update the changelog, commit, & tag release
|
||||
|
||||
This project is tooled for [Conventional Changelog](https://github.com/conventional-changelog/conventional-changelog) to make managing releases easier. See the [standard-version](https://github.com/conventional-changelog/standard-version) documentation for more information on the workflow, or [`CHANGELOG.md`](CHANGELOG.md) for an example.
|
||||
It's recommended that you install [`commitizen`](https://github.com/commitizen/cz-cli) to make commits to your project.
|
||||
|
||||
```bash
|
||||
npm install -g commitizen
|
||||
|
||||
# commit your changes:
|
||||
git cz
|
||||
```
|
||||
|
||||
This project is tooled for [conventional changelog](https://github.com/conventional-changelog/conventional-changelog) to make managing releases easier. See the [standard-version](https://github.com/conventional-changelog/standard-version) documentation for more information on the workflow, or [`CHANGELOG.md`](CHANGELOG.md) for an example.
|
||||
|
||||
```bash
|
||||
# bump package.json version, update CHANGELOG.md, git tag the release
|
||||
yarn changelog
|
||||
npm run changelog
|
||||
```
|
||||
|
||||
## One-step publish preparation script
|
||||
|
||||
Bringing together many of the steps above, this repo includes a one-step release command.
|
||||
Bringing together many of the steps above, this repo includes a one-step release preparation command.
|
||||
|
||||
```bash
|
||||
# Standard release
|
||||
yarn release
|
||||
# Release without bumping package.json version
|
||||
yarn changelog -- --first-release
|
||||
# PGP sign the release
|
||||
yarn changelog -- --sign
|
||||
# Prepare a standard release:
|
||||
npm run prepare-release
|
||||
```
|
||||
|
||||
This command runs:
|
||||
- `yarn reset`: cleans the repo by removing all untracked files and resetting `--hard` to the latest commit. (**Note: this could be destructive.**)
|
||||
- `yarn test`: build and fully test the project
|
||||
- `yarn docs:publish`: generate and publish the latest version of the documentation to GitHub Pages
|
||||
- `yarn changelog`: bump package.json version, update CHANGELOG.md, and git tag the release
|
||||
You can also prepare a non-standard release:
|
||||
|
||||
```bash
|
||||
# Or a non-standard release:
|
||||
|
||||
# Build everything
|
||||
npm run all
|
||||
|
||||
# Then version it
|
||||
npm run version -- --first-release # don't bump package.json version
|
||||
npm run version -- --sign # PGP sign it
|
||||
npm run version -- --prerelease alpha # alpha release
|
||||
|
||||
# And don't forget to push the docs to GitHub pages:
|
||||
npm run docs:publish
|
||||
```
|
||||
|
||||
This command runs the following tasks:
|
||||
|
||||
* `reset`: cleans the repo by removing all untracked files and resetting `--hard` to the latest commit. (**Note: this could be destructive.**)
|
||||
* `test`: build and fully test the project
|
||||
* `docs:html`: generate the latest version of the documentation
|
||||
* `docs:publish`: publish the documentation to GitHub Pages
|
||||
* `changelog`: bump package.json version, update CHANGELOG.md, and git tag the release
|
||||
|
||||
When the script finishes, it will log the final command needed to push the release commit to the repo and publish the package on the `npm` registry:
|
||||
|
||||
```
|
||||
```bash
|
||||
git push --follow-tags origin master; npm publish
|
||||
```
|
||||
|
||||
Look over the release if you'd like, then execute the command to publish everything.
|
||||
|
||||
## All package scripts
|
||||
## Get scripts info
|
||||
|
||||
You can run the `info` script for information on each script intended to be individually run.
|
||||
|
||||
```
|
||||
yarn run info
|
||||
npm run info
|
||||
|
||||
info:
|
||||
Display information about the scripts
|
||||
build:
|
||||
(Trash and re)build the library
|
||||
lint:
|
||||
Lint all typescript source files
|
||||
unit:
|
||||
Build the library and run unit tests
|
||||
test:
|
||||
Lint, build, and test the library
|
||||
watch:
|
||||
Watch source files, rebuild library on changes, rerun relevant tests
|
||||
cov:
|
||||
Run tests, generate the HTML coverage report, and open it in a browser
|
||||
docs:
|
||||
Generate HTML API documentation and open it in a browser
|
||||
docs:publish:
|
||||
Generate HTML API documentation and push it to GitHub Pages
|
||||
docs:json:
|
||||
Generate API documentation in typedoc JSON format
|
||||
release:
|
||||
Bump package.json version, update CHANGELOG.md, tag a release
|
||||
reset:
|
||||
Delete all untracked files and reset the repo to the last commit
|
||||
publish:
|
||||
Reset, build, test, publish docs, and prepare release (a one-step publish process)
|
||||
> npm-scripts-info
|
||||
|
||||
info:
|
||||
Display information about the package scripts
|
||||
build:
|
||||
Clean and rebuild the project
|
||||
fix:
|
||||
Try to automatically fix any linting problems
|
||||
test:
|
||||
Lint and unit test the project
|
||||
watch:
|
||||
Watch and rebuild the project on save, then rerun relevant tests
|
||||
cov:
|
||||
Rebuild, run tests, then create and open the coverage report
|
||||
doc:
|
||||
Generate HTML API documentation and open it in a browser
|
||||
doc:json:
|
||||
Generate API documentation in typedoc JSON format
|
||||
changelog:
|
||||
Bump package.json version, update CHANGELOG.md, tag release
|
||||
reset:
|
||||
Delete all untracked files and reset the repo to the last commit
|
||||
release:
|
||||
One-step: clean, build, test, publish docs, and prep a release
|
||||
```
|
||||
## Notes
|
||||
|
||||
### Multiple builds (`main`, `module`, and `browser`)
|
||||
# FAQs
|
||||
|
||||
The `src` of `typescript-starter` is compiled into three separate builds: `main`, `module`, and `browser`. The `main` build is [configured to use the CommonJS module system](https://github.com/bitjson/typescript-starter/blob/master/tsconfig.json#L8), while the `module` build [uses the new ES6 module system](https://github.com/bitjson/typescript-starter/blob/master/config/tsconfig.module.json). The browser build contains two bundles, an ES6 module (the preferred export) and a CommonJS bundle (primarily used for testing).
|
||||
## Why are there two builds? (`main` and `module`)
|
||||
|
||||
Because Node.js does not yet support the ES6 module system, Node.js projects which depend on typescript-starter will follow the `main` field in [`package.json`](https://github.com/bitjson/typescript-starter/blob/master/package.json). Tools which support the new system (like [Rollup](https://github.com/rollup/rollup)) will follow the `module` field, giving them the ability to statically analyze typescript-starter. When building for the browser, newer tools follow the `browser` field, which will resolve to the browser build's ES6 module.
|
||||
The `src` of `typescript-starter` is compiled into two separate builds: `main` and `module`. The `main` build is [configured to use the CommonJS module system](https://github.com/bitjson/typescript-starter/blob/master/tsconfig.json#L8). The `module` build [uses the new es6 module system](https://github.com/bitjson/typescript-starter/blob/master/config/tsconfig.module.json).
|
||||
|
||||
### Testing
|
||||
Because Node.js LTS releases do not yet support the es6 module system, some projects which depend on your project will follow the `main` field in [`package.json`](https://github.com/bitjson/typescript-starter/blob/master/package.json). Tools which support the new system (like [Rollup](https://github.com/rollup/rollup), [Webpack](https://webpack.js.org/), or [Parcel](https://parceljs.org/)) will follow the `module` field, giving them the ability to statically analyze your project. These tools can tree-shake your `module` build to import only the code they need.
|
||||
|
||||
By convention, tests in `typescript-starter` are co-located with the files they test. The project is configured to allow tests to be written in Typescript and your library to be imported as if it were being used by another project. (E.g. `import { double, power } from 'typescript-starter'`.) This makes tests both intuitive to write and easy to read as another form of documentation.
|
||||
## Why put tests next to the source code?
|
||||
|
||||
Note, tests are compiled and performed on the final builds in the standard Node.js runtime (rather than an alternative like [ts-node](https://github.com/TypeStrong/ts-node)) to ensure tests pass in that environment. If you are using [ts-node in production](https://github.com/TypeStrong/ts-node/issues/104), you can modify this project to skip test compilation.
|
||||
By convention, sample tests in this project are adjacent to the files they test.
|
||||
|
||||
### Browser libraries
|
||||
* Such tests are easy to find.
|
||||
* You see at a glance if a part of your project lacks tests.
|
||||
* Nearby tests can reveal how a part works in context.
|
||||
* When you move the source (inevitable), you remember to move the test.
|
||||
* When you rename the source file (inevitable), you remember to rename the test file.
|
||||
|
||||
While both the browser and the Node.js versions of the library are tested, this starter currently does **not** run the browser tests in a real browser ([AVA](https://github.com/avajs/ava) is currently Node-only). While the current testing system will be sufficient for most use cases, some projects will (also) need to implement a browser-based testing system like [karma-ava](https://github.com/avajs/karma-ava). (Pull requests welcome!)
|
||||
(Bullet points taken from [Angular's Testing Guide](https://angular.io/guide/testing#q-spec-file-location).)
|
||||
|
||||
Note: test coverage is only checked against the Node.js implementation. This is much simpler, and works well for libraries where the node and browser implementations have different dependencies and only minor adapter code. With only a few lines of differences (e.g. `src/adapters/crypto.browser.ts`), including those few lines in test coverage analysis usually isn't necessary.
|
||||
## Can I move the tests?
|
||||
|
||||
### Building browser dependencies
|
||||
Yes. For some projects, separating tests from the code they test may be desirable. This project is already configured to test any `*.spec.ts` files located in the `src` directory, so reorganize your tests however you'd like. You can put them all in a single folder, add tests that test more than one file, or mix and match strategies (e.g. for other types of tests, like integration or e2e tests).
|
||||
|
||||
This starter demonstrates importing and using a CommonJS module ([`hash.js`](https://github.com/indutny/hash.js)) for it's `hash256` method when built for the browser. See the `build:browser-deps` [package script](./package.json) and [rollup.config.js](./config/exports/rollup.config.js) for more details. Of course, your project likely does not need this dependency, so it can be removed. If your library doesn't need to bundle external dependencies for the browser, several other devDependencies can also be removed (`browserify`, `rollup-plugin-alias`, `rollup-plugin-commonjs`, `rollup-plugin-node-resolve`, etc).
|
||||
## Can I use ts-node for all the things?
|
||||
|
||||
### Dependency on `tslib`
|
||||
Tests are compiled and performed on the final builds in the standard Node.js runtime (rather than an alternative like [ts-node](https://github.com/TypeStrong/ts-node)) to ensure that they pass in that environment. If you are build a Node.js application, and you are using [ts-node in production](https://github.com/TypeStrong/ts-node/issues/104), you can modify this project to use `ts-node` rather than a `build` step.
|
||||
|
||||
By default, this project requires [tslib](https://github.com/Microsoft/tslib) as a dependency. This is the recommended way to use Typescript's es6 & es7 transpiling for sizable projects, but you can remove this dependency by removing the `importHelpers` compiler option in `tsconfig.json`. Depending on your usage, this may increase the size of your library significantly, as the Typescript compiler will inject it's helper functions directly into every file which uses them. (See also: [`noEmitHelpers` →](https://www.typescriptlang.org/docs/handbook/compiler-options.html))
|
||||
**However, if you're building any kind of library, you should always compile to javascript.**
|
||||
|
||||
### Targeting older environments
|
||||
Library authors sometimes make the mistake of distributing their libraries in typescript. Intuitively, this seems like a reasonable course of action, especially if all of your intended consumers will be using typescript as well.
|
||||
|
||||
By default, this library targets environments with native (or already-polyfilled) support for es6 features. If your library needs to target Internet Explorer, outdated Android browsers, or versions of Node older than v4, you may need to change the `target` in `tsconfig.json` to `es5` (rather than `es6`) and bring in a Promise polyfill (such as [es6-promise](https://github.com/stefanpenner/es6-promise)).
|
||||
TypeScript has versions, and different versions of TypeScript may not be compatible. Upgrading to a new major version of TypeScript sometimes requires code changes, and must be done project-by-project. Additionally, if you're using the latest version of TypeScript to build your library, and one of your consumers is using an older version in their application, their compiler will be unable to compile your library.
|
||||
|
||||
It's a good idea to maintain 100% unit test coverage, and always test in the environments you target.
|
||||
## How do I bundle my library for the browser?
|
||||
|
||||
## typescript-starter in the wild
|
||||
The short answer is: **don't pre-bundle your library**.
|
||||
|
||||
Previous versions of `typescript-starter` included browser bundling using [Rollup](https://github.com/rollup/rollup). This feature has since been removed, since very few libraries should ever be pre-bundled.
|
||||
|
||||
If the consumer of your library is using Node.js, bundling is especially unnecessary, since Node.js can reliably resolve dependencies, and bundling may even make debugging more difficult.
|
||||
|
||||
If the consumer of your library is a browser application, **the application likely has its own build tooling**. Very few serious applications are manually bundling their javascript, especially with easy to use, no configuration tools like [Parcel](https://parceljs.org/) available.
|
||||
|
||||
Your library is most useful to downstream consumers as a clean, modular codebase, properly exporting features using es6 exports. Consumers can import the exact es6 exports they need from your library, and tree-shake the rest.
|
||||
|
||||
## How can my library provide different functionality between Node.js and the browser?
|
||||
|
||||
In the past, complex javascript libraries have used solutions like [Browserify](http://browserify.org/) to bundle a version of their application for the browser. Most of these solutions work by allowing library developers to extensively configure and manually override various dependencies with respective browser versions.
|
||||
|
||||
For example, where a Node.js application might use Node.js' built-in [`crypto` module](https://nodejs.org/api/crypto.html), a browser version would need to fall back to a polyfill-like alternative dependency like [`crypto-browserify`](https://github.com/crypto-browserify/crypto-browserify).
|
||||
|
||||
With es6, this customization and configuration is no longer necessary. Your library can now export different functionality for different consumers. While browser consumers may import a native JavaScript crypto implementation which your library exports, Node.js users can choose to import a different, faster implementation which your library exports.
|
||||
|
||||
See [hash.ts](./src/lib/hash.ts) for a complete example. Two different functions are exported, `sha256`, and `sha256Native`. Browser consumers will not be able to import `sha256Native`, since their bundler will be unable to resolve the built-in Node.js dependency (their bundler will throw an error). Node.js users, however, will be able to import it normally. Each consumer can import the exact functionality they need.
|
||||
|
||||
One perceived downside of this solution is that it complicates the library's API. Browser consumers will sometimes import one feature while Node.js users import another. While this argument has merit, we should weigh it against the benefits.
|
||||
|
||||
Providing a public API where consumer code is the same between browsers and Node.js is desirable, but it comes at the cost of significant configuration and complexity. In many cases, it requires that code be aware of its environment at runtime, requiring additional complexity and testing.
|
||||
|
||||
A better way to provide this developer experience is to provide similar APIs for each environment, and then encourage the use of es6 import aliasing to standardize between them.
|
||||
|
||||
For example, in the documentation for `typescript-starter`, we encourage Node.js users to import `sha256Native as sha256`. With this convention, we get a standard API without loaders or dependency substitution hacks.
|
||||
|
||||
```js
|
||||
// browser-application.js
|
||||
import { sha256 } from 'typescript-starter';
|
||||
|
||||
// fully-portable code
|
||||
console.log(sha256('test'));
|
||||
```
|
||||
|
||||
```js
|
||||
// node-application.js
|
||||
import { sha256Native as sha256 } from 'typescript-starter';
|
||||
|
||||
// fully-portable code
|
||||
console.log(sha256('test'));
|
||||
```
|
||||
|
||||
## What about Git hooks to validate commit messages?
|
||||
|
||||
This project uses [standard-version](https://github.com/conventional-changelog/standard-version) to automatically update the changelog based on commit messages since the last release. To do this, each relevant commit must be properly formatted.
|
||||
|
||||
To ensure all commits follow the proper conventions, you can use a package like [commitlint](https://github.com/marionebl/commitlint) with [Husky](https://github.com/typicode/husky). However, keep in mind that commit hooks can be confusing, especially for new contributors. They also interfere with some development tools and workflows.
|
||||
|
||||
If your project is private, or will primarily receive contributions from long-running contributors, this may be a good fit. Otherwise, this setup may raise the barrier to one-off contributions slightly.
|
||||
|
||||
Note, as a maintainer, if you manage your project on GitHub or a similar website, you can now use the `Squash and Merge` option to add a properly formatted, descriptive commit messages when merging each pull request. This is likely to be more valuable than trying to force one-time contributors to adhere to commit conventions, since you can also maintain a more consistent language style. Because this is the best choice for the vast majority of projects, `typescript-starter` does not bundle any commit message validation.
|
||||
|
||||
# Contributing
|
||||
|
||||
To work on the CLI, clone and build the repo, then use `npm link` to install it globally.
|
||||
|
||||
```
|
||||
git clone https://github.com/bitjson/typescript-starter.git
|
||||
cd typescript-starter
|
||||
npm install
|
||||
npm test
|
||||
npm link
|
||||
```
|
||||
|
||||
To manually test the CLI, you can use the `TYPESCRIPT_STARTER_REPO_URL` environment variable to test a clone from your local repo. Run `npm run watch` as you're developing, then in a different testing directory:
|
||||
|
||||
```
|
||||
mkdir typescript-starter-testing
|
||||
cd typescript-starter-testing
|
||||
TYPESCRIPT_STARTER_REPO_URL='/local/path/to/typescript-starter' typescript-starter
|
||||
```
|
||||
|
||||
You can also `TYPESCRIPT_STARTER_REPO_URL` to any valid Git URL, such as your fork of this repo:
|
||||
|
||||
```
|
||||
TYPESCRIPT_STARTER_REPO_URL='https://github.com/YOUR_USERNAME/typescript-starter.git' typescript-starter
|
||||
```
|
||||
|
||||
If you're using [VS Code](https://code.visualstudio.com/), the `Debug CLI` launch configuration also allows you to immediately build and step through execution of the CLI.
|
||||
|
||||
# In the wild
|
||||
|
||||
You can find more advanced configurations, usage examples, and inspiration from projects using `typescript-starter`.
|
||||
|
||||
- [BitAuth](https://github.com/bitauth/) – A universal identity and authentication protocol, based on bitcoin
|
||||
- [s6: Super Simple Secrets * Simple Secure Storage](https://gitlab.com/td7x/s6/) – An NPM library and tool to sprawl secrets with S3, ease, and encryption
|
||||
* [BitAuth](https://github.com/bitauth/) – A universal identity and authentication protocol, based on bitcoin
|
||||
* [s6: Super Simple Secrets \* Simple Secure Storage](https://gitlab.com/td7x/s6/) – An NPM library and tool to sprawl secrets with S3, ease, and encryption
|
||||
|
||||
Using `typescript-starter` for your project? Please send a pull request to add it to the list!
|
||||
|
||||
9
bin/typescript-starter
Executable file
9
bin/typescript-starter
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* This file needs the 'x' permission to be spawned by tests. Since TypeScript
|
||||
* doesn't currently offer a way to set permissions of generated files
|
||||
* (https://github.com/Microsoft/TypeScript/issues/16667), we track this file
|
||||
* with Git, and simply require the generated CLI.
|
||||
*/
|
||||
require('../build/main/cli/cli.js');
|
||||
@@ -1,53 +0,0 @@
|
||||
// this script watches the tests exported by typescript, copies them to the test directories, and modifies the require("PKG.NAME") statements to test each build
|
||||
const cpx = require("cpx");
|
||||
const path = require("path");
|
||||
const Transform = require("stream").Transform;
|
||||
const pkg = require('../../package');
|
||||
|
||||
const req = (path) => `require("${path}")`;
|
||||
|
||||
// replace instances of pkg.name with the proper route to the build being tested
|
||||
const makeTransform = (rootDir, buildPath, specPath) => {
|
||||
const specDir = path.dirname(specPath)
|
||||
const testDir = specDir.replace(path.join(rootDir, 'build'), path.join(rootDir, 'test'))
|
||||
|
||||
const newRequire = path.relative(testDir, buildPath)
|
||||
.split(path.sep).join('/')
|
||||
|
||||
return new Transform({
|
||||
transform(chunk, encoding, done) {
|
||||
const str = chunk
|
||||
.toString()
|
||||
.replace(req(pkg.name), req(newRequire))
|
||||
this.push(str);
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// copy, then watch for changes to the tests
|
||||
const testsFromRoot = 'build/main/**/*.spec.js';
|
||||
const watchMode = process.argv.indexOf('-w') !== -1 ? true : false;
|
||||
const browserTests = process.argv.indexOf('--no-browser') !== -1 ? true : false;
|
||||
const task = watchMode ? cpx.watch : cpx.copy;
|
||||
|
||||
const rootDir = path.resolve('.');
|
||||
const mainBuildPath = path.resolve(pkg.main);
|
||||
|
||||
task(testsFromRoot, 'test/main', {
|
||||
transform: (specPath) => makeTransform(
|
||||
rootDir,
|
||||
mainBuildPath,
|
||||
path.resolve(specPath))
|
||||
});
|
||||
|
||||
if (!browserTests) {
|
||||
const browserBuildPath = path.resolve(pkg.browser);
|
||||
|
||||
task(testsFromRoot, 'test/browser', {
|
||||
transform: (specPath) => makeTransform(
|
||||
rootDir,
|
||||
browserBuildPath.replace('.js', '.cjs.js'),
|
||||
path.resolve(specPath))
|
||||
});
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// rollup.config.js
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import nodeResolve from 'rollup-plugin-node-resolve';
|
||||
import alias from 'rollup-plugin-alias';
|
||||
import json from 'rollup-plugin-json';
|
||||
|
||||
const substituteModulePaths = {
|
||||
'crypto': 'build/module/adapters/crypto.browser.js',
|
||||
'hash.js': 'build/temp/hash.js'
|
||||
}
|
||||
|
||||
export default {
|
||||
entry: 'build/module/index.js',
|
||||
sourceMap: true,
|
||||
plugins: [
|
||||
alias(substituteModulePaths),
|
||||
nodeResolve({
|
||||
browser: true
|
||||
}),
|
||||
commonjs(),
|
||||
json()
|
||||
]
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../build/module",
|
||||
"rootDir": "../../src",
|
||||
"module": "es6",
|
||||
"declaration": false
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitAny" : true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
}
|
||||
}
|
||||
12730
package-lock.json
generated
12730
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
201
package.json
201
package.json
@@ -1,86 +1,15 @@
|
||||
{
|
||||
"name": "typescript-starter",
|
||||
"version": "1.4.1",
|
||||
"version": "2.0.0",
|
||||
"description": "A typescript starter for building javascript libraries and projects",
|
||||
"bin": {
|
||||
"typescript-starter": "./bin/typescript-starter"
|
||||
},
|
||||
"main": "build/main/index.js",
|
||||
"typings": "build/main/index.d.ts",
|
||||
"module": "build/module/index.js",
|
||||
"browser": "build/browser/index.js",
|
||||
"repository": "https://github.com/bitjson/typescript-starter",
|
||||
"author": "Jason Dreyzehner <jason@dreyzehner.com>",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"info": "npm-scripts-info",
|
||||
"build": "trash build && yarn build:main && yarn build:module && yarn build:browser-deps && yarn build:browser && yarn build:browser-cjs && yarn build:resolve-sourcemaps",
|
||||
"build:main": "tsc -p tsconfig.json",
|
||||
"build:module": "tsc -p config/exports/tsconfig.module.json",
|
||||
"build:browser-deps": "mkdirp build/temp && browserify node_modules/hash.js/lib/hash.js --standalone hash -o build/temp/hash.js",
|
||||
"build:browser": "rollup -c config/exports/rollup.config.js -f es -o build/browser/index.js",
|
||||
"build:browser-cjs": "rollup -c config/exports/rollup.config.js -f cjs -o build/browser/index.cjs.js",
|
||||
"build:resolve-sourcemaps": "sorcery -i build/browser/index.js && sorcery -i build/browser/index.cjs.js",
|
||||
"build:tests": "trash test && node config/exports/build-tests.js",
|
||||
"lint": "tslint --project . --type-check src/**/*.ts",
|
||||
"unit": "yarn build && yarn build:tests && nyc ava",
|
||||
"check-coverage": "nyc check-coverage --lines 100 --functions 100 --branches 100",
|
||||
"test": "yarn lint && yarn unit && yarn check-coverage",
|
||||
"watch": "yarn build && yarn build:tests -- --no-browser && concurrently -r --kill-others \"npm run --silent build:main -- -w\" \"npm run --silent build:tests -- -w --no-browser\" \"sleepms 2000 && ava --watch\"",
|
||||
"cov": "yarn unit && yarn html-coverage && opn coverage/index.html",
|
||||
"html-coverage": "nyc report --reporter=html",
|
||||
"send-coverage": "nyc report --reporter=lcov > coverage.lcov && codecov",
|
||||
"docs": "yarn docs:html && opn build/docs/index.html",
|
||||
"docs:html": "typedoc src/index.ts --excludePrivate --mode file --theme minimal --out build/docs",
|
||||
"docs:json": "typedoc --mode file --json build/docs/typedoc.json src/index.ts",
|
||||
"docs:publish": "yarn docs:html && gh-pages -d build/docs",
|
||||
"changelog": "standard-version",
|
||||
"release": "yarn reset && yarn test && yarn docs:publish && yarn changelog",
|
||||
"reset": "git clean -dfx && git reset --hard && yarn"
|
||||
},
|
||||
"scripts-info": {
|
||||
"info": "Display information about the scripts",
|
||||
"build": "(Trash and re)build the library",
|
||||
"lint": "Lint all typescript source files",
|
||||
"unit": "Build the library and run unit tests",
|
||||
"test": "Lint, build, and test the library",
|
||||
"watch": "Watch source files, rebuild library on changes, rerun relevant tests",
|
||||
"cov": "Run tests, generate the HTML coverage report, and open it in a browser",
|
||||
"docs": "Generate HTML API documentation and open it in a browser",
|
||||
"docs:publish": "Generate HTML API documentation and push it to GitHub Pages",
|
||||
"docs:json": "Generate API documentation in typedoc JSON format",
|
||||
"changelog": "Bump package.json version, update CHANGELOG.md, tag a release",
|
||||
"reset": "Delete all untracked files and reset the repo to the last commit",
|
||||
"release": "Clean, build, test, publish docs, and prepare release (a one-step publish process)"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^8.0.4",
|
||||
"ava": "^0.21.0",
|
||||
"browserify": "^14.1.0",
|
||||
"codecov": "^2.2.0",
|
||||
"concurrently": "^3.4.0",
|
||||
"cpx": "^1.5.0",
|
||||
"gh-pages": "^1.0.0",
|
||||
"hash.js": "^1.0.3",
|
||||
"mkdirp": "^0.5.1",
|
||||
"npm-scripts-info": "^0.3.6",
|
||||
"nyc": "^11.0.3",
|
||||
"opn-cli": "^3.1.0",
|
||||
"rollup": "^0.44.0",
|
||||
"rollup-plugin-alias": "^1.2.0",
|
||||
"rollup-plugin-commonjs": "^8.0.2",
|
||||
"rollup-plugin-json": "^2.3.0",
|
||||
"rollup-plugin-node-resolve": "^3.0.0",
|
||||
"rollup-watch": "^4.0.0",
|
||||
"sleep-ms": "^2.0.1",
|
||||
"sorcery": "^0.10.0",
|
||||
"standard-version": "^4.0.0",
|
||||
"trash-cli": "^1.4.0",
|
||||
"tslint": "^5.4.3",
|
||||
"tslint-config-standard": "^6.0.1",
|
||||
"typedoc": "^0.8.0",
|
||||
"typescript": "^2.4.1"
|
||||
},
|
||||
"keywords": [
|
||||
"async",
|
||||
"ava",
|
||||
@@ -102,20 +31,120 @@
|
||||
"typescript",
|
||||
"typings"
|
||||
],
|
||||
"nyc": {
|
||||
"exclude": [
|
||||
"**/*.spec.js",
|
||||
"build/browser/**"
|
||||
]
|
||||
"scripts": {
|
||||
"info": "npm-scripts-info",
|
||||
"build": "run-s clean && run-p build:*",
|
||||
"build:main": "tsc -p tsconfig.json",
|
||||
"build:module": "tsc -p tsconfig.module.json",
|
||||
"fix": "run-s fix:*",
|
||||
"fix:prettier": "prettier 'src/**/*.ts' --write",
|
||||
"fix:tslint": "tslint --fix --project .",
|
||||
"test": "run-s build test:*",
|
||||
"test:lint": "tslint --project . && prettier 'src/**/*.ts' --list-different",
|
||||
"test:unit": "nyc --silent ava",
|
||||
"test:nsp": "nsp check",
|
||||
"watch": "run-s clean build:main && run-p 'build:main -- -w' 'test:unit -- --watch'",
|
||||
"cov": "run-s build test:unit cov:html && opn coverage/index.html",
|
||||
"cov:html": "nyc report --reporter=html",
|
||||
"cov:send": "nyc report --reporter=lcov > coverage.lcov && codecov",
|
||||
"cov:check": "nyc report && nyc check-coverage --lines 100 --functions 100 --branches 100",
|
||||
"doc": "run-s doc:html && opn build/docs/index.html",
|
||||
"doc:html": "typedoc src/ --target ES6 --mode file --out build/docs",
|
||||
"doc:json": "typedoc src/ --target ES6 --mode file --json build/docs/typedoc.json",
|
||||
"doc:publish": "gh-pages -d build/docs",
|
||||
"version": "standard-version",
|
||||
"reset": "git clean -dfx && git reset --hard && npm i",
|
||||
"clean": "trash build test",
|
||||
"all": "run-s reset test cov:check doc:html",
|
||||
"prepare-release": "run-s all version doc:publish"
|
||||
},
|
||||
"scripts-info": {
|
||||
"info": "Display information about the package scripts",
|
||||
"build": "Clean and rebuild the project",
|
||||
"fix": "Try to automatically fix any linting problems",
|
||||
"test": "Lint and unit test the project",
|
||||
"watch": "Watch and rebuild the project on save, then rerun relevant tests",
|
||||
"cov": "Rebuild, run tests, then create and open the coverage report",
|
||||
"doc": "Generate HTML API documentation and open it in a browser",
|
||||
"doc:json": "Generate API documentation in typedoc JSON format",
|
||||
"changelog": "Bump package.json version, update CHANGELOG.md, tag release",
|
||||
"reset": "Delete all untracked files and reset the repo to the last commit",
|
||||
"prepare-release": "One-step: clean, build, test, publish docs, and prep a release"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9"
|
||||
},
|
||||
"NOTE": "These dependencies are for the CLI, and will be removed automatically.",
|
||||
"dependencies": {
|
||||
"@types/globby": "^6.1.0",
|
||||
"chalk": "^2.3.1",
|
||||
"del": "^3.0.0",
|
||||
"execa": "^0.9.0",
|
||||
"github-username": "^4.1.0",
|
||||
"globby": "^8.0.1",
|
||||
"gradient-string": "^1.0.0",
|
||||
"inquirer": "^5.1.0",
|
||||
"meow": "^4.0.0",
|
||||
"ora": "^2.0.0",
|
||||
"project-version": "^1.0.0",
|
||||
"replace-in-file": "^3.1.1",
|
||||
"sha.js": "^2.4.10",
|
||||
"update-notifier": "^2.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/del": "^3.0.0",
|
||||
"@types/execa": "^0.8.1",
|
||||
"@types/inquirer": "0.0.37",
|
||||
"@types/meow": "^4.0.1",
|
||||
"@types/nock": "^9.1.2",
|
||||
"@types/node": "^8.9.5",
|
||||
"@types/ora": "^1.3.2",
|
||||
"@types/update-notifier": "^2.2.0",
|
||||
"ava": "^1.0.0-beta.3",
|
||||
"codecov": "^3.0.0",
|
||||
"cz-conventional-changelog": "^2.1.0",
|
||||
"gh-pages": "^1.0.0",
|
||||
"md5-file": "^3.2.3",
|
||||
"nock": "^9.2.3",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"npm-scripts-info": "^0.3.6",
|
||||
"nsp": "^3.2.1",
|
||||
"nyc": "^11.5.0",
|
||||
"opn-cli": "^3.1.0",
|
||||
"prettier": "^1.10.2",
|
||||
"standard-version": "^4.0.0",
|
||||
"trash-cli": "^1.4.0",
|
||||
"tslint": "^5.4.3",
|
||||
"tslint-config-prettier": "^1.8.0",
|
||||
"tslint-immutable": "^4.5.1",
|
||||
"typedoc": "^0.11.1",
|
||||
"typescript": "^2.4.1"
|
||||
},
|
||||
"ava": {
|
||||
"source": [
|
||||
"test/**/*.js",
|
||||
"build/**/*.js",
|
||||
"!build/**/*.spec.js"
|
||||
"failFast": true,
|
||||
"files": [
|
||||
"build/main/**/*.spec.js"
|
||||
],
|
||||
"sources": [
|
||||
"build/main/**/*.js"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^1.6.0"
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"greenkeeper": {
|
||||
"ignore": [
|
||||
"@types/node"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"singleQuote": true
|
||||
},
|
||||
"nyc": {
|
||||
"exclude": [
|
||||
"**/*.spec.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// Must first be built by browserify.
|
||||
// https://github.com/rollup/rollup-plugin-commonjs/issues/105#issuecomment-281917166
|
||||
import hash from 'hash.js'
|
||||
|
||||
/**
|
||||
* Simulate the Node.js crypto.createHash function using hash.js' implementation.
|
||||
* @internal
|
||||
* @hidden (TypeDoc currently doesn't understand @internal)
|
||||
*/
|
||||
export function createHash (algorithm: 'sha256') {
|
||||
return hash.sha256()
|
||||
}
|
||||
112
src/cli/args.ts
Normal file
112
src/cli/args.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
// tslint:disable:no-console no-if-statement no-expression-statement
|
||||
|
||||
import meow from 'meow';
|
||||
import { Package, UpdateInfo, UpdateNotifier } from 'update-notifier';
|
||||
import { Runner, TypescriptStarterUserOptions, validateName } from './utils';
|
||||
|
||||
export async function checkArgs(): Promise<
|
||||
Partial<TypescriptStarterUserOptions>
|
||||
> {
|
||||
const cli = meow(
|
||||
`
|
||||
Usage
|
||||
$ npx typescript-starter
|
||||
|
||||
Non-Interactive Usage
|
||||
$ npx typescript-starter <project-name> [options]
|
||||
|
||||
Options
|
||||
--description, -d package.json description
|
||||
--yarn use yarn (default: npm)
|
||||
--node include node.js type definitions
|
||||
--dom include DOM type definitions
|
||||
--no-install skip yarn/npm install
|
||||
--strict Enable stricter type-checking
|
||||
--no-immutable Don't enable tslint-immutable
|
||||
--no-vscode Don't include VS Code debugging config
|
||||
|
||||
Non-Interactive Example
|
||||
$ npx typescript-starter my-library -d 'do something, better'
|
||||
`,
|
||||
{
|
||||
flags: {
|
||||
description: {
|
||||
alias: 'd',
|
||||
default: 'a typescript-starter project',
|
||||
type: 'string'
|
||||
},
|
||||
dom: {
|
||||
default: false,
|
||||
type: 'boolean'
|
||||
},
|
||||
immutable: {
|
||||
default: true,
|
||||
type: 'boolean'
|
||||
},
|
||||
install: {
|
||||
default: true,
|
||||
type: 'boolean'
|
||||
},
|
||||
node: {
|
||||
default: false,
|
||||
type: 'boolean'
|
||||
},
|
||||
strict: {
|
||||
default: false,
|
||||
type: 'boolean'
|
||||
},
|
||||
vscode: {
|
||||
default: true,
|
||||
type: 'boolean'
|
||||
},
|
||||
yarn: {
|
||||
default: false,
|
||||
type: 'boolean'
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// immediately check for updates every time we run typescript-starter
|
||||
const updateInfo = await new Promise<UpdateInfo>((resolve, reject) => {
|
||||
const notifier = new UpdateNotifier({
|
||||
callback: (error, update) => {
|
||||
error ? reject(error) : resolve(update);
|
||||
},
|
||||
pkg: cli.pkg as Package
|
||||
});
|
||||
notifier.check();
|
||||
});
|
||||
if (updateInfo.type !== 'latest') {
|
||||
throw new Error(`
|
||||
Your version of typescript-starter is outdated.
|
||||
Consider using 'npx typescript-starter' to always get the latest version.
|
||||
`);
|
||||
}
|
||||
|
||||
const input = cli.input[0];
|
||||
if (!input) {
|
||||
// no project-name provided, return to collect options in interactive mode
|
||||
// note: we always return `install`, so --no-install always works
|
||||
// (important for test performance)
|
||||
return {
|
||||
install: cli.flags.install
|
||||
};
|
||||
}
|
||||
const validOrMsg = await validateName(input);
|
||||
if (typeof validOrMsg === 'string') {
|
||||
throw new Error(validOrMsg);
|
||||
}
|
||||
|
||||
return {
|
||||
description: cli.flags.description,
|
||||
domDefinitions: cli.flags.dom,
|
||||
immutable: cli.flags.immutable,
|
||||
install: cli.flags.install,
|
||||
nodeDefinitions: cli.flags.node,
|
||||
projectName: input,
|
||||
runner: cli.flags.yarn ? Runner.Yarn : Runner.Npm,
|
||||
strict: cli.flags.strict,
|
||||
vscode: cli.flags.vscode
|
||||
};
|
||||
}
|
||||
27
src/cli/cli.ts
Normal file
27
src/cli/cli.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// tslint:disable:no-expression-statement no-console
|
||||
import chalk from 'chalk';
|
||||
import { checkArgs } from './args';
|
||||
import { inquire } from './inquire';
|
||||
import { getInferredOptions, LiveTasks } from './tasks';
|
||||
import { typescriptStarter } from './typescript-starter';
|
||||
import { getIntro, TypescriptStarterUserOptions } from './utils';
|
||||
|
||||
(async () => {
|
||||
const cliOptions = await checkArgs();
|
||||
const userOptions = cliOptions.projectName
|
||||
? (cliOptions as TypescriptStarterUserOptions)
|
||||
: {
|
||||
...(await (async () => {
|
||||
console.log(getIntro(process.stdout.columns));
|
||||
return inquire();
|
||||
})()),
|
||||
...cliOptions // merge in cliOptions.install
|
||||
};
|
||||
const inferredOptions = await getInferredOptions();
|
||||
return typescriptStarter({ ...inferredOptions, ...userOptions }, LiveTasks);
|
||||
})().catch((err: Error) => {
|
||||
console.error(`
|
||||
${chalk.red(err.message)}
|
||||
`);
|
||||
process.exit(1);
|
||||
});
|
||||
145
src/cli/inquire.ts
Normal file
145
src/cli/inquire.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { prompt, Question } from 'inquirer';
|
||||
import { Runner, TypescriptStarterUserOptions, validateName } from './utils';
|
||||
|
||||
export async function inquire(): Promise<TypescriptStarterUserOptions> {
|
||||
const packageNameQuestion: Question = {
|
||||
filter: (answer: string) => answer.trim(),
|
||||
message: '📦 Enter the new package name:',
|
||||
name: 'projectName',
|
||||
type: 'input',
|
||||
validate: validateName
|
||||
};
|
||||
|
||||
enum ProjectType {
|
||||
Node = 'node',
|
||||
Library = 'lib'
|
||||
}
|
||||
const projectTypeQuestion: Question = {
|
||||
choices: [
|
||||
{ name: 'Node.js application', value: ProjectType.Node },
|
||||
{ name: 'Javascript library', value: ProjectType.Library }
|
||||
],
|
||||
message: '🔨 What are you making?',
|
||||
name: 'type',
|
||||
type: 'list'
|
||||
};
|
||||
|
||||
const packageDescriptionQuestion: Question = {
|
||||
filter: (answer: string) => answer.trim(),
|
||||
message: '💬 Enter the package description:',
|
||||
name: 'description',
|
||||
type: 'input',
|
||||
validate: (answer: string) => answer.length > 0
|
||||
};
|
||||
|
||||
const runnerQuestion: Question = {
|
||||
choices: [
|
||||
{ name: 'npm', value: Runner.Npm },
|
||||
{ name: 'yarn', value: Runner.Yarn }
|
||||
],
|
||||
message: '🚄 Will this project use npm or yarn?',
|
||||
name: 'runner',
|
||||
type: 'list'
|
||||
};
|
||||
|
||||
enum TypeDefinitions {
|
||||
none = 'none',
|
||||
node = 'node',
|
||||
dom = 'dom',
|
||||
nodeAndDom = 'both'
|
||||
}
|
||||
|
||||
const typeDefsQuestion: Question = {
|
||||
choices: [
|
||||
{
|
||||
name: `None — the library won't use any globals or modules from Node.js or the DOM`,
|
||||
value: TypeDefinitions.none
|
||||
},
|
||||
{
|
||||
name: `Node.js — parts of the library require access to Node.js globals or built-in modules`,
|
||||
value: TypeDefinitions.node
|
||||
},
|
||||
{
|
||||
name: `DOM — parts of the library require access to the Document Object Model (DOM)`,
|
||||
value: TypeDefinitions.dom
|
||||
},
|
||||
{
|
||||
name: `Both Node.js and DOM — some parts of the library require Node.js, other parts require DOM access`,
|
||||
value: TypeDefinitions.nodeAndDom
|
||||
}
|
||||
],
|
||||
message: '📚 Which global type definitions do you want to include?',
|
||||
name: 'definitions',
|
||||
type: 'list',
|
||||
when: (answers: any) => answers.type === ProjectType.Library
|
||||
};
|
||||
|
||||
enum Extras {
|
||||
strict = 'strict',
|
||||
immutable = 'immutable',
|
||||
vscode = 'vscode'
|
||||
}
|
||||
const extrasQuestion: Question = {
|
||||
choices: [
|
||||
{
|
||||
name: 'Enable stricter type-checking',
|
||||
value: Extras.strict
|
||||
},
|
||||
{
|
||||
checked: true,
|
||||
name: 'Enable tslint-immutable',
|
||||
value: Extras.immutable
|
||||
},
|
||||
{
|
||||
checked: true,
|
||||
name: 'Include VS Code debugging config',
|
||||
value: Extras.vscode
|
||||
}
|
||||
],
|
||||
message: '🚀 More fun stuff:',
|
||||
name: 'extras',
|
||||
type: 'checkbox'
|
||||
};
|
||||
|
||||
return prompt([
|
||||
packageNameQuestion,
|
||||
projectTypeQuestion,
|
||||
packageDescriptionQuestion,
|
||||
runnerQuestion,
|
||||
typeDefsQuestion,
|
||||
extrasQuestion
|
||||
]).then(answers => {
|
||||
const {
|
||||
definitions,
|
||||
description,
|
||||
extras,
|
||||
projectName,
|
||||
runner
|
||||
} = answers as {
|
||||
readonly definitions?: TypeDefinitions;
|
||||
readonly description: string;
|
||||
readonly extras: ReadonlyArray<string>;
|
||||
readonly projectName: string;
|
||||
readonly runner: Runner;
|
||||
};
|
||||
return {
|
||||
description,
|
||||
domDefinitions: definitions
|
||||
? [TypeDefinitions.dom, TypeDefinitions.nodeAndDom].includes(
|
||||
definitions
|
||||
)
|
||||
: false,
|
||||
immutable: extras.includes(Extras.immutable),
|
||||
install: true,
|
||||
nodeDefinitions: definitions
|
||||
? [TypeDefinitions.node, TypeDefinitions.nodeAndDom].includes(
|
||||
definitions
|
||||
)
|
||||
: false,
|
||||
projectName,
|
||||
runner,
|
||||
strict: extras.includes(Extras.strict),
|
||||
vscode: extras.includes(Extras.vscode)
|
||||
};
|
||||
});
|
||||
}
|
||||
162
src/cli/tasks.ts
Normal file
162
src/cli/tasks.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
// tslint:disable:no-console no-if-statement no-expression-statement
|
||||
import execa, { ExecaStatic, Options, StdIOOption } from 'execa';
|
||||
import githubUsername from 'github-username';
|
||||
import { join } from 'path';
|
||||
import { Runner, TypescriptStarterInferredOptions } from './utils';
|
||||
|
||||
// TODO: await https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24209
|
||||
const inherit = 'inherit' as StdIOOption;
|
||||
|
||||
export enum Placeholders {
|
||||
email = 'YOUR_EMAIL',
|
||||
name = 'YOUR_NAME',
|
||||
username = 'YOUR_GITHUB_USER_NAME'
|
||||
}
|
||||
|
||||
// We implement these as function factories to make unit testing easier.
|
||||
|
||||
export const cloneRepo = (
|
||||
spawner: ExecaStatic,
|
||||
suppressOutput = false
|
||||
) => async (repoURL: string, workingDirectory: string, dir: string) => {
|
||||
const projectDir = join(workingDirectory, dir);
|
||||
const gitHistoryDir = join(projectDir, '.git');
|
||||
try {
|
||||
await spawner('git', ['clone', '--depth=1', repoURL, dir], {
|
||||
cwd: workingDirectory,
|
||||
stdio: suppressOutput ? 'pipe' : 'inherit'
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
throw new Error(`
|
||||
Git is not installed on your PATH. Please install Git and try again.
|
||||
|
||||
For more information, visit: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
|
||||
`);
|
||||
} else {
|
||||
throw new Error(`Git clone failed.`);
|
||||
}
|
||||
}
|
||||
try {
|
||||
const revParseResult = await spawner('git', ['rev-parse', 'HEAD'], {
|
||||
cwd: projectDir,
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', inherit]
|
||||
});
|
||||
const commitHash = revParseResult.stdout;
|
||||
return { commitHash, gitHistoryDir };
|
||||
} catch (err) {
|
||||
throw new Error(`Git rev-parse failed.`);
|
||||
}
|
||||
};
|
||||
|
||||
export const getGithubUsername = (fetcher: any) => async (
|
||||
email: string | undefined
|
||||
): Promise<string> => {
|
||||
if (email === Placeholders.email) {
|
||||
return Placeholders.username;
|
||||
}
|
||||
return fetcher(email).catch(() => {
|
||||
return Placeholders.username;
|
||||
});
|
||||
};
|
||||
|
||||
export const getUserInfo = (spawner: ExecaStatic) => async () => {
|
||||
const opts: Options = {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', inherit]
|
||||
};
|
||||
try {
|
||||
const nameResult = await spawner('git', ['config', 'user.name'], opts);
|
||||
const emailResult = await spawner('git', ['config', 'user.email'], opts);
|
||||
return {
|
||||
gitEmail: emailResult.stdout,
|
||||
gitName: nameResult.stdout
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
gitEmail: Placeholders.email,
|
||||
gitName: Placeholders.name
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const initialCommit = (spawner: ExecaStatic) => async (
|
||||
hash: string,
|
||||
projectDir: string
|
||||
): Promise<void> => {
|
||||
const opts: Options = {
|
||||
cwd: projectDir,
|
||||
encoding: 'utf8',
|
||||
stdio: 'pipe'
|
||||
};
|
||||
await spawner('git', ['init'], opts);
|
||||
await spawner('git', ['add', '-A'], opts);
|
||||
await spawner(
|
||||
'git',
|
||||
[
|
||||
'commit',
|
||||
'-m',
|
||||
`Initial commit\n\nCreated with typescript-starter@${hash}`
|
||||
],
|
||||
opts
|
||||
);
|
||||
};
|
||||
|
||||
export const install = (spawner: ExecaStatic) => async (
|
||||
runner: Runner,
|
||||
projectDir: string
|
||||
) => {
|
||||
const opts: Options = {
|
||||
cwd: projectDir,
|
||||
encoding: 'utf8',
|
||||
stdio: 'inherit'
|
||||
};
|
||||
try {
|
||||
runner === Runner.Npm
|
||||
? spawner('npm', ['install'], opts)
|
||||
: spawner('yarn', opts);
|
||||
} catch (err) {
|
||||
throw new Error(`Installation failed. You'll need to install manually.`);
|
||||
}
|
||||
};
|
||||
|
||||
export const getRepoUrl = () => {
|
||||
return (
|
||||
process.env.TYPESCRIPT_STARTER_REPO_URL ||
|
||||
'https://github.com/bitjson/typescript-starter.git'
|
||||
);
|
||||
};
|
||||
|
||||
export interface Tasks {
|
||||
readonly cloneRepo: (
|
||||
repoURL: string,
|
||||
workingDirectory: string,
|
||||
dir: string
|
||||
) => Promise<{ readonly commitHash: string; readonly gitHistoryDir: string }>;
|
||||
readonly initialCommit: (
|
||||
hash: string,
|
||||
projectDir: string,
|
||||
name: string
|
||||
) => Promise<void>;
|
||||
readonly install: (runner: Runner, projectDir: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const LiveTasks: Tasks = {
|
||||
cloneRepo: cloneRepo(execa),
|
||||
initialCommit: initialCommit(execa),
|
||||
install: install(execa)
|
||||
};
|
||||
export const getInferredOptions = async (): Promise<
|
||||
TypescriptStarterInferredOptions
|
||||
> => {
|
||||
const { gitName, gitEmail } = await getUserInfo(execa)();
|
||||
const username = await getGithubUsername(githubUsername)(gitEmail);
|
||||
return {
|
||||
email: gitEmail,
|
||||
fullName: gitName,
|
||||
githubUsername: username,
|
||||
repoURL: getRepoUrl(),
|
||||
workingDirectory: process.cwd()
|
||||
};
|
||||
};
|
||||
430
src/cli/tests/cli.integration.spec.ts
Normal file
430
src/cli/tests/cli.integration.spec.ts
Normal file
@@ -0,0 +1,430 @@
|
||||
/**
|
||||
* Tests in this file actually run the CLI and attempt to validate its behavior.
|
||||
* Git must be installed on the PATH of the testing machine.
|
||||
*
|
||||
* We hash every file in the directories after each test, and compare the hashes
|
||||
* to the "approved" hashes in this file.
|
||||
*
|
||||
* When making a change to this project, run the tests and note which files have
|
||||
* been modified. After manually reviewing the file for accuracy, simply update
|
||||
* the relevant hash below. You may find it helpful to view the differences
|
||||
* between a certain file in each test project. E.g.:
|
||||
*
|
||||
* `diff build/test-one/package.json build/test-two/package.json`
|
||||
*/
|
||||
|
||||
// tslint:disable:no-expression-statement
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import del from 'del';
|
||||
import execa from 'execa';
|
||||
import globby from 'globby';
|
||||
import md5File from 'md5-file';
|
||||
import meow from 'meow';
|
||||
import { join, relative } from 'path';
|
||||
import { cloneRepo, Placeholders, Tasks } from '../tasks';
|
||||
import { typescriptStarter } from '../typescript-starter';
|
||||
import { Runner } from '../utils';
|
||||
|
||||
/**
|
||||
* NOTE: many of the tests below validate file modification. The filesystem is
|
||||
* not mocked, and these tests make real changes. Proceed with caution.
|
||||
*
|
||||
* Filesystem changes made by these tests should be contained in the `build`
|
||||
* directory for easier clean up.
|
||||
*/
|
||||
|
||||
const repoURL = process.cwd();
|
||||
const buildDir = join(process.cwd(), 'build');
|
||||
|
||||
enum TestDirectories {
|
||||
one = 'test-1',
|
||||
two = 'test-2',
|
||||
three = 'test-3',
|
||||
four = 'test-4',
|
||||
five = 'test-5',
|
||||
six = 'test-6'
|
||||
}
|
||||
|
||||
// If the tests all pass, the TestDirectories will automatically be cleaned up.
|
||||
test.after(async () => {
|
||||
await del([
|
||||
`./build/${TestDirectories.one}`,
|
||||
`./build/${TestDirectories.two}`,
|
||||
`./build/${TestDirectories.three}`,
|
||||
`./build/${TestDirectories.four}`,
|
||||
`./build/${TestDirectories.five}`,
|
||||
`./build/${TestDirectories.six}`
|
||||
]);
|
||||
});
|
||||
|
||||
test('returns version', async t => {
|
||||
const expected = meow('').pkg.version;
|
||||
t.truthy(typeof expected === 'string');
|
||||
const { stdout } = await execa(`./bin/typescript-starter`, ['--version']);
|
||||
t.is(stdout, expected);
|
||||
});
|
||||
|
||||
test('returns help/usage', async t => {
|
||||
const { stdout } = await execa(`./bin/typescript-starter`, ['--help']);
|
||||
t.regex(stdout, /Usage/);
|
||||
});
|
||||
|
||||
test('errors if project name collides with an existing path', async t => {
|
||||
const existingDir = 'build';
|
||||
const error = await t.throws(
|
||||
execa(`./bin/typescript-starter`, [existingDir])
|
||||
);
|
||||
t.regex(error.stderr, /"build" path already exists/);
|
||||
});
|
||||
|
||||
test('errors if project name is not in kebab-case', async t => {
|
||||
const error = await t.throws(
|
||||
execa(`./bin/typescript-starter`, ['name with spaces'])
|
||||
);
|
||||
t.regex(error.stderr, /should be in-kebab-case/);
|
||||
});
|
||||
|
||||
async function hashAllTheThings(
|
||||
projectName: string,
|
||||
sandboxed = false
|
||||
): Promise<{ readonly [filename: string]: string }> {
|
||||
const projectDir = join(buildDir, projectName);
|
||||
const rawFilePaths: ReadonlyArray<string> = await globby(projectDir);
|
||||
const filePaths = sandboxed
|
||||
? rawFilePaths
|
||||
: rawFilePaths.filter(
|
||||
path =>
|
||||
// When not sandboxed, these files will change based on git config
|
||||
!['LICENSE', 'package.json'].includes(relative(projectDir, path))
|
||||
);
|
||||
const hashAll = filePaths.map<Promise<string>>(
|
||||
path =>
|
||||
new Promise<string>((resolve, reject) => {
|
||||
md5File(path, (err: Error, result: string) => {
|
||||
err ? reject(err) : resolve(result);
|
||||
});
|
||||
})
|
||||
);
|
||||
const hashes = await Promise.all(hashAll);
|
||||
return hashes.reduce<{ readonly [filename: string]: string }>(
|
||||
(acc, hash, i) => {
|
||||
const trimmedFilePath = relative(buildDir, filePaths[i]);
|
||||
return {
|
||||
...acc,
|
||||
[trimmedFilePath]: hash
|
||||
};
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
test(`${
|
||||
TestDirectories.one
|
||||
}: parses CLI arguments, handles default options`, async t => {
|
||||
const description = 'example description 1';
|
||||
const { stdout } = await execa(
|
||||
`../bin/typescript-starter`,
|
||||
[
|
||||
`${TestDirectories.one}`,
|
||||
// (user entered `-d='example description 1'`)
|
||||
`-d=${description}`,
|
||||
'--no-install'
|
||||
],
|
||||
{
|
||||
cwd: buildDir,
|
||||
env: {
|
||||
TYPESCRIPT_STARTER_REPO_URL: repoURL
|
||||
}
|
||||
}
|
||||
);
|
||||
t.regex(stdout, new RegExp(`Created ${TestDirectories.one} 🎉`));
|
||||
const map = await hashAllTheThings(TestDirectories.one);
|
||||
t.deepEqual(map, {
|
||||
'test-1/README.md': '7a9f4efa9213266c3800f3cc82a53ba7',
|
||||
'test-1/bin/typescript-starter': 'a4ad3923f37f50df986b43b1adb9f6b3',
|
||||
'test-1/src/index.ts': '5991bedc40ac87a01d880c6db16fe349',
|
||||
'test-1/src/lib/number.spec.ts': '40ebb014eb7871d1f810c618aba1d589',
|
||||
'test-1/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
|
||||
'test-1/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
|
||||
'test-1/tsconfig.json': '0e04adfce2f26c6473f079f6dabd108a',
|
||||
'test-1/tsconfig.module.json': '2fda4c8760c6cfa3462b40df0645850d',
|
||||
'test-1/tslint.json': '7ac167ffbcb724a6c270e8dc4e747067'
|
||||
});
|
||||
});
|
||||
|
||||
test(`${
|
||||
TestDirectories.two
|
||||
}: parses CLI arguments, handles all options`, async t => {
|
||||
const description = 'example description 2';
|
||||
const { stdout } = await execa(
|
||||
`../bin/typescript-starter`,
|
||||
[
|
||||
`${TestDirectories.two}`,
|
||||
// (user entered `--description 'example description 2'`)
|
||||
`--description`,
|
||||
`${description}`,
|
||||
'--yarn',
|
||||
'--node',
|
||||
'--dom',
|
||||
'--no-install'
|
||||
],
|
||||
{
|
||||
cwd: buildDir,
|
||||
env: {
|
||||
TYPESCRIPT_STARTER_REPO_URL: repoURL
|
||||
}
|
||||
}
|
||||
);
|
||||
t.regex(stdout, new RegExp(`Created ${TestDirectories.two} 🎉`));
|
||||
const map = await hashAllTheThings(TestDirectories.two);
|
||||
t.deepEqual(map, {
|
||||
'test-2/README.md': 'ddaf27da4cc4ca5225785f0ac8f4da58',
|
||||
'test-2/bin/typescript-starter': 'a4ad3923f37f50df986b43b1adb9f6b3',
|
||||
'test-2/src/index.ts': 'fbc67c2cbf3a7d37e4e02583bf06eec9',
|
||||
'test-2/src/lib/async.spec.ts': '1e83b84de3f3b068244885219acb42bd',
|
||||
'test-2/src/lib/async.ts': '9012c267bb25fa98ad2561929de3d4e2',
|
||||
'test-2/src/lib/hash.spec.ts': '87bfca3c0116fd86a353750fcf585ecf',
|
||||
'test-2/src/lib/hash.ts': 'a4c552897f25da5963f410e375264bd1',
|
||||
'test-2/src/lib/number.spec.ts': '40ebb014eb7871d1f810c618aba1d589',
|
||||
'test-2/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
|
||||
'test-2/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
|
||||
'test-2/tsconfig.json': '8a55379f60e4e6d4fad1f0b2318b74c4',
|
||||
'test-2/tsconfig.module.json': '2fda4c8760c6cfa3462b40df0645850d',
|
||||
'test-2/tslint.json': '7ac167ffbcb724a6c270e8dc4e747067'
|
||||
});
|
||||
});
|
||||
|
||||
const down = '\x1B\x5B\x42';
|
||||
const up = '\x1B\x5B\x41';
|
||||
const enter = '\x0D';
|
||||
const ms = (milliseconds: number) =>
|
||||
new Promise<void>(resolve => setTimeout(resolve, milliseconds));
|
||||
|
||||
async function testInteractive(
|
||||
t: ExecutionContext<{}>,
|
||||
projectName: string,
|
||||
entry: ReadonlyArray<string | ReadonlyArray<string>>
|
||||
): Promise<execa.ExecaReturns> {
|
||||
const typeDefs = entry[3] !== '';
|
||||
const proc = execa(`../bin/typescript-starter`, ['--no-install'], {
|
||||
cwd: buildDir,
|
||||
env: {
|
||||
TYPESCRIPT_STARTER_REPO_URL: repoURL
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: missing in Node.js type definition's ChildProcess.stdin?
|
||||
// https://nodejs.org/api/process.html#process_process_stdin
|
||||
// proc.stdin.setEncoding('utf8');
|
||||
|
||||
// tslint:disable-next-line:prefer-const no-let
|
||||
let buffer = '';
|
||||
const checkBuffer = (regex: RegExp) => t.regex(buffer, regex);
|
||||
const type = (input: string) => proc.stdin.write(input);
|
||||
const clearBuffer = () => (buffer = '');
|
||||
proc.stdout.on('data', (chunk: Buffer) => {
|
||||
buffer += chunk.toString();
|
||||
});
|
||||
|
||||
// wait for first chunk to be emitted
|
||||
await new Promise(resolve => {
|
||||
proc.stdout.once('data', resolve);
|
||||
});
|
||||
await ms(200);
|
||||
checkBuffer(
|
||||
new RegExp(`typescript-starter[\\s\\S]*Enter the new package name`)
|
||||
);
|
||||
clearBuffer();
|
||||
type(`${projectName}${enter}`);
|
||||
await ms(200);
|
||||
checkBuffer(new RegExp(`${projectName}[\\s\\S]*What are you making?`));
|
||||
clearBuffer();
|
||||
type(`${entry[0][0]}${enter}`);
|
||||
await ms(200);
|
||||
checkBuffer(
|
||||
new RegExp(`${entry[0][1]}[\\s\\S]*Enter the package description`)
|
||||
);
|
||||
clearBuffer();
|
||||
type(`${entry[1]}${enter}`);
|
||||
await ms(200);
|
||||
checkBuffer(new RegExp(`${entry[1]}[\\s\\S]*npm or yarn\\?`));
|
||||
clearBuffer();
|
||||
type(`${entry[2][0]}${enter}`);
|
||||
await ms(200);
|
||||
const search = `\\? ${entry[2][1]}`;
|
||||
const exp = typeDefs
|
||||
? new RegExp(`${search}`) // should match
|
||||
: new RegExp(`(?!${search})`); // should not match
|
||||
checkBuffer(exp);
|
||||
// tslint:disable-next-line:no-if-statement
|
||||
if (typeDefs) {
|
||||
clearBuffer();
|
||||
type(`${entry[3][0]}${enter}`);
|
||||
await ms(200);
|
||||
checkBuffer(new RegExp(`${entry[3][1]}[\\s\\S]*More fun stuff`));
|
||||
}
|
||||
clearBuffer();
|
||||
type(`${entry[4][0]}${enter}`);
|
||||
await ms(200);
|
||||
checkBuffer(new RegExp(`${entry[4][1]}`));
|
||||
return proc;
|
||||
}
|
||||
|
||||
test(`${
|
||||
TestDirectories.three
|
||||
}: interactive mode: javascript library`, async t => {
|
||||
t.plan(8);
|
||||
const proc = await testInteractive(t, `${TestDirectories.three}`, [
|
||||
[`${down}${up}${down}`, `Javascript library`],
|
||||
`integration test 3 description`,
|
||||
[`${down}${up}${down}${enter}`, `yarn`],
|
||||
[`${down}${down}${down}${enter}`, `Both Node.js and DOM`],
|
||||
[' ', 'stricter type-checking[\\s\\S]*tslint-immutable[\\s\\S]*VS Code']
|
||||
]);
|
||||
await proc;
|
||||
const map = await hashAllTheThings(TestDirectories.three);
|
||||
t.deepEqual(map, {
|
||||
'test-3/README.md': 'c52631ebf78f6b030af9a109b769b647',
|
||||
'test-3/bin/typescript-starter': 'a4ad3923f37f50df986b43b1adb9f6b3',
|
||||
'test-3/src/index.ts': 'fbc67c2cbf3a7d37e4e02583bf06eec9',
|
||||
'test-3/src/lib/async.spec.ts': '1e83b84de3f3b068244885219acb42bd',
|
||||
'test-3/src/lib/async.ts': '9012c267bb25fa98ad2561929de3d4e2',
|
||||
'test-3/src/lib/hash.spec.ts': '87bfca3c0116fd86a353750fcf585ecf',
|
||||
'test-3/src/lib/hash.ts': 'a4c552897f25da5963f410e375264bd1',
|
||||
'test-3/src/lib/number.spec.ts': '40ebb014eb7871d1f810c618aba1d589',
|
||||
'test-3/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
|
||||
'test-3/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
|
||||
'test-3/tsconfig.json': '43817952d399db9e44977b3703edd7cf',
|
||||
'test-3/tsconfig.module.json': '2fda4c8760c6cfa3462b40df0645850d',
|
||||
'test-3/tslint.json': '7ac167ffbcb724a6c270e8dc4e747067'
|
||||
});
|
||||
});
|
||||
|
||||
test(`${
|
||||
TestDirectories.four
|
||||
}: interactive mode: node.js application`, async t => {
|
||||
t.plan(7);
|
||||
const proc = await testInteractive(t, `${TestDirectories.four}`, [
|
||||
[`${down}${up}`, `Node.js application`],
|
||||
`integration test 4 description`,
|
||||
[`${down}${up}${enter}`, `npm`],
|
||||
'',
|
||||
[`${down} `, 'VS Code']
|
||||
]);
|
||||
await proc;
|
||||
const map = await hashAllTheThings(TestDirectories.four);
|
||||
t.deepEqual(map, {
|
||||
'test-4/README.md': 'a3e0699b39498df4843c9dde95f1e000',
|
||||
'test-4/bin/typescript-starter': 'a4ad3923f37f50df986b43b1adb9f6b3',
|
||||
'test-4/src/index.ts': '5991bedc40ac87a01d880c6db16fe349',
|
||||
'test-4/src/lib/number.spec.ts': '40ebb014eb7871d1f810c618aba1d589',
|
||||
'test-4/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
|
||||
'test-4/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
|
||||
'test-4/tsconfig.json': '0e04adfce2f26c6473f079f6dabd108a',
|
||||
'test-4/tsconfig.module.json': '2fda4c8760c6cfa3462b40df0645850d',
|
||||
'test-4/tslint.json': '99f6f8fa763bfc2a32377739b3e5dd5c'
|
||||
});
|
||||
});
|
||||
|
||||
const sandboxTasks = (
|
||||
t: ExecutionContext<{}>,
|
||||
commit: boolean,
|
||||
install: boolean
|
||||
): Tasks => {
|
||||
return {
|
||||
cloneRepo: cloneRepo(execa, true),
|
||||
initialCommit: async () => {
|
||||
commit ? t.pass() : t.fail();
|
||||
},
|
||||
install: async () => {
|
||||
install ? t.pass() : t.fail();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const sandboxOptions = {
|
||||
description: 'this is an example description',
|
||||
githubUsername: 'SOME_GITHUB_USERNAME',
|
||||
repoURL,
|
||||
workingDirectory: buildDir
|
||||
};
|
||||
|
||||
const silenceConsole = (console: any) => {
|
||||
// tslint:disable-next-line:no-object-mutation
|
||||
console.log = () => {
|
||||
// mock console.log to silence it
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
||||
test(`${
|
||||
TestDirectories.five
|
||||
}: Sandboxed: npm install, initial commit`, async t => {
|
||||
t.plan(3);
|
||||
const options = {
|
||||
...sandboxOptions,
|
||||
domDefinitions: false,
|
||||
email: 'email@example.com',
|
||||
fullName: 'Satoshi Nakamoto',
|
||||
immutable: true,
|
||||
install: true,
|
||||
nodeDefinitions: false,
|
||||
projectName: TestDirectories.five,
|
||||
runner: Runner.Npm,
|
||||
strict: true,
|
||||
vscode: false
|
||||
};
|
||||
silenceConsole(console);
|
||||
await typescriptStarter(options, sandboxTasks(t, true, true));
|
||||
const map = await hashAllTheThings(TestDirectories.five, true);
|
||||
t.deepEqual(map, {
|
||||
'test-5/LICENSE': 'd11b4dba04062af8bd80b052066daf1c',
|
||||
'test-5/README.md': '8fc7ecb21d7d47289e4b2469eea4db39',
|
||||
'test-5/bin/typescript-starter': 'a4ad3923f37f50df986b43b1adb9f6b3',
|
||||
'test-5/package.json': '350b37ef2d615462eeefc27d84d1eb21',
|
||||
'test-5/src/index.ts': '5991bedc40ac87a01d880c6db16fe349',
|
||||
'test-5/src/lib/number.spec.ts': '40ebb014eb7871d1f810c618aba1d589',
|
||||
'test-5/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
|
||||
'test-5/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
|
||||
'test-5/tsconfig.json': 'f36dc6407fc898f41a23cb620b2f4884',
|
||||
'test-5/tsconfig.module.json': '2fda4c8760c6cfa3462b40df0645850d',
|
||||
'test-5/tslint.json': '7ac167ffbcb724a6c270e8dc4e747067'
|
||||
});
|
||||
});
|
||||
|
||||
test(`${TestDirectories.six}: Sandboxed: yarn, no initial commit`, async t => {
|
||||
t.plan(2);
|
||||
const options = {
|
||||
...sandboxOptions,
|
||||
domDefinitions: true,
|
||||
email: Placeholders.email,
|
||||
fullName: Placeholders.name,
|
||||
immutable: true,
|
||||
install: true,
|
||||
nodeDefinitions: true,
|
||||
projectName: TestDirectories.six,
|
||||
runner: Runner.Yarn,
|
||||
strict: false,
|
||||
vscode: true
|
||||
};
|
||||
silenceConsole(console);
|
||||
await typescriptStarter(options, sandboxTasks(t, false, true));
|
||||
const map = await hashAllTheThings(TestDirectories.six, true);
|
||||
t.deepEqual(map, {
|
||||
'test-6/LICENSE': '1dfe8c78c6af40fc14ea3b40133f1fa5',
|
||||
'test-6/README.md': 'd809bcbf240f44b51b575a3d49936232',
|
||||
'test-6/bin/typescript-starter': 'a4ad3923f37f50df986b43b1adb9f6b3',
|
||||
'test-6/package.json': '635cf9c0e9fd16e9c29679108b8ebc3d',
|
||||
'test-6/src/index.ts': 'fbc67c2cbf3a7d37e4e02583bf06eec9',
|
||||
'test-6/src/lib/async.spec.ts': '1e83b84de3f3b068244885219acb42bd',
|
||||
'test-6/src/lib/async.ts': '9012c267bb25fa98ad2561929de3d4e2',
|
||||
'test-6/src/lib/hash.spec.ts': '87bfca3c0116fd86a353750fcf585ecf',
|
||||
'test-6/src/lib/hash.ts': 'a4c552897f25da5963f410e375264bd1',
|
||||
'test-6/src/lib/number.spec.ts': '40ebb014eb7871d1f810c618aba1d589',
|
||||
'test-6/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
|
||||
'test-6/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
|
||||
'test-6/tsconfig.json': '8a55379f60e4e6d4fad1f0b2318b74c4',
|
||||
'test-6/tsconfig.module.json': '2fda4c8760c6cfa3462b40df0645850d',
|
||||
'test-6/tslint.json': '7ac167ffbcb724a6c270e8dc4e747067'
|
||||
});
|
||||
});
|
||||
213
src/cli/tests/cli.unit.spec.ts
Normal file
213
src/cli/tests/cli.unit.spec.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
// tslint:disable:no-expression-statement
|
||||
import test from 'ava';
|
||||
import { ExecaStatic } from 'execa';
|
||||
import meow from 'meow';
|
||||
import nock from 'nock';
|
||||
import { checkArgs } from '../args';
|
||||
import {
|
||||
cloneRepo,
|
||||
getGithubUsername,
|
||||
getRepoUrl,
|
||||
getUserInfo,
|
||||
initialCommit,
|
||||
install,
|
||||
Placeholders
|
||||
} from '../tasks';
|
||||
import { getIntro, Runner } from '../utils';
|
||||
|
||||
test('errors if outdated', async t => {
|
||||
nock.disableNetConnect();
|
||||
nock('https://registry.npmjs.org:443')
|
||||
.get('/typescript-starter')
|
||||
.reply(200, {
|
||||
'dist-tags': { latest: '9000.0.1' },
|
||||
name: 'typescript-starter',
|
||||
versions: {
|
||||
'9000.0.1': {
|
||||
version: '9000.0.1'
|
||||
}
|
||||
}
|
||||
});
|
||||
const error = await t.throws(checkArgs);
|
||||
t.regex(error.message, /is outdated/);
|
||||
});
|
||||
|
||||
const passUpdateNotifier = (version: string) => {
|
||||
nock.disableNetConnect();
|
||||
nock('https://registry.npmjs.org:443')
|
||||
.get('/typescript-starter')
|
||||
.reply(200, {
|
||||
'dist-tags': { latest: version },
|
||||
name: 'typescript-starter',
|
||||
versions: {
|
||||
[version]: {
|
||||
version
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
test("doesn't error if not outdated", async t => {
|
||||
const currentVersion = meow('').pkg.version;
|
||||
t.truthy(typeof currentVersion === 'string');
|
||||
passUpdateNotifier(currentVersion);
|
||||
await t.notThrows(checkArgs);
|
||||
});
|
||||
|
||||
test('errors if update-notifier fails', async t => {
|
||||
nock.disableNetConnect();
|
||||
nock('https://registry.npmjs.org:443')
|
||||
.get('/typescript-starter')
|
||||
.reply(404, {});
|
||||
const error = await t.throws(checkArgs);
|
||||
t.regex(error.message, /doesn\'t exist/);
|
||||
});
|
||||
|
||||
test('checkArgs returns the right options', async t => {
|
||||
passUpdateNotifier('1.0.0');
|
||||
// tslint:disable-next-line:no-object-mutation
|
||||
process.argv = [
|
||||
'path/to/node',
|
||||
'path/to/typescript-starter',
|
||||
'example-project',
|
||||
`-description "example description"`,
|
||||
'--yarn',
|
||||
'--node',
|
||||
'--dom',
|
||||
'--no-install',
|
||||
'--strict',
|
||||
'--no-immutable',
|
||||
'--no-vscode'
|
||||
];
|
||||
const opts = await checkArgs();
|
||||
t.deepEqual(opts, {
|
||||
description: '',
|
||||
domDefinitions: true,
|
||||
immutable: false,
|
||||
install: false,
|
||||
nodeDefinitions: true,
|
||||
projectName: 'example-project',
|
||||
runner: Runner.Yarn,
|
||||
strict: true,
|
||||
vscode: false
|
||||
});
|
||||
});
|
||||
|
||||
test('checkArgs always returns { install } (so --no-install works in interactive mode)', async t => {
|
||||
passUpdateNotifier('1.0.0');
|
||||
// tslint:disable-next-line:no-object-mutation
|
||||
process.argv = ['path/to/node', 'path/to/typescript-starter'];
|
||||
const opts = await checkArgs();
|
||||
t.deepEqual(opts, { install: true });
|
||||
});
|
||||
|
||||
test('ascii art shows if stdout has 85+ columns', async t => {
|
||||
const jumbo = getIntro(100);
|
||||
const snippet = `| __| | | | '_ \\ / _ \\/ __|/ __| '__| | '_ \\|`;
|
||||
t.regex(jumbo, new RegExp(snippet));
|
||||
});
|
||||
|
||||
const mockErr = (code?: string | number) =>
|
||||
((() => {
|
||||
const err = new Error();
|
||||
// tslint:disable-next-line:no-object-mutation
|
||||
(err as any).code = code;
|
||||
throw err;
|
||||
}) as any) as ExecaStatic;
|
||||
|
||||
test('cloneRepo: errors when Git is not installed on PATH', async t => {
|
||||
const error = await t.throws(cloneRepo(mockErr('ENOENT'))('r', 'd', 'p'));
|
||||
t.regex(error.message, /Git is not installed on your PATH/);
|
||||
});
|
||||
|
||||
test('cloneRepo: throws when clone fails', async t => {
|
||||
const error = await t.throws(cloneRepo(mockErr(128))('r', 'd', 'p'));
|
||||
t.regex(error.message, /Git clone failed./);
|
||||
});
|
||||
|
||||
test('cloneRepo: throws when rev-parse fails', async t => {
|
||||
// tslint:disable-next-line:prefer-const no-let
|
||||
let calls = 0;
|
||||
const mock = ((async () => {
|
||||
calls++;
|
||||
return calls === 1 ? {} : (mockErr(128) as any)();
|
||||
}) as any) as ExecaStatic;
|
||||
const error = await t.throws(cloneRepo(mock)('r', 'd', 'p'));
|
||||
t.regex(error.message, /Git rev-parse failed./);
|
||||
});
|
||||
|
||||
test('getGithubUsername: returns found users', async t => {
|
||||
const mockFetcher = async (email: string) => email.split('@')[0];
|
||||
const username: string = await getGithubUsername(mockFetcher)(
|
||||
'bitjson@github.com'
|
||||
);
|
||||
t.is(username, 'bitjson');
|
||||
});
|
||||
|
||||
test("getGithubUsername: returns placeholder if user doesn't have Git user.email set", async t => {
|
||||
const mockFetcher = async () => t.fail();
|
||||
const username: string = await getGithubUsername(mockFetcher)(
|
||||
Placeholders.email
|
||||
);
|
||||
t.is(username, Placeholders.username);
|
||||
});
|
||||
|
||||
test('getGithubUsername: returns placeholder if not found', async t => {
|
||||
const mockFetcher = async () => {
|
||||
throw new Error();
|
||||
};
|
||||
const username: string = await getGithubUsername(mockFetcher)(
|
||||
'bitjson@github.com'
|
||||
);
|
||||
t.is(username, Placeholders.username);
|
||||
});
|
||||
|
||||
test('getUserInfo: suppresses errors and returns empty strings', async t => {
|
||||
const result = await getUserInfo(mockErr(1))();
|
||||
t.deepEqual(result, {
|
||||
gitEmail: Placeholders.email,
|
||||
gitName: Placeholders.name
|
||||
});
|
||||
});
|
||||
|
||||
test('getUserInfo: returns results properly', async t => {
|
||||
const mock = ((async () => {
|
||||
return {
|
||||
stdout: 'result'
|
||||
};
|
||||
}) as any) as ExecaStatic;
|
||||
const result = await getUserInfo(mock)();
|
||||
t.deepEqual(result, {
|
||||
gitEmail: 'result',
|
||||
gitName: 'result'
|
||||
});
|
||||
});
|
||||
|
||||
test('initialCommit: throws generated errors', async t => {
|
||||
const error = await t.throws(initialCommit(mockErr(1))('deadbeef', 'fail'));
|
||||
t.is(error.code, 1);
|
||||
});
|
||||
|
||||
test('initialCommit: spawns 3 times', async t => {
|
||||
t.plan(4);
|
||||
const mock = ((async () => {
|
||||
t.pass();
|
||||
}) as any) as ExecaStatic;
|
||||
await t.notThrows(initialCommit(mock)('commit', 'dir'));
|
||||
});
|
||||
|
||||
test('install: uses the correct runner', async t => {
|
||||
const mock = ((async (runner: Runner) => {
|
||||
runner === Runner.Yarn ? t.pass() : t.fail();
|
||||
}) as any) as ExecaStatic;
|
||||
await install(mock)(Runner.Yarn, 'pass');
|
||||
});
|
||||
|
||||
test('install: throws pretty error on failure', async t => {
|
||||
const error = await t.throws(install(mockErr())(Runner.Npm, 'fail'));
|
||||
t.is(error.message, "Installation failed. You'll need to install manually.");
|
||||
});
|
||||
|
||||
test("getRepoUrl: returns GitHub repo when TYPESCRIPT_STARTER_REPO_URL isn't set", async t => {
|
||||
t.is(getRepoUrl(), 'https://github.com/bitjson/typescript-starter.git');
|
||||
});
|
||||
256
src/cli/typescript-starter.ts
Normal file
256
src/cli/typescript-starter.ts
Normal file
@@ -0,0 +1,256 @@
|
||||
// tslint:disable:no-console no-if-statement no-expression-statement
|
||||
import chalk from 'chalk';
|
||||
import del from 'del';
|
||||
import { readFileSync, renameSync, writeFileSync } from 'fs';
|
||||
import ora from 'ora';
|
||||
import { join } from 'path';
|
||||
import replace from 'replace-in-file';
|
||||
import { Placeholders, Tasks } from './tasks';
|
||||
import { Runner, TypescriptStarterOptions } from './utils';
|
||||
|
||||
export async function typescriptStarter(
|
||||
{
|
||||
description,
|
||||
domDefinitions,
|
||||
email,
|
||||
fullName,
|
||||
githubUsername,
|
||||
immutable,
|
||||
install,
|
||||
nodeDefinitions,
|
||||
projectName,
|
||||
repoURL,
|
||||
runner,
|
||||
strict,
|
||||
vscode,
|
||||
workingDirectory
|
||||
}: TypescriptStarterOptions,
|
||||
tasks: Tasks
|
||||
): Promise<void> {
|
||||
console.log();
|
||||
const { commitHash, gitHistoryDir } = await tasks.cloneRepo(
|
||||
repoURL,
|
||||
workingDirectory,
|
||||
projectName
|
||||
);
|
||||
await del([gitHistoryDir]);
|
||||
console.log(`
|
||||
${chalk.dim(`Cloned at commit: ${commitHash}`)}
|
||||
`);
|
||||
|
||||
const spinnerPackage = ora('Updating package.json').start();
|
||||
const projectPath = join(workingDirectory, projectName);
|
||||
const pkgPath = join(projectPath, 'package.json');
|
||||
|
||||
const keptDevDeps: ReadonlyArray<string> = [
|
||||
'ava',
|
||||
'codecov',
|
||||
'cz-conventional-changelog',
|
||||
'gh-pages',
|
||||
'npm-run-all',
|
||||
'npm-scripts-info',
|
||||
'nsp',
|
||||
'nyc',
|
||||
'opn-cli',
|
||||
'prettier',
|
||||
'standard-version',
|
||||
'trash-cli',
|
||||
'tslint',
|
||||
'tslint-config-prettier',
|
||||
'tslint-immutable',
|
||||
'typedoc',
|
||||
'typescript'
|
||||
];
|
||||
|
||||
// dependencies to retain for Node.js applications
|
||||
const nodeKeptDeps: ReadonlyArray<string> = ['sha.js'];
|
||||
|
||||
const filterAllBut = (
|
||||
keep: ReadonlyArray<string>,
|
||||
from: { readonly [module: string]: number }
|
||||
) =>
|
||||
keep.reduce<{ readonly [module: string]: number }>(
|
||||
(acc, moduleName: string) => {
|
||||
return { ...acc, [moduleName]: from[moduleName] };
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const pkg = readPackageJson(pkgPath);
|
||||
const newPkg = {
|
||||
...pkg,
|
||||
dependencies: nodeDefinitions
|
||||
? filterAllBut(nodeKeptDeps, pkg.dependencies)
|
||||
: {},
|
||||
description,
|
||||
devDependencies: filterAllBut(keptDevDeps, pkg.devDependencies),
|
||||
keywords: [],
|
||||
repository: `https://github.com/${githubUsername}/${projectName}`,
|
||||
scripts:
|
||||
runner === Runner.Yarn
|
||||
? {
|
||||
...pkg.scripts,
|
||||
preinstall: `node -e \"if(process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('${projectName} must be installed with Yarn: https://yarnpkg.com/')\"`
|
||||
}
|
||||
: { ...pkg.scripts },
|
||||
version: '1.0.0'
|
||||
};
|
||||
|
||||
// tslint:disable:no-delete no-object-mutation
|
||||
delete newPkg.bin;
|
||||
delete newPkg.NOTE;
|
||||
// tslint:enable:no-delete no-object-mutation
|
||||
|
||||
writePackageJson(pkgPath, newPkg);
|
||||
spinnerPackage.succeed();
|
||||
|
||||
const spinnerGitignore = ora('Updating .gitignore').start();
|
||||
if (runner === Runner.Yarn) {
|
||||
await replace({
|
||||
files: join(projectPath, '.gitignore'),
|
||||
from: 'yarn.lock',
|
||||
to: 'package-lock.json'
|
||||
});
|
||||
}
|
||||
spinnerGitignore.succeed();
|
||||
|
||||
const spinnerNpmignore = ora('Updating .npmignore').start();
|
||||
await replace({
|
||||
files: join(projectPath, '.npmignore'),
|
||||
from: 'examples\n',
|
||||
to: ''
|
||||
});
|
||||
spinnerNpmignore.succeed();
|
||||
|
||||
const spinnerLicense = ora('Updating LICENSE').start();
|
||||
await replace({
|
||||
files: join(projectPath, 'LICENSE'),
|
||||
from: 'Jason Dreyzehner',
|
||||
to: fullName
|
||||
});
|
||||
spinnerLicense.succeed();
|
||||
|
||||
const spinnerDelete = ora('Deleting unnecessary files').start();
|
||||
|
||||
await del([
|
||||
join(projectPath, 'examples'),
|
||||
join(projectPath, 'CHANGELOG.md'),
|
||||
join(projectPath, 'README.md'),
|
||||
join(projectPath, 'package-lock.json'),
|
||||
join(projectPath, 'src', 'cli'),
|
||||
join(projectPath, 'src', 'types', 'cli.d.ts')
|
||||
]);
|
||||
if (!vscode) {
|
||||
del([join(projectPath, '.vscode')]);
|
||||
}
|
||||
spinnerDelete.succeed();
|
||||
|
||||
const spinnerTsconfigModule = ora('Removing traces of the CLI').start();
|
||||
await replace({
|
||||
files: join(projectPath, 'tsconfig.module.json'),
|
||||
from: /,\s+\/\/ typescript-starter:[\s\S]*"src\/cli\/\*\*\/\*\.ts"/,
|
||||
to: ''
|
||||
});
|
||||
if (vscode) {
|
||||
await replace({
|
||||
files: join(projectPath, '.vscode', 'launch.json'),
|
||||
from: /,[\s]*\/\/ --- cut here ---[\s\S]*]/,
|
||||
to: ']'
|
||||
});
|
||||
}
|
||||
spinnerTsconfigModule.succeed();
|
||||
|
||||
const spinnerReadme = ora('Creating README.md').start();
|
||||
renameSync(
|
||||
join(projectPath, 'README-starter.md'),
|
||||
join(projectPath, 'README.md')
|
||||
);
|
||||
await replace({
|
||||
files: join(projectPath, 'README.md'),
|
||||
from: '[package-name]',
|
||||
to: projectName
|
||||
});
|
||||
await replace({
|
||||
files: join(projectPath, 'README.md'),
|
||||
from: '[description]',
|
||||
to: description
|
||||
});
|
||||
spinnerReadme.succeed();
|
||||
|
||||
if (!strict) {
|
||||
const spinnerStrict = ora(`tsconfig: disable strict`).start();
|
||||
await replace({
|
||||
files: join(projectPath, 'tsconfig.json'),
|
||||
from: '"strict": true',
|
||||
to: '// "strict": true'
|
||||
});
|
||||
spinnerStrict.succeed();
|
||||
}
|
||||
|
||||
if (!domDefinitions) {
|
||||
const spinnerDom = ora(`tsconfig: don't include "dom" lib`).start();
|
||||
await replace({
|
||||
files: join(projectPath, 'tsconfig.json'),
|
||||
from: '"lib": ["es2017", "dom"]',
|
||||
to: '"lib": ["es2017"]'
|
||||
});
|
||||
spinnerDom.succeed();
|
||||
}
|
||||
|
||||
if (!nodeDefinitions) {
|
||||
const spinnerNode = ora(`tsconfig: don't include "node" types`).start();
|
||||
await replace({
|
||||
files: join(projectPath, 'tsconfig.json'),
|
||||
from: '"types": ["node"]',
|
||||
to: '"types": []'
|
||||
});
|
||||
await replace({
|
||||
files: join(projectPath, 'src', 'index.ts'),
|
||||
from: `export * from './lib/hash';\n`,
|
||||
to: ''
|
||||
});
|
||||
await del([
|
||||
join(projectPath, 'src', 'lib', 'hash.ts'),
|
||||
join(projectPath, 'src', 'lib', 'hash.spec.ts'),
|
||||
join(projectPath, 'src', 'lib', 'async.ts'),
|
||||
join(projectPath, 'src', 'lib', 'async.spec.ts')
|
||||
]);
|
||||
spinnerNode.succeed();
|
||||
}
|
||||
|
||||
if (!immutable) {
|
||||
const spinnerTslint = ora(`tslint: disable tslint-immutable`).start();
|
||||
await replace({
|
||||
files: join(projectPath, 'tslint.json'),
|
||||
from: /,[\s]*\/\* tslint-immutable rules \*\/[\s\S]*\/\* end tslint-immutable rules \*\//,
|
||||
to: ''
|
||||
});
|
||||
spinnerTslint.succeed();
|
||||
}
|
||||
|
||||
if (install) {
|
||||
await tasks.install(runner, projectPath);
|
||||
}
|
||||
|
||||
const gitIsConfigured =
|
||||
fullName !== Placeholders.name && email !== Placeholders.email
|
||||
? true
|
||||
: false;
|
||||
if (gitIsConfigured) {
|
||||
const spinnerGitInit = ora(`Initializing git repository...`).start();
|
||||
await tasks.initialCommit(commitHash, projectPath, fullName);
|
||||
spinnerGitInit.succeed();
|
||||
}
|
||||
|
||||
console.log(`\n${chalk.blue.bold(`Created ${projectName} 🎉`)}\n`);
|
||||
}
|
||||
|
||||
const readPackageJson = (path: string) =>
|
||||
JSON.parse(readFileSync(path, 'utf8'));
|
||||
|
||||
const writePackageJson = (path: string, pkg: any) => {
|
||||
// write using the same format as npm:
|
||||
// https://github.com/npm/npm/blob/latest/lib/install/update-package-json.js#L48
|
||||
const stringified = JSON.stringify(pkg, null, 2) + '\n';
|
||||
return writeFileSync(path, stringified);
|
||||
};
|
||||
54
src/cli/utils.ts
Normal file
54
src/cli/utils.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import chalk from 'chalk';
|
||||
import { existsSync } from 'fs';
|
||||
import gradient from 'gradient-string';
|
||||
export enum Runner {
|
||||
Npm = 'npm',
|
||||
Yarn = 'yarn'
|
||||
}
|
||||
|
||||
export interface TypescriptStarterUserOptions {
|
||||
readonly description: string;
|
||||
readonly domDefinitions: boolean;
|
||||
readonly immutable: boolean;
|
||||
readonly install: boolean;
|
||||
readonly nodeDefinitions: boolean;
|
||||
readonly projectName: string;
|
||||
readonly runner: Runner;
|
||||
readonly strict: boolean;
|
||||
readonly vscode: boolean;
|
||||
}
|
||||
|
||||
export interface TypescriptStarterInferredOptions {
|
||||
readonly githubUsername: string;
|
||||
readonly fullName: string;
|
||||
readonly email: string;
|
||||
readonly repoURL: string;
|
||||
readonly workingDirectory: string;
|
||||
}
|
||||
|
||||
export interface TypescriptStarterOptions
|
||||
extends TypescriptStarterUserOptions,
|
||||
TypescriptStarterInferredOptions {}
|
||||
|
||||
export function validateName(input: string): true | string {
|
||||
return !/^\s*[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\s*$/.test(input)
|
||||
? 'Name should be in-kebab-case'
|
||||
: existsSync(input)
|
||||
? `The "${input}" path already exists in this directory.`
|
||||
: true;
|
||||
}
|
||||
|
||||
export function getIntro(columns: number | undefined): string {
|
||||
const ascii = `
|
||||
_ _ _ _ _
|
||||
| |_ _ _ _ __ ___ ___ ___ _ __(_)_ __ | |_ ___| |_ __ _ _ __| |_ ___ _ __
|
||||
| __| | | | '_ \\ / _ \\/ __|/ __| '__| | '_ \\| __|____/ __| __/ _\` | '__| __/ _ \\ '__|
|
||||
| |_| |_| | |_) | __/\\__ \\ (__| | | | |_) | ||_____\\__ \\ || (_| | | | || __/ |
|
||||
\\__|\\__, | .__/ \\___||___/\\___|_| |_| .__/ \\__| |___/\\__\\__,_|_| \\__\\___|_|
|
||||
|___/|_| |_|
|
||||
`;
|
||||
|
||||
return columns && columns >= 85
|
||||
? chalk.bold(gradient.mind(ascii))
|
||||
: `\n${chalk.cyan.bold.underline('typescript-starter')}\n`;
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './lib/async'
|
||||
export * from './lib/hash'
|
||||
export * from './lib/number'
|
||||
export * from './lib/async';
|
||||
export * from './lib/hash';
|
||||
export * from './lib/number';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { test } from 'ava'
|
||||
import { asyncABC } from 'typescript-starter'
|
||||
// tslint:disable:no-expression-statement
|
||||
import { test } from 'ava';
|
||||
import { asyncABC } from './async';
|
||||
|
||||
test('getABC', async t => {
|
||||
t.deepEqual(await asyncABC(), ['a','b', 'c'])
|
||||
})
|
||||
t.deepEqual(await asyncABC(), ['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
@@ -17,16 +17,16 @@
|
||||
*
|
||||
* @returns a Promise which should contain `['a','b','c']`
|
||||
*/
|
||||
export async function asyncABC () {
|
||||
function somethingSlow (index: 0 | 1 | 2) {
|
||||
let storage = 'abc'.charAt(index)
|
||||
return new Promise<string>(resolve => {
|
||||
// here we pretend to wait on the network
|
||||
setTimeout(() => resolve(storage), 0)
|
||||
})
|
||||
export async function asyncABC(): Promise<ReadonlyArray<string>> {
|
||||
function somethingSlow(index: 0 | 1 | 2): Promise<string> {
|
||||
const storage = 'abc'.charAt(index);
|
||||
return new Promise<string>(resolve =>
|
||||
// later...
|
||||
resolve(storage)
|
||||
);
|
||||
}
|
||||
let a = await somethingSlow(0)
|
||||
let b = await somethingSlow(1)
|
||||
let c = await somethingSlow(2)
|
||||
return [a, b, c]
|
||||
const a = await somethingSlow(0);
|
||||
const b = await somethingSlow(1);
|
||||
const c = await somethingSlow(2);
|
||||
return [a, b, c];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
import { test } from 'ava'
|
||||
import { sha256 } from 'typescript-starter'
|
||||
// tslint:disable:no-expression-statement no-object-mutation
|
||||
import { Macro, test } from 'ava';
|
||||
import { sha256, sha256Native } from './hash';
|
||||
|
||||
test('sha256', t => {
|
||||
t.deepEqual(sha256('test'), '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08')
|
||||
})
|
||||
const hash: Macro = (t, input: string, expected: string) => {
|
||||
t.is(sha256(input), expected);
|
||||
t.is(sha256Native(input), expected);
|
||||
};
|
||||
|
||||
hash.title = (providedTitle: string, input: string, expected: string) =>
|
||||
`${providedTitle}: ${input} => ${expected}`;
|
||||
|
||||
test(
|
||||
'sha256',
|
||||
hash,
|
||||
'test',
|
||||
'9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
|
||||
);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { createHash } from 'crypto'
|
||||
import { createHash } from 'crypto';
|
||||
import shaJs from 'sha.js';
|
||||
|
||||
/**
|
||||
* Calculate the sha256 digest of a string. On Node.js, this will use the native module, in the browser, it will fall back to a pure javascript implementation.
|
||||
* Calculate the sha256 digest of a string.
|
||||
*
|
||||
* ### Example (es imports)
|
||||
* ```js
|
||||
@@ -12,6 +13,26 @@ import { createHash } from 'crypto'
|
||||
*
|
||||
* @returns sha256 message digest
|
||||
*/
|
||||
export function sha256 (message: string) {
|
||||
return createHash('sha256').update(message).digest('hex')
|
||||
export function sha256(message: string): string {
|
||||
return shaJs('sha256')
|
||||
.update(message)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* A faster implementation of [[sha256]] which requires the native Node.js module. Browser consumers should use [[sha256]], instead.
|
||||
*
|
||||
* ### Example (es imports)
|
||||
* ```js
|
||||
* import { sha256Native as sha256 } from 'typescript-starter'
|
||||
* sha256('test')
|
||||
* // => '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
|
||||
* ```
|
||||
*
|
||||
* @returns sha256 message digest
|
||||
*/
|
||||
export function sha256Native(message: string): string {
|
||||
return createHash('sha256')
|
||||
.update(message)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { test } from 'ava'
|
||||
import { double, power } from 'typescript-starter'
|
||||
// tslint:disable:no-expression-statement
|
||||
import { test } from 'ava';
|
||||
import { double, power } from './number';
|
||||
|
||||
test('double', t => {
|
||||
t.deepEqual(double(2), 4)
|
||||
})
|
||||
t.is(double(2), 4);
|
||||
});
|
||||
|
||||
test('power', t => {
|
||||
t.deepEqual(power(2,4), 16)
|
||||
})
|
||||
t.is(power(2, 4), 16);
|
||||
});
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
* @returns Comment describing the return type.
|
||||
* @anotherNote Some other value.
|
||||
*/
|
||||
export function double (value: number) {
|
||||
return value * 2
|
||||
export function double(value: number): number {
|
||||
return value * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,7 +40,7 @@ export function double (value: number) {
|
||||
* // => 8
|
||||
* ```
|
||||
*/
|
||||
export function power (base: number, exponent: number) {
|
||||
export function power(base: number, exponent: number): number {
|
||||
// This is a proposed es7 operator, which should be transpiled by Typescript
|
||||
return base ** exponent
|
||||
return base ** exponent;
|
||||
}
|
||||
|
||||
7
src/types/cli.d.ts
vendored
Normal file
7
src/types/cli.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// We develop typescript-starter with the `strict` compiler option to ensure it
|
||||
// works out of the box for downstream users. This file is deleted by the CLI,
|
||||
// so its purpose is just to squelch noImplicitAny errors.
|
||||
declare module 'github-username';
|
||||
declare module 'gradient-string';
|
||||
declare module 'md5-file';
|
||||
declare module 'replace-in-file';
|
||||
34
src/types/example.d.ts
vendored
Normal file
34
src/types/example.d.ts
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* If you import a dependency which does not include its own type definitions,
|
||||
* TypeScript will try to find a definition for it by following the `typeRoots`
|
||||
* compiler option in tsconfig.json. For this project, we've configured it to
|
||||
* fall back to this folder if nothing is found in node_modules/@types.
|
||||
*
|
||||
* Often, you can install the DefinitelyTyped
|
||||
* (https://github.com/DefinitelyTyped/DefinitelyTyped) type definition for the
|
||||
* dependency in question. However, if no one has yet contributed definitions
|
||||
* for the package, you may want to declare your own. (If you're using the
|
||||
* `noImplicitAny` compiler options, you'll be required to declare it.)
|
||||
*
|
||||
* This is an example type definition for the `sha.js` package, used in hash.ts.
|
||||
*
|
||||
* (This definition was primarily extracted from:
|
||||
* https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/v8/index.d.ts
|
||||
*/
|
||||
declare module 'sha.js' {
|
||||
export default function shaJs(algorithm: string): Hash;
|
||||
|
||||
type Utf8AsciiLatin1Encoding = 'utf8' | 'ascii' | 'latin1';
|
||||
type HexBase64Latin1Encoding = 'latin1' | 'hex' | 'base64';
|
||||
|
||||
export interface Hash extends NodeJS.ReadWriteStream {
|
||||
// tslint:disable:no-method-signature
|
||||
update(
|
||||
data: string | Buffer | DataView,
|
||||
inputEncoding?: Utf8AsciiLatin1Encoding
|
||||
): Hash;
|
||||
digest(): Buffer;
|
||||
digest(encoding: HexBase64Latin1Encoding): string;
|
||||
// tslint:enable:no-method-signature
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,45 @@
|
||||
{
|
||||
"extends": "./config/tsconfig.flexible", // also available: "./config/tsconfig.strict"
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"target": "es2017",
|
||||
"outDir": "build/main",
|
||||
"rootDir": "src",
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"importHelpers": true,
|
||||
"inlineSourceMap": true,
|
||||
"listFiles": false,
|
||||
"traceResolution": false,
|
||||
"pretty": true,
|
||||
"lib" : [
|
||||
"es6"
|
||||
],
|
||||
"types" : [
|
||||
"node"
|
||||
],
|
||||
"baseUrl": ".", // required for "paths"
|
||||
"paths": {
|
||||
"typescript-starter": ["src/index.ts"] // write tests without relative paths
|
||||
}
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
// "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||
// "strictNullChecks": true /* Enable strict null checks. */,
|
||||
// "strictFunctionTypes": true /* Enable strict checking of function types. */,
|
||||
// "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */,
|
||||
// "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
|
||||
// "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */,
|
||||
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": true /* Report errors on unused locals. */,
|
||||
"noUnusedParameters": true /* Report errors on unused parameters. */,
|
||||
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
|
||||
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
|
||||
|
||||
/* Debugging Options */
|
||||
"traceResolution": false /* Report module resolution log messages. */,
|
||||
"listEmittedFiles": false /* Print names of generated files part of the compilation. */,
|
||||
"listFiles": false /* Print names of files part of the compilation. */,
|
||||
"pretty": true /* Stylize errors and messages using color and context. */,
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
|
||||
// "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */,
|
||||
|
||||
"lib": ["es2017", "dom"],
|
||||
"types": ["node"],
|
||||
"typeRoots": ["node_modules/@types", "src/types"]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules/**"
|
||||
],
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules/**"],
|
||||
"compileOnSave": false
|
||||
}
|
||||
|
||||
14
tsconfig.module.json
Normal file
14
tsconfig.module.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"outDir": "build/module",
|
||||
"module": "esnext"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules/**",
|
||||
// typescript-starter: exclude CLI from module build, since it's exclusively
|
||||
// for Node.js. (This should also be stripped from the generated project.)
|
||||
"src/cli/**/*.ts"
|
||||
]
|
||||
}
|
||||
33
tslint.json
33
tslint.json
@@ -1,3 +1,34 @@
|
||||
{
|
||||
"extends": "tslint-config-standard"
|
||||
"extends": ["tslint:latest", "tslint-config-prettier", "tslint-immutable"],
|
||||
"rules": {
|
||||
"interface-name": [true, "never-prefix"],
|
||||
// TODO: allow devDependencies only in **/*.spec.ts files:
|
||||
// waiting on https://github.com/palantir/tslint/pull/3708
|
||||
"no-implicit-dependencies": [true, "dev"],
|
||||
|
||||
/* tslint-immutable rules */
|
||||
// Recommended built-in rules
|
||||
"no-var-keyword": true,
|
||||
"no-parameter-reassignment": true,
|
||||
"typedef": [true, "call-signature"],
|
||||
|
||||
// Immutability rules
|
||||
"readonly-keyword": true,
|
||||
"readonly-array": true,
|
||||
"no-let": true,
|
||||
"no-object-mutation": true,
|
||||
"no-delete": true,
|
||||
"no-method-signature": true,
|
||||
|
||||
// Functional style rules
|
||||
"no-this": true,
|
||||
"no-class": true,
|
||||
"no-mixed-interface": true,
|
||||
"no-expression-statement": [
|
||||
true,
|
||||
{ "ignore-prefix": ["console.", "process.exit"] }
|
||||
],
|
||||
"no-if-statement": true
|
||||
/* end tslint-immutable rules */
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user