Initial commit
Some checks are pending
/ checks (push) Waiting to run
/ deploy (push) Blocked by required conditions

This commit is contained in:
Noah Holt 2026-08-10 21:44:23 -05:00
commit 5d4f3c0965
11 changed files with 194 additions and 0 deletions

4
.dockerignore Normal file
View file

@ -0,0 +1,4 @@
node_modules
dist
.git
.forgejo

View file

@ -0,0 +1,16 @@
# Full pipeline: checks -> build+push image -> bump tag in gitops repo.
# The heavy lifting lives in nholtware/workflows; this file is all a new
# app needs.
on:
push:
branches: [main]
jobs:
checks:
uses: nholtware/workflows/.forgejo/workflows/node-checks.yaml@main
deploy:
needs: checks
uses: nholtware/workflows/.forgejo/workflows/build-and-deploy.yaml@main
with:
app-name: demo-app
secrets: inherit

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules/
dist/

20
Dockerfile Normal file
View file

@ -0,0 +1,20 @@
# Build stage: full toolchain, compile TS -> dist/
FROM node:22 AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
# Runtime stage: no dev deps (this service has zero runtime deps)
FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
ARG GIT_SHA=dev
ENV GIT_SHA=${GIT_SHA}
COPY --from=build /app/dist ./dist
COPY package.json ./
USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]

26
README.md Normal file
View file

@ -0,0 +1,26 @@
# demo-app
Minimal TypeScript microservice proving out the homelab CI/CD pipeline:
```
push to main
-> Forgejo Actions (nholtware/workflows reusable workflows)
checks: npm ci + lint + test (docker label)
build: docker build + push -> 192.168.1.230:3000/nholtware/demo-app:<sha>
deploy: kustomize edit set image -> commit to nholtware/gitops
-> ArgoCD syncs gitops -> k3s
-> served by Traefik at Host: demo-app.k3s.local
```
## Endpoints
- `GET /` — JSON with service name, git sha, pod hostname
- `GET /healthz` — liveness/readiness probe target
## Local dev
```sh
npm ci
npm test
npm run build && npm start # listens on :3000 (PORT to override)
```

50
package-lock.json generated Normal file
View file

@ -0,0 +1,50 @@
{
"name": "demo-app",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "demo-app",
"version": "1.0.0",
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.6.0"
},
"engines": {
"node": ">=22"
}
},
"node_modules/@types/node": {
"version": "22.20.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz",
"integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
}
}
}

20
package.json Normal file
View file

@ -0,0 +1,20 @@
{
"name": "demo-app",
"version": "1.0.0",
"description": "TypeScript microservice demonstrating the Forgejo Actions -> registry -> ArgoCD -> k3s pipeline",
"type": "module",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"test": "npm run build && node --test dist/index.test.js",
"lint": "tsc --noEmit"
},
"engines": {
"node": ">=22"
},
"devDependencies": {
"typescript": "^5.6.0",
"@types/node": "^22.0.0"
}
}

3
src/greeting.ts Normal file
View file

@ -0,0 +1,3 @@
export function greeting(): string {
return "Hello from the homelab pipeline!";
}

7
src/index.test.ts Normal file
View file

@ -0,0 +1,7 @@
import test from "node:test";
import assert from "node:assert/strict";
import { greeting } from "./greeting.js";
test("greeting mentions the homelab", () => {
assert.match(greeting(), /homelab/);
});

32
src/index.ts Normal file
View file

@ -0,0 +1,32 @@
import { createServer } from "node:http";
import { hostname } from "node:os";
import { greeting } from "./greeting.js";
const port = Number(process.env.PORT ?? 3000);
const sha = process.env.GIT_SHA ?? "dev";
const server = createServer((req, res) => {
if (req.url === "/healthz") {
res.writeHead(200, { "content-type": "text/plain" });
res.end("ok");
return;
}
res.writeHead(200, { "content-type": "application/json" });
res.end(
JSON.stringify(
{
service: "demo-app",
message: greeting(),
sha,
pod: hostname(),
time: new Date().toISOString(),
},
null,
2,
) + "\n",
);
});
server.listen(port, () => {
console.log(`demo-app ${sha} listening on :${port}`);
});

14
tsconfig.json Normal file
View file

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src"]
}