1
0
mirror of synced 2025-11-08 04:48:04 +00:00

feat: TS v4, tslint -> eslint, add cspell support (#240)

Update all dependencies, migrate from tslint (deprecated) to typescript-eslint, and add support for
cspell.

BREAKING CHANGE: migrated from tslint (deprecated) to eslint.
This commit is contained in:
Jason Dreyzehner
2020-09-01 19:13:25 -04:00
committed by GitHub
parent ab50e80ec8
commit 390041b510
31 changed files with 6526 additions and 4298 deletions

View File

@@ -4,7 +4,6 @@ jobs:
'node-10':
docker:
- image: circleci/node:10
working_directory: ~/typescript-starter
steps:
- checkout
# Download and cache dependencies
@@ -21,10 +20,26 @@ jobs:
- run: npm test
- run: npm run cov:send
- run: npm run cov:check
'node-12':
docker:
- image: circleci/node:12
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
- v1-dependencies-
- run: npm install
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
- run: npm test
- run: npm run cov:send
- run: npm run cov:check
'node-latest':
docker:
- image: circleci/node:latest
working_directory: ~/typescript-starter
steps:
- checkout
- restore_cache:
@@ -45,4 +60,5 @@ workflows:
build:
jobs:
- 'node-10'
- 'node-12'
- 'node-latest'

33
.cspell.json Normal file
View File

@@ -0,0 +1,33 @@
{
"version": "0.1",
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/master/cspell.schema.json",
"language": "en",
"words": [
"bitjson",
"bitauth",
"cimg",
"circleci",
"codecov",
"commitlint",
"dependabot",
"editorconfig",
"execa",
"exponentiate",
"globby",
"libauth",
"mkdir",
"prettierignore",
"sandboxed",
"transpiled",
"typedoc",
"untracked"
],
"flagWords": [],
"ignorePaths": [
"package.json",
"package-lock.json",
"yarn.lock",
"tsconfig.json",
"node_modules/**"
]
}

34
.eslintrc.json Normal file
View File

@@ -0,0 +1,34 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": { "project": "./tsconfig.json" },
"env": { "es6": true },
"ignorePatterns": ["node_modules", "build", "coverage"],
"plugins": ["import", "eslint-comments", "functional"],
"extends": [
"eslint:recommended",
"plugin:eslint-comments/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/typescript",
"plugin:functional/lite",
"prettier",
"prettier/@typescript-eslint"
],
"globals": { "BigInt": true, "console": true, "WebAssembly": true },
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off",
"eslint-comments/disable-enable-pair": [
"error",
{ "allowWholeFile": true }
],
"eslint-comments/no-unused-disable": "error",
"import/order": [
"error",
{ "newlines-between": "always", "alphabetize": { "order": "asc" } }
],
"sort-imports": [
"error",
{ "ignoreDeclarationSort": true, "ignoreCase": true }
]
}
}

View File

@@ -1,11 +1,9 @@
* **I'm submitting a ...**
[ ] bug report
[ ] feature request
[ ] question about the decisions made in the repository
[ ] question about how to use this project
- **I'm submitting a ...**
[ ] bug report
[ ] feature request
[ ] question about the decisions made in the repository
[ ] question about how to use this project
* **Summary**
- **Summary**
* **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.)
- **Other information** (e.g. detailed explanation, stack traces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.)

View File

@@ -1,13 +1,7 @@
* **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
- **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
- **What is the current behavior?** (You can also link to an open issue here)
- **What is the new behavior (if this is a feature change)?**
* **What is the current behavior?** (You can also link to an open issue here)
* **What is the new behavior (if this is a feature change)?**
* **Other information**:
- **Other information**:

View File

@@ -1,14 +0,0 @@
src
test
tsconfig.json
tsconfig.module.json
tslint.json
.travis.yml
.github
.prettierignore
.vscode
build/docs
**/*.spec.*
coverage
.nyc_output
*.log

52
.vscode/debug-ts.js vendored
View File

@@ -1,52 +0,0 @@
'use strict';
const meow = require('meow');
const path = require('path');
const tsFile = getTSFile();
const jsFile = TS2JS(tsFile);
replaceCLIArg(tsFile, jsFile);
// Ava debugger
require('ava/profile');
/**
* get ts file path from CLI args
*
* @return string path
*/
function getTSFile() {
const cli = meow();
return cli.input[0];
}
/**
* get associated compiled js file path
*
* @param tsFile path
* @return string path
*/
function TS2JS(tsFile) {
const srcFolder = path.join(__dirname, '..', 'src');
const distFolder = path.join(__dirname, '..', 'build', 'main');
const tsPathObj = path.parse(tsFile);
return path.format({
dir: tsPathObj.dir.replace(srcFolder, distFolder),
ext: '.js',
name: tsPathObj.name,
root: tsPathObj.root
});
}
/**
* replace a value in CLI args
*
* @param search value to search
* @param replace value to replace
* @return void
*/
function replaceCLIArg(search, replace) {
process.argv[process.argv.indexOf(search)] = replace;
}

8
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"eamodio.gitlens",
"streetsidesoftware.code-spell-checker",
]
}

100
.vscode/launch.json vendored
View File

@@ -1,84 +1,44 @@
{
"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"
},
"configurations": [
// To debug, make sure a *.spec.ts file is active in the editor, then run a configuration
{
"type": "node",
"request": "launch",
"name": "Debug Spec",
"program": "${workspaceRoot}/.vscode/debug-ts.js",
"args": ["${file}"],
"name": "Debug Active Spec",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ava",
"runtimeArgs": ["debug", "--break", "--serial", "${file}"],
"port": 9229,
"outputCapture": "std",
"skipFiles": ["<node_internals>/**/*.js"],
// Consider using `npm run watch` or `yarn watch` for faster debugging
// "preLaunchTask": "npm: build",
// "smartStep": true,
"runtimeArgs": ["--nolazy"]
"preLaunchTask": "npm: build"
// "smartStep": true
},
{
// Use this one if you're already running `yarn watch`
"type": "node",
"request": "launch",
"name": "Debug Active Spec (no build)",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ava",
"runtimeArgs": ["debug", "--break", "--serial", "${file}"],
"port": 9229,
"outputCapture": "std",
"skipFiles": ["<node_internals>/**/*.js"]
// "smartStep": true
},
// --- 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"]
"name": "Try CLI",
"program": "${workspaceFolder}/bin/typescript-starter",
"cwd": "${workspaceFolder}/build",
"env": {
"TYPESCRIPT_STARTER_REPO_URL": "${workspaceFolder}"
},
"args": ["debug"],
"skipFiles": ["<node_internals>/**/*.js"]
}
]
}
}

View File

@@ -1,5 +1,7 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
// "typescript.implementationsCodeLens.enabled": true
// "typescript.referencesCodeLens.enabled": true
"cSpell.userWords": [], // only use words from .cspell.json
"cSpell.enabled": true,
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}

109
README.md
View File

