feat: add build

This commit is contained in:
Morten Olsen
2025-10-16 22:07:56 +02:00
parent 11828da073
commit 2ea453ea2f
19 changed files with 267 additions and 23 deletions

48
.github/release-drafter-config.yml vendored Normal file
View 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
View 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 }}

45
.github/workflows/job-build.yaml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Build
on:
workflow_call:
env:
DO_NOT_TRACK: '1'
NODE_VERSION: '23.x'
PNPM_VERSION: 10.18.0
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
- name: Run tests
run: pnpm test

View 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 }}

79
.github/workflows/pipeline-default.yaml vendored Normal file
View File

@@ -0,0 +1,79 @@
name: Build and release
on:
push:
branches:
- main
pull_request:
types:
- opened
- synchronize
env:
DO_NOT_TRACK: '1'
DOCKER_REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
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
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ github.repository_owner }}
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: Setup QEMU
uses: docker/setup-qemu-action@v3
- name: Setup Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: network=host
- 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 }}

View File

@@ -8,7 +8,7 @@
"test:unit": "vitest --run --passWithNoTests", "test:unit": "vitest --run --passWithNoTests",
"test": "pnpm run \"/^test:/\"" "test": "pnpm run \"/^test:/\""
}, },
"packageManager": "pnpm@10.6.0", "packageManager": "pnpm@10.18.0",
"files": [ "files": [
"dist" "dist"
], ],

3
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,3 @@
onlyBuiltDependencies:
- better-sqlite3
- esbuild

View File

@@ -1,8 +1,9 @@
import { type FastifyPluginAsync } from 'fastify'; import { type FastifyPluginAsync } from 'fastify';
import { z } from 'zod';
import { manageEndpoints } from './endpoints/endpoints.manage.ts'; import { manageEndpoints } from './endpoints/endpoints.manage.ts';
import { authPlugin } from './plugins/plugins.auth.ts'; import { authPlugin } from './plugins/plugins.auth.ts';
import { messageEndpoints } from './endpoints/endpoints.message.ts'; import { messageEndpoints } from './endpoints/endpoints.message.ts';
import { z } from 'zod';
const api: FastifyPluginAsync = async (fastify) => { const api: FastifyPluginAsync = async (fastify) => {
fastify.route({ fastify.route({

View File

@@ -1,8 +1,9 @@
import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
import { z } from 'zod';
import { JwtAuth } from '#root/auth/auth.jwt.ts'; import { JwtAuth } from '#root/auth/auth.jwt.ts';
import { statementSchema } from '#root/auth/auth.schemas.ts'; import { statementSchema } from '#root/auth/auth.schemas.ts';
import { Config } from '#root/config/config.ts'; import { Config } from '#root/config/config.ts';
import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
import { z } from 'zod';
const manageEndpoints: FastifyPluginAsyncZod = async (fastify) => { const manageEndpoints: FastifyPluginAsyncZod = async (fastify) => {
const config = fastify.services.get(Config); const config = fastify.services.get(Config);

View File

@@ -1,8 +1,9 @@
import { Config } from '#root/config/config.ts';
import { MqttServer } from '#root/server/server.ts';
import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod'; import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
import { z } from 'zod'; import { z } from 'zod';
import { Config } from '#root/config/config.ts';
import { MqttServer } from '#root/server/server.ts';
const messageEndpoints: FastifyPluginAsyncZod = async (fastify) => { const messageEndpoints: FastifyPluginAsyncZod = async (fastify) => {
const config = fastify.services.get(Config); const config = fastify.services.get(Config);

View File

@@ -1,6 +1,7 @@
import { SessionProvider } from '#root/services/sessions/sessions.provider.ts';
import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod'; import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
import { SessionProvider } from '#root/services/sessions/sessions.provider.ts';
const authPlugin: FastifyPluginAsyncZod = async (fastify) => { const authPlugin: FastifyPluginAsyncZod = async (fastify) => {
fastify.addHook('onRequest', async (req, reply) => { fastify.addHook('onRequest', async (req, reply) => {
const authProvider = req.headers['x-auth-provider']; const authProvider = req.headers['x-auth-provider'];

View File

@@ -1,8 +1,9 @@
import type { Services } from '#root/utils/services.ts';
import { Config } from '#root/config/config.ts';
import type { AuthProvider } from './auth.provider.ts'; import type { AuthProvider } from './auth.provider.ts';
import { ADMIN_STATEMENTS } from './auth.consts.ts'; import { ADMIN_STATEMENTS } from './auth.consts.ts';
import type { Services } from '#root/utils/services.ts';
import { Config } from '#root/config/config.ts';
class AdminAuth implements AuthProvider { class AdminAuth implements AuthProvider {
#services: Services; #services: Services;

View File

@@ -2,10 +2,10 @@ import jwt from 'jsonwebtoken';
import type { Statement } from './auth.schemas.ts'; import type { Statement } from './auth.schemas.ts';
import type { AuthProvider } from './auth.provider.ts'; import type { AuthProvider } from './auth.provider.ts';
import { ADMIN_STATEMENTS, READER_STATEMENTS, WRITER_STATEMENTS } from './auth.consts.ts';
import type { Services } from '#root/utils/services.ts'; import type { Services } from '#root/utils/services.ts';
import { Config } from '#root/config/config.ts'; import { Config } from '#root/config/config.ts';
import { ADMIN_STATEMENTS, READER_STATEMENTS, WRITER_STATEMENTS } from './auth.consts.ts';
class OidcAuth implements AuthProvider { class OidcAuth implements AuthProvider {
#services: Services; #services: Services;

View File

@@ -59,7 +59,7 @@ class Config {
} }
public get tcp() { public get tcp() {
const enabled = (process.env.TCP_ENABLED = 'true'); const enabled = process.env.TCP_ENABLED === 'true';
const port = process.env.TCP_PORT ? parseInt(process.env.TCP_PORT) : 1883; const port = process.env.TCP_PORT ? parseInt(process.env.TCP_PORT) : 1883;
return { return {
enabled, enabled,

View File

@@ -2,13 +2,7 @@ import tcp from 'node:net';
import type { IncomingMessage } from 'node:http'; import type { IncomingMessage } from 'node:http';
import swagger from '@fastify/swagger'; import swagger from '@fastify/swagger';
import type { ZodTypeProvider } from 'fastify-type-provider-zod'; import { jsonSchemaTransform, serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod';
import {
jsonSchemaTransform,
createJsonSchemaTransform,
serializerCompiler,
validatorCompiler,
} from 'fastify-type-provider-zod';
import scalar from '@scalar/fastify-api-reference'; import scalar from '@scalar/fastify-api-reference';
import { import {
type AuthenticateHandler, type AuthenticateHandler,
@@ -21,14 +15,14 @@ import aedes from 'aedes';
import fastify, { type FastifyInstance } from 'fastify'; import fastify, { type FastifyInstance } from 'fastify';
import fastifyWebSocket from '@fastify/websocket'; import fastifyWebSocket from '@fastify/websocket';
import { createWebSocketStream } from 'ws'; import { createWebSocketStream } from 'ws';
import fastifySensible from '@fastify/sensible';
import { api } from '../api/api.ts'; import { api } from '../api/api.ts';
import { TopicsHandler } from '#root/topics/topics.handler.ts'; import { TopicsHandler } from '#root/topics/topics.handler.ts';
import type { Services } from '#root/utils/services.ts'; import { destroy, type Services } from '#root/utils/services.ts';
import { Session } from '#root/services/sessions/sessions.session.ts'; import { Session } from '#root/services/sessions/sessions.session.ts';
import { SessionProvider } from '#root/services/sessions/sessions.provider.ts'; import { SessionProvider } from '#root/services/sessions/sessions.provider.ts';
import fastifySensible from '@fastify/sensible';
import { Config } from '#root/config/config.ts'; import { Config } from '#root/config/config.ts';
type Aedes = ReturnType<typeof aedes.createBroker>; type Aedes = ReturnType<typeof aedes.createBroker>;
@@ -188,6 +182,25 @@ class MqttServer {
} }
return this.#tcp; return this.#tcp;
}; };
[destroy] = async () => {
if (this.#http) {
const http = await this.#http;
await http.close();
}
await new Promise<void>((resolve, reject) => {
if (this.#tcp) {
this.#tcp.close((err) => {
if (err) {
return reject(err);
}
resolve();
});
} else {
resolve();
}
});
};
} }
export { MqttServer }; export { MqttServer };

View File

@@ -1,6 +1,7 @@
import type { AuthProvider } from '#root/auth/auth.provider.ts';
import { Session } from './sessions.session.ts'; import { Session } from './sessions.session.ts';
import type { AuthProvider } from '#root/auth/auth.provider.ts';
class SessionProvider { class SessionProvider {
#handlers: Map<string, AuthProvider>; #handlers: Map<string, AuthProvider>;

View File

@@ -39,6 +39,7 @@ describe('mqtt', () => {
it('should not be able to publish if not allowed', async () => { it('should not be able to publish if not allowed', async () => {
const [client] = await world.connect([]); const [client] = await world.connect([]);
// eslint-disable-next-line
const promise = client.publishAsync('test', 'test'); const promise = client.publishAsync('test', 'test');
// TODO: why does this not throw? // TODO: why does this not throw?

View File

@@ -1,4 +1,4 @@
import type { Statement } from '#root/access/access.schemas.ts'; import type { Statement } from '#root/auth/auth.schemas.ts';
const statements = { const statements = {
all: [ all: [

View File

@@ -33,6 +33,16 @@ const createWorld = async (options: WorldOptions) => {
backbone.services.set(Config, { backbone.services.set(Config, {
jwtSecret: 'test', jwtSecret: 'test',
adminToken: 'test', adminToken: 'test',
api: {
enabled: true,
},
ws: {
enabled: true,
},
tcp: {
enabled: false,
port: 1883,
},
}); });
const accessTokens = backbone.services.get(JwtAuth); const accessTokens = backbone.services.get(JwtAuth);
backbone.sessionProvider.register('token', accessTokens); backbone.sessionProvider.register('token', accessTokens);
@@ -61,7 +71,7 @@ const createWorld = async (options: WorldOptions) => {
}, },
destroy: async () => { destroy: async () => {
await Promise.all(sockets.map((s) => s.endAsync())); await Promise.all(sockets.map((s) => s.endAsync()));
await fastify.close(); await backbone.destroy();
}, },
}; };
}; };