Skip to content

Instantly share code, notes, and snippets.

@samialdury
Last active August 4, 2023 19:10
Show Gist options
  • Select an option

  • Save samialdury/38a1ecc3436cb4ea4f8ac95ab94356ec to your computer and use it in GitHub Desktop.

Select an option

Save samialdury/38a1ecc3436cb4ea4f8ac95ab94356ec to your computer and use it in GitHub Desktop.

Config files for TS projects

.DS_Store
.cache
node_modules
build
dist
coverage
.env
.git
.husky
.DS_Store
.cache
node_modules
build
dist
coverage
pnpm-lock.yaml
*.md
name: Release
on:
workflow_dispatch:
push:
branches:
- 'main'
concurrency: release-${{ github.ref_name }}
env:
NODE_VERSION: 18
PNPM_VERSION: 8
jobs:
release:
runs-on: ubuntu-22.04
if: startsWith(github.event.head_commit.message, 'chore(release):')
steps:
- name: Checkout
uses: actions/checkout@v3
timeout-minutes: 2
- name: Set up pnpm
uses: pnpm/action-setup@v2
timeout-minutes: 2
with:
version: ${{ env.PNPM_VERSION }}
- name: Set up Node.js
uses: actions/setup-node@v3
timeout-minutes: 2
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Get pnpm store directory
id: pnpm-store
timeout-minutes: 2
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Restore pnpm cache
uses: actions/cache@v3
timeout-minutes: 2
with:
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
key: ${{ runner.os }}-node-${{ env.NODE_VERSION }}-pnpm-${{ env.PNPM_VERSION }}-store-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-node-${{ env.NODE_VERSION }}-pnpm-${{ env.PNPM_VERSION }}-store-
- name: Install Task
uses: arduino/setup-task@v1
timeout-minutes: 2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install NPM dependencies
timeout-minutes: 2
run: task install:ci
- name: Create GitHub release and publish to NPM
uses: changesets/action@v1
timeout-minutes: 2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
with:
publish: task release
name: Version
on:
workflow_dispatch:
push:
branches:
- 'main'
concurrency: version-${{ github.ref_name }}
env:
NODE_VERSION: 18
PNPM_VERSION: 8
jobs:
version:
runs-on: ubuntu-22.04
if: "!startsWith(github.event.head_commit.message, 'chore(release):')"
steps:
- name: Checkout
uses: actions/checkout@v3
timeout-minutes: 2
- name: Set up pnpm
uses: pnpm/action-setup@v2
timeout-minutes: 2
with:
version: ${{ env.PNPM_VERSION }}
- name: Set up Node.js
uses: actions/setup-node@v3
timeout-minutes: 2
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Get pnpm store directory
id: pnpm-store
timeout-minutes: 2
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Restore pnpm cache
uses: actions/cache@v3
timeout-minutes: 2
with:
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
key: ${{ runner.os }}-node-${{ env.NODE_VERSION }}-pnpm-${{ env.PNPM_VERSION }}-store-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-node-${{ env.NODE_VERSION }}-pnpm-${{ env.PNPM_VERSION }}-store-
- name: Install Task
uses: arduino/setup-task@v1
timeout-minutes: 2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install NPM dependencies
timeout-minutes: 2
run: task install:ci
- name: Changesets pull request
uses: changesets/action@v1
timeout-minutes: 2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
title: 'chore(release): version package'
commit: 'chore(release): version package'
name: CI
on:
workflow_dispatch:
push:
branches:
- 'main'
tags:
- 'v*'
pull_request:
types: [opened, synchronize]
branches:
- 'main'
concurrency: ci-${{ github.ref_name }}
env:
NODE_VERSION: 18
PNPM_VERSION: 8
IMAGE_PLATFORMS: 'linux/amd64,linux/arm64'
IMAGE_REGISTRY: 'ghcr.io'
IMAGE_NAME: ${{ github.repository }}
COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
BRANCH_OR_TAG: ${{ github.event.pull_request.head.ref || github.ref_name }}
IS_MAIN: ${{ github.ref_name == 'main' }}
IS_PULL_REQUEST: ${{ github.event_name == 'pull_request' }}
jobs:
check:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
timeout-minutes: 2
- name: Set up pnpm
uses: pnpm/action-setup@v2
timeout-minutes: 2
with:
version: ${{ env.PNPM_VERSION }}
- name: Set up Node.js
uses: actions/setup-node@v3
timeout-minutes: 2
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Get pnpm store directory
id: pnpm-store
timeout-minutes: 2
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Restore pnpm cache
uses: actions/cache@v3
timeout-minutes: 2
with:
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
key: ${{ runner.os }}-node-${{ env.NODE_VERSION }}-pnpm-${{ env.PNPM_VERSION }}-store-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-node-${{ env.NODE_VERSION }}-pnpm-${{ env.PNPM_VERSION }}-store-
- name: Install Task
uses: arduino/setup-task@v1
timeout-minutes: 2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install NPM dependencies
timeout-minutes: 2
run: task install:ci
- name: Build TypeScript
timeout-minutes: 2
run: task build:ci
- name: Run tests
timeout-minutes: 2
run: task test:ci
- name: Prepare base coverage report
if: env.IS_MAIN == 'true'
timeout-minutes: 2
run: |
mv coverage/lcov.info coverage/base-lcov.info
- name: Cache base coverage report
uses: actions/cache@v3
if: env.IS_MAIN == 'true'
timeout-minutes: 2
with:
path: coverage/base-lcov.info
key: base-coverage-${{ env.COMMIT_SHA }}
restore-keys: |
base-coverage-
- name: Restore base coverage report from cache
uses: actions/cache/restore@v3
if: env.IS_PULL_REQUEST == 'true'
with:
path: coverage/base-lcov.info
fail-on-cache-miss: true
key: base-coverage-
restore-keys: |
base-coverage-
- name: Code coverage report comment
if: env.IS_PULL_REQUEST == 'true'
uses: barecheck/code-coverage-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
lcov-file: coverage/lcov.info
base-lcov-file: coverage/base-lcov.info
app-name: ${{ github.repository }}
send-summary-comment: true
show-annotations: 'warning'
minimum-ratio: 0
- name: Format & lint
timeout-minutes: 2
run: task lint:ci
docker:
needs: [check]
runs-on: ubuntu-22.04
permissions:
packages: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v3
timeout-minutes: 2
- name: Set up QEMU
if: contains(env.IMAGE_PLATFORMS, ',')
uses: docker/setup-qemu-action@v2
timeout-minutes: 2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
timeout-minutes: 2
- name: Extract metadata for Docker
id: docker-meta
uses: docker/metadata-action@v4
timeout-minutes: 2
with:
images: |
${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
timeout-minutes: 2
with:
registry: ${{ env.IMAGE_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Git metadata
id: git-meta
timeout-minutes: 2
run: |
echo "BRANCH_OR_TAG_NORMALIZED_WITH_SHA=$(echo ${{ env.BRANCH_OR_TAG }} | tr '/' '-'),${{ env.COMMIT_SHA }}" >> $GITHUB_OUTPUT
- name: Build and push image to registry
uses: docker/build-push-action@v4
timeout-minutes: 20
with:
context: .
push: true
provenance: false
build-args: |
NODE_VERSION=${{ env.NODE_VERSION }}
PNPM_VERSION=${{ env.PNPM_VERSION }}
COMMIT_SHA=${{ steps.git-meta.outputs.BRANCH_OR_TAG_NORMALIZED_WITH_SHA }}
# https://github.com/docker/build-push-action/issues/545
# cache-from: type=gha
# cache-to: type=gha,mode=max
platforms: ${{ env.IMAGE_PLATFORMS }}
tags: ${{ steps.docker-meta.outputs.tags }}
labels: ${{ steps.docker-meta.outputs.labels }}
name: Clean actions cache
# https://github.com/actions/gh-actions-cache
on:
workflow_dispatch:
inputs:
branch:
description: 'Branch'
required: true
limit:
description: 'Limit'
required: true
concurrency: clean-actions-cache-${{ inputs.branch }}
jobs:
clean:
runs-on: ubuntu-22.04
permissions:
actions: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v3
timeout-minutes: 2
- name: Clean actions cache
timeout-minutes: 20
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh extension install actions/gh-actions-cache
REPO=${{ github.repository }}
BRANCH_INPUT=${{ inputs.branch }}
LIMIT_INPUT=${{ inputs.limit }}
MAX_LIMIT=100
echo "Cleaning cache for repo $REPO and branch $BRANCH_INPUT with limit $LIMIT_INPUT"
set +e
delete_caches() {
local limit=$1
local cacheKeys=$(gh actions-cache list -L $limit -R $REPO -B $BRANCH_INPUT --sort size --order desc | cut -f1)
echo "delete_caches() - limit $limit, repo $REPO, branch $BRANCH_INPUT"
for cacheKey in $cacheKeys; do
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH_INPUT --confirm
done
}
# The max limit supported by the script is defined by the MAX_LIMIT variable.
# If it's greater, we need to split the limit into multiple requests.
if [ "$LIMIT_INPUT" -le "$MAX_LIMIT" ]; then
delete_caches $LIMIT_INPUT
else
TIMES=$((LIMIT_INPUT / MAX_LIMIT))
REMAINDER=$((LIMIT_INPUT % MAX_LIMIT))
for i in $(seq 1 "$TIMES"); do
delete_caches $MAX_LIMIT
done
if [ "$REMAINDER" -gt 0 ]; then
delete_caches $REMAINDER
fi
fi
set -e
echo "Cache cleaned successfully"
ARG NODE_VERSION=18
ARG PNPM_VERSION=8
ARG TINI_VERSION="v0.19.0"
ARG WORK_DIR="/app"
ARG COMMIT_SHA="unknown"
################################################################
# #
# Prepare alpine image #
# #
################################################################
FROM node:${NODE_VERSION}-alpine as node-alpine
ARG PNPM_VERSION
ARG TINI_VERSION
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static /tini
RUN chmod +x /tini
RUN apk --no-cache add curl
RUN curl -sf https://gobinaries.com/tj/node-prune | sh
RUN npm install --global pnpm@${PNPM_VERSION}
################################################################
# #
# Prepare distroless image #
# #
################################################################
FROM gcr.io/distroless/nodejs${NODE_VERSION}-debian11:nonroot as node-distroless
################################################################
# #
# Install all dependencies and build TypeScript #
# #
################################################################
FROM node-alpine as build-js
ARG WORK_DIR
WORKDIR ${WORK_DIR}
COPY package.json package.json
COPY pnpm-lock.yaml pnpm-lock.yaml
RUN pnpm fetch
COPY tsconfig.base.json tsconfig.base.json
COPY tsconfig.prod.json tsconfig.prod.json
COPY src src
RUN pnpm install --offline --frozen-lockfile
RUN ./node_modules/.bin/tsc --project ./tsconfig.prod.json
################################################################
# #
# Install only production dependencies & prune unused files #
# #
################################################################
FROM node-alpine as install-prod-deps
ARG WORK_DIR
WORKDIR ${WORK_DIR}
ENV NODE_ENV="production"
COPY --from=build-js ${WORK_DIR}/package.json package.json
COPY --from=build-js ${WORK_DIR}/pnpm-lock.yaml pnpm-lock.yaml
RUN pnpm fetch
RUN pnpm install --offline --frozen-lockfile --prod
RUN node-prune
################################################################
# #
# Copy only necessary data for runtime #
# #
################################################################
FROM node-distroless as final
ARG WORK_DIR
ARG COMMIT_SHA
WORKDIR ${WORK_DIR}
ENV NODE_OPTIONS="--enable-source-maps"
ENV NODE_ENV="production"
ENV COMMIT_SHA=${COMMIT_SHA}
COPY --from=node-alpine --chown=nonroot:nonroot /tini /tini
COPY --from=build-js --chown=nonroot:nonroot ${WORK_DIR}/package.json package.json
COPY --from=build-js --chown=nonroot:nonroot ${WORK_DIR}/build build
COPY --from=install-prod-deps --chown=nonroot:nonroot ${WORK_DIR}/node_modules node_modules
USER nonroot:nonroot
ENTRYPOINT ["/tini", "--"]
CMD ["/nodejs/bin/node", "./build/src/main.js"]
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:js-app",
":semanticCommits",
":timezone(Europe/Prague)",
"schedule:earlyMondays",
":disableRateLimiting",
":label(dependencies)",
"npm:unpublishSafe",
"docker:disableMajor",
":separateMultipleMajorReleases",
"group:allNonMajor",
"group:definitelyTyped",
"group:jsTestNonMajor",
"group:linters",
":automergePr",
":automergeRequireAllStatusChecks",
":automergeDigest",
":automergeMinor",
":automergePatch",
":automergeLinters",
":automergeTesters",
":automergeTypes"
],
"platformCommit": true,
"platformAutomerge": true
}
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:js-lib",
":semanticCommits",
":timezone(Europe/Prague)",
"schedule:earlyMondays",
":disableRateLimiting",
":label(dependencies)",
"npm:unpublishSafe",
":separateMultipleMajorReleases",
"group:allNonMajor",
"group:definitelyTyped",
"group:jsTestNonMajor",
"group:linters",
":automergePr",
":automergeRequireAllStatusChecks",
":automergeDigest",
":automergeMinor",
":automergePatch",
":automergeLinters",
":automergeTesters",
":automergeTypes"
],
"platformCommit": true,
"platformAutomerge": true
}
{
"search.exclude": {
"**/.git": true,
"**/.DS_Store": true,
"**/.cache": true,
"**/node_modules": true,
"**/build": true,
"**/dist": true,
"**/coverage": true,
"**/pnpm-lock.yaml": true
},
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
// Strict settings
// Source: https://github.com/tsconfig/bases/blob/main/bases/strictest.json
"strict": true,
"allowUnusedLabels": false,
"allowUnreachableCode": false,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"verbatimModuleSyntax": true,
"checkJs": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
// Modules
"target": "ES2022",
"lib": ["ES2022"],
"module": "Node16",
"moduleResolution": "node16",
// Other
"removeComments": true,
"sourceMap": true,
"sourceRoot": "/",
"rootDir": ".",
"outDir": "./build"
}
}
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.base.json",
"compilerOptions": {
"types": ["vitest/globals"],
"noErrorTruncation": true
},
"include": ["./src", "./test"],
"exclude": []
}
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.base.json",
"include": ["./src"],
"exclude": ["./src/**/*.test.ts"]
}
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
include: ['./src/**/*.test.ts', './test/**/*.test.ts'],
setupFiles: ['./test/setup.ts'],
globals: true,
restoreMocks: true,
unstubEnvs: true,
coverage: {
provider: 'c8',
reporter: ['text', 'lcovonly', 'html'],
include: ['src/**/*.ts'],
exclude: ['src/**/types.ts', 'src/**/*.d.ts', 'src/**/*.test.ts'],
all: true,
},
cache: {
dir: '.cache/.vitest',
},
},
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment