Skip to content

Instantly share code, notes, and snippets.

@SHaTRO
Last active March 9, 2025 07:00
Show Gist options
  • Select an option

  • Save SHaTRO/a2396deb3424478b6e805549e3de37da to your computer and use it in GitHub Desktop.

Select an option

Save SHaTRO/a2396deb3424478b6e805549e3de37da to your computer and use it in GitHub Desktop.
Setting up TS (fp-ts, io-ts) Project with NodeJS
##
## Script to setup a Node TS project with Jest, ESLint, and fp-ts/io-ts
##
## If you want to skip SonarQube support do:
## SKIP_SONAR=true node-typescript-fp-setup.sh
##
# tested with npm 10.2.3, Node v20.10.0; 2023-12-02
# tested with npm 10.8.3, Node v22.9.0; 2024-09-21
SETUP_NODE_LTS=${SETUP_NODE_LTS:-"22"}
if ( [ "${SETUP_NODE_LTS}" = "18" ] ); then
echo "using Node v18"
SETUP_NODE_VERSION="18.22.0"
SETUP_NPM_VERSION="10.2.3"
elif ( [ "${SETUP_NODE_LTS}" = "20" ] ); then
echo "using Node v20"
SETUP_NODE_VERSION="20.10.0"
SETUP_NPM_VERSION="10.2.3"
elif ( [ "${SETUP_NODE_LTS}" = "22" ] ); then
echo "using Node v22"
SETUP_NODE_VERSION="22.9.0"
SETUP_NPM_VERSION="10.8.3"
else
echo "Unsupported Node LTS version: ${SETUP_NODE_LTS}"
exit 1
fi
testForJQ() {
if ! command -v jq &> /dev/null
then
echo "command 'jq' could not be found and is required"
exit 1
fi
JQ_VERSION=$(jq --version | cut -d '-' -f 2)
echo "Using jq version: ${JQ_VERSION}"
}
initPackageNPM() {
echo "Initializing NPM project..."
npm init
ENGINES_APPEND_FILE="/tmp/engines-$$.json" && \
(cat <<EOT >> "${ENGINES_APPEND_FILE}"
{
"engines": {
"npm": ">=${SETUP_NPM_VERSION}",
"node": ">=${SETUP_NODE_VERSION}"
}
}
EOT
) && \
SCRIPTS_APPEND_FILE="/tmp/scripts-$$.json" && \
(cat <<EOT >> "${SCRIPTS_APPEND_FILE}"
{
"scripts": {
"clean:libs": "rm -rf ./node_modules",
"clean:dist": "rm -rf ./dist",
"clean": "npm run clean:libs && npm run clean:dist",
"build": "tsc --incremental",
"lint": "eslint .",
"lint:fix" : "npm run lint -- --fix",
"test": "jest"
}
}
EOT
) && \
jq -s add package.json "${ENGINES_APPEND_FILE}" > package.json-new && \
mv package.json-new package.json && \
jq -s add package.json "${SCRIPTS_APPEND_FILE}" > package.json-new && \
mv package.json-new package.json && \
PROJECT_NAME=$(jq -r '.name' package.json) && \
echo "NPM Project '${PROJECT_NAME}' initialized."
}
initProjectNPM() {
if [ -f "package.json" ]; then
echo "NPM project already initialized."
INIT_PROJECT=exists
else
initPackageNPM
INIT_PROJECT=initialized
fi
}
checkEngine() {
echo "Checking engine versions..."
npx check-engine || exit 1
CHECK_ENGINE=true
echo "Engine check is good."
}
installDevDeps() {
echo "Installing primary dev dependencies..." \
&& \
npm install --save-dev \
@tsconfig/node${SETUP_NODE_LTS} @types/node typescript ts-node jest @types/jest ts-jest \
&& \
echo "Primary dev dependencies installed." \
|| exit 1
}
installESLintDeps() {
echo "Installing ESLint dev dependencies..." \
&& \
npm install --srave-dev \
eslint globals @eslint/js typescript-eslint \
eslint-plugin eslint-plugin-jest \
eslint-plugin-unused-imports \
&& \
echo "ESLint dev dependencies installed." \
|| exit 1
}
installOptionalDeps() {
echo "Installing 'optional' dependencies..." \
&& \
npm install --save-optional fsevents@latest \
&& \
echo "Optional dependencies installed." \
|| exit 1
}
installMainDeps() {
echo "Installing main dependencies..." \
&& \
npm install --save tslib fp-ts io-ts io-ts-types monocle-ts newtype-ts \
&& \
echo "Main dependencies installed." \
|| exit 1
}
setupConfigTS() {
(cat <<EOT >> tsconfig.json
{
"extends": "@tsconfig/node${SETUP_NODE_LTS}/tsconfig.json",
"\$schema": "https://json.schemastore.org/tsconfig",
"display": "Node 22",
"compilerOptions": {
"lib": ["es2022"],
"target": "es2022",
"outDir": "./dist",
"rootDir": "./",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
EOT
) \
&& \
echo "TSConfig setup." \
|| exit 1
}
setupConfigESLint() {
(cat <<EOT >> eslint.config.mjs
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
export default [
{files: ["**/*.{js,mjs,cjs,ts}"]},
{languageOptions: { globals: globals.browser }},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
];
EOT
) \
&& \
echo "ESLint configured." \
|| exit 1
}
setupConfigJest() {
JEST_APPEND_FILE="/tmp/jest-$$.json" && \
(cat <<EOT >> "${JEST_APPEND_FILE}"
{
"jest": {
"transform": {
"^.+\\\\.tsx?\$": "ts-jest"
},
"testPathIgnorePatterns": [
"/node_modules/",
"/dist/"
]
}
}
EOT
) && \
jq -s add package.json "${JEST_APPEND_FILE}" > package.json-new \
&& \
jq '.scripts |= . + { "test": "jest" }' package.json-new > package.json-new2 \
&& \
mv package.json-new2 package.json && \
echo "Jest configured." \
|| exit 1
}
setupSourceCode() {
echo "Setting up source code..."
mkdir src && \
(cat <<EOT >> "src/hello.ts"
export const hello = () => 'Hello, world!';
console.log(hello());
EOT
) && \
(cat <<EOT >> "src/hello.test.ts"
import { hello } from './hello';
describe('hello', () => {
it('should return "Hello, world!"', () => {
expect(hello()).toBe('Hello, world!');
});
});
EOT
)
}
testLinting() {
echo "Testing linting..."
npm run lint && echo "Linting good."
}
testCompilation() {
echo "Testing compilation..."
npx tsc && echo "Compilation good."
}
testTesting() {
echo "Testing testing..."
npm test && echo "Testing good."
}
testExecution() {
echo "Testing execution..."
node ./dist/src/hello.js && npm test && echo "Execution good."
}
setupSonar() {
if [[ "${SKIP_SONAR}" = '' ]]; then
echo "Doing sonar setup..."
( \
npm install --save-dev sonar-project-properties && \
jq '.scripts |= . + { "pre-sonar": "update-sonar-properties -v --sp" }' package.json > package.json-new && \
mv package.json-new package.json && \
if [ ! -f "sonar-project.properties" ]; then
(cat <<EOT >> sonar-project.properties
# PROJECT KEY WILL BE REPLACED WITH "name" from package.json
sonar.projectKey=some-foo-project-key
## OPTIONAL
# --- optional properties ---
# defaults to project key
#sonar.projectName="Your Project Here"
# defaults to 'not provided'
sonar.projectVersion=1.0.0
# Path is relative to the sonar-project.properties file. Defaults to .
sonar.sources=./src
sonar.coverage.exclusions=**/*.test.ts,**/*.spec.ts
sonar.javascript.lcov.reportPaths=./coverage/lcov.info
# Encoding of the source code. Default is default system encoding
#sonar.sourceEncoding=UTF-8
EOT
) \
fi && \
npm run pre-sonar && \
echo "Done with sonar setup"
) \
|| \
echo "Failed to do sonar setup"
fi
}
## main
testForJQ && \
initProjectNPM && \
checkEngine && \
installDevDeps && \
installESLintDeps && \
installMainDeps && \
installOptionalDeps && \
setupConfigTS && \
setupConfigESLint && \
setupConfigJest && \
setupSourceCode && \
testLinting && \
testTesting && \
testCompilation && \
testExecution && \
setupSonar \
|| exit 1
echo "Done with project configuration."
@SHaTRO
Copy link
Author

SHaTRO commented Mar 17, 2022

For the latest revision:

  • updated to latest npm and node 16 and es2021
  • added engine check for lowest tested versions
  • added package check for 'jq'

@SHaTRO
Copy link
Author

SHaTRO commented Jul 3, 2022

  • added workaround optional dependency for fsevents so ci actions work correctly
  • added unused imports plugin for eslint
  • update eslint rules

@SHaTRO
Copy link
Author

SHaTRO commented Jul 6, 2022

  • add SonarQube (properties) setup

@SHaTRO
Copy link
Author

SHaTRO commented Feb 19, 2024

  • updated to latest npm and node 20
  • turned into functions
  • added eslint
  • more dynamics

@SHaTRO
Copy link
Author

SHaTRO commented Sep 22, 2024

Added support for SETUP_NODE_LTS, allowed values are 18, 20, 22.
Updated to flat config for ESLint
Added npm "scripts" to package.json

@SHaTRO
Copy link
Author

SHaTRO commented Dec 29, 2024

  • updated to latest npm and node 20 and 22
  • improved flat config for ESLint with ESLint-style (@stylistic/eslint-plugin) support

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment