Sunday, 12 July 2026

Roject — Session Summary

Full project directory restructure and first production deployment — source/, build/app/, build/data/, source/pages/; Roject live at roject.rokojori.com via nginx + systemd on Server A.

What we built

Directory restructure

The project root was reorganised into two top-level directories that cleanly separate source from build artefacts and runtime data:

  • source/ — all TypeScript source (components, editor, server, rojos, locales, library-ts submodule) plus locale data and HTML pages
  • build/app/ — compiled frontend output (was public/)
  • build/data/db/ — JSON user/project data (was data/)
  • build/data/storage/ — project files, sessions, layouts (was storage/)

The git submodule (library-ts) was moved with git mv so .gitmodules stayed in sync automatically. The entire build/ tree is gitignored; source/ is fully tracked.

source/ build/app/ build/data/ git mv

source/pages/ + copy-pages build step

HTML pages (editor.html, dashboard.html, etc.) moved from build/app/ into source/pages/ so they are tracked in git as source files. A new scripts/copy-pages.js (plain Node.js, no extra dependencies) copies them to build/app/ as the second step of npm run build:

tsc --build tsconfig.client.json && node scripts/copy-pages.js
source/pages/ scripts/copy-pages.js npm run build

First production deployment — roject.rokojori.com

Roject deployed to Server A and served publicly at https://roject.rokojori.com. Stack: Node.js 20, ts-node running source/server/index.ts managed by a systemd service, nginx as TLS-terminating reverse proxy with a Let's Encrypt certificate, port 3000 kept closed at the IONOS firewall.

Several build gaps were discovered and fixed during the deploy: component CSS files were never in source control (moved to source/components/); vendor libraries were in the gitignored build/app/vendor/ (moved to source/vendor/); mkdirSync calls in the server lacked { recursive: true } causing a crash on a fresh clone; and the library-ts browser tsconfig.roject.json still referenced the old public/ outDir instead of build/app/. The copy script was also expanded to copy all non-TS static assets (CSS, SVG, etc.) from all frontend source directories.

roject.rokojori.com systemd nginx Let's Encrypt Node.js 20

EmailService — SMTP email sending

Added a thin email layer to the backend under source/server/email/: an EmailSender interface, an SMTPEmailSender implementation using Nodemailer, and an EmailService static facade that instantiates the sender from environment variables (SMTP_HOST, SMTP_PORT, SMTP_SECURE, SMTP_USER, SMTP_PASS, SMTP_FROM). Swapping the implementation requires changing one line in EmailService.ts.

Nodemailer EmailSender interface static facade env vars

Key Decisions

Two locale directories: source/locales/ vs source/locale-data/

src/locales/ (TypeScript locale management code) moved to source/locales/. The root locales/ (raw locale data files — en/) moved to source/locale-data/. Merging them into one directory would have mixed TypeScript source with data files; keeping them separate makes each directory's purpose unambiguous.

build/ fully gitignored, source/pages/ tracked

The old setup had public/ gitignored with a !public/*.html exception that never actually tracked the files. The new setup is cleaner: all hand-written HTML pages live in source/pages/ (tracked), build/ is entirely ignored, and the copy step bridges the two at build time.

systemd over PM2

systemd is already present on every Linux server and integrates with journalctl for log management. PM2 is easier for day-to-day Node.js process management but adds an extra global dependency. For a single-service deployment systemd is sufficient and keeps the server dependency surface minimal.

Static assets belong in source/, not build/

CSS files, vendor libraries, and SVGs were historically placed directly in public/ (gitignored) and never tracked. A fresh server clone exposes this immediately — nothing in build/ exists until the build runs, and the build can only copy what it finds in source/. Moving all static assets to source/ makes them first-class source files and ensures a clean deploy from any fresh clone.

Plain Node.js copy script, no npm dependency

scripts/copy-pages.js uses only fs and path from the Node.js standard library. Adding a package like copyfiles or cpx for a four-line operation would be unnecessary complexity.

Structural Changes

src/source/ (git mv, including library-ts submodule)
server/source/server/ (git mv)
locales/source/locale-data/ (git mv)
public/build/app/ (filesystem move, gitignored)
data/build/data/db/ (filesystem move, gitignored)
storage/build/data/storage/ (filesystem move, gitignored)
source/pages/ — new: hand-written HTML pages (tracked)
scripts/copy-pages.js — new: copies pages to build/app/ on build
tsconfig.client.json — rootDir, outDir, include, exclude, references updated
tsconfig.json — include updated
tsconfig.ts-node.json — include updated
source/library-ts/browser/tsconfig.roject.json — outDir updated
package.json — main and build script updated
.gitignore — replaced data/, storage/, public/ with build/
source/server/ — all __dirname paths and library-ts imports updated
source/components/*.css — moved from build/app/ into source control
source/vendor/ — vendor libs moved from build/app/ into source control
source/library-ts/browser/tsconfig.roject.json — outDir fixed in submodule, committed and pushed
scripts/copy-pages.js — expanded to copy all non-TS static assets from all frontend source dirs
source/server/db.ts, storage.ts — mkdirSync updated to use { recursive: true }
/etc/systemd/system/roject.service — new on Server A
/etc/nginx/sites-available/roject — new on Server A