@@ -25,20 +25,20 @@ The interactive CLI will help you create and configure your project automaticall
# 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 and flexible [typescript configurations](config/tsconfig.json) available
- 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 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/)
- 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 [`typescript-eslint`](https://github.com/typescript-eslint/typescript-eslint) and [Prettier](https://prettier.io/)
## But first, a good editor
@@ -46,7 +46,7 @@ Before you start, consider using an [editor with good typescript support](https:
[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.
Also consider installing editor extensions for [ESLint](https://github.com/Microsoft/vscode-eslint) 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">
@@ -79,6 +79,14 @@ To make getting started easier, the default `tsconfig.json` is using a very flex
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).
## Auto-fix and format project
To automatically fix `eslint` and `prettier` formatting issues, run:
```
npm run fix
```
## View test coverage
To generate and view test coverage, run:
@@ -154,11 +162,11 @@ npm run prepare-release
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
* `version`: bump package.json version, update CHANGELOG.md, and git tag the release
- `hard-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
- `version`: 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:
@@ -174,7 +182,7 @@ You can also prepare a non-standard release:
# Or a non-standard release:
# Reset the repo to the latest commit and build everything
npm run reset && npm run test && npm run cov:check && npm run doc:html
npm run hard-reset && npm run test && npm run cov:check && npm run doc:html
# Then version it with standard-version options. e.g.:
# don't bump package.json version
@@ -192,39 +200,6 @@ npm run version -- --first-release
npm run doc:publish
```
## Describe package scripts
You can run the `describe` script for a description of the available package scripts.
```
npm run describe
> 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
version:
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
```
# FAQs
## Why are there two builds? (`main` and `module`)
@@ -237,13 +212,13 @@ Because Node.js LTS releases do not yet support the es6 module system, some proj
By convention, sample tests in this project are adjacent to the files they test.
* 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.
- 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.
(Bullet points taken from [Angular's Testing Guide](https://angular.io/guide/testing#q-spec-file-location).)
(Bullet points taken from [the Angular Testing Guide](https://angular.io/guide/testing#q-spec-file-location).)
## Can I move the tests?
@@ -309,15 +284,18 @@ console.log(sha256('test'));
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.
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.
<details>
<summary><strong>Contributing</strong></summary>
Pull Requests welcome! 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
@@ -327,7 +305,7 @@ 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:
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 build:main -- -w` as you're developing, then in a different testing directory:
```
mkdir typescript-starter-testing
@@ -345,11 +323,4 @@ If `TYPESCRIPT_STARTER_REPO_BRANCH` is not provided, it will default to `master`
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 other 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
Using `typescript-starter` for your project? Please send a pull request to add it to the list!
</details>

View File

@@ -2,6 +2,7 @@ environment:
matrix:
- nodejs_version: '10'
- nodejs_version: '12'
- nodejs_version: '14'
version: '{build}'

9344
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,7 @@
"typedoc",
"ava",
"nyc",
"tslint",
"eslint",
"prettier",
"standard-version",
"conventional-changelog",
@@ -29,96 +29,104 @@
"appveyor"
],
"scripts": {
"describe": "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 .",
"fix:lint": "eslint . --ext .ts --fix",
"test": "run-s build test:*",
"test:lint": "tslint --project . && prettier \"src/**/*.ts\" --list-different",
"test:lint": "eslint . --ext .ts",
"test:prettier": "prettier \"src/**/*.ts\" --list-different",
"test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"",
"test:unit": "nyc --silent ava",
"watch": "run-s clean build:main && run-p \"build:main -- -w\" \"test:unit -- --watch\"",
"cov": "run-s build test:unit cov:html && open-cli coverage/index.html",
"cov": "run-s build test:unit cov:html cov:lcov && open-cli coverage/index.html",
"cov:html": "nyc report --reporter=html",
"cov:send": "nyc report --reporter=lcov && codecov",
"cov:lcov": "nyc report --reporter=lcov",
"cov:send": "run-s cov:lcov && codecov",
"cov:check": "nyc report && nyc check-coverage --lines 100 --functions 100 --branches 100",
"doc": "run-s doc:html && open-cli build/docs/index.html",
"doc:html": "typedoc src/ --exclude **/*.spec.ts --target ES6 --mode file --out build/docs",
"doc:json": "typedoc src/ --exclude **/*.spec.ts --target ES6 --mode file --json build/docs/typedoc.json",
"doc:publish": "gh-pages -m \"[ci skip] Updates\" -d build/docs",
"version": "standard-version",
"reset": "git clean -dfx && git reset --hard && npm i",
"reset-hard": "git clean -dfx && git reset --hard && npm i",
"clean": "trash build test",
"prepare-release": "run-s reset test cov:check doc:html 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",
"version": "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"
"prepare-release": "run-s reset-hard test cov:check doc:html version doc:publish"
},
"engines": {
"node": ">=8.9"
"node": ">=10"
},
"NOTE": "These dependencies are for the CLI, and will be removed automatically.",
"NOTE": "These dependencies are for the CLI and will not be included when you run 'npx typescript-starter'.",
"dependencies": {
"chalk": "^2.4.2",
"@bitauth/libauth": "^1.17.1",
"chalk": "^4.1.0",
"del": "^5.0.0",
"execa": "^2.0.3",
"execa": "^4.0.3",
"github-username": "^5.0.1",
"globby": "^10.0.1",
"globby": "^11.0.1",
"gradient-string": "^1.2.0",
"inquirer": "^6.4.1",
"meow": "^5.0.0",
"ora": "^3.4.0",
"project-version": "^1.2.0",
"replace-in-file": "^4.1.1",
"sha.js": "^2.4.11",
"update-notifier": "^3.0.1",
"inquirer": "^7.3.3",
"meow": "^7.1.1",
"ora": "^5.0.0",
"replace-in-file": "^6.1.0",
"update-notifier": "^4.1.1",
"validate-npm-package-name": "^3.0.0"
},
"NOTE_2": "Many of these devDependencies are for the CLI and will not be included when you run 'npx typescript-starter'.",
"devDependencies": {
"@bitjson/npm-scripts-info": "^1.0.0",
"@bitjson/typedoc": "^0.15.0-0",
"@istanbuljs/nyc-config-typescript": "^0.1.3",
"@types/inquirer": "^6.5.0",
"@types/meow": "^5.0.0",
"@types/nock": "^10.0.3",
"@types/node": "^10",
"@types/update-notifier": "^2.5.0",
"ava": "2.2.0",
"@ava/typescript": "^1.1.1",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@types/gradient-string": "^1.1.1",
"@types/inquirer": "^7.3.1",
"@types/md5-file": "^4.0.2",
"@types/node": "^14.6.2",
"@types/update-notifier": "^4.1.1",
"@types/validate-npm-package-name": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^4.0.1",
"@typescript-eslint/parser": "^4.0.1",
"ava": "^3.12.1",
"codecov": "^3.5.0",
"cz-conventional-changelog": "^2.1.0",
"gh-pages": "^2.0.1",
"md5-file": "^4.0.0",
"nock": "^11.0.0-beta.13",
"cspell": "^4.1.0",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^7.8.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-functional": "^3.0.2",
"eslint-plugin-import": "^2.22.0",
"gh-pages": "^3.1.0",
"md5-file": "^5.0.0",
"nock": "^13.0.4",
"npm-run-all": "^4.1.5",
"nyc": "^14.1.1",
"open-cli": "^5.0.0",
"prettier": "^1.18.2",
"standard-version": "^6.0.1",
"nyc": "^15.1.0",
"open-cli": "^6.0.1",
"prettier": "^2.1.1",
"standard-version": "^9.0.0",
"trash-cli": "^3.0.0",
"tslint": "^5.18.0",
"tslint-config-prettier": "^1.18.0",
"tslint-immutable": "^6.0.1",
"typescript": "^3.5.3"
"ts-node": "^9.0.0",
"typedoc": "^0.19.0",
"typescript": "^4.0.2"
},
"files": [
"build/main",
"build/module",
"!**/*.spec.*",
"!**/*.json",
"CHANGELOG.md",
"LICENSE",
"README.md"
],
"ava": {
"failFast": true,
"timeout": "20s",
"typescript": {
"rewritePaths": {
"src/": "build/main/"
}
},
"files": [
"build/main/**/*.spec.js"
],
"sources": [
"build/main/**/*.js"
"!build/module/**"
]
},
"config": {

View File

@@ -1,7 +1,6 @@
// tslint:disable:no-console no-if-statement no-expression-statement
import meow from 'meow';
import { Package, UpdateInfo, UpdateNotifier } from 'update-notifier';
import { Package, UpdateNotifier } from 'update-notifier';
import { Runner, TypescriptStarterArgsOptions, validateName } from './utils';
export async function checkArgs(): Promise<TypescriptStarterArgsOptions> {
@@ -23,8 +22,9 @@ export async function checkArgs(): Promise<TypescriptStarterArgsOptions> {
--yarn use yarn (default: npm)
--no-circleci don't include CircleCI
--no-cspell don't include cspell
--no-editorconfig don't include .editorconfig
--no-immutable don't enable tslint-immutable
--no-functional don't enable eslint-plugin-functional
--no-install skip yarn/npm install
--no-vscode don't include VS Code debugging config
@@ -35,103 +35,107 @@ export async function checkArgs(): Promise<TypescriptStarterArgsOptions> {
flags: {
appveyor: {
default: false,
type: 'boolean'
type: 'boolean',
},
circleci: {
default: true,
type: 'boolean'
type: 'boolean',
},
cspell: {
default: true,
type: 'boolean',
},
description: {
alias: 'd',
default: 'a typescript-starter project',
type: 'string'
type: 'string',
},
dom: {
default: false,
type: 'boolean'
type: 'boolean',
},
editorconfig: {
default: true,
type: 'boolean'
type: 'boolean',
},
immutable: {
functional: {
default: true,
type: 'boolean'
type: 'boolean',
},
install: {
default: true,
type: 'boolean'
type: 'boolean',
},
node: {
default: false,
type: 'boolean'
type: 'boolean',
},
strict: {
default: false,
type: 'boolean'
type: 'boolean',
},
travis: {
default: false,
type: 'boolean'
type: 'boolean',
},
vscode: {
default: true,
type: 'boolean'
type: 'boolean',
},
yarn: {
default: false,
type: 'boolean'
}
}
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') {
const info = await new UpdateNotifier({
pkg: cli.pkg as Package,
}).fetchInfo();
if (info.type !== 'latest') {
// eslint-disable-next-line functional/no-throw-statement
throw new Error(`
Your version of typescript-starter is outdated.
Consider using 'npx typescript-starter' to always get the latest version.
`);
Your version of typescript-starter is outdated.
Consider using 'npx typescript-starter' to always get the latest version.
`);
}
const version = cli.pkg.version as string;
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)
/**
* 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,
starterVersion: cli.pkg.version
starterVersion: version,
};
}
const validOrMsg = await validateName(input);
const validOrMsg = validateName(input);
if (typeof validOrMsg === 'string') {
// eslint-disable-next-line functional/no-throw-statement
throw new Error(validOrMsg);
}
return {
appveyor: cli.flags.appveyor,
circleci: cli.flags.circleci,
cspell: cli.flags.cspell,
description: cli.flags.description,
domDefinitions: cli.flags.dom,
editorconfig: cli.flags.editorconfig,
immutable: cli.flags.immutable,
functional: cli.flags.functional,
install: cli.flags.install,
nodeDefinitions: cli.flags.node,
projectName: input,
runner: cli.flags.yarn ? Runner.Yarn : Runner.Npm,
starterVersion: cli.pkg.version,
starterVersion: version,
strict: cli.flags.strict,
travis: cli.flags.travis,
vscode: cli.flags.vscode
vscode: cli.flags.vscode,
};
}

View File

@@ -1,5 +1,5 @@
// tslint:disable:no-expression-statement no-console
import chalk from 'chalk';
import { checkArgs } from './args';
import { inquire } from './inquire';
import { addInferredOptions, LiveTasks } from './tasks';
@@ -15,7 +15,7 @@ import { getIntro, hasCLIOptions, TypescriptStarterUserOptions } from './utils';
console.log(getIntro(process.stdout.columns));
return inquire();
})()),
...argInfo
...argInfo,
};
const options = await addInferredOptions(userOptions);
return typescriptStarter(options, LiveTasks);

View File

@@ -1,4 +1,5 @@
import { DistinctQuestion, prompt } from 'inquirer';
import { Runner, TypescriptStarterCLIOptions, validateName } from './utils';
export async function inquire(): Promise<TypescriptStarterCLIOptions> {
@@ -7,22 +8,21 @@ export async function inquire(): Promise<TypescriptStarterCLIOptions> {
message: '📦 Enter the new package name:',
name: 'projectName',
type: 'input',
validate: validateName
validate: validateName,
};
enum ProjectType {
Node = 'node',
Library = 'lib'
Library = 'lib',
}
const projectTypeQuestion: DistinctQuestion = {
// tslint:disable-next-line:readonly-array
choices: [
{ name: 'Node.js application', value: ProjectType.Node },
{ name: 'Javascript library', value: ProjectType.Library }
{ name: 'Javascript library', value: ProjectType.Library },
],
message: '🔨 What are you making?',
name: 'type',
type: 'list'
type: 'list',
};
const packageDescriptionQuestion: DistinctQuestion = {
@@ -30,103 +30,106 @@ export async function inquire(): Promise<TypescriptStarterCLIOptions> {
message: '💬 Enter the package description:',
name: 'description',
type: 'input',
validate: (answer: string) => answer.length > 0
validate: (answer: string) => answer.length > 0,
};
const runnerQuestion: DistinctQuestion = {
// tslint:disable-next-line:readonly-array
choices: [
{ name: 'npm', value: Runner.Npm },
{ name: 'yarn', value: Runner.Yarn }
{ name: 'yarn', value: Runner.Yarn },
],
message: '🚄 Will this project use npm or yarn?',
name: 'runner',
type: 'list'
type: 'list',
};
enum TypeDefinitions {
none = 'none',
node = 'node',
dom = 'dom',
nodeAndDom = 'both'
nodeAndDom = 'both',
}
const typeDefsQuestion: DistinctQuestion = {
// tslint:disable-next-line:readonly-array
choices: [
{
name: `None — the library won't use any globals or modules from Node.js or the DOM`,
value: TypeDefinitions.none
value: TypeDefinitions.none,
},
{
name: `Node.js — parts of the library require access to Node.js globals or built-in modules`,
value: TypeDefinitions.node
value: TypeDefinitions.node,
},
{
name: `DOM — parts of the library require access to the Document Object Model (DOM)`,
value: TypeDefinitions.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
}
value: TypeDefinitions.nodeAndDom,
},
],
message: '📚 Which global type definitions do you want to include?',
name: 'definitions',
type: 'list',
when: (answers: any) => answers.type === ProjectType.Library
when: (answers) => answers.type === ProjectType.Library,
};
enum Extras {
appveyor = 'appveyor',
circleci = 'circleci',
cspell = 'cspell',
editorconfig = 'editorconfig',
immutable = 'immutable',
functional = 'functional',
strict = 'strict',
travis = 'travis',
vscode = 'vscode'
vscode = 'vscode',
}
const extrasQuestion: DistinctQuestion = {
// tslint:disable-next-line:readonly-array
choices: [
{
name: 'Enable stricter type-checking',
value: Extras.strict
value: Extras.strict,
},
{
checked: true,
name: 'Enable tslint-immutable',
value: Extras.immutable
name: 'Enable eslint-plugin-functional',
value: Extras.functional,
},
{
checked: true,
name: 'Include .editorconfig',
value: Extras.editorconfig
value: Extras.editorconfig,
},
{
checked: true,
name: 'Include cspell',
value: Extras.cspell,
},
{
checked: true,
name: 'Include VS Code debugging config',
value: Extras.vscode
value: Extras.vscode,
},
{
checked: true,
name: 'Include CircleCI config',
value: Extras.circleci
value: Extras.circleci,
},
{
checked: false,
name: 'Include Appveyor (Windows-based CI) config',
value: Extras.appveyor
value: Extras.appveyor,
},
{
checked: false,
name: 'Include Travis CI config',
value: Extras.travis
}
value: Extras.travis,
},
],
message: '🚀 More fun stuff:',
name: 'extras',
type: 'checkbox'
type: 'checkbox',
};
return prompt([
@@ -135,15 +138,15 @@ export async function inquire(): Promise<TypescriptStarterCLIOptions> {
packageDescriptionQuestion,
runnerQuestion,
typeDefsQuestion,
extrasQuestion
]).then(answers => {
extrasQuestion,
]).then((answers) => {
const {
definitions,
description,
extras,
projectName,
runner,
type
type,
} = answers as {
readonly definitions?: TypeDefinitions;
readonly description: string;
@@ -155,6 +158,7 @@ export async function inquire(): Promise<TypescriptStarterCLIOptions> {
return {
appveyor: extras.includes(Extras.appveyor),
circleci: extras.includes(Extras.circleci),
cspell: extras.includes(Extras.cspell),
description,
domDefinitions: definitions
? [TypeDefinitions.dom, TypeDefinitions.nodeAndDom].includes(
@@ -162,7 +166,7 @@ export async function inquire(): Promise<TypescriptStarterCLIOptions> {
)
: false,
editorconfig: extras.includes(Extras.editorconfig),
immutable: extras.includes(Extras.immutable),
functional: extras.includes(Extras.functional),
install: true,
nodeDefinitions: definitions
? [TypeDefinitions.node, TypeDefinitions.nodeAndDom].includes(
@@ -173,7 +177,7 @@ export async function inquire(): Promise<TypescriptStarterCLIOptions> {
runner,
strict: extras.includes(Extras.strict),
travis: extras.includes(Extras.travis),
vscode: extras.includes(Extras.vscode)
vscode: extras.includes(Extras.vscode),
};
});
}

View File

@@ -1,18 +1,19 @@
// tslint:disable:no-console no-if-statement no-expression-statement
import { join } from 'path';
import execa, { Options } from 'execa';
import githubUsername from 'github-username';
import { join } from 'path';
import {
Runner,
TypescriptStarterInferredOptions,
TypescriptStarterOptions,
TypescriptStarterUserOptions
TypescriptStarterUserOptions,
} from './utils';
export enum Placeholders {
email = 'YOUR_EMAIL',
name = 'YOUR_NAME',
username = 'YOUR_GITHUB_USER_NAME'
username = 'YOUR_GITHUB_USER_NAME',
}
// We implement these as function factories to make unit testing easier.
@@ -38,21 +39,23 @@ export const cloneRepo = (
'--depth=1',
`--branch=${repoInfo.branch}`,
repoInfo.repo,
dir
dir,
];
try {
await spawner('git', args, {
cwd: workingDirectory,
stdio: suppressOutput ? 'pipe' : 'inherit'
stdio: suppressOutput ? 'pipe' : 'inherit',
});
} catch (err) {
if (err.exitCodeName === 'ENOENT') {
// eslint-disable-next-line functional/no-throw-statement
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 {
// eslint-disable-next-line functional/no-throw-statement
throw new Error(`Git clone failed.`);
}
}
@@ -60,19 +63,20 @@ export const cloneRepo = (
const revParseResult = await spawner('git', ['rev-parse', 'HEAD'], {
cwd: projectDir,
encoding: 'utf8',
// tslint:disable-next-line:readonly-array
stdio: ['pipe', 'pipe', 'inherit']
stdio: ['pipe', 'pipe', 'inherit'],
});
const commitHash = revParseResult.stdout;
return { commitHash, gitHistoryDir };
} catch (err) {
// eslint-disable-next-line functional/no-throw-statement
throw new Error(`Git rev-parse failed.`);
}
};
export const getGithubUsername = (fetcher: any) => async (
email: string | undefined
): Promise<string> => {
export const getGithubUsername = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fetcher: any
) => async (email: string | undefined): Promise<string> => {
if (email === Placeholders.email) {
return Placeholders.username;
}
@@ -84,20 +88,19 @@ export const getGithubUsername = (fetcher: any) => async (
export const getUserInfo = (spawner: typeof execa) => async () => {
const opts: Options = {
encoding: 'utf8',
// tslint:disable-next-line:readonly-array
stdio: ['pipe', 'pipe', 'inherit']
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
gitName: nameResult.stdout,
};
} catch (err) {
return {
gitEmail: Placeholders.email,
gitName: Placeholders.name
gitName: Placeholders.name,
};
}
};
@@ -109,7 +112,7 @@ export const initialCommit = (spawner: typeof execa) => async (
const opts: Options = {
cwd: projectDir,
encoding: 'utf8',
stdio: 'pipe'
stdio: 'pipe',
};
await spawner('git', ['init'], opts);
await spawner('git', ['add', '-A'], opts);
@@ -118,7 +121,7 @@ export const initialCommit = (spawner: typeof execa) => async (
[
'commit',
'-m',
`Initial commit\n\nCreated with bitjson/typescript-starter@${hash}`
`Initial commit\n\nCreated with bitjson/typescript-starter@${hash}`,
],
opts
);
@@ -131,13 +134,14 @@ export const install = (spawner: typeof execa) => async (
const opts: Options = {
cwd: projectDir,
encoding: 'utf8',
stdio: 'inherit'
stdio: 'inherit',
};
try {
runner === Runner.Npm
? spawner('npm', ['install'], opts)
: spawner('yarn', opts);
} catch (err) {
// eslint-disable-next-line functional/no-throw-statement
throw new Error(`Installation failed. You'll need to install manually.`);
}
};
@@ -155,15 +159,15 @@ export const getRepoInfo = (starterVersion: string) => {
branch: process.env.TYPESCRIPT_STARTER_REPO_BRANCH
? process.env.TYPESCRIPT_STARTER_REPO_BRANCH
: 'master',
repo: process.env.TYPESCRIPT_STARTER_REPO_URL
repo: process.env.TYPESCRIPT_STARTER_REPO_URL,
}
: {
branch: `v${starterVersion}`,
repo: 'https://github.com/bitjson/typescript-starter.git'
repo: 'https://github.com/bitjson/typescript-starter.git',
};
};
export interface Tasks {
export type Tasks = {
readonly cloneRepo: (
repoInfo: {
readonly branch: string;
@@ -178,12 +182,12 @@ export interface Tasks {
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)
install: install(execa),
};
export const addInferredOptions = async (
userOptions: TypescriptStarterUserOptions
@@ -195,22 +199,23 @@ export const addInferredOptions = async (
fullName: gitName,
githubUsername: username,
repoInfo: getRepoInfo(userOptions.starterVersion),
workingDirectory: process.cwd()
workingDirectory: process.cwd(),
};
return {
...inferredOptions,
appveyor: userOptions.appveyor,
circleci: userOptions.circleci,
cspell: userOptions.cspell,
description: userOptions.description,
domDefinitions: userOptions.domDefinitions,
editorconfig: userOptions.editorconfig,
immutable: userOptions.immutable,
functional: userOptions.functional,
install: userOptions.install,
nodeDefinitions: userOptions.nodeDefinitions,
projectName: userOptions.projectName,
runner: userOptions.runner,
strict: userOptions.strict,
travis: userOptions.travis,
vscode: userOptions.vscode
vscode: userOptions.vscode,
};
};

View File

@@ -13,14 +13,15 @@
* `diff build/test-one/package.json build/test-two/package.json`
*/
// tslint:disable:no-expression-statement
import { join, relative } from 'path';
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 { normalizePath, Runner } from '../utils';
@@ -37,17 +38,17 @@ const branch = execa.sync('git', [
'rev-parse',
'--symbolic-full-name',
'--abbrev-ref',
'HEAD'
'HEAD',
]).stdout;
const repoInfo = {
// if local repo is in a detached HEAD state, providing --branch to `git clone` will fail.
branch: branch === 'HEAD' ? '.' : branch,
repo: process.cwd()
repo: process.cwd(),
};
const buildDir = join(process.cwd(), 'build');
const env = {
TYPESCRIPT_STARTER_REPO_BRANCH: repoInfo.branch,
TYPESCRIPT_STARTER_REPO_URL: repoInfo.repo
TYPESCRIPT_STARTER_REPO_URL: repoInfo.repo,
};
enum TestDirectories {
@@ -56,7 +57,7 @@ enum TestDirectories {
three = 'test-3',
four = 'test-4',
five = 'test-5',
six = 'test-6'
six = 'test-6',
}
// If the tests all pass, the TestDirectories will automatically be cleaned up.
@@ -67,23 +68,23 @@ test.after(async () => {
`./build/${TestDirectories.three}`,
`./build/${TestDirectories.four}`,
`./build/${TestDirectories.five}`,
`./build/${TestDirectories.six}`
`./build/${TestDirectories.six}`,
]);
});
test('returns version', async t => {
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 => {
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 => {
test('errors if project name collides with an existing path', async (t) => {
const existingDir = 'build';
const error = await t.throwsAsync<execa.ExecaError>(
execa(`./bin/typescript-starter`, [existingDir])
@@ -91,7 +92,7 @@ test('errors if project name collides with an existing path', async t => {
t.regex(error.stderr, /"build" path already exists/);
});
test('errors if project name is not in kebab-case', async t => {
test('errors if project name is not in kebab-case', async (t) => {
const error = await t.throwsAsync<execa.ExecaError>(
execa(`./bin/typescript-starter`, ['name with spaces'])
);
@@ -106,24 +107,17 @@ async function hashAllTheThings(
const rawFilePaths: ReadonlyArray<string> = await globby(
[projectDir, `!${projectDir}/.git`],
{
dot: true
dot: true,
}
);
const filePaths = sandboxed
? rawFilePaths
: rawFilePaths.filter(
path =>
(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 hashAll = filePaths.map<Promise<string>>((path) => md5File(path));
const hashes = await Promise.all(hashAll);
return hashes.reduce<{ readonly [filename: string]: string }>(
(acc, hash, i) => {
@@ -132,7 +126,7 @@ async function hashAllTheThings(
);
return {
...acc,
[trimmedNormalizedFilePath]: hash
[trimmedNormalizedFilePath]: hash,
};
},
{}
@@ -140,19 +134,19 @@ async function hashAllTheThings(
}
/**
* Since we're using Greenkeeper to keep dependencies fresh, including
* Since we're using Dependabot to keep dependencies fresh, including
* `package.json` in these file fingerprint tests guarantees that every
* Greenkeeper PR will trigger a false-positive test failure.
* Dependabot PR will trigger a false-positive test failure.
*
* Here we trade complete assurance that `package.json` is correct for much less
* noisy build results.
*/
const ignorePackageJson = (map: { readonly [file: string]: string }) =>
Object.entries(map)
.filter(entry => !entry[0].includes('package.json'))
.filter((entry) => !entry[0].includes('package.json'))
.reduce((ret, [path, hash]) => ({ ...ret, [path]: hash }), {});
test(`${TestDirectories.one}: parses CLI arguments, handles default options`, async t => {
test(`${TestDirectories.one}: parses CLI arguments, handles default options`, async (t) => {
const description = 'example description 1';
const { stdout } = await execa(
`../bin/typescript-starter`,
@@ -160,40 +154,40 @@ test(`${TestDirectories.one}: parses CLI arguments, handles default options`, as
`${TestDirectories.one}`,
// (user entered `-d='example description 1'`)
`-d=${description}`,
'--no-install'
'--no-install',
],
{
cwd: buildDir,
env
env,
}
);
t.regex(stdout, new RegExp(`Created ${TestDirectories.one} 🎉`));
const map = await hashAllTheThings(TestDirectories.one);
t.deepEqual(map, {
'test-1/.circleci/config.yml': 'd29447823209aa9a230f08af7e7bfcc7',
'test-1/.circleci/config.yml': '44d2fd424c93d381d41030f789efabba',
'test-1/.cspell.json': '5b2e0ad337bc05ef2ce5635270d983fd',
'test-1/.editorconfig': '44a3e6c69d9267b0f756986fd970a8f4',
'test-1/.eslintrc.json': '4e74756d24eaccb7f28d4999e4bd6f0d',
'test-1/.github/CONTRIBUTING.md': '5f0dfa7fdf9bf828e3a3aa8fcaeece08',
'test-1/.github/ISSUE_TEMPLATE.md': '82d1b99b29f32d851627b317195e73d2',
'test-1/.github/ISSUE_TEMPLATE.md': 'e70a0b70402765682b1a961af855040e',
'test-1/.github/PULL_REQUEST_TEMPLATE.md':
'710eb5973a8cda83fc568cb1bbe7c026',
'70f4b97f3914e2f399bcc5868e072c29',
'test-1/.gitignore': '892227b7f662b74410e9bf6fb2ae887f',
'test-1/.npmignore': '49c9375c9a1b4a1b74076f62379b0297',
'test-1/.prettierignore': '1da1ce4fdb868f0939608fafd38f9683',
'test-1/.vscode/debug-ts.js': '23eb6ab10faaa25a95f5bd3325d0455c',
'test-1/.vscode/launch.json': '669e4d1dda91c781177c6adae7aa7e00',
'test-1/.vscode/settings.json': '10c634c5fef6ecd298b6e41bf159f2cc',
'test-1/.vscode/extensions.json': '2d26a716ba181656faac4cd2d38ec139',
'test-1/.vscode/launch.json': '140e17d591e03b8850c456ade3aefb1f',
'test-1/.vscode/settings.json': '1671948882faee285f18d7491f0fc913',
'test-1/README.md': '7a9f4efa9213266c3800f3cc82a53ba7',
'test-1/src/index.ts': '5025093b4dc30524d349fd1cc465ed30',
'test-1/src/lib/number.spec.ts': '6a9a00630b10e7d57a79678c74a0e4df',
'test-1/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
'test-1/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
'test-1/src/lib/number.spec.ts': '0592001d71aa3b3e6bf72f4cd95dc1b9',
'test-1/src/lib/number.ts': 'dcbcc98fea337d07e81728c1a6526a1e',
'test-1/src/types/example.d.ts': '76642861732b16754b0110fb1de49823',
'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 => {
test(`${TestDirectories.two}: parses CLI arguments, handles all options`, async (t) => {
const description = 'example description 2';
const { stdout } = await execa(
`../bin/typescript-starter`,
@@ -205,40 +199,40 @@ test(`${TestDirectories.two}: parses CLI arguments, handles all options`, async
'--yarn',
'--node',
'--dom',
'--no-install'
'--no-install',
],
{
cwd: buildDir,
env
env,
}
);
t.regex(stdout, new RegExp(`Created ${TestDirectories.two} 🎉`));
const map = await hashAllTheThings(TestDirectories.two);
t.deepEqual(map, {
'test-2/.circleci/config.yml': 'd29447823209aa9a230f08af7e7bfcc7',
'test-2/.circleci/config.yml': '44d2fd424c93d381d41030f789efabba',
'test-2/.cspell.json': '5b2e0ad337bc05ef2ce5635270d983fd',
'test-2/.editorconfig': '44a3e6c69d9267b0f756986fd970a8f4',
'test-2/.eslintrc.json': '4e74756d24eaccb7f28d4999e4bd6f0d',
'test-2/.github/CONTRIBUTING.md': '5f0dfa7fdf9bf828e3a3aa8fcaeece08',
'test-2/.github/ISSUE_TEMPLATE.md': '82d1b99b29f32d851627b317195e73d2',
'test-2/.github/ISSUE_TEMPLATE.md': 'e70a0b70402765682b1a961af855040e',
'test-2/.github/PULL_REQUEST_TEMPLATE.md':
'710eb5973a8cda83fc568cb1bbe7c026',
'70f4b97f3914e2f399bcc5868e072c29',
'test-2/.gitignore': 'af817565c661f1b15514584c8ea9e469',
'test-2/.npmignore': '49c9375c9a1b4a1b74076f62379b0297',
'test-2/.prettierignore': '1da1ce4fdb868f0939608fafd38f9683',
'test-2/.vscode/debug-ts.js': '23eb6ab10faaa25a95f5bd3325d0455c',
'test-2/.vscode/launch.json': '669e4d1dda91c781177c6adae7aa7e00',
'test-2/.vscode/settings.json': '10c634c5fef6ecd298b6e41bf159f2cc',
'test-2/.vscode/extensions.json': '2d26a716ba181656faac4cd2d38ec139',
'test-2/.vscode/launch.json': '140e17d591e03b8850c456ade3aefb1f',
'test-2/.vscode/settings.json': '1671948882faee285f18d7491f0fc913',
'test-2/README.md': 'ddaf27da4cc4ca5225785f0ac8f4da58',
'test-2/src/index.ts': 'fbc67c2cbf3a7d37e4e02583bf06eec9',
'test-2/src/lib/async.spec.ts': '1f51a721fffe53832fb289429baba971',
'test-2/src/lib/async.ts': '9012c267bb25fa98ad2561929de3d4e2',
'test-2/src/lib/hash.spec.ts': '11589e1960ddd75e7597c9de6854cd08',
'test-2/src/lib/hash.ts': 'a4c552897f25da5963f410e375264bd1',
'test-2/src/lib/number.spec.ts': '6a9a00630b10e7d57a79678c74a0e4df',
'test-2/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
'test-2/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
'test-2/src/lib/async.spec.ts': '65b10546885ebad41c098318b896f23c',
'test-2/src/lib/async.ts': '926732fef1285cb0abb5c6e287ed24df',
'test-2/src/lib/hash.spec.ts': '06f6bfc1b03f893a16448bb6d6806ea2',
'test-2/src/lib/hash.ts': 'cf3659937ff3162bc525c8bfac18b7cf',
'test-2/src/lib/number.spec.ts': '0592001d71aa3b3e6bf72f4cd95dc1b9',
'test-2/src/lib/number.ts': 'dcbcc98fea337d07e81728c1a6526a1e',
'test-2/src/types/example.d.ts': '76642861732b16754b0110fb1de49823',
'test-2/tsconfig.json': '8a55379f60e4e6d4fad1f0b2318b74c4',
'test-2/tsconfig.module.json': '2fda4c8760c6cfa3462b40df0645850d',
'test-2/tslint.json': '7ac167ffbcb724a6c270e8dc4e747067'
});
});
@@ -246,7 +240,7 @@ const down = '\x1B\x5B\x42';
const up = '\x1B\x5B\x41';
const enter = '\x0D';
const ms = (milliseconds: number) =>
new Promise<void>(resolve => setTimeout(resolve, milliseconds));
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
async function testInteractive(
projectName: string,
@@ -255,18 +249,18 @@ async function testInteractive(
const typeDefs = entry[3] !== '';
const proc = execa(`../bin/typescript-starter`, ['--no-install'], {
cwd: buildDir,
env
env,
});
// TODO: missing in Node.js type definition's ChildProcess.stdin?
// https://nodejs.org/api/process.html#process_process_stdin
// proc.stdin.setEncoding('utf8');
const type = (input: string) => proc.stdin.write(input);
const type = (input: string) => proc.stdin?.write(input);
// wait for first chunk to be emitted
await new Promise(resolve => {
proc.stdout.once('data', resolve);
await new Promise((resolve) => {
proc.stdout?.once('data', resolve);
});
await ms(200);
type(`${projectName}${enter}`);
@@ -277,7 +271,6 @@ async function testInteractive(
await ms(200);
type(`${entry[2][0]}${enter}`);
await ms(200);
// tslint:disable-next-line:no-if-statement
if (typeDefs) {
type(`${entry[3][0]}${enter}`);
await ms(200);
@@ -287,84 +280,87 @@ async function testInteractive(
return proc;
}
test(`${TestDirectories.three}: interactive mode: javascript library`, async t => {
test(`${TestDirectories.three}: interactive mode: javascript library`, async (t) => {
const proc = await testInteractive(`${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']
[
' ',
'stricter type-checking[\\s\\S]*eslint-plugin-functional[\\s\\S]*VS Code',
],
]);
await proc;
const map = await hashAllTheThings(TestDirectories.three);
t.deepEqual(map, {
'test-3/.circleci/config.yml': 'd29447823209aa9a230f08af7e7bfcc7',
'test-3/.circleci/config.yml': '44d2fd424c93d381d41030f789efabba',
'test-3/.cspell.json': '5b2e0ad337bc05ef2ce5635270d983fd',
'test-3/.editorconfig': '44a3e6c69d9267b0f756986fd970a8f4',
'test-3/.eslintrc.json': '4e74756d24eaccb7f28d4999e4bd6f0d',
'test-3/.github/CONTRIBUTING.md': '5f0dfa7fdf9bf828e3a3aa8fcaeece08',
'test-3/.github/ISSUE_TEMPLATE.md': '82d1b99b29f32d851627b317195e73d2',
'test-3/.github/ISSUE_TEMPLATE.md': 'e70a0b70402765682b1a961af855040e',
'test-3/.github/PULL_REQUEST_TEMPLATE.md':
'710eb5973a8cda83fc568cb1bbe7c026',
'70f4b97f3914e2f399bcc5868e072c29',
'test-3/.gitignore': 'af817565c661f1b15514584c8ea9e469',
'test-3/.npmignore': '49c9375c9a1b4a1b74076f62379b0297',
'test-3/.prettierignore': '1da1ce4fdb868f0939608fafd38f9683',
'test-3/.vscode/debug-ts.js': '23eb6ab10faaa25a95f5bd3325d0455c',
'test-3/.vscode/launch.json': '669e4d1dda91c781177c6adae7aa7e00',
'test-3/.vscode/settings.json': '10c634c5fef6ecd298b6e41bf159f2cc',
'test-3/.vscode/extensions.json': '2d26a716ba181656faac4cd2d38ec139',
'test-3/.vscode/launch.json': '140e17d591e03b8850c456ade3aefb1f',
'test-3/.vscode/settings.json': '1671948882faee285f18d7491f0fc913',
'test-3/README.md': 'c52631ebf78f6b030af9a109b769b647',
'test-3/src/index.ts': 'fbc67c2cbf3a7d37e4e02583bf06eec9',
'test-3/src/lib/async.spec.ts': '1f51a721fffe53832fb289429baba971',
'test-3/src/lib/async.ts': '9012c267bb25fa98ad2561929de3d4e2',
'test-3/src/lib/hash.spec.ts': '11589e1960ddd75e7597c9de6854cd08',
'test-3/src/lib/hash.ts': 'a4c552897f25da5963f410e375264bd1',
'test-3/src/lib/number.spec.ts': '6a9a00630b10e7d57a79678c74a0e4df',
'test-3/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
'test-3/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
'test-3/src/lib/async.spec.ts': '65b10546885ebad41c098318b896f23c',
'test-3/src/lib/async.ts': '926732fef1285cb0abb5c6e287ed24df',
'test-3/src/lib/hash.spec.ts': '06f6bfc1b03f893a16448bb6d6806ea2',
'test-3/src/lib/hash.ts': 'cf3659937ff3162bc525c8bfac18b7cf',
'test-3/src/lib/number.spec.ts': '0592001d71aa3b3e6bf72f4cd95dc1b9',
'test-3/src/lib/number.ts': 'dcbcc98fea337d07e81728c1a6526a1e',
'test-3/src/types/example.d.ts': '76642861732b16754b0110fb1de49823',
'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 => {
test(`${TestDirectories.four}: interactive mode: node.js application`, async (t) => {
const proc = await testInteractive(`${TestDirectories.four}`, [
[`${down}${up}`, `Node.js application`],
`integration test 4 description`,
[`${down}${up}${enter}`, `npm`],
'',
[`${down} `, 'VS Code']
[`${down} `, 'VS Code'],
]);
await proc;
const map = await hashAllTheThings(TestDirectories.four);
t.deepEqual(map, {
'test-4/.circleci/config.yml': 'd29447823209aa9a230f08af7e7bfcc7',
'test-4/.circleci/config.yml': '44d2fd424c93d381d41030f789efabba',
'test-4/.cspell.json': '5b2e0ad337bc05ef2ce5635270d983fd',
'test-4/.editorconfig': '44a3e6c69d9267b0f756986fd970a8f4',
'test-4/.eslintrc.json': '941448b089cd055bbe476a84c8f96cfe',
'test-4/.github/CONTRIBUTING.md': '5f0dfa7fdf9bf828e3a3aa8fcaeece08',
'test-4/.github/ISSUE_TEMPLATE.md': '82d1b99b29f32d851627b317195e73d2',
'test-4/.github/ISSUE_TEMPLATE.md': 'e70a0b70402765682b1a961af855040e',
'test-4/.github/PULL_REQUEST_TEMPLATE.md':
'710eb5973a8cda83fc568cb1bbe7c026',
'70f4b97f3914e2f399bcc5868e072c29',
'test-4/.gitignore': '892227b7f662b74410e9bf6fb2ae887f',
'test-4/.npmignore': '49c9375c9a1b4a1b74076f62379b0297',
'test-4/.prettierignore': '1da1ce4fdb868f0939608fafd38f9683',
'test-4/.vscode/debug-ts.js': '23eb6ab10faaa25a95f5bd3325d0455c',
'test-4/.vscode/launch.json': '669e4d1dda91c781177c6adae7aa7e00',
'test-4/.vscode/settings.json': '10c634c5fef6ecd298b6e41bf159f2cc',
'test-4/.vscode/extensions.json': '2d26a716ba181656faac4cd2d38ec139',
'test-4/.vscode/launch.json': '140e17d591e03b8850c456ade3aefb1f',
'test-4/.vscode/settings.json': '1671948882faee285f18d7491f0fc913',
'test-4/README.md': 'a3e0699b39498df4843c9dde95f1e000',
'test-4/src/index.ts': 'fbc67c2cbf3a7d37e4e02583bf06eec9',
'test-4/src/lib/async.spec.ts': '1f51a721fffe53832fb289429baba971',
'test-4/src/lib/async.ts': '9012c267bb25fa98ad2561929de3d4e2',
'test-4/src/lib/hash.spec.ts': '11589e1960ddd75e7597c9de6854cd08',
'test-4/src/lib/hash.ts': 'a4c552897f25da5963f410e375264bd1',
'test-4/src/lib/number.spec.ts': '6a9a00630b10e7d57a79678c74a0e4df',
'test-4/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
'test-4/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
'test-4/src/lib/async.spec.ts': '65b10546885ebad41c098318b896f23c',
'test-4/src/lib/async.ts': '926732fef1285cb0abb5c6e287ed24df',
'test-4/src/lib/hash.spec.ts': '06f6bfc1b03f893a16448bb6d6806ea2',
'test-4/src/lib/hash.ts': 'cf3659937ff3162bc525c8bfac18b7cf',
'test-4/src/lib/number.spec.ts': '0592001d71aa3b3e6bf72f4cd95dc1b9',
'test-4/src/lib/number.ts': 'dcbcc98fea337d07e81728c1a6526a1e',
'test-4/src/types/example.d.ts': '76642861732b16754b0110fb1de49823',
'test-4/tsconfig.json': 'e41d08f0aca16cb05430b61e4b6286db',
'test-4/tsconfig.module.json': '2fda4c8760c6cfa3462b40df0645850d',
'test-4/tslint.json': '99f6f8fa763bfc2a32377739b3e5dd5c'
});
});
const sandboxTasks = (
t: ExecutionContext<{}>,
t: ExecutionContext<unknown>,
commit: boolean,
install: boolean
): Tasks => {
@@ -375,7 +371,7 @@ const sandboxTasks = (
},
install: async () => {
install ? t.pass() : t.fail();
}
},
};
};
@@ -383,108 +379,109 @@ const sandboxOptions = {
description: 'this is an example description',
githubUsername: 'SOME_GITHUB_USERNAME',
repoInfo,
workingDirectory: buildDir
workingDirectory: buildDir,
};
const silenceConsole = (console: any) => {
// tslint:disable-next-line:no-object-mutation no-console
const silenceConsole = (console: Console) => {
// eslint-disable-next-line functional/immutable-data
console.log = () => {
// mock console.log to silence it
return;
};
};
test(`${TestDirectories.five}: Sandboxed: npm install, initial commit`, async t => {
test(`${TestDirectories.five}: Sandboxed: npm install, initial commit`, async (t) => {
t.plan(3);
const options = {
...sandboxOptions,
appveyor: false,
circleci: false,
cspell: false,
domDefinitions: false,
editorconfig: false,
email: 'email@example.com',
// cspell: disable-next-line
fullName: 'Satoshi Nakamoto',
immutable: true,
functional: true,
install: true,
nodeDefinitions: false,
projectName: TestDirectories.five,
runner: Runner.Npm,
strict: true,
travis: false,
vscode: false
vscode: false,
};
silenceConsole(console);
await typescriptStarter(options, sandboxTasks(t, true, true));
const map = await hashAllTheThings(TestDirectories.five, true);
t.deepEqual(ignorePackageJson(map), {
'test-5/.eslintrc.json': '4e74756d24eaccb7f28d4999e4bd6f0d',
'test-5/.github/CONTRIBUTING.md': '5f0dfa7fdf9bf828e3a3aa8fcaeece08',
'test-5/.github/ISSUE_TEMPLATE.md': '82d1b99b29f32d851627b317195e73d2',
'test-5/.github/ISSUE_TEMPLATE.md': 'e70a0b70402765682b1a961af855040e',
'test-5/.github/PULL_REQUEST_TEMPLATE.md':
'710eb5973a8cda83fc568cb1bbe7c026',
'70f4b97f3914e2f399bcc5868e072c29',
'test-5/.gitignore': '892227b7f662b74410e9bf6fb2ae887f',
'test-5/.npmignore': '49c9375c9a1b4a1b74076f62379b0297',
'test-5/.prettierignore': '1da1ce4fdb868f0939608fafd38f9683',
'test-5/LICENSE': '317693126d229a3cdd19725a624a56fc',
'test-5/README.md': '8fc7ecb21d7d47289e4b2469eea4db39',
'test-5/src/index.ts': '5025093b4dc30524d349fd1cc465ed30',
'test-5/src/lib/number.spec.ts': '6a9a00630b10e7d57a79678c74a0e4df',
'test-5/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
'test-5/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
'test-5/src/lib/number.spec.ts': '0592001d71aa3b3e6bf72f4cd95dc1b9',
'test-5/src/lib/number.ts': 'dcbcc98fea337d07e81728c1a6526a1e',
'test-5/src/types/example.d.ts': '76642861732b16754b0110fb1de49823',
'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 => {
test(`${TestDirectories.six}: Sandboxed: yarn, no initial commit`, async (t) => {
t.plan(2);
const options = {
...sandboxOptions,
appveyor: true,
circleci: true,
cspell: false,
domDefinitions: true,
editorconfig: true,
email: Placeholders.email,
fullName: Placeholders.name,
immutable: true,
functional: true,
install: true,
nodeDefinitions: true,
projectName: TestDirectories.six,
runner: Runner.Yarn,
strict: false,
travis: true,
vscode: true
vscode: true,
};
silenceConsole(console);
await typescriptStarter(options, sandboxTasks(t, false, true));
const map = await hashAllTheThings(TestDirectories.six, true);
t.deepEqual(ignorePackageJson(map), {
'test-6/.circleci/config.yml': 'd29447823209aa9a230f08af7e7bfcc7',
'test-6/.circleci/config.yml': '44d2fd424c93d381d41030f789efabba',
'test-6/.editorconfig': '44a3e6c69d9267b0f756986fd970a8f4',
'test-6/.eslintrc.json': '4e74756d24eaccb7f28d4999e4bd6f0d',
'test-6/.github/CONTRIBUTING.md': '5f0dfa7fdf9bf828e3a3aa8fcaeece08',
'test-6/.github/ISSUE_TEMPLATE.md': '82d1b99b29f32d851627b317195e73d2',
'test-6/.github/ISSUE_TEMPLATE.md': 'e70a0b70402765682b1a961af855040e',
'test-6/.github/PULL_REQUEST_TEMPLATE.md':
'710eb5973a8cda83fc568cb1bbe7c026',
'70f4b97f3914e2f399bcc5868e072c29',
'test-6/.gitignore': 'af817565c661f1b15514584c8ea9e469',
'test-6/.npmignore': '49c9375c9a1b4a1b74076f62379b0297',
'test-6/.prettierignore': '1da1ce4fdb868f0939608fafd38f9683',
'test-6/.travis.yml': 'b56cf7194d8ff58d6cf51c34b0c645e0',
'test-6/.vscode/debug-ts.js': '23eb6ab10faaa25a95f5bd3325d0455c',
'test-6/.vscode/launch.json': '669e4d1dda91c781177c6adae7aa7e00',
'test-6/.vscode/settings.json': '10c634c5fef6ecd298b6e41bf159f2cc',
'test-6/.vscode/extensions.json': '2d26a716ba181656faac4cd2d38ec139',
'test-6/.vscode/launch.json': '140e17d591e03b8850c456ade3aefb1f',
'test-6/.vscode/settings.json': 'f70eb64341e74d24d901055a26dc8242',
'test-6/LICENSE': '03ffa741a4f7e356b69353efa4937d2b',
'test-6/README.md': 'd809bcbf240f44b51b575a3d49936232',
'test-6/appveyor.yml': '27c787d8e288f89c71357b1ac32b42e8',
'test-6/appveyor.yml': '214e043a9baa2a9d2579a0af0a5621a3',
'test-6/src/index.ts': 'fbc67c2cbf3a7d37e4e02583bf06eec9',
'test-6/src/lib/async.spec.ts': '1f51a721fffe53832fb289429baba971',
'test-6/src/lib/async.ts': '9012c267bb25fa98ad2561929de3d4e2',
'test-6/src/lib/hash.spec.ts': '11589e1960ddd75e7597c9de6854cd08',
'test-6/src/lib/hash.ts': 'a4c552897f25da5963f410e375264bd1',
'test-6/src/lib/number.spec.ts': '6a9a00630b10e7d57a79678c74a0e4df',
'test-6/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
'test-6/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
'test-6/src/lib/async.spec.ts': '65b10546885ebad41c098318b896f23c',
'test-6/src/lib/async.ts': '926732fef1285cb0abb5c6e287ed24df',
'test-6/src/lib/hash.spec.ts': '06f6bfc1b03f893a16448bb6d6806ea2',
'test-6/src/lib/hash.ts': 'cf3659937ff3162bc525c8bfac18b7cf',
'test-6/src/lib/number.spec.ts': '0592001d71aa3b3e6bf72f4cd95dc1b9',
'test-6/src/lib/number.ts': 'dcbcc98fea337d07e81728c1a6526a1e',
'test-6/src/types/example.d.ts': '76642861732b16754b0110fb1de49823',
'test-6/tsconfig.json': '8a55379f60e4e6d4fad1f0b2318b74c4',
'test-6/tsconfig.module.json': '2fda4c8760c6cfa3462b40df0645850d',
'test-6/tslint.json': '7ac167ffbcb724a6c270e8dc4e747067'
});
});

View File

@@ -1,8 +1,8 @@
// tslint:disable:no-expression-statement
import test from 'ava';
import execa from 'execa';
import meow from 'meow';
import nock from 'nock';
import { checkArgs } from '../args';
import {
cloneRepo,
@@ -11,11 +11,11 @@ import {
getUserInfo,
initialCommit,
install,
Placeholders
Placeholders,
} from '../tasks';
import { getIntro, Runner, validateName } from '../utils';
test('errors if outdated', async t => {
test('errors if outdated', async (t) => {
nock.disableNetConnect();
nock('https://registry.npmjs.org:443')
.get('/typescript-starter')
@@ -24,9 +24,9 @@ test('errors if outdated', async t => {
name: 'typescript-starter',
versions: {
'9000.0.1': {
version: '9000.0.1'
}
}
version: '9000.0.1',
},
},
});
const error = await t.throwsAsync(checkArgs);
t.regex(error.message, /is outdated/);
@@ -41,20 +41,20 @@ const pretendLatestVersionIs = (version: string) => {
name: 'typescript-starter',
versions: {
[version]: {
version
}
}
version,
},
},
});
};
test("doesn't error if not outdated", async t => {
const currentVersion = meow('').pkg.version;
test("doesn't error if not outdated", async (t) => {
const currentVersion = meow('').pkg.version as string;
t.truthy(typeof currentVersion === 'string');
pretendLatestVersionIs(currentVersion);
await t.notThrows(checkArgs);
});
test('errors if update-notifier fails', async t => {
test('errors if update-notifier fails', async (t) => {
nock.disableNetConnect();
nock('https://registry.npmjs.org:443')
.get('/typescript-starter')
@@ -63,35 +63,38 @@ test('errors if update-notifier fails', async t => {
t.regex(error.message, /could not be found/);
});
test('checkArgs returns the right options', async t => {
test('checkArgs returns the right options', async (t) => {
pretendLatestVersionIs('1.0.0');
// tslint:disable-next-line:no-object-mutation
// eslint-disable-next-line functional/immutable-data
process.argv = [
'path/to/node',
'path/to/typescript-starter',
'example-project',
'--appveyor',
`-description "example description"`,
'--description',
'"example description"',
'--dom',
'--node',
'--strict',
'--travis',
'--yarn',
'--no-circleci',
'--no-cspell',
'--no-editorconfig',
'--no-immutable',
'--no-functional',
'--no-install',
'--no-vscode'
'--no-vscode',
];
const opts = await checkArgs();
const currentVersion = meow('').pkg.version;
const currentVersion = meow('').pkg.version as string;
t.deepEqual(opts, {
appveyor: true,
circleci: false,
description: '',
cspell: false,
description: 'example description',
domDefinitions: true,
editorconfig: false,
immutable: false,
functional: false,
install: false,
nodeDefinitions: true,
projectName: 'example-project',
@@ -99,75 +102,78 @@ test('checkArgs returns the right options', async t => {
starterVersion: currentVersion,
strict: true,
travis: true,
vscode: false
vscode: false,
});
});
test('checkArgs always returns a TypescriptStarterRequiredConfig, even in interactive mode', async t => {
test('checkArgs always returns a TypescriptStarterRequiredConfig, even in interactive mode', async (t) => {
pretendLatestVersionIs('1.0.0');
// tslint:disable-next-line:no-object-mutation
// eslint-disable-next-line functional/immutable-data
process.argv = ['path/to/node', 'path/to/typescript-starter'];
const opts = await checkArgs();
t.true(typeof opts.install === 'boolean');
t.true(typeof opts.starterVersion === 'string');
});
test('only accepts valid package names', async t => {
test('only accepts valid package names', async (t) => {
t.true(validateName('package-name'));
t.true(validateName('package-name-2'));
t.true(validateName('@example/package-name-2'));
});
test('ascii art shows if stdout has 85+ columns', async t => {
test('ascii art shows if stdout has 85+ columns', async (t) => {
const jumbo = getIntro(100);
const snippet = `| __| | | | '_ \\ / _ \\/ __|/ __| '__| | '_ \\|`;
t.regex(jumbo, new RegExp(snippet));
});
test('small ascii art shows if stdout has 74-84 columns', async t => {
test('small ascii art shows if stdout has 74-84 columns', async (t) => {
const jumbo = getIntro(80);
const snippet = `| _| || | '_ \\/ -_|_-</ _| '_| | '_ \\ _|`;
t.regex(jumbo, new RegExp(snippet));
});
const mockErr = (code: number = 1, name: string = 'ERR') =>
const mockErr = (code = 1, name = 'ERR') =>
((() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const err: any = new Error();
// tslint:disable-next-line:no-object-mutation
// eslint-disable-next-line functional/immutable-data
err.exitCode = code;
// tslint:disable-next-line:no-object-mutation
// eslint-disable-next-line functional/immutable-data
err.exitCodeName = name;
// eslint-disable-next-line functional/no-throw-statement
throw err;
}) as any) as typeof execa;
}) as unknown) as typeof execa;
test('cloneRepo: errors when Git is not installed on PATH', async t => {
test('cloneRepo: errors when Git is not installed on PATH', async (t) => {
const error = await t.throwsAsync(
cloneRepo(mockErr(1, 'ENOENT'))({ repo: 'r', branch: '.' }, 'd', 'p')
);
t.regex(error.message, /Git is not installed on your PATH/);
});
test('cloneRepo: throws when clone fails', async t => {
test('cloneRepo: throws when clone fails', async (t) => {
const error = await t.throwsAsync(
cloneRepo(mockErr(128))({ repo: 'r', branch: 'b' }, '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
test('cloneRepo: throws when rev-parse fails', async (t) => {
// eslint-disable-next-line functional/no-let
let calls = 0;
const mock = ((async () => {
calls++;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return calls === 1 ? {} : (mockErr(128) as any)();
}) as any) as typeof execa;
}) as unknown) as typeof execa;
const error = await t.throwsAsync(
cloneRepo(mock)({ repo: 'r', branch: 'b' }, 'd', 'p')
);
t.regex(error.message, /Git rev-parse failed./);
});
test('getGithubUsername: returns found users', async t => {
test('getGithubUsername: returns found users', async (t) => {
const mockFetcher = async (email: string) => email.split('@')[0];
const username: string = await getGithubUsername(mockFetcher)(
'bitjson@github.com'
@@ -175,7 +181,7 @@ test('getGithubUsername: returns found users', async t => {
t.is(username, 'bitjson');
});
test("getGithubUsername: returns placeholder if user doesn't have Git user.email set", async t => {
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
@@ -183,8 +189,9 @@ test("getGithubUsername: returns placeholder if user doesn't have Git user.email
t.is(username, Placeholders.username);
});
test('getGithubUsername: returns placeholder if not found', async t => {
test('getGithubUsername: returns placeholder if not found', async (t) => {
const mockFetcher = async () => {
// eslint-disable-next-line functional/no-throw-statement
throw new Error();
};
const username: string = await getGithubUsername(mockFetcher)(
@@ -193,72 +200,72 @@ test('getGithubUsername: returns placeholder if not found', async t => {
t.is(username, Placeholders.username);
});
test('getUserInfo: suppresses errors and returns empty strings', async t => {
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
gitName: Placeholders.name,
});
});
test('getUserInfo: returns results properly', async t => {
test('getUserInfo: returns results properly', async (t) => {
const mock = ((async () => {
return {
stdout: 'result'
stdout: 'result',
};
}) as any) as typeof execa;
}) as unknown) as typeof execa;
const result = await getUserInfo(mock)();
t.deepEqual(result, {
gitEmail: 'result',
gitName: 'result'
gitName: 'result',
});
});
test('initialCommit: throws generated errors', async t => {
test('initialCommit: throws generated errors', async (t) => {
const error = await t.throwsAsync<execa.ExecaError>(
initialCommit(mockErr(1))('deadbeef', 'fail')
);
t.is(error.exitCode, 1);
});
test('initialCommit: spawns 3 times', async t => {
test('initialCommit: spawns 3 times', async (t) => {
t.plan(4);
const mock = ((async () => {
t.pass();
}) as any) as typeof execa;
}) as unknown) as typeof execa;
await t.notThrowsAsync(initialCommit(mock)('commit', 'dir'));
});
test('install: uses the correct runner', async t => {
test('install: uses the correct runner', async (t) => {
const mock = ((async (runner: Runner) => {
runner === Runner.Yarn ? t.pass() : t.fail();
}) as any) as typeof execa;
}) as unknown) as typeof execa;
await install(mock)(Runner.Yarn, 'pass');
});
test('install: throws pretty error on failure', async t => {
test('install: throws pretty error on failure', async (t) => {
const error = await t.throwsAsync(install(mockErr())(Runner.Npm, 'fail'));
t.is(error.message, "Installation failed. You'll need to install manually.");
});
test("getRepoInfo: returns defaults when TYPESCRIPT_STARTER_REPO_URL/BRANCH aren't set", async t => {
test("getRepoInfo: returns defaults when TYPESCRIPT_STARTER_REPO_URL/BRANCH aren't set", async (t) => {
const thisRelease = '9000.0.1';
t.deepEqual(getRepoInfo(thisRelease), {
branch: `v${thisRelease}`,
repo: 'https://github.com/bitjson/typescript-starter.git'
repo: 'https://github.com/bitjson/typescript-starter.git',
});
const url = 'https://another/repo';
// tslint:disable-next-line:no-object-mutation
// eslint-disable-next-line functional/immutable-data
process.env.TYPESCRIPT_STARTER_REPO_URL = url;
t.deepEqual(getRepoInfo(thisRelease), {
branch: `master`,
repo: url
repo: url,
});
const branch = 'test';
// tslint:disable-next-line:no-object-mutation
// eslint-disable-next-line functional/immutable-data
process.env.TYPESCRIPT_STARTER_REPO_BRANCH = branch;
t.deepEqual(getRepoInfo(thisRelease), {
branch,
repo: url
repo: url,
});
});

View File

@@ -1,24 +1,36 @@
// tslint:disable:no-console no-if-statement no-expression-statement
import { readFileSync, renameSync, writeFileSync } from 'fs';
import { join } from 'path';
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 { replaceInFile } from 'replace-in-file';
import { Placeholders, Tasks } from './tasks';
import { normalizePath, Runner, TypescriptStarterOptions } from './utils';
const readPackageJson = (path: string) =>
JSON.parse(readFileSync(path, 'utf8'));
const writePackageJson = (path: string, pkg: unknown) => {
// 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);
};
export async function typescriptStarter(
{
appveyor,
circleci,
cspell,
description,
domDefinitions,
editorconfig,
email,
fullName,
githubUsername,
immutable,
functional,
install,
nodeDefinitions,
projectName,
@@ -27,7 +39,7 @@ export async function typescriptStarter(
strict,
travis,
vscode,
workingDirectory
workingDirectory,
}: TypescriptStarterOptions,
tasks: Tasks
): Promise<void> {
@@ -47,29 +59,35 @@ export async function typescriptStarter(
const pkgPath = join(projectPath, 'package.json');
const keptDevDeps: ReadonlyArray<string> = [
'@bitjson/npm-scripts-info',
'@bitjson/typedoc',
'@ava/typescript',
'@istanbuljs/nyc-config-typescript',
'@typescript-eslint/eslint-plugin',
'@typescript-eslint/parser',
'ava',
'codecov',
'cspell',
'cz-conventional-changelog',
'eslint',
'eslint-config-prettier',
'eslint-plugin-eslint-comments',
...(functional ? ['eslint-plugin-functional'] : []),
'eslint-plugin-import',
'gh-pages',
'npm-run-all',
'npm-scripts-info',
'nyc',
'open-cli',
'prettier',
'standard-version',
'trash-cli',
'tslint',
'tslint-config-prettier',
'tslint-immutable',
'ts-node',
'typedoc',
'typescript'
'typescript',
];
// dependencies to retain for Node.js applications
const nodeKeptDeps: ReadonlyArray<string> = ['sha.js'];
/**
* dependencies to retain for Node.js applications
*/
const nodeKeptDeps: ReadonlyArray<string> = ['@bitauth/libauth'];
const filterAllBut = (
keep: ReadonlyArray<string>,
@@ -90,7 +108,6 @@ export async function typescriptStarter(
: {},
description,
devDependencies: filterAllBut(keptDevDeps, pkg.devDependencies),
// tslint:disable-next-line:readonly-array
keywords: [],
name: projectName,
repository: `https://github.com/${githubUsername}/${projectName}`,
@@ -98,40 +115,43 @@ export async function typescriptStarter(
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/')\"`
'reset-hard': `git clean -dfx && git reset --hard && yarn`,
}
: { ...pkg.scripts },
version: '1.0.0'
version: '1.0.0',
};
// tslint:disable:no-delete no-object-mutation
// eslint-disable-next-line functional/immutable-data
delete newPkg.bin;
// eslint-disable-next-line functional/immutable-data
delete newPkg.NOTE;
// tslint:enable:no-delete no-object-mutation
// eslint-disable-next-line functional/immutable-data
delete newPkg.NOTE_2;
writePackageJson(pkgPath, newPkg);
spinnerPackage.succeed();
const spinnerGitignore = ora('Updating .gitignore').start();
if (runner === Runner.Yarn) {
await replace({
await replaceInFile({
files: join(projectPath, '.gitignore'),
from: 'yarn.lock',
to: 'package-lock.json'
to: 'package-lock.json',
});
}
spinnerGitignore.succeed();
const spinnerLicense = ora('Updating LICENSE').start();
await replace({
await replaceInFile({
files: join(projectPath, 'LICENSE'),
// cspell: disable-next-line
from: 'Jason Dreyzehner',
to: fullName
to: fullName,
});
await replace({
await replaceInFile({
files: join(projectPath, 'LICENSE'),
from: '2017',
to: new Date().getUTCFullYear().toString()
to: new Date().getUTCFullYear().toString(),
});
spinnerLicense.succeed();
@@ -143,7 +163,6 @@ export async function typescriptStarter(
normalizePath(join(projectPath, 'package-lock.json')),
normalizePath(join(projectPath, 'bin')),
normalizePath(join(projectPath, 'src', 'cli')),
normalizePath(join(projectPath, 'src', 'types', 'cli.d.ts'))
]);
if (!appveyor) {
del([normalizePath(join(projectPath, 'appveyor.yml'))]);
@@ -151,6 +170,21 @@ export async function typescriptStarter(
if (!circleci) {
del([normalizePath(join(projectPath, '.circleci'))]);
}
if (!cspell) {
del([normalizePath(join(projectPath, '.cspell.json'))]);
if (vscode) {
await replaceInFile({
files: join(projectPath, '.vscode', 'settings.json'),
from: ` "cSpell.userWords": [], // only use words from .cspell.json\n`,
to: '',
});
await replaceInFile({
files: join(projectPath, '.vscode', 'settings.json'),
from: ` "cSpell.enabled": true,\n`,
to: '',
});
}
}
if (!travis) {
del([normalizePath(join(projectPath, '.travis.yml'))]);
}
@@ -163,16 +197,16 @@ export async function typescriptStarter(
spinnerDelete.succeed();
const spinnerTsconfigModule = ora('Removing traces of the CLI').start();
await replace({
await replaceInFile({
files: join(projectPath, 'tsconfig.module.json'),
from: /,\s+\/\/ typescript-starter:[\s\S]*"src\/cli\/\*\*\/\*\.ts"/,
to: ''
to: '',
});
if (vscode) {
await replace({
await replaceInFile({
files: join(projectPath, '.vscode', 'launch.json'),
from: /,[\s]*\/\/ --- cut here ---[\s\S]*]/,
to: ']'
to: ']',
});
}
spinnerTsconfigModule.succeed();
@@ -182,67 +216,74 @@ export async function typescriptStarter(
join(projectPath, 'README-starter.md'),
join(projectPath, 'README.md')
);
await replace({
await replaceInFile({
files: join(projectPath, 'README.md'),
from: '[package-name]',
to: projectName
to: projectName,
});
await replace({
await replaceInFile({
files: join(projectPath, 'README.md'),
from: '[description]',
to: description
to: description,
});
spinnerReadme.succeed();
if (!strict) {
const spinnerStrict = ora(`tsconfig: disable strict`).start();
await replace({
await replaceInFile({
files: join(projectPath, 'tsconfig.json'),
from: '"strict": true',
to: '// "strict": true'
to: '// "strict": true',
});
spinnerStrict.succeed();
}
if (!domDefinitions) {
const spinnerDom = ora(`tsconfig: don't include "dom" lib`).start();
await replace({
await replaceInFile({
files: join(projectPath, 'tsconfig.json'),
from: '"lib": ["es2017", "dom"]',
to: '"lib": ["es2017"]'
to: '"lib": ["es2017"]',
});
spinnerDom.succeed();
}
if (!nodeDefinitions) {
const spinnerNode = ora(`tsconfig: don't include "node" types`).start();
await replace({
await replaceInFile({
files: join(projectPath, 'tsconfig.json'),
from: '"types": ["node"]',
to: '"types": []'
to: '"types": []',
});
await replace({
await replaceInFile({
files: join(projectPath, 'src', 'index.ts'),
from: /^export[\S\s]*hash';\s*/,
to: ''
to: '',
});
await del([
normalizePath(join(projectPath, 'src', 'lib', 'hash.ts')),
normalizePath(join(projectPath, 'src', 'lib', 'hash.spec.ts')),
normalizePath(join(projectPath, 'src', 'lib', 'async.ts')),
normalizePath(join(projectPath, 'src', 'lib', 'async.spec.ts'))
normalizePath(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: ''
if (!functional) {
const spinnerEslint = ora(
`eslint: disable eslint-plugin-functional`
).start();
await replaceInFile({
files: join(projectPath, '.eslintrc.json'),
from: '"plugins": ["import", "eslint-comments", "functional"]',
to: '"plugins": ["import", "eslint-comments"]',
});
spinnerTslint.succeed();
await replaceInFile({
files: join(projectPath, '.eslintrc.json'),
from: '"plugin:functional/lite",\n',
to: '',
});
spinnerEslint.succeed();
}
if (install) {
@@ -261,13 +302,3 @@ export async function typescriptStarter(
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);
};

View File

@@ -1,20 +1,22 @@
import chalk from 'chalk';
import { existsSync } from 'fs';
import gradient from 'gradient-string';
import { normalize } from 'path';
import chalk from 'chalk';
import gradient from 'gradient-string';
import validateNpmPackageName from 'validate-npm-package-name';
export enum Runner {
Npm = 'npm',
Yarn = 'yarn'
Yarn = 'yarn',
}
export interface TypescriptStarterCLIOptions {
export type TypescriptStarterCLIOptions = {
readonly appveyor: boolean;
readonly circleci: boolean;
readonly cspell: boolean;
readonly description: string;
readonly domDefinitions: boolean;
readonly editorconfig: boolean;
readonly immutable: boolean;
readonly functional: boolean;
readonly install: boolean;
readonly nodeDefinitions: boolean;
readonly projectName: string;
@@ -22,12 +24,12 @@ export interface TypescriptStarterCLIOptions {
readonly strict: boolean;
readonly travis: boolean;
readonly vscode: boolean;
}
};
export interface TypescriptStarterRequiredConfig {
export type TypescriptStarterRequiredConfig = {
readonly starterVersion: string;
readonly install: boolean;
}
};
export type TypescriptStarterUserOptions = TypescriptStarterCLIOptions &
TypescriptStarterRequiredConfig;
@@ -36,7 +38,7 @@ export type TypescriptStarterArgsOptions =
| TypescriptStarterUserOptions
| TypescriptStarterRequiredConfig;
export interface TypescriptStarterInferredOptions {
export type TypescriptStarterInferredOptions = {
readonly githubUsername: string;
readonly fullName: string;
readonly email: string;
@@ -45,13 +47,12 @@ export interface TypescriptStarterInferredOptions {
readonly branch: string;
};
readonly workingDirectory: string;
}
};
export interface TypescriptStarterOptions
extends TypescriptStarterCLIOptions,
TypescriptStarterInferredOptions {
// readonly starterVersion?: string;
}
export type TypescriptStarterOptions = TypescriptStarterCLIOptions &
TypescriptStarterInferredOptions & {
// readonly starterVersion?: string;
};
export function hasCLIOptions(
opts: TypescriptStarterArgsOptions
@@ -96,5 +97,5 @@ _ _ _ _ _
* On Windows, normalize returns "\\" as the path separator.
* This method normalizes with POSIX.
*/
export const normalizePath = (path: string) =>
export const normalizePath = (path: string): string =>
normalize(path).replace(/\\/g, '/');

View File

@@ -1,7 +1,7 @@
// tslint:disable:no-expression-statement
import test from 'ava';
import { asyncABC } from './async';
test('getABC', async t => {
test('getABC', async (t) => {
t.deepEqual(await asyncABC(), ['a', 'b', 'c']);
});

View File

@@ -1,5 +1,5 @@
/**
* A sample async function (to demo Typescript's es7 async/await downleveling).
* A sample async function (to demo Typescript's es7 async/await down-leveling).
*
* ### Example (es imports)
* ```js
@@ -15,18 +15,18 @@
* // => ['a','b','c']
* ```
*
* @returns a Promise which should contain `['a','b','c']`
* @returns a Promise which should contain `['a','b','c']`
*/
export async function asyncABC(): Promise<ReadonlyArray<string>> {
function somethingSlow(index: 0 | 1 | 2): Promise<string> {
export const asyncABC = async () => {
const somethingSlow = (index: 0 | 1 | 2) => {
const storage = 'abc'.charAt(index);
return new Promise<string>(resolve =>
return new Promise<string>((resolve) =>
// later...
resolve(storage)
);
}
};
const a = await somethingSlow(0);
const b = await somethingSlow(1);
const c = await somethingSlow(2);
return [a, b, c];
}
};

View File

@@ -1,11 +1,11 @@
// tslint:disable:no-expression-statement no-object-mutation
import test from 'ava';
import { sha256, sha256Native } from './hash';
test(
'sha256',
(t, input: string, expected: string) => {
t.is(sha256(input), expected);
async (t, input: string, expected: string) => {
t.is(await sha256(input), expected);
t.is(sha256Native(input), expected);
},
'test',

View File

@@ -1,5 +1,6 @@
import { createHash } from 'crypto';
import shaJs from 'sha.js';
import { binToHex, instantiateSha256, utf8ToBin } from '@bitauth/libauth';
/**
* Calculate the sha256 digest of a string.
@@ -7,32 +8,34 @@ import shaJs from 'sha.js';
* ### Example (es imports)
* ```js
* import { sha256 } from 'typescript-starter'
* sha256('test')
* // => '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
*
* (async () => {
* console.log(await sha256('test'));
* // => '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
* });
* ```
*
* @param message - the string to hash
* @returns sha256 message digest
*/
export function sha256(message: string): string {
return shaJs('sha256')
.update(message)
.digest('hex');
}
export const sha256 = async (message: string) => {
const sha256 = await instantiateSha256();
return binToHex(sha256.hash(utf8ToBin(message)));
};
/**
* A faster implementation of [[sha256]] which requires the native Node.js module. Browser consumers should use [[sha256]], instead.
* A synchronous implementation of `sha256` which uses the native Node.js
* module. (Browser consumers should use the `sha256` method.)
*
* ### Example (es imports)
* ```js
* import { sha256Native as sha256 } from 'typescript-starter'
* sha256('test')
* console.log(sha256('test'));
* // => '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
* ```
*
* @param message - the string to hash
* @returns sha256 message digest
*/
export function sha256Native(message: string): string {
return createHash('sha256')
.update(message)
.digest('hex');
}
export const sha256Native = (message: string) => {
return createHash('sha256').update(message).digest('hex');
};

View File

@@ -1,11 +1,11 @@
// tslint:disable:no-expression-statement
import test from 'ava';
import { double, power } from './number';
test('double', t => {
test('double', (t) => {
t.is(double(2), 4);
});
test('power', t => {
test('power', (t) => {
t.is(power(2, 4), 16);
});

View File

@@ -1,5 +1,5 @@
/**
* Multiplies a value by 2. (Also a full example of Typedoc's functionality.)
* Multiplies a value by 2. (Also a full example of TypeDoc's functionality.)
*
* ### Example (es module)
* ```js
@@ -15,16 +15,17 @@
* // => 8
* ```
*
* @param value Comment describing the `value` parameter.
* @returns Comment describing the return type.
* @anotherNote Some other value.
* @param value - Comment describing the `value` parameter.
* @returns Comment describing the return type.
* @anotherNote Some other value.
*/
export function double(value: number): number {
export const double = (value: number) => {
return value * 2;
}
};
/**
* Raise the value of the first parameter to the power of the second using the es7 `**` operator.
* Raise the value of the first parameter to the power of the second using the
* es7 exponentiation operator (`**`).
*
* ### Example (es module)
* ```js
@@ -39,8 +40,12 @@ export function double(value: number): number {
* console.log(power(2,3))
* // => 8
* ```
* @param base - the base to exponentiate
* @param exponent - the power to which to raise the base
*/
export function power(base: number, exponent: number): number {
// This is a proposed es7 operator, which should be transpiled by Typescript
export const power = (base: number, exponent: number) => {
/**
* This es7 exponentiation operator is transpiled by TypeScript
*/
return base ** exponent;
}
};

8
src/types/cli.d.ts vendored
View File

@@ -1,8 +0,0 @@
// 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';
declare module 'validate-npm-package-name';

View File

@@ -10,25 +10,15 @@
* 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
* This is an example type definition which allows import from `module-name`,
* e.g.:
* ```ts
* import something from 'module-name';
* something();
* ```
*/
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
}
declare module 'module-name' {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const whatever: any;
export = whatever;
}

View File

@@ -1,34 +0,0 @@
{
"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 */
}
}