Merge commit 'cbc5f0d6868f6c9588159e45547630d157bc4119'

This commit is contained in:
Alexa Amundson
2025-11-25 13:36:51 -06:00
8 changed files with 225 additions and 10 deletions

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ coverage
# But keep the actual source .ts files # But keep the actual source .ts files
!*.ts !*.ts
!*.tsx !*.tsx
dist

View File

@@ -1 +1,84 @@
# blackroad-os # BlackRoad OS
A microservice infrastructure management platform for the BlackRoad ecosystem.
## Service Purpose
BlackRoad OS provides:
- Health monitoring endpoints
- Version tracking and build information
- Express and Fastify-based API routing
- Job scheduling with BullMQ
- React component library for UI dashboards
## Local Development
### Prerequisites
- Node.js 18+
- npm
### Installation
```bash
npm install
```
### Running the Development Server
```bash
npm run dev
```
The server will start at `http://localhost:8080`.
### Running Tests
```bash
npm test
```
## Build & Deploy
### Building for Production
```bash
npm run build
```
This compiles TypeScript to JavaScript in the `dist/` directory.
### Running in Production
```bash
npm start
```
### Railway Deployment
This project is configured for automatic deployment on Railway. The `railway.toml` file defines:
- **Builder**: Nixpacks
- **Start Command**: `npm run start`
- **Healthcheck**: `/health` endpoint
- **Default Port**: 8080
## Healthcheck
The `/health` endpoint returns:
```json
{
"status": "ok",
"service": "blackroad-os"
}
```
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `PORT` | Server port | `8080` |
| `SERVICE_NAME` | Service identifier | `blackroad-os` |
| `ENVIRONMENT` | Runtime environment | `production` |
| `APP_VERSION` | Application version | `1.0.0` |
| `APP_COMMIT` | Git commit hash | Auto-detected |

108
package-lock.json generated
View File

@@ -20,6 +20,7 @@
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
"@types/express": "^5.0.5",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@types/react": "^19.2.6", "@types/react": "^19.2.6",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
@@ -1779,6 +1780,17 @@
"@babel/types": "^7.28.2" "@babel/types": "^7.28.2"
} }
}, },
"node_modules/@types/body-parser": {
"version": "1.19.6",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/connect": "*",
"@types/node": "*"
}
},
"node_modules/@types/chai": { "node_modules/@types/chai": {
"version": "5.2.3", "version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -1790,6 +1802,16 @@
"assertion-error": "^2.0.1" "assertion-error": "^2.0.1"
} }
}, },
"node_modules/@types/connect": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/cookiejar": { "node_modules/@types/cookiejar": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz",
@@ -1811,6 +1833,38 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/express": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz",
"integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^5.0.0",
"@types/serve-static": "^1"
}
},
"node_modules/@types/express-serve-static-core": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz",
"integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
"@types/range-parser": "*",
"@types/send": "*"
}
},
"node_modules/@types/http-errors": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/methods": { "node_modules/@types/methods": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
@@ -1818,6 +1872,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "24.10.1", "version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
@@ -1828,6 +1889,20 @@
"undici-types": "~7.16.0" "undici-types": "~7.16.0"
} }
}, },
"node_modules/@types/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/range-parser": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.2.6", "version": "19.2.6",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.6.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.6.tgz",
@@ -1848,6 +1923,39 @@
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }
}, },
"node_modules/@types/send": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
"integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/serve-static": {
"version": "1.15.10",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz",
"integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/http-errors": "*",
"@types/node": "*",
"@types/send": "<1"
}
},
"node_modules/@types/serve-static/node_modules/@types/send": {
"version": "0.17.6",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz",
"integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/mime": "^1",
"@types/node": "*"
}
},
"node_modules/@types/superagent": { "node_modules/@types/superagent": {
"version": "8.1.9", "version": "8.1.9",
"resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz",

View File

@@ -1,11 +1,13 @@
{ {
"name": "blackroad-os", "name": "blackroad-os",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "BlackRoad OS - A microservice infrastructure management platform",
"main": "index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"test": "vitest", "test": "vitest",
"build": "tsc" "build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node src/index.ts"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@@ -23,6 +25,7 @@
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
"@types/express": "^5.0.5",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@types/react": "^19.2.6", "@types/react": "^19.2.6",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",

12
railway.toml Normal file
View File

@@ -0,0 +1,12 @@
[build]
builder = "NIXPACKS"
[deploy]
startCommand = "npm run start"
healthcheckPath = "/health"
restartPolicy = "on-failure"
[variables]
SERVICE_NAME = "blackroad-os"
PORT = "8080"
ENVIRONMENT = "production"

View File

@@ -1,10 +1,15 @@
import Fastify from "fastify"; import Fastify from "fastify";
import { getBuildInfo } from "./utils/buildInfo"; import { getBuildInfo } from "./utils/buildInfo";
const SERVICE_NAME = process.env.SERVICE_NAME || "blackroad-os";
export async function createServer() { export async function createServer() {
const server = Fastify({ logger: true }); const server = Fastify({ logger: true });
server.get("/health", async () => ({ status: "ok" })); server.get("/health", async () => ({
status: "ok",
service: SERVICE_NAME
}));
server.get("/version", async () => { server.get("/version", async () => {
const info = getBuildInfo(); const info = getBuildInfo();
@@ -15,7 +20,7 @@ export async function createServer() {
} }
if (require.main === module) { if (require.main === module) {
const port = Number(process.env.PORT || 3000); const port = Number(process.env.PORT || 8080);
createServer() createServer()
.then((server) => server.listen({ port, host: "0.0.0.0" })) .then((server) => server.listen({ port, host: "0.0.0.0" }))
.then((address) => { .then((address) => {

View File

@@ -33,7 +33,7 @@ describe("Fastify public routes", () => {
it("returns health", async () => { it("returns health", async () => {
const response = await server.inject({ method: "GET", url: "/health" }); const response = await server.inject({ method: "GET", url: "/health" });
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response.json()).toEqual({ status: "ok" }); expect(response.json()).toEqual({ status: "ok", service: "blackroad-os" });
}); });
it("returns version", async () => { it("returns version", async () => {

View File

@@ -1,14 +1,17 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "target": "ES2020",
"module": "ESNext", "module": "CommonJS",
"moduleResolution": "Node", "moduleResolution": "Node",
"esModuleInterop": true, "esModuleInterop": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"strict": true, "strict": true,
"baseUrl": "./", "baseUrl": "./",
"outDir": "./dist",
"rootDir": "./src",
"skipLibCheck": true,
"types": ["node", "vitest/globals", "@testing-library/jest-dom"] "types": ["node", "vitest/globals", "@testing-library/jest-dom"]
}, },
"include": ["src", "lib", "components", "tests", "app", "vitest.config.ts"], "include": ["src"],
"exclude": ["node_modules"] "exclude": ["node_modules", "dist"]
} }