├── .eslintignore ├── .eslintrc.cjs ├── .git-hooks └── pre-commit ├── .github └── workflows │ ├── compile.yaml │ ├── format.yaml │ ├── lint.yaml │ └── test.yaml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .vimspector.json ├── .vscode └── launch.json ├── .yarn └── releases │ └── yarn-4.2.2.cjs ├── .yarnrc.yml ├── LICENSE ├── README.md ├── babel.config.cjs ├── jest.config.cjs ├── package.json ├── renovate.json ├── src ├── dummy.ts ├── hello.test.ts ├── hello.ts └── index.ts ├── test └── index.ts ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | # Taken from https://github.com/github/gitignore/blob/main/Node.gitignore 2 | # and added custom ignores 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # Snowpack dependency directory (https://snowpack.dev/) 49 | web_modules/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional stylelint cache 61 | .stylelintcache 62 | 63 | # Microbundle cache 64 | .rpt2_cache/ 65 | .rts2_cache_cjs/ 66 | .rts2_cache_es/ 67 | .rts2_cache_umd/ 68 | 69 | # Optional REPL history 70 | .node_repl_history 71 | 72 | # Output of 'npm pack' 73 | *.tgz 74 | 75 | # Yarn Integrity file 76 | .yarn-integrity 77 | 78 | # dotenv environment variable files 79 | .env 80 | .env.development.local 81 | .env.test.local 82 | .env.production.local 83 | .env.local 84 | 85 | # parcel-bundler cache (https://parceljs.org/) 86 | .cache 87 | .parcel-cache 88 | 89 | # Next.js build output 90 | .next 91 | out 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | 97 | # Gatsby files 98 | .cache/ 99 | # Comment in the public line in if your project uses Gatsby and not Next.js 100 | # https://nextjs.org/blog/next-9-1#public-directory-support 101 | # public 102 | 103 | # vuepress build output 104 | .vuepress/dist 105 | 106 | # vuepress v2.x temp and cache directory 107 | .temp 108 | .cache 109 | 110 | # Docusaurus cache and generated files 111 | .docusaurus 112 | 113 | # Serverless directories 114 | .serverless/ 115 | 116 | # FuseBox cache 117 | .fusebox/ 118 | 119 | # DynamoDB Local files 120 | .dynamodb/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # yarn v2 129 | .yarn/cache 130 | .yarn/unplugged 131 | .yarn/build-state.yml 132 | .yarn/install-state.gz 133 | .pnp.* 134 | 135 | # custom added ignores below 136 | bundle 137 | dist 138 | node_modules 139 | TODO 140 | # eslintignore specific 141 | .yarn 142 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | extends: [ 4 | /** 5 | * Here I simply want to disable all rules. I feel like a pretty strict 6 | * tsconfig already catches a lot of the recommended rules and I want to 7 | * avoid any mental overhead of unnecessary "duplicated warnings/errors". 8 | * If you want to go with the "default/recommended" version, simply 9 | * uncomment the following lines in the extends array. 10 | */ 11 | // "eslint:recommended", 12 | // "plugin:@typescript-eslint/recommended", 13 | // "plugin:@typescript-eslint/recommended-requiring-type-checking", 14 | /* This prettier extends is used to disable all the formatting rules that 15 | * are enabled by the different "recommended" rules. 16 | */ 17 | // "prettier", 18 | ], 19 | plugins: ["@typescript-eslint"], 20 | parser: "@typescript-eslint/parser", 21 | parserOptions: { 22 | project: true, 23 | tsconfigRootDir: __dirname, 24 | }, 25 | /** 26 | * You can use the rules inside this overrides to specify the rules you want 27 | * to use on a one by one / case by case basis. If you simply want to go with 28 | * the default, just remove or uncomment the whole "rules" section inside the 29 | * "overrides" property and you are done. 30 | * 31 | * The following rules are my personal preference and reflect a subset of the 32 | * recommended options. They also include a lot of the more strict options NOT 33 | * included in the recommended ones. My goal is to simplify having a 34 | * consistent code base/code style, to avoid catchable bugs early and advocate 35 | * for usage of newer features of the language. 36 | **/ 37 | overrides: [ 38 | { 39 | // Enables type checking for typescript files. 40 | // Src for the overrides from here : 41 | // https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/eslint-recommended.ts 42 | files: ["*.ts", "*.tsx", "*.mts", "*.cts"], 43 | rules: { 44 | // "eslint" rules 45 | // check https://eslint.org/docs/latest/rules/ for reference 46 | "no-cond-assign": ["error", "always"], 47 | eqeqeq: ["error"], 48 | "no-constant-binary-expression": "error", 49 | curly: "error", 50 | "default-case": "error", 51 | "default-case-last": "error", 52 | "no-constant-condition": "error", 53 | "no-duplicate-imports": "error", 54 | "no-fallthrough": "error", 55 | "use-isnan": "error", 56 | "arrow-body-style": ["error", "always"], 57 | "no-loss-of-precision": "error", 58 | "no-promise-executor-return": "error", 59 | // See "when not to use it", and check your use case, if you think this 60 | // rule should be disabled. 61 | "no-await-in-loop": "error", 62 | "no-useless-escape": "error", 63 | "prefer-object-spread": "error", 64 | "prefer-spread": "error", 65 | "no-empty": "error", 66 | "no-useless-catch": "error", 67 | // See "when not to use it", and check your use case, if you think this 68 | // rule should be disabled. 69 | "no-bitwise": "error", 70 | // typescript-eslint rules 71 | // check https://typescript-eslint.io/rules/ for reference 72 | "@typescript-eslint/array-type": "error", 73 | "@typescript-eslint/consistent-type-definitions": ["error", "type"], 74 | "@typescript-eslint/no-unnecessary-condition": "error", 75 | "@typescript-eslint/prefer-includes": "error", 76 | "@typescript-eslint/prefer-optional-chain": "error", 77 | "@typescript-eslint/prefer-reduce-type-parameter": "error", 78 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 79 | "@typescript-eslint/ban-types": "error", 80 | "@typescript-eslint/no-explicit-any": "error", 81 | "@typescript-eslint/no-for-in-array": "error", 82 | "@typescript-eslint/no-unsafe-call": "error", 83 | "@typescript-eslint/no-unsafe-return": "error", 84 | "@typescript-eslint/no-unsafe-member-access": "error", 85 | "@typescript-eslint/no-var-requires": "error", 86 | "@typescript-eslint/restrict-plus-operands": "error", 87 | }, 88 | }, 89 | ], 90 | root: true, 91 | }; 92 | -------------------------------------------------------------------------------- /.git-hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Runs prettier on all files before comitting them. Has to be placed inside 4 | # .git/hooks/. 5 | # src: https://prettier.io/docs/en/precommit.html 6 | 7 | FILES=$(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g') 8 | [ -z "$FILES" ] && exit 0 9 | 10 | echo "Running prettier to format files before commiting..." 11 | 12 | # Prettify all selected files 13 | echo "$FILES" | xargs ./node_modules/.bin/prettier --ignore-unknown --write 14 | 15 | echo "Done!" 16 | 17 | # Add back the modified/prettified files to staging 18 | echo "$FILES" | xargs git add 19 | 20 | exit 0 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/compile.yaml: -------------------------------------------------------------------------------- 1 | name: compile typescript 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | compile: 7 | runs-on: ${{ matrix.os }} 8 | 9 | strategy: 10 | matrix: 11 | node-version: [20.x] 12 | os: [ubuntu-latest, macos-latest, windows-latest] 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: Install Dependencies 21 | run: yarn --immutable 22 | - name: Compile 23 | run: yarn compile 24 | -------------------------------------------------------------------------------- /.github/workflows/format.yaml: -------------------------------------------------------------------------------- 1 | name: check formatting 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | format: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Use Node.js 20.x 12 | uses: actions/setup-node@v4 13 | with: 14 | node-version: 20.x 15 | - name: Install Dependencies 16 | run: yarn --immutable 17 | - name: Format 18 | run: yarn format:check 19 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: check for linting issues 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Use Node.js 20.x 12 | uses: actions/setup-node@v4 13 | with: 14 | node-version: 20.x 15 | - name: Install Dependencies 16 | run: yarn --immutable 17 | - name: Lint 18 | run: yarn lint:check 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test codebase 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | 9 | strategy: 10 | matrix: 11 | node-version: [20.x] 12 | os: [ubuntu-latest, macos-latest, windows-latest] 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: Install Dependencies 21 | run: yarn --immutable 22 | - name: Test 23 | run: yarn test 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Taken from https://github.com/github/gitignore/blob/main/Node.gitignore 2 | # and added custom ignores 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # Snowpack dependency directory (https://snowpack.dev/) 49 | web_modules/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional stylelint cache 61 | .stylelintcache 62 | 63 | # Microbundle cache 64 | .rpt2_cache/ 65 | .rts2_cache_cjs/ 66 | .rts2_cache_es/ 67 | .rts2_cache_umd/ 68 | 69 | # Optional REPL history 70 | .node_repl_history 71 | 72 | # Output of 'npm pack' 73 | *.tgz 74 | 75 | # Yarn Integrity file 76 | .yarn-integrity 77 | 78 | # dotenv environment variable files 79 | .env 80 | .env.development.local 81 | .env.test.local 82 | .env.production.local 83 | .env.local 84 | 85 | # parcel-bundler cache (https://parceljs.org/) 86 | .cache 87 | .parcel-cache 88 | 89 | # Next.js build output 90 | .next 91 | out 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | 97 | # Gatsby files 98 | .cache/ 99 | # Comment in the public line in if your project uses Gatsby and not Next.js 100 | # https://nextjs.org/blog/next-9-1#public-directory-support 101 | # public 102 | 103 | # vuepress build output 104 | .vuepress/dist 105 | 106 | # vuepress v2.x temp and cache directory 107 | .temp 108 | .cache 109 | 110 | # Docusaurus cache and generated files 111 | .docusaurus 112 | 113 | # Serverless directories 114 | .serverless/ 115 | 116 | # FuseBox cache 117 | .fusebox/ 118 | 119 | # DynamoDB Local files 120 | .dynamodb/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # yarn v2 129 | .yarn/cache 130 | .yarn/unplugged 131 | .yarn/build-state.yml 132 | .yarn/install-state.gz 133 | .pnp.* 134 | 135 | # custom added ignores below 136 | bundle 137 | dist 138 | node_modules 139 | TODO 140 | .envrc 141 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.13.1 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Taken from https://github.com/github/gitignore/blob/main/Node.gitignore 2 | # and added custom ignores 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # Snowpack dependency directory (https://snowpack.dev/) 49 | web_modules/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional stylelint cache 61 | .stylelintcache 62 | 63 | # Microbundle cache 64 | .rpt2_cache/ 65 | .rts2_cache_cjs/ 66 | .rts2_cache_es/ 67 | .rts2_cache_umd/ 68 | 69 | # Optional REPL history 70 | .node_repl_history 71 | 72 | # Output of 'npm pack' 73 | *.tgz 74 | 75 | # Yarn Integrity file 76 | .yarn-integrity 77 | 78 | # dotenv environment variable files 79 | .env 80 | .env.development.local 81 | .env.test.local 82 | .env.production.local 83 | .env.local 84 | 85 | # parcel-bundler cache (https://parceljs.org/) 86 | .cache 87 | .parcel-cache 88 | 89 | # Next.js build output 90 | .next 91 | out 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | 97 | # Gatsby files 98 | .cache/ 99 | # Comment in the public line in if your project uses Gatsby and not Next.js 100 | # https://nextjs.org/blog/next-9-1#public-directory-support 101 | # public 102 | 103 | # vuepress build output 104 | .vuepress/dist 105 | 106 | # vuepress v2.x temp and cache directory 107 | .temp 108 | .cache 109 | 110 | # Docusaurus cache and generated files 111 | .docusaurus 112 | 113 | # Serverless directories 114 | .serverless/ 115 | 116 | # FuseBox cache 117 | .fusebox/ 118 | 119 | # DynamoDB Local files 120 | .dynamodb/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # yarn v2 129 | .yarn/cache 130 | .yarn/unplugged 131 | .yarn/build-state.yml 132 | .yarn/install-state.gz 133 | .pnp.* 134 | 135 | # custom added ignores below 136 | bundle 137 | dist 138 | node_modules 139 | TODO 140 | # prettierignore specific 141 | .yarn 142 | -------------------------------------------------------------------------------- /.vimspector.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": { 3 | "run - js-debug": { 4 | "adapter": "js-debug", 5 | "configuration": { 6 | "request": "attach", 7 | "outDir": "${workspaceRoot}/dist", 8 | "skipFiles": ["/**"], 9 | "cwd": "${workspaceRoot}" 10 | }, 11 | "breakpoints": { 12 | "exception": { 13 | "all": "", 14 | "uncaught": "" 15 | } 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "debug", 6 | "type": "node", 7 | "request": "attach", 8 | "skipFiles": ["/**"], 9 | "outFiles": ["${workspaceFolder}/dist/**/*.js"] 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: false 4 | 5 | nodeLinker: node-modules 6 | 7 | yarnPath: .yarn/releases/yarn-4.2.2.cjs 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Pierre Dahmani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node.js Typescript Modern Starter 2 | 3 | While developing with Typescript and Node.js is awesome, **setting up a new 4 | project is painful**. 5 | This minimal and modern starter repo is here to help you get started with 6 | Node.js and Typecript without the pain. 7 | 8 | ## Overview 9 | 10 | This starter uses a bare-bones and minimal approach to get anyone up and 11 | running with a new project in no time. It provides: 12 | 13 | - Typescript 5 with a strict tsconfig. 14 | - Yarn/Npm scripts ready to do everything you commonly need. Supporting `compile`, 15 | `clean`, `test`, `bundle`, `dev` and `start`. These scripts are created to be 16 | compatible with the operating systems linux, macos and windows. 17 | - Github Actions in place runnung with current node LTS version (20) on linux, 18 | macos and windows to automatically (for each PR): 19 | - test the code 20 | - compile the codebase from ts to js 21 | - check for formatting issues 22 | - check for linting issues 23 | - Testing via [jest](https://www.npmjs.com/package/jest) 24 | - Formatting via [prettier](https://prettier.io/). 25 | - Linting via [eslint](https://eslint.org/) and 26 | [typescript-eslint](https://typescript-eslint.io/) 27 | - Bundling via [esbuild](https://esbuild.github.io/), a fast bundler that "just 28 | works". 29 | - Debugging set up with examples for vscode and vim. 30 | - Automated dependency updates via 31 | [renovate](https://github.com/renovatebot/renovate). 32 | - Using the current LTS, nodejs 20 33 | 34 | #### Project Goals 35 | 36 | - Help you to just **get started** with a Node.js Typescript setup and **not 37 | worry about configuration**. 38 | - All scripts compatible with linux, macos and windows. 39 | - No magic. Everything kept as simple as possible while configuring anything you 40 | might need. 41 | - Advocate for **testing your code**. The common approaches of _tests and code 42 | side by side_ as well as _all tests in a seperate folder_ already working and 43 | set up for you. 44 | - Advocate for **using CI/CD** (in this case Github Actions). Automatically 45 | check formatting, check linting, compile and test the code base. Everything running 46 | on each PR. 47 | - Advocate establishing best practices via linting rules using eslint and 48 | typescript-eslint. However, still giving a documented way to quickly and 49 | easily disable them, if that is preferred. 50 | - Use modern tools like esbuild, typescript 5 and the nodejs test runner. 51 | - Be open for any framework or library that you prefer. This setup should be 52 | useful to everyone. You can easily add your preferred packages in no time. 53 | 54 | ## Prerequisites 55 | 56 | - [nvm](https://github.com/nvm-sh/nvm) (or something like 57 | [nvm-windows](https://github.com/coreybutler/nvm-windows) if you are on 58 | windows) 59 | 60 | ## Quickstart 61 | 62 | - Clone the repo `git clone git@github.com:xddq/nodejs-typescript-modern-starter` 63 | - Remove the .git folder `cd nodejs-typescript-modern-starter && rm -rf .git` 64 | - (optional) Update the package.json name, author, keywords, etc.. 65 | - Set up your own git folder and create your first commit. Run `git init && git 66 | add . && git commit -am "initial commit"` 67 | - (optional) Set up the git hook for formatting your code. `cp 68 | .git-hooks/pre-commit .git/hooks/pre-commit`. For windows you need to use 69 | [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) to use this. 70 | - Use the node version specified in .nvmrc `nvm install && nvm use` (on windows you need to specify the node version in the command) 71 | - Enable corepack and update yarn `corepack enable` 72 | - Install dependencies `yarn` 73 | - You're done🎉 What about you try running the tests? Run `yarn test`. See the 74 | section below for all available commands together with their explanation. 75 | 76 | ## Scripts and their explanation 77 | 78 | All scripts can be found inside the package.json file under the "scripts" 79 | attribute. 80 | 81 | - `yarn bundle` -> Bundles the whole code into a single javascript file which 82 | will be stored inside the dist folder. For prod deployments you typically just 83 | copy this file somewhere and then run something like `node --enable-source-maps 84 | ./index.js`. 85 | - `yarn clean` -> Removes bundled files by deleting the dist folder. Normally 86 | there is no need to invoke this manually. 87 | - `yarn compile` -> Runs the typescript compiler against the typescript 88 | codebase. Displays any errors if they occur. 89 | - `yarn compile:watch` -> Runs the typescript compiler every time you make 90 | changes to a file. It is good to open this in another terminal while 91 | developing to spot typescript issues. 92 | - `yarn dev` -> This should be used for running the code while developing. It 93 | watches all changes you make to your typescript codebase and automatically 94 | rebuilds the project. It does also watch all changes made to the built project 95 | and restarts the code whenever changes are detected. This enables a quick 96 | feedback loop. 97 | - `yarn debug` -> Starts the app in debugging mode. Waits for a debugger to 98 | attach. See Debugging below for more info. 99 | - If you want to restart the debugging process every time you change the code, 100 | you can use something like `nodemon --watch src --watch test --ext ts,json 101 | --exec 'yarn debug'` or when debugging tests with `nodemon --watch src --watch 102 | test --ext ts,json --exec 'yarn debug:test'` 103 | - `yarn debug:test` -> Starts the test run in debugging mode. Waits for a 104 | debugger to attach. See Debugging below for more info. 105 | - `yarn format` -> Formats the code using prettier. 106 | - `yarn format:check` -> Checks for formatting errors using prettier. This is 107 | typically only invoked by the CI/CD pipeline. 108 | - `yarn lint` -> Lints the code using eslint. Fixes problems that are 109 | auto-fixable and reports the rest of them to you. 110 | - `yarn lint:check` -> Checks for linting errors using eslint. This is typically 111 | only invoked by the CI/CD pipeline. 112 | - `yarn start` -> Runs the code. This only works if the code was bundled before ;). 113 | - `yarn test` -> Tests your codebase. Basic tests are created for both major 114 | common approaches of putting tests beside the source code as well as putting 115 | tests in a separate folder. 116 | - You can inspect the code coverage in depth by running `npx http-server 117 | ./coverage/lcov-report` and then browsing http://localhost:8080. 118 | 119 | ## Debugging 120 | 121 | An enourmous amount of people default to `console log debugging` since 122 | understanding the setup for debugging typescript can be somewhat awful and 123 | painful. This repo provides a debug config and guide ready to use for 124 | [vscode](git@github.com:microsoft/vscode.git) and for vim using 125 | [vimspector](https://github.com/puremourning/vimspector). Both use the mostly 126 | DAP compliant debugger 127 | [vscode-js-debug](https://github.com/microsoft/vscode-js-debug). 128 | 129 | ### Debugging Code 130 | 131 | There are somewhat "different" ways of starting the debugger. Once is by 132 | starting the app and waiting for a debugger to connect and the other one is 133 | starting the app initiated by the debugger. I made the experience that the 134 | former works on any given code base, no matter the amount of transipilation or 135 | bundling steps and custom steups while the latter does fail in extremely 136 | customized scenarios. Therefore here only the first one is covered with 137 | examples. 138 | 139 | #### Vim or Vscode 140 | 141 | - Run `yarn debug` in another terminal 142 | - Open src/index.ts `vim ./src/index.ts` (or code ./src/index.ts) in another terminal. 143 | - Set breakpoint somewhere in the file at the console log (F9 is the default mapping). 144 | - Start by pressing F5 145 | - Press F5 again, should see the console.log output 146 | - Done🎉 147 | 148 | ### Debugging Tests 149 | 150 | #### Vim or Vscode 151 | 152 | - Run `yarn debug:test` in another terminal 153 | - Open src/index.ts `vim ./src/hello.test.ts` in another terminal. 154 | - Set breakpoint in the line of the console log in the test file. 155 | - Start by pressing F5 (then skip the jest internal file once with F5) 156 | - Check the terminal where you ran `yarn debug:test`, it should not display the console log yet. 157 | - Press F5 again, should see the console.log output there now. 158 | - Done🎉 159 | 160 | ## Linting 161 | 162 | This repo has [eslint](https://eslint.org/) and 163 | [typescript-eslint](https://typescript-eslint.io/) as well as an automated 164 | Github Action to check for linting set up and ready to go. 165 | 166 | The rules in this project are my personal preference and reflect a subset of the 167 | recommended options. They also include a lot of the more strict options (NOT 168 | included in the recommended ones). My goal is to simplify having a consistent 169 | code base/code style, to avoid catchable bugs early and advocate for usage of 170 | newer features of the language. 171 | 172 | However, I made it **dead simple** to enable the default/recommended eslint 173 | rules, if you want to use them instead. Everything is documented, just browse to 174 | [./.eslintrc.cjs](https://github.com/xddq/nodejs-typescript-modern-starter/blob/main/.eslintrc.cjs) 175 | and adapt the code. 176 | 177 | ## Automated Dependency Updates 178 | 179 | After using this repo (either via the github template or by simply cloning it) 180 | you have to set up a renovate bot. For github this can easily be done via the 181 | Github Apps [renovate](https://github.com/apps/renovate) as well as 182 | [renovate-approve](https://github.com/apps/renovate-approve). To be able to the 183 | mimic the approach used in this repo, you should set up: 184 | 185 | - the repo setting to allow auto-merge 186 | - a branch protection rule for the main branch to require approval (will be 187 | handled via renovate-approve) 188 | -------------------------------------------------------------------------------- /babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | "@babel/preset-typescript", 5 | ], 6 | // required for easily mocking module exports. 7 | // see: https://stackoverflow.com/questions/67872622/jest-spyon-not-working-on-index-file-cannot-redefine-property 8 | assumptions: { 9 | constantReexports: true, 10 | }, 11 | plugins: [], 12 | }; 13 | -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | /** 3 | * For a detailed explanation regarding each configuration property, visit: 4 | * https://jestjs.io/docs/configuration 5 | */ 6 | 7 | /** @type {import('jest').Config} */ 8 | const config = { 9 | // Automatically clear mock calls, instances, contexts and results before every test 10 | clearMocks: true, 11 | // Indicates whether the coverage information should be collected while executing the test 12 | collectCoverage: true, 13 | coverageReporters: ["lcov", "text"], 14 | collectCoverageFrom: [ 15 | "**/*.ts", 16 | "!**/node_modules/**", 17 | "!**/dist/**", 18 | "!**/*.d.ts", 19 | ], 20 | // The directory where Jest should output its coverage files 21 | coverageDirectory: "coverage", 22 | // Indicates which provider should be used to instrument code for coverage 23 | coverageProvider: "v8", 24 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 25 | testPathIgnorePatterns: ["/node_modules/", "/dist/"], 26 | // Indicates whether each individual test should be reported during the run 27 | verbose: true, 28 | testMatch: [ 29 | "**/test/**/*.[jt]s?(x)", 30 | "!**/test/util/**/*.[jt]s?(x)", 31 | "!**/test/fixture/**/*.[jt]s?(x)", 32 | "**/?(*.)+(spec|test).[tj]s?(x)", 33 | ], 34 | }; 35 | 36 | module.exports = config; 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-typescript-modern-starter", 3 | "version": "1.0.0", 4 | "main": "dist/src/index.js", 5 | "typings": "dist/src/index.d.ts", 6 | "bin": { 7 | "nodejs-typescript-modern-starter": "dist/src/index.js", 8 | "ntms": "dist/src/index.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/xddq/schema2typebox.git" 13 | }, 14 | "keywords": [ 15 | "nodejs", 16 | "typescript", 17 | "modern", 18 | "starter", 19 | "nodejs typescript modern starter", 20 | "debugging", 21 | "ts", 22 | "esbuild", 23 | "prettier", 24 | "typescript5", 25 | "CI/CD", 26 | "minimal" 27 | ], 28 | "author": "Pierre Dahmani ", 29 | "license": "MIT", 30 | "scripts": { 31 | "bundle": "rimraf dist && esbuild --outdir=dist --sourcemap --bundle --platform=node --target=node20.10.0 ./src/index.ts", 32 | "compile": "tsc", 33 | "compile:watch": "tsc -w", 34 | "dev": "nodemon --watch src --watch test --ext ts,json --exec 'yarn bundle && yarn start'", 35 | "debug": "yarn bundle && node --enable-source-maps --inspect-brk ./dist/index.js", 36 | "debug:test": "node --inspect-brk ./node_modules/.bin/jest --runInBand .", 37 | "format": "prettier . --write", 38 | "format:check": "prettier . --check", 39 | "lint": "eslint . --fix", 40 | "lint:check": "eslint .", 41 | "start": "node --enable-source-maps ./dist/index.js", 42 | "test": "jest --runInBand ." 43 | }, 44 | "devDependencies": { 45 | "@babel/core": "7.27.4", 46 | "@babel/preset-env": "7.27.2", 47 | "@babel/preset-typescript": "7.27.1", 48 | "@jest/globals": "29.7.0", 49 | "@tsconfig/node20": "20.1.5", 50 | "@types/jest": "29.5.14", 51 | "@types/node": "20.17.57", 52 | "@typescript-eslint/eslint-plugin": "6.21.0", 53 | "@typescript-eslint/parser": "6.21.0", 54 | "esbuild": "0.21.3", 55 | "eslint": "8.57.1", 56 | "eslint-config-prettier": "9.1.0", 57 | "jest": "29.7.0", 58 | "nodemon": "3.1.10", 59 | "prettier": "3.5.3", 60 | "rimraf": "5.0.10", 61 | "typescript": "5.8.3" 62 | }, 63 | "packageManager": "yarn@4.2.2" 64 | } 65 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "prHourlyLimit": 0, 5 | "schedule": ["after 10am on monday"], 6 | "packageRules": [ 7 | { 8 | "matchDepTypes": ["devDependencies"], 9 | "matchUpdateTypes": ["minor", "patch"], 10 | "matchCurrentVersion": "!/^0/", 11 | "automerge": true, 12 | "automergeType": "pr", 13 | "platformAutomerge": true, 14 | "minimumReleaseAge": "5 days", 15 | "groupName": "all non-major dependencies", 16 | "groupSlug": "all-minor-patch" 17 | }, 18 | { 19 | "matchUpdateTypes": ["major"], 20 | "minimumReleaseAge": "5 days", 21 | "dependencyDashboardApproval": true 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /src/dummy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is just some dummy code taken from the current typescript playground and 3 | * then adapted a little so that github displays this repo as a typescript repo. 4 | * Based on the little amount of typescript compared to js it would otherwise be 5 | * displayed as js repo, which is quite ironic. 6 | * Typescript playground: https://www.typescriptlang.org/play 7 | * TL;DR: Delete this dummy file after cloning the repo. 8 | */ 9 | 10 | type ValidateRow = { 11 | [K in keyof T]: K extends `pdf${string}` 12 | ? string 13 | : K extends `thumbnail${string}` 14 | ? readonly [string, string] 15 | : 16 | | { 17 | readonly text: string; 18 | readonly type: "text"; 19 | } 20 | | { 21 | text: string; 22 | type: "image"; 23 | thumbnail: string; 24 | }; 25 | }; 26 | 27 | function createRow>(row: T): T { 28 | return row; 29 | } 30 | 31 | export const doStuff = () => { 32 | createRow({ 33 | givenName: { 34 | text: "Foo", 35 | type: "text", 36 | }, 37 | familyName: { 38 | text: "Bar", 39 | type: "text", 40 | }, 41 | picture: { 42 | text: "abc.png", 43 | type: "image", 44 | thumbnail: "https://example.com/thumbnail/sample.png", 45 | }, 46 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 47 | thumbnail62882329b9baf800217efe7c: [ 48 | "https://example.com/thumbnail/head.png", 49 | "https://example.com/thumbnail/rail.png", 50 | ], 51 | }); 52 | 53 | createRow({ 54 | givenName: { 55 | text: "Foo", 56 | type: "text", 57 | }, 58 | familyName: { 59 | text: "Bar", 60 | type: "text", 61 | }, 62 | picture: { 63 | text: "abc.png", 64 | type: "image", 65 | thumbnail: "https://example.com/thumbnail/sample.png", 66 | }, 67 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 68 | thumbnail62882329b9baf800217efe7c: [ 69 | "https://example.com/thumbnail/head.png", 70 | "https://example.com/thumbnail/rail.png", 71 | ], 72 | }); 73 | 74 | createRow({ 75 | givenName: { 76 | text: "Foo", 77 | type: "text", 78 | }, 79 | familyName: { 80 | text: "Bar", 81 | type: "text", 82 | }, 83 | picture: { 84 | text: "abc.png", 85 | type: "image", 86 | thumbnail: "https://example.com/thumbnail/sample.png", 87 | }, 88 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 89 | thumbnail62882329b9baf800217efe7c: [ 90 | "https://example.com/thumbnail/head.png", 91 | "https://example.com/thumbnail/rail.png", 92 | ], 93 | }); 94 | 95 | createRow({ 96 | givenName: { 97 | text: "Foo", 98 | type: "text", 99 | }, 100 | familyName: { 101 | text: "Bar", 102 | type: "text", 103 | }, 104 | picture: { 105 | text: "abc.png", 106 | type: "image", 107 | thumbnail: "https://example.com/thumbnail/sample.png", 108 | }, 109 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 110 | thumbnail62882329b9baf800217efe7c: [ 111 | "https://example.com/thumbnail/head.png", 112 | "https://example.com/thumbnail/rail.png", 113 | ], 114 | }); 115 | 116 | createRow({ 117 | givenName: { 118 | text: "Foo", 119 | type: "text", 120 | }, 121 | familyName: { 122 | text: "Bar", 123 | type: "text", 124 | }, 125 | picture: { 126 | text: "abc.png", 127 | type: "image", 128 | thumbnail: "https://example.com/thumbnail/sample.png", 129 | }, 130 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 131 | thumbnail62882329b9baf800217efe7c: [ 132 | "https://example.com/thumbnail/head.png", 133 | "https://example.com/thumbnail/rail.png", 134 | ], 135 | }); 136 | 137 | createRow({ 138 | givenName: { 139 | text: "Foo", 140 | type: "text", 141 | }, 142 | familyName: { 143 | text: "Bar", 144 | type: "text", 145 | }, 146 | picture: { 147 | text: "abc.png", 148 | type: "image", 149 | thumbnail: "https://example.com/thumbnail/sample.png", 150 | }, 151 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 152 | thumbnail62882329b9baf800217efe7c: [ 153 | "https://example.com/thumbnail/head.png", 154 | "https://example.com/thumbnail/rail.png", 155 | ], 156 | }); 157 | 158 | createRow({ 159 | givenName: { 160 | text: "Foo", 161 | type: "text", 162 | }, 163 | familyName: { 164 | text: "Bar", 165 | type: "text", 166 | }, 167 | picture: { 168 | text: "abc.png", 169 | type: "image", 170 | thumbnail: "https://example.com/thumbnail/sample.png", 171 | }, 172 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 173 | thumbnail62882329b9baf800217efe7c: [ 174 | "https://example.com/thumbnail/head.png", 175 | "https://example.com/thumbnail/rail.png", 176 | ], 177 | }); 178 | 179 | createRow({ 180 | givenName: { 181 | text: "Foo", 182 | type: "text", 183 | }, 184 | familyName: { 185 | text: "Bar", 186 | type: "text", 187 | }, 188 | picture: { 189 | text: "abc.png", 190 | type: "image", 191 | thumbnail: "https://example.com/thumbnail/sample.png", 192 | }, 193 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 194 | thumbnail62882329b9baf800217efe7c: [ 195 | "https://example.com/thumbnail/head.png", 196 | "https://example.com/thumbnail/rail.png", 197 | ], 198 | }); 199 | 200 | createRow({ 201 | givenName: { 202 | text: "Foo", 203 | type: "text", 204 | }, 205 | familyName: { 206 | text: "Bar", 207 | type: "text", 208 | }, 209 | picture: { 210 | text: "abc.png", 211 | type: "image", 212 | thumbnail: "https://example.com/thumbnail/sample.png", 213 | }, 214 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 215 | thumbnail62882329b9baf800217efe7c: [ 216 | "https://example.com/thumbnail/head.png", 217 | "https://example.com/thumbnail/rail.png", 218 | ], 219 | }); 220 | 221 | createRow({ 222 | givenName: { 223 | text: "Foo", 224 | type: "text", 225 | }, 226 | familyName: { 227 | text: "Bar", 228 | type: "text", 229 | }, 230 | picture: { 231 | text: "abc.png", 232 | type: "image", 233 | thumbnail: "https://example.com/thumbnail/sample.png", 234 | }, 235 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 236 | thumbnail62882329b9baf800217efe7c: [ 237 | "https://example.com/thumbnail/head.png", 238 | "https://example.com/thumbnail/rail.png", 239 | ], 240 | }); 241 | 242 | createRow({ 243 | givenName: { 244 | text: "Foo", 245 | type: "text", 246 | }, 247 | familyName: { 248 | text: "Bar", 249 | type: "text", 250 | }, 251 | picture: { 252 | text: "abc.png", 253 | type: "image", 254 | thumbnail: "https://example.com/thumbnail/sample.png", 255 | }, 256 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 257 | thumbnail62882329b9baf800217efe7c: [ 258 | "https://example.com/thumbnail/head.png", 259 | "https://example.com/thumbnail/rail.png", 260 | ], 261 | }); 262 | 263 | createRow({ 264 | givenName: { 265 | text: "Foo", 266 | type: "text", 267 | }, 268 | familyName: { 269 | text: "Bar", 270 | type: "text", 271 | }, 272 | picture: { 273 | text: "abc.png", 274 | type: "image", 275 | thumbnail: "https://example.com/thumbnail/sample.png", 276 | }, 277 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 278 | thumbnail62882329b9baf800217efe7c: [ 279 | "https://example.com/thumbnail/head.png", 280 | "https://example.com/thumbnail/rail.png", 281 | ], 282 | }); 283 | 284 | createRow({ 285 | givenName: { 286 | text: "Foo", 287 | type: "text", 288 | }, 289 | familyName: { 290 | text: "Bar", 291 | type: "text", 292 | }, 293 | picture: { 294 | text: "abc.png", 295 | type: "image", 296 | thumbnail: "https://example.com/thumbnail/sample.png", 297 | }, 298 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 299 | thumbnail62882329b9baf800217efe7c: [ 300 | "https://example.com/thumbnail/head.png", 301 | "https://example.com/thumbnail/rail.png", 302 | ], 303 | }); 304 | 305 | createRow({ 306 | givenName: { 307 | text: "Foo", 308 | type: "text", 309 | }, 310 | familyName: { 311 | text: "Bar", 312 | type: "text", 313 | }, 314 | picture: { 315 | text: "abc.png", 316 | type: "image", 317 | thumbnail: "https://example.com/thumbnail/sample.png", 318 | }, 319 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 320 | thumbnail62882329b9baf800217efe7c: [ 321 | "https://example.com/thumbnail/head.png", 322 | "https://example.com/thumbnail/rail.png", 323 | ], 324 | }); 325 | 326 | createRow({ 327 | givenName: { 328 | text: "Foo", 329 | type: "text", 330 | }, 331 | familyName: { 332 | text: "Bar", 333 | type: "text", 334 | }, 335 | picture: { 336 | text: "abc.png", 337 | type: "image", 338 | thumbnail: "https://example.com/thumbnail/sample.png", 339 | }, 340 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 341 | thumbnail62882329b9baf800217efe7c: [ 342 | "https://example.com/thumbnail/head.png", 343 | "https://example.com/thumbnail/rail.png", 344 | ], 345 | }); 346 | 347 | createRow({ 348 | givenName: { 349 | text: "Foo", 350 | type: "text", 351 | }, 352 | familyName: { 353 | text: "Bar", 354 | type: "text", 355 | }, 356 | picture: { 357 | text: "abc.png", 358 | type: "image", 359 | thumbnail: "https://example.com/thumbnail/sample.png", 360 | }, 361 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 362 | thumbnail62882329b9baf800217efe7c: [ 363 | "https://example.com/thumbnail/head.png", 364 | "https://example.com/thumbnail/rail.png", 365 | ], 366 | }); 367 | 368 | createRow({ 369 | givenName: { 370 | text: "Foo", 371 | type: "text", 372 | }, 373 | familyName: { 374 | text: "Bar", 375 | type: "text", 376 | }, 377 | picture: { 378 | text: "abc.png", 379 | type: "image", 380 | thumbnail: "https://example.com/thumbnail/sample.png", 381 | }, 382 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 383 | thumbnail62882329b9baf800217efe7c: [ 384 | "https://example.com/thumbnail/head.png", 385 | "https://example.com/thumbnail/rail.png", 386 | ], 387 | }); 388 | 389 | createRow({ 390 | givenName: { 391 | text: "Foo", 392 | type: "text", 393 | }, 394 | familyName: { 395 | text: "Bar", 396 | type: "text", 397 | }, 398 | picture: { 399 | text: "abc.png", 400 | type: "image", 401 | thumbnail: "https://example.com/thumbnail/sample.png", 402 | }, 403 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 404 | thumbnail62882329b9baf800217efe7c: [ 405 | "https://example.com/thumbnail/head.png", 406 | "https://example.com/thumbnail/rail.png", 407 | ], 408 | }); 409 | 410 | createRow({ 411 | givenName: { 412 | text: "Foo", 413 | type: "text", 414 | }, 415 | familyName: { 416 | text: "Bar", 417 | type: "text", 418 | }, 419 | picture: { 420 | text: "abc.png", 421 | type: "image", 422 | thumbnail: "https://example.com/thumbnail/sample.png", 423 | }, 424 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 425 | thumbnail62882329b9baf800217efe7c: [ 426 | "https://example.com/thumbnail/head.png", 427 | "https://example.com/thumbnail/rail.png", 428 | ], 429 | }); 430 | 431 | createRow({ 432 | givenName: { 433 | text: "Foo", 434 | type: "text", 435 | }, 436 | familyName: { 437 | text: "Bar", 438 | type: "text", 439 | }, 440 | picture: { 441 | text: "abc.png", 442 | type: "image", 443 | thumbnail: "https://example.com/thumbnail/sample.png", 444 | }, 445 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 446 | thumbnail62882329b9baf800217efe7c: [ 447 | "https://example.com/thumbnail/head.png", 448 | "https://example.com/thumbnail/rail.png", 449 | ], 450 | }); 451 | 452 | createRow({ 453 | givenName: { 454 | text: "Foo", 455 | type: "text", 456 | }, 457 | familyName: { 458 | text: "Bar", 459 | type: "text", 460 | }, 461 | picture: { 462 | text: "abc.png", 463 | type: "image", 464 | thumbnail: "https://example.com/thumbnail/sample.png", 465 | }, 466 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 467 | thumbnail62882329b9baf800217efe7c: [ 468 | "https://example.com/thumbnail/head.png", 469 | "https://example.com/thumbnail/rail.png", 470 | ], 471 | }); 472 | 473 | createRow({ 474 | givenName: { 475 | text: "Foo", 476 | type: "text", 477 | }, 478 | familyName: { 479 | text: "Bar", 480 | type: "text", 481 | }, 482 | picture: { 483 | text: "abc.png", 484 | type: "image", 485 | thumbnail: "https://example.com/thumbnail/sample.png", 486 | }, 487 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 488 | thumbnail62882329b9baf800217efe7c: [ 489 | "https://example.com/thumbnail/head.png", 490 | "https://example.com/thumbnail/rail.png", 491 | ], 492 | }); 493 | 494 | createRow({ 495 | givenName: { 496 | text: "Foo", 497 | type: "text", 498 | }, 499 | familyName: { 500 | text: "Bar", 501 | type: "text", 502 | }, 503 | picture: { 504 | text: "abc.png", 505 | type: "image", 506 | thumbnail: "https://example.com/thumbnail/sample.png", 507 | }, 508 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 509 | thumbnail62882329b9baf800217efe7c: [ 510 | "https://example.com/thumbnail/head.png", 511 | "https://example.com/thumbnail/rail.png", 512 | ], 513 | }); 514 | 515 | createRow({ 516 | givenName: { 517 | text: "Foo", 518 | type: "text", 519 | }, 520 | familyName: { 521 | text: "Bar", 522 | type: "text", 523 | }, 524 | picture: { 525 | text: "abc.png", 526 | type: "image", 527 | thumbnail: "https://example.com/thumbnail/sample.png", 528 | }, 529 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 530 | thumbnail62882329b9baf800217efe7c: [ 531 | "https://example.com/thumbnail/head.png", 532 | "https://example.com/thumbnail/rail.png", 533 | ], 534 | }); 535 | 536 | createRow({ 537 | givenName: { 538 | text: "Foo", 539 | type: "text", 540 | }, 541 | familyName: { 542 | text: "Bar", 543 | type: "text", 544 | }, 545 | picture: { 546 | text: "abc.png", 547 | type: "image", 548 | thumbnail: "https://example.com/thumbnail/sample.png", 549 | }, 550 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 551 | thumbnail62882329b9baf800217efe7c: [ 552 | "https://example.com/thumbnail/head.png", 553 | "https://example.com/thumbnail/rail.png", 554 | ], 555 | }); 556 | 557 | createRow({ 558 | givenName: { 559 | text: "Foo", 560 | type: "text", 561 | }, 562 | familyName: { 563 | text: "Bar", 564 | type: "text", 565 | }, 566 | picture: { 567 | text: "abc.png", 568 | type: "image", 569 | thumbnail: "https://example.com/thumbnail/sample.png", 570 | }, 571 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 572 | thumbnail62882329b9baf800217efe7c: [ 573 | "https://example.com/thumbnail/head.png", 574 | "https://example.com/thumbnail/rail.png", 575 | ], 576 | }); 577 | 578 | createRow({ 579 | givenName: { 580 | text: "Foo", 581 | type: "text", 582 | }, 583 | familyName: { 584 | text: "Bar", 585 | type: "text", 586 | }, 587 | picture: { 588 | text: "abc.png", 589 | type: "image", 590 | thumbnail: "https://example.com/thumbnail/sample.png", 591 | }, 592 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 593 | thumbnail62882329b9baf800217efe7c: [ 594 | "https://example.com/thumbnail/head.png", 595 | "https://example.com/thumbnail/rail.png", 596 | ], 597 | }); 598 | 599 | createRow({ 600 | givenName: { 601 | text: "Foo", 602 | type: "text", 603 | }, 604 | familyName: { 605 | text: "Bar", 606 | type: "text", 607 | }, 608 | picture: { 609 | text: "abc.png", 610 | type: "image", 611 | thumbnail: "https://example.com/thumbnail/sample.png", 612 | }, 613 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 614 | thumbnail62882329b9baf800217efe7c: [ 615 | "https://example.com/thumbnail/head.png", 616 | "https://example.com/thumbnail/rail.png", 617 | ], 618 | }); 619 | 620 | createRow({ 621 | givenName: { 622 | text: "Foo", 623 | type: "text", 624 | }, 625 | familyName: { 626 | text: "Bar", 627 | type: "text", 628 | }, 629 | picture: { 630 | text: "abc.png", 631 | type: "image", 632 | thumbnail: "https://example.com/thumbnail/sample.png", 633 | }, 634 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 635 | thumbnail62882329b9baf800217efe7c: [ 636 | "https://example.com/thumbnail/head.png", 637 | "https://example.com/thumbnail/rail.png", 638 | ], 639 | }); 640 | 641 | createRow({ 642 | givenName: { 643 | text: "Foo", 644 | type: "text", 645 | }, 646 | familyName: { 647 | text: "Bar", 648 | type: "text", 649 | }, 650 | picture: { 651 | text: "abc.png", 652 | type: "image", 653 | thumbnail: "https://example.com/thumbnail/sample.png", 654 | }, 655 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 656 | thumbnail62882329b9baf800217efe7c: [ 657 | "https://example.com/thumbnail/head.png", 658 | "https://example.com/thumbnail/rail.png", 659 | ], 660 | }); 661 | 662 | createRow({ 663 | givenName: { 664 | text: "Foo", 665 | type: "text", 666 | }, 667 | familyName: { 668 | text: "Bar", 669 | type: "text", 670 | }, 671 | picture: { 672 | text: "abc.png", 673 | type: "image", 674 | thumbnail: "https://example.com/thumbnail/sample.png", 675 | }, 676 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 677 | thumbnail62882329b9baf800217efe7c: [ 678 | "https://example.com/thumbnail/head.png", 679 | "https://example.com/thumbnail/rail.png", 680 | ], 681 | }); 682 | 683 | createRow({ 684 | givenName: { 685 | text: "Foo", 686 | type: "text", 687 | }, 688 | familyName: { 689 | text: "Bar", 690 | type: "text", 691 | }, 692 | picture: { 693 | text: "abc.png", 694 | type: "image", 695 | thumbnail: "https://example.com/thumbnail/sample.png", 696 | }, 697 | pdf62882329b9baf800217efe7c: "https://example.com/pdf/genarated_pdf.pdf", 698 | thumbnail62882329b9baf800217efe7c: [ 699 | "https://example.com/thumbnail/head.png", 700 | "https://example.com/thumbnail/rail.png", 701 | ], 702 | }); 703 | }; 704 | 705 | // No error! 706 | -------------------------------------------------------------------------------- /src/hello.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "@jest/globals"; 2 | import { returnHelloWorld } from "./hello"; 3 | 4 | describe("unit tests and code side by side", () => { 5 | describe("when using the returnHelloWorld function", () => { 6 | it("returns 'hello world'", () => { 7 | console.log("testing returnHelloWorld()"); 8 | expect(returnHelloWorld()).toBe("hello world"); 9 | console.log("Done"); 10 | }); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/hello.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is just a dummy file to show the "tests and code side by side" approach. 3 | */ 4 | export const returnHelloWorld = (): "hello world" => { 5 | return "hello world"; 6 | }; 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // NOTE: You can remove the first line if you don't plan to release an 3 | // executable package. E.g. code that can be used as cli like prettier or eslint 4 | 5 | const main = () => { 6 | console.log("hello Node.js and Typescript world :]"); 7 | console.log("live reloading"); 8 | console.log("live reloading2"); 9 | }; 10 | 11 | // This was just here to force a linting error for now to demonstrate/test the 12 | // eslint pipeline. You can uncomment this and run "yarn lint:check" to test the 13 | // linting. 14 | // const x: number[] = [1, 2]; 15 | // const y: Array = [3, 4]; 16 | // if (x == y) { 17 | // console.log("equal!"); 18 | // } 19 | 20 | main(); 21 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from "@jest/globals"; 2 | 3 | describe("when having tests in seperate folder", () => { 4 | it("works", () => { 5 | expect(true).toBe(true); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "outDir": "dist", 6 | "baseUrl": ".", 7 | "allowUnusedLabels": false, 8 | "allowUnreachableCode": false, 9 | "noFallthroughCasesInSwitch": true, 10 | "noUncheckedIndexedAccess": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "declaration": true, 15 | "sourceMap": true, 16 | "resolveJsonModule": true 17 | }, 18 | "include": ["src", "test"], 19 | "exclude": ["dist", "node_modules"] 20 | } 21 | --------------------------------------------------------------------------------