mirror of
https://github.com/morten-olsen/reservoir.git
synced 2026-02-08 01:46:24 +01:00
init
This commit is contained in:
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
|
||||||
48
.github/release-drafter-config.yml
vendored
Normal file
48
.github/release-drafter-config.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
name-template: '$RESOLVED_VERSION 🌈'
|
||||||
|
tag-template: '$RESOLVED_VERSION'
|
||||||
|
categories:
|
||||||
|
- title: '🚀 Features'
|
||||||
|
labels:
|
||||||
|
- 'feature'
|
||||||
|
- 'enhancement'
|
||||||
|
- title: '🐛 Bug Fixes'
|
||||||
|
labels:
|
||||||
|
- 'fix'
|
||||||
|
- 'bugfix'
|
||||||
|
- 'bug'
|
||||||
|
- title: '🧰 Maintenance'
|
||||||
|
label: 'chore'
|
||||||
|
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
|
||||||
|
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
|
||||||
|
version-resolver:
|
||||||
|
major:
|
||||||
|
labels:
|
||||||
|
- 'major'
|
||||||
|
minor:
|
||||||
|
labels:
|
||||||
|
- 'minor'
|
||||||
|
patch:
|
||||||
|
labels:
|
||||||
|
- 'patch'
|
||||||
|
default: patch
|
||||||
|
autolabeler:
|
||||||
|
- label: 'chore'
|
||||||
|
files:
|
||||||
|
- '*.md'
|
||||||
|
branch:
|
||||||
|
- '/docs{0,1}\/.+/'
|
||||||
|
- label: 'bug'
|
||||||
|
branch:
|
||||||
|
- '/fix\/.+/'
|
||||||
|
title:
|
||||||
|
- '/fix/i'
|
||||||
|
- label: 'enhancement'
|
||||||
|
branch:
|
||||||
|
- '/feature\/.+/'
|
||||||
|
- '/feat\/.+/'
|
||||||
|
title:
|
||||||
|
- '/feat:.+/'
|
||||||
|
template: |
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
$CHANGES
|
||||||
21
.github/workflows/auto-labeler.yaml
vendored
Normal file
21
.github/workflows/auto-labeler.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: Auto Labeler
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, reopened, synchronize]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
auto-labeler:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: release-drafter/release-drafter@v6
|
||||||
|
with:
|
||||||
|
config-name: release-drafter-config.yml
|
||||||
|
disable-releaser: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
55
.github/workflows/job-build.yaml
vendored
Normal file
55
.github/workflows/job-build.yaml
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
name: Build
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '${{ env.NODE_VERSION }}'
|
||||||
|
registry-url: '${{ env.NODE_REGISTRY }}'
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
name: Install pnpm
|
||||||
|
with:
|
||||||
|
version: ${{ env.PNPM_VERSION }}
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
name: Setup pnpm cache
|
||||||
|
with:
|
||||||
|
path: ${{ env.STORE_PATH }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: pnpm test
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: lib
|
||||||
|
retention-days: 5
|
||||||
|
path: |
|
||||||
|
packages/*/dist
|
||||||
|
extensions/*/dist
|
||||||
|
server/*/dist
|
||||||
|
package.json
|
||||||
|
README.md
|
||||||
18
.github/workflows/job-draft-release.yaml
vendored
Normal file
18
.github/workflows/job-draft-release.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: Draft release
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
jobs:
|
||||||
|
draft-release:
|
||||||
|
name: Update release drafter
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
environment: release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: release-drafter/release-drafter@v6
|
||||||
|
with:
|
||||||
|
config-name: release-drafter-config.yml
|
||||||
|
publish: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
114
.github/workflows/pipeline-default.yaml
vendored
Normal file
114
.github/workflows/pipeline-default.yaml
vendored
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
name: Build and release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- synchronize
|
||||||
|
|
||||||
|
env:
|
||||||
|
environment: test
|
||||||
|
release_channel: latest
|
||||||
|
DO_NOT_TRACK: "1"
|
||||||
|
NODE_VERSION: "23.x"
|
||||||
|
NODE_REGISTRY: "https://registry.npmjs.org"
|
||||||
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
DOCKER_REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
PNPM_VERSION: 10.6.0
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: read
|
||||||
|
pull-requests: write
|
||||||
|
id-token: write
|
||||||
|
actions: read
|
||||||
|
security-events: write
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
uses: ./.github/workflows/job-build.yaml
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
update-release-draft:
|
||||||
|
needs: build
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
uses: ./.github/workflows/job-draft-release.yaml
|
||||||
|
|
||||||
|
release:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
attestations: write
|
||||||
|
id-token: write
|
||||||
|
pages: write
|
||||||
|
name: Release
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: update-release-draft
|
||||||
|
environment: release
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# - uses: actions/setup-node@v4
|
||||||
|
# with:
|
||||||
|
# node-version: '${{ env.NODE_VERSION }}'
|
||||||
|
# registry-url: '${{ env.NODE_REGISTRY }}'
|
||||||
|
#
|
||||||
|
# - uses: pnpm/action-setup@v4
|
||||||
|
# name: Install pnpm
|
||||||
|
# with:
|
||||||
|
# version: ${{ env.PNPM_VERSION }}
|
||||||
|
# run_install: false
|
||||||
|
#
|
||||||
|
# - name: Install dependencies
|
||||||
|
# run: pnpm install
|
||||||
|
# env:
|
||||||
|
# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
#
|
||||||
|
# - uses: actions/download-artifact@v4
|
||||||
|
# with:
|
||||||
|
# name: lib
|
||||||
|
# path: ./
|
||||||
|
#
|
||||||
|
# - name: Publish to npm
|
||||||
|
# run: |
|
||||||
|
# git config user.name "Github Actions Bot"
|
||||||
|
# git config user.email "<>"
|
||||||
|
# node ./scripts/set-version.mjs $(git describe --tag --abbrev=0)
|
||||||
|
# pnpm publish -r --no-git-checks --access public
|
||||||
|
# env:
|
||||||
|
# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||||
|
with:
|
||||||
|
images: ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: push
|
||||||
|
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
|
# - name: Generate artifact attestation
|
||||||
|
# uses: actions/attest-build-provenance@v2
|
||||||
|
# with:
|
||||||
|
# subject-name: ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME}}
|
||||||
|
# subject-digest: ${{ steps.push.outputs.digest }}
|
||||||
|
# push-to-registry: true
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/node_modules
|
||||||
|
.turbo/
|
||||||
|
/.env
|
||||||
|
/coverage/
|
||||||
18
.prettierrc.json
Normal file
18
.prettierrc.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"arrowParens": "always",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"htmlWhitespaceSensitivity": "css",
|
||||||
|
"insertPragma": false,
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"printWidth": 120,
|
||||||
|
"proseWrap": "preserve",
|
||||||
|
"quoteProps": "as-needed",
|
||||||
|
"requirePragma": false,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"useTabs": false,
|
||||||
|
"singleAttributePerLine": false
|
||||||
|
}
|
||||||
41
.u8.json
Normal file
41
.u8.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"values": {
|
||||||
|
"monoRepo": true,
|
||||||
|
"packagePrefix": "@morten-olsen/reservoir-",
|
||||||
|
"packageVersion": "1.0.0"
|
||||||
|
},
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"timestamp": "2025-11-03T09:33:18.994Z",
|
||||||
|
"template": "monorepo",
|
||||||
|
"values": {
|
||||||
|
"monoRepo": true,
|
||||||
|
"packagePrefix": "@morten-olsen/reservoir-",
|
||||||
|
"packageVersion": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2025-11-03T09:33:32.155Z",
|
||||||
|
"template": "eslint",
|
||||||
|
"values": {
|
||||||
|
"monoRepo": true,
|
||||||
|
"packagePrefix": "@morten-olsen/reservoir-",
|
||||||
|
"packageVersion": "1.0.0",
|
||||||
|
"target-pkg": {
|
||||||
|
"name": "@morten-olsen/reservoir-repo",
|
||||||
|
"dir": "/Users/alice/Projects/private/incubator/reservoir"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2025-11-03T09:33:49.149Z",
|
||||||
|
"template": "pkg",
|
||||||
|
"values": {
|
||||||
|
"monoRepo": true,
|
||||||
|
"packagePrefix": "@morten-olsen/reservoir-",
|
||||||
|
"packageVersion": "1.0.0",
|
||||||
|
"packageName": "server"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
9
docker-compose.yaml
Normal file
9
docker-compose.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
name: reservoir
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./packages/server/Dockerfile
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- 9111:9111
|
||||||
52
eslint.config.mjs
Normal file
52
eslint.config.mjs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { defineConfig } from 'eslint/config';
|
||||||
|
import { FlatCompat } from '@eslint/eslintrc';
|
||||||
|
import importPlugin from 'eslint-plugin-import';
|
||||||
|
import eslint from '@eslint/js';
|
||||||
|
import eslintConfigPrettier from 'eslint-config-prettier';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: import.meta.__dirname,
|
||||||
|
resolvePluginsRelativeTo: import.meta.__dirname,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default defineConfig(
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
...tseslint.configs.stylistic,
|
||||||
|
eslintConfigPrettier,
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsxx}'],
|
||||||
|
extends: [importPlugin.flatConfigs.recommended, importPlugin.flatConfigs.typescript],
|
||||||
|
rules: {
|
||||||
|
'import/no-unresolved': 'off',
|
||||||
|
'import/extensions': ['error', 'ignorePackages'],
|
||||||
|
'import/exports-last': 'error',
|
||||||
|
'import/no-default-export': 'error',
|
||||||
|
'import/order': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
|
||||||
|
'newlines-between': 'always',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'import/no-duplicates': 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/consistent-type-definitions': ['error', 'type'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**.d.ts'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/triple-slash-reference': 'off',
|
||||||
|
'@typescript-eslint/consistent-type-definitions': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...compat.extends('plugin:prettier/recommended'),
|
||||||
|
{
|
||||||
|
ignores: ['**/node_modules/', '**/dist/', '**/.turbo/', '**/generated/'],
|
||||||
|
},
|
||||||
|
);
|
||||||
33
package.json
Normal file
33
package.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"name": "@morten-olsen/reservoir-repo",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"test:lint": "eslint",
|
||||||
|
"build": "turbo build",
|
||||||
|
"build:dev": "tsc --build --watch",
|
||||||
|
"test:unit": "vitest --run --coverage --passWithNoTests",
|
||||||
|
"test": "pnpm run \"/^test:.+/\""
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.6.0",
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*",
|
||||||
|
"apps/*"
|
||||||
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
"turbo": "2.6.0",
|
||||||
|
"typescript": "5.9.3",
|
||||||
|
"vitest": "4.0.6",
|
||||||
|
"@vitest/coverage-v8": "4.0.6",
|
||||||
|
"@eslint/eslintrc": "3.3.1",
|
||||||
|
"@eslint/js": "9.39.0",
|
||||||
|
"@pnpm/find-workspace-packages": "6.0.9",
|
||||||
|
"eslint": "9.39.0",
|
||||||
|
"eslint-config-prettier": "10.1.8",
|
||||||
|
"eslint-plugin-import": "2.32.0",
|
||||||
|
"eslint-plugin-prettier": "5.5.4",
|
||||||
|
"prettier": "3.6.2",
|
||||||
|
"typescript-eslint": "8.46.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
packages/configs/package.json
Normal file
4
packages/configs/package.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "@morten-olsen/reservoir-configs",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
20
packages/configs/tsconfig.json
Normal file
20
packages/configs/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"isolatedModules": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"allowImportingTsExtensions": true
|
||||||
|
}
|
||||||
|
}
|
||||||
4
packages/server/.gitignore
vendored
Normal file
4
packages/server/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/node_modules/
|
||||||
|
/dist/
|
||||||
|
/coverage/
|
||||||
|
/.env
|
||||||
27
packages/server/Dockerfile
Normal file
27
packages/server/Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
FROM node:23-slim AS base
|
||||||
|
RUN corepack enable
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
FROM base AS builder
|
||||||
|
RUN npm i -g turbo
|
||||||
|
COPY . .
|
||||||
|
RUN turbo prune @morten-olsen/reservoir-server --docker
|
||||||
|
|
||||||
|
FROM base AS installer
|
||||||
|
COPY --from=builder /app/out/json/ .
|
||||||
|
RUN pnpm install --prod --frozen-lockfile
|
||||||
|
COPY --from=builder /app/out/full/ .
|
||||||
|
|
||||||
|
FROM base AS runner
|
||||||
|
ENV \
|
||||||
|
SERVER_HOST=0.0.0.0 \
|
||||||
|
DB_URL=/data/db.sqlite
|
||||||
|
RUN \
|
||||||
|
addgroup --system --gid 1001 nodejs \
|
||||||
|
&& adduser --system --uid 1001 nodejs \
|
||||||
|
&& mkdir /data \
|
||||||
|
&& chown nodejs:nodejs /data
|
||||||
|
USER nodejs
|
||||||
|
|
||||||
|
COPY --from=installer /app /app
|
||||||
|
CMD ["node", "/app/packages/server/src/start.ts"]
|
||||||
42
packages/server/package.json
Normal file
42
packages/server/package.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/exports.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc --build",
|
||||||
|
"test:unit": "vitest --run --passWithNoTests",
|
||||||
|
"test": "pnpm run \"/^test:/\""
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.6.0",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": "./dist/exports.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@morten-olsen/reservoir-configs": "workspace:*",
|
||||||
|
"@morten-olsen/reservoir-tests": "workspace:*",
|
||||||
|
"@types/node": "24.10.0",
|
||||||
|
"@vitest/coverage-v8": "4.0.6",
|
||||||
|
"typescript": "5.9.3",
|
||||||
|
"vitest": "4.0.6"
|
||||||
|
},
|
||||||
|
"name": "@morten-olsen/reservoir-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"imports": {
|
||||||
|
"#root/*": "./src/*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/swagger": "^9.5.2",
|
||||||
|
"@scalar/fastify-api-reference": "^1.38.1",
|
||||||
|
"better-sqlite3": "^12.4.1",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"fastify": "^5.6.1",
|
||||||
|
"fastify-type-provider-zod": "^6.1.0",
|
||||||
|
"knex": "^3.1.0",
|
||||||
|
"pg": "^8.16.3",
|
||||||
|
"pino": "^10.1.0",
|
||||||
|
"pino-pretty": "^13.1.2",
|
||||||
|
"zod": "^4.1.12"
|
||||||
|
}
|
||||||
|
}
|
||||||
36
packages/server/src/api/api.documents.ts
Normal file
36
packages/server/src/api/api.documents.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
|
||||||
|
|
||||||
|
import { DocumentsService } from '#root/services/documents/documents.ts';
|
||||||
|
import {
|
||||||
|
upsertDocumentRequestSchema,
|
||||||
|
upsertDocumentResponseSchema,
|
||||||
|
} from '#root/services/documents/documents.schemas.ts';
|
||||||
|
|
||||||
|
const documentsPlugin: FastifyPluginAsyncZod = async (app) => {
|
||||||
|
app.route({
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
schema: {
|
||||||
|
operationId: 'v1.documents.put',
|
||||||
|
tags: ['documents'],
|
||||||
|
summary: 'Upsert documents',
|
||||||
|
body: z.object({
|
||||||
|
items: z.array(upsertDocumentRequestSchema),
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
items: z.array(upsertDocumentResponseSchema),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
handler: async (req, reply) => {
|
||||||
|
const documentsService = app.services.get(DocumentsService);
|
||||||
|
const { items } = req.body;
|
||||||
|
const results = await Promise.all(items.map((item) => documentsService.upsert(item)));
|
||||||
|
return reply.send({ items: results });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { documentsPlugin };
|
||||||
52
packages/server/src/api/api.ts
Normal file
52
packages/server/src/api/api.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import fastify from 'fastify';
|
||||||
|
import fastifySwagger from '@fastify/swagger';
|
||||||
|
import fastifyScalar from '@scalar/fastify-api-reference';
|
||||||
|
import { jsonSchemaTransform, serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod';
|
||||||
|
|
||||||
|
import { documentsPlugin } from './api.documents.ts';
|
||||||
|
|
||||||
|
import { Services } from '#root/utils/utils.services.ts';
|
||||||
|
import { DatabaseService } from '#root/database/database.ts';
|
||||||
|
|
||||||
|
const createApi = async (services: Services = new Services()) => {
|
||||||
|
const db = services.get(DatabaseService);
|
||||||
|
await db.ready();
|
||||||
|
const app = fastify({
|
||||||
|
logger: {
|
||||||
|
level: 'warn',
|
||||||
|
transport: {
|
||||||
|
target: 'pino-pretty',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
app.setValidatorCompiler(validatorCompiler);
|
||||||
|
app.setSerializerCompiler(serializerCompiler);
|
||||||
|
app.decorate('services', services);
|
||||||
|
|
||||||
|
await app.register(fastifySwagger, {
|
||||||
|
openapi: {
|
||||||
|
info: {
|
||||||
|
title: 'Reservoir',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
servers: [],
|
||||||
|
},
|
||||||
|
transform: jsonSchemaTransform,
|
||||||
|
});
|
||||||
|
|
||||||
|
await app.register(fastifyScalar, {
|
||||||
|
routePrefix: '/docs',
|
||||||
|
});
|
||||||
|
|
||||||
|
await app.register(documentsPlugin, {
|
||||||
|
prefix: '/api/v1/documents',
|
||||||
|
});
|
||||||
|
|
||||||
|
app.addHook('onReady', async () => {
|
||||||
|
app.swagger();
|
||||||
|
});
|
||||||
|
await app.ready();
|
||||||
|
return app;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { createApi };
|
||||||
10
packages/server/src/config/config.ts
Normal file
10
packages/server/src/config/config.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
class ConfigService {
|
||||||
|
public get database() {
|
||||||
|
return {
|
||||||
|
client: process.env.DB_CLIENT || 'better-sqlite3',
|
||||||
|
connection: process.env.DB_URL || ':memory:',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ConfigService };
|
||||||
52
packages/server/src/database/database.ts
Normal file
52
packages/server/src/database/database.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import knex, { type Knex } from 'knex';
|
||||||
|
|
||||||
|
import { migrationSource } from './migrations/migrations.ts';
|
||||||
|
|
||||||
|
import { destroy, Services } from '#root/utils/utils.services.ts';
|
||||||
|
import { ConfigService } from '#root/config/config.ts';
|
||||||
|
|
||||||
|
class DatabaseService {
|
||||||
|
#services: Services;
|
||||||
|
#instance?: Promise<Knex>;
|
||||||
|
|
||||||
|
constructor(services: Services) {
|
||||||
|
this.#services = services;
|
||||||
|
}
|
||||||
|
|
||||||
|
#setup = async () => {
|
||||||
|
const configService = this.#services.get(ConfigService);
|
||||||
|
const db = knex({
|
||||||
|
client: configService.database.client,
|
||||||
|
connection: configService.database.connection,
|
||||||
|
useNullAsDefault: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.migrate.latest({
|
||||||
|
migrationSource,
|
||||||
|
});
|
||||||
|
|
||||||
|
return db;
|
||||||
|
};
|
||||||
|
|
||||||
|
public getInstance = () => {
|
||||||
|
if (!this.#instance) {
|
||||||
|
this.#instance = this.#setup();
|
||||||
|
}
|
||||||
|
return this.#instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
[destroy] = async () => {
|
||||||
|
if (!this.#instance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const instance = await this.#instance;
|
||||||
|
await instance.destroy();
|
||||||
|
};
|
||||||
|
|
||||||
|
public ready = async () => {
|
||||||
|
await this.getInstance();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { tableNames, type Tables } from './migrations/migrations.ts';
|
||||||
|
export { DatabaseService };
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import type { Migration } from './migrations.types.ts';
|
||||||
|
|
||||||
|
const tableNames = {
|
||||||
|
documents: 'documents',
|
||||||
|
};
|
||||||
|
|
||||||
|
const init: Migration = {
|
||||||
|
name: 'init',
|
||||||
|
up: async (knex) => {
|
||||||
|
await knex.schema.createTable(tableNames.documents, (table) => {
|
||||||
|
table.string('id').notNullable();
|
||||||
|
table.string('type').notNullable();
|
||||||
|
table.string('source').nullable();
|
||||||
|
table.jsonb('data').notNullable();
|
||||||
|
table.datetime('createdAt').notNullable();
|
||||||
|
table.datetime('updatedAt').notNullable();
|
||||||
|
table.datetime('deletedAt').nullable();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
down: async (knex) => {
|
||||||
|
await knex.schema.dropTableIfExists(tableNames.documents);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type DocumentRow = {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
source: string | null;
|
||||||
|
data: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Tables = {
|
||||||
|
document: DocumentRow;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type { Tables };
|
||||||
|
export { init, tableNames };
|
||||||
15
packages/server/src/database/migrations/migrations.ts
Normal file
15
packages/server/src/database/migrations/migrations.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { Knex } from 'knex';
|
||||||
|
|
||||||
|
import { init, tableNames, type Tables } from './migrations.001-init.ts';
|
||||||
|
import type { Migration } from './migrations.types.ts';
|
||||||
|
|
||||||
|
const migrations = [init];
|
||||||
|
|
||||||
|
const migrationSource: Knex.MigrationSource<Migration> = {
|
||||||
|
getMigration: async (migration) => migration,
|
||||||
|
getMigrationName: (migration: Migration) => migration.name,
|
||||||
|
getMigrations: async () => migrations,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { tableNames, type Tables };
|
||||||
|
export { migrationSource };
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { Knex } from 'knex';
|
||||||
|
|
||||||
|
type Migration = {
|
||||||
|
name: string;
|
||||||
|
up: (knex: Knex) => Promise<void>;
|
||||||
|
down: (knex: Knex) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type { Migration };
|
||||||
9
packages/server/src/global.d.ts
vendored
Normal file
9
packages/server/src/global.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import 'fastify';
|
||||||
|
import type { Services } from './utils/utils.services.ts';
|
||||||
|
|
||||||
|
declare module 'fastify' {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||||
|
interface FastifyInstance {
|
||||||
|
services: Services;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
packages/server/src/services/documents/documents.schemas.ts
Normal file
22
packages/server/src/services/documents/documents.schemas.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const upsertDocumentRequestSchema = z.object({
|
||||||
|
id: z.string().min(1).optional(),
|
||||||
|
type: z.string().min(1),
|
||||||
|
source: z.string().min(1).nullable(),
|
||||||
|
data: z.unknown(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type UpsertDocumentRequest = z.infer<typeof upsertDocumentRequestSchema>;
|
||||||
|
|
||||||
|
const upsertDocumentResponseSchema = upsertDocumentRequestSchema.extend({
|
||||||
|
createdAt: z.iso.datetime(),
|
||||||
|
updatedAt: z.iso.datetime(),
|
||||||
|
deletedAt: z.iso.datetime().nullable(),
|
||||||
|
action: z.enum(['inserted', 'updated', 'skipped']),
|
||||||
|
});
|
||||||
|
|
||||||
|
type UpsertDocumentResponse = z.input<typeof upsertDocumentResponseSchema>;
|
||||||
|
|
||||||
|
export type { UpsertDocumentRequest, UpsertDocumentResponse };
|
||||||
|
export { upsertDocumentRequestSchema, upsertDocumentResponseSchema };
|
||||||
77
packages/server/src/services/documents/documents.ts
Normal file
77
packages/server/src/services/documents/documents.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import equal from 'fast-deep-equal';
|
||||||
|
|
||||||
|
import type { UpsertDocumentRequest, UpsertDocumentResponse } from './documents.schemas.ts';
|
||||||
|
|
||||||
|
import { DatabaseService, tableNames, type Tables } from '#root/database/database.ts';
|
||||||
|
import type { Services } from '#root/utils/utils.services.ts';
|
||||||
|
|
||||||
|
class DocumentsService {
|
||||||
|
#services: Services;
|
||||||
|
|
||||||
|
constructor(services: Services) {
|
||||||
|
this.#services = services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public upsert = async (document: UpsertDocumentRequest): Promise<UpsertDocumentResponse> => {
|
||||||
|
const dbService = this.#services.get(DatabaseService);
|
||||||
|
const db = await dbService.getInstance();
|
||||||
|
|
||||||
|
const id = document.id || crypto.randomUUID();
|
||||||
|
|
||||||
|
const [current] = await db<Tables['document']>(tableNames).where({
|
||||||
|
id,
|
||||||
|
type: document.type,
|
||||||
|
});
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
if (!current) {
|
||||||
|
await db<Tables['document']>(tableNames.documents).insert({
|
||||||
|
id,
|
||||||
|
type: document.type,
|
||||||
|
createdAt: now.toISOString(),
|
||||||
|
updatedAt: now.toISOString(),
|
||||||
|
data: JSON.stringify(document.data),
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
data: document.data,
|
||||||
|
id,
|
||||||
|
type: document.type,
|
||||||
|
source: document.source || null,
|
||||||
|
createdAt: now.toISOString(),
|
||||||
|
updatedAt: now.toISOString(),
|
||||||
|
deletedAt: null,
|
||||||
|
action: 'inserted',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const currentData = JSON.parse(current.data);
|
||||||
|
if (equal(currentData, document.data)) {
|
||||||
|
return {
|
||||||
|
...current,
|
||||||
|
data: currentData,
|
||||||
|
id,
|
||||||
|
createdAt: current.createdAt,
|
||||||
|
updatedAt: current.updatedAt,
|
||||||
|
deletedAt: current.deletedAt || null,
|
||||||
|
action: 'skipped',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
await db<Tables['document']>(tableNames.documents)
|
||||||
|
.update({
|
||||||
|
source: document.source,
|
||||||
|
data: JSON.stringify(document.data),
|
||||||
|
updatedAt: now.toISOString(),
|
||||||
|
})
|
||||||
|
.where({ id, type: document.type });
|
||||||
|
return {
|
||||||
|
...current,
|
||||||
|
id,
|
||||||
|
data: document.data,
|
||||||
|
createdAt: current.createdAt,
|
||||||
|
updatedAt: now.toISOString(),
|
||||||
|
deletedAt: current.deletedAt || null,
|
||||||
|
action: 'updated',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { DocumentsService };
|
||||||
7
packages/server/src/start.ts
Normal file
7
packages/server/src/start.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { createApi } from './api/api.ts';
|
||||||
|
|
||||||
|
const app = await createApi();
|
||||||
|
await app.listen({
|
||||||
|
port: 9111,
|
||||||
|
host: process.env.SERVER_HOST,
|
||||||
|
});
|
||||||
51
packages/server/src/utils/utils.services.ts
Normal file
51
packages/server/src/utils/utils.services.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
const destroy = Symbol('destroy');
|
||||||
|
const instanceKey = Symbol('instances');
|
||||||
|
|
||||||
|
type ServiceDependency<T> = new (services: Services) => T & {
|
||||||
|
[destroy]?: () => Promise<void> | void;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Services {
|
||||||
|
[instanceKey]: Map<ServiceDependency<unknown>, unknown>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this[instanceKey] = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get = <T>(service: ServiceDependency<T>) => {
|
||||||
|
if (!this[instanceKey].has(service)) {
|
||||||
|
this[instanceKey].set(service, new service(this));
|
||||||
|
}
|
||||||
|
const instance = this[instanceKey].get(service);
|
||||||
|
if (!instance) {
|
||||||
|
throw new Error('Could not generate instance');
|
||||||
|
}
|
||||||
|
return instance as T;
|
||||||
|
};
|
||||||
|
|
||||||
|
public set = <T>(service: ServiceDependency<T>, instance: Partial<T>) => {
|
||||||
|
this[instanceKey].set(service, instance);
|
||||||
|
};
|
||||||
|
|
||||||
|
public clone = () => {
|
||||||
|
const services = new Services();
|
||||||
|
services[instanceKey] = Object.fromEntries(this[instanceKey].entries());
|
||||||
|
};
|
||||||
|
|
||||||
|
public destroy = async () => {
|
||||||
|
await Promise.all(
|
||||||
|
this[instanceKey].values().map(async (instance) => {
|
||||||
|
if (
|
||||||
|
typeof instance === 'object' &&
|
||||||
|
instance &&
|
||||||
|
destroy in instance &&
|
||||||
|
typeof instance[destroy] === 'function'
|
||||||
|
) {
|
||||||
|
await instance[destroy]();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Services, destroy };
|
||||||
10
packages/server/tsconfig.json
Normal file
10
packages/server/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"extends": "@morten-olsen/reservoir-configs/tsconfig.json"
|
||||||
|
}
|
||||||
12
packages/server/vitest.config.ts
Normal file
12
packages/server/vitest.config.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
import { getAliases } from '@morten-olsen/reservoir-tests/vitest';
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default defineConfig(async () => {
|
||||||
|
const aliases = await getAliases();
|
||||||
|
return {
|
||||||
|
resolve: {
|
||||||
|
alias: aliases,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
4
packages/tests/.gitignore
vendored
Normal file
4
packages/tests/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/node_modules
|
||||||
|
/dist
|
||||||
|
/coverage
|
||||||
|
/.env
|
||||||
27
packages/tests/package.json
Normal file
27
packages/tests/package.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/exports.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc --build"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.6.0",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": "./dist/exports.js",
|
||||||
|
"./vitest": "./dist/vitest.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "24.10.0",
|
||||||
|
"@vitest/coverage-v8": "4.0.6",
|
||||||
|
"typescript": "5.9.3",
|
||||||
|
"vitest": "4.0.6",
|
||||||
|
"@morten-olsen/reservoir-configs": "workspace:*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@pnpm/find-workspace-packages": "6.0.9"
|
||||||
|
},
|
||||||
|
"name": "@morten-olsen/reservoir-tests",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
1
packages/tests/src/exports.ts
Normal file
1
packages/tests/src/exports.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
console.log('Hello World');
|
||||||
10
packages/tests/src/vitest.ts
Normal file
10
packages/tests/src/vitest.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
|
import { findWorkspacePackages } from '@pnpm/find-workspace-packages';
|
||||||
|
|
||||||
|
const getAliases = async () => {
|
||||||
|
const packages = await findWorkspacePackages(process.cwd());
|
||||||
|
return Object.fromEntries(packages.map((pkg) => [pkg.manifest.name, resolve(pkg.dir, 'src', 'exports.ts')]));
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getAliases };
|
||||||
9
packages/tests/tsconfig.json
Normal file
9
packages/tests/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"extends": "@morten-olsen/reservoir-configs/tsconfig.json"
|
||||||
|
}
|
||||||
5603
pnpm-lock.yaml
generated
Normal file
5603
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
5
pnpm-workspace.yaml
Normal file
5
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
packages:
|
||||||
|
- ./packages/*
|
||||||
|
- ./apps/*
|
||||||
|
onlyBuiltDependencies:
|
||||||
|
- better-sqlite3
|
||||||
16
scripts/set-version.mjs
Normal file
16
scripts/set-version.mjs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { readFile, writeFile } from 'fs/promises';
|
||||||
|
import { join } from 'path';
|
||||||
|
import process from 'process';
|
||||||
|
|
||||||
|
import { findWorkspacePackages } from '@pnpm/find-workspace-packages';
|
||||||
|
|
||||||
|
const packages = await findWorkspacePackages(process.cwd());
|
||||||
|
|
||||||
|
for (const pkg of packages) {
|
||||||
|
const pkgPath = join(pkg.dir, 'package.json');
|
||||||
|
const pkgJson = JSON.parse(await readFile(pkgPath, 'utf-8'));
|
||||||
|
|
||||||
|
pkgJson.version = process.argv[2];
|
||||||
|
|
||||||
|
await writeFile(pkgPath, JSON.stringify(pkgJson, null, 2) + '\n');
|
||||||
|
}
|
||||||
37
turbo.json
Normal file
37
turbo.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://turbo.build/schema.json",
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": [
|
||||||
|
"^build"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"dist/**",
|
||||||
|
"public/**"
|
||||||
|
],
|
||||||
|
"inputs": [
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.ts",
|
||||||
|
"./tsconfig.*",
|
||||||
|
"../../pnpm-lock.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"cache": false
|
||||||
|
},
|
||||||
|
"clean": {},
|
||||||
|
"dev": {
|
||||||
|
"dependsOn": [
|
||||||
|
"^build"
|
||||||
|
],
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
},
|
||||||
|
"demo": {
|
||||||
|
"dependsOn": [
|
||||||
|
"^build"
|
||||||
|
],
|
||||||
|
"persistent": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
vitest.config.ts
Normal file
14
vitest.config.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { defineConfig, type UserConfigExport } from 'vitest/config';
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default defineConfig(async () => {
|
||||||
|
const config: UserConfigExport = {
|
||||||
|
test: {
|
||||||
|
coverage: {
|
||||||
|
provider: 'v8',
|
||||||
|
include: ['packages/**/src/**/*.ts'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return config;
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user