mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 14:21:46 +00:00
chore: fix monorepo build pipeline and migrate shared to built package (#1056)
* chore: fix monorepo build pipeline and migrate shared to built package
- Root package.json: add workspace scripts (dev, build, test, test:cov, test:e2e)
that delegate to actual scripts in shared/server/client workspaces
- shared: add tsup build step (CJS + ESM dual output, .d.ts); consumers now import
from the built dist instead of raw TS source via path aliases
- server: replace tsc-alias with tsconfig-paths (tsc-alias mangled node_modules
paths); fix MCP SDK path aliases to point to root node_modules (../node_modules)
- server/scripts/dev.mjs: delay node --watch until tsc -w signals first-pass done,
eliminating the spurious restart on every dev startup
- client/vite.config.js + vitest.config.ts: remove @trek/shared path alias (no longer
needed now that shared is a proper package)
- Consolidate package-lock.json at the workspace root; drop per-workspace lock files
* chore: fix test script to reflect root package.json
* chore: add missing lint and prettier script in root package.json
* fix(ci): build shared before tests; fix vitest MCP SDK alias paths
vitest.config.ts aliases pointed at ./node_modules/ (server-local) but
packages are hoisted to the root node_modules/ in the npm workspace —
changed to ../node_modules/.
CI jobs now install and build shared before running server/client tests
so that @trek/shared's dist/ exists when vitest resolves the package.
* fix(docker): update Dockerfile and CI for monorepo workspace structure
Dockerfile:
- Add shared-builder stage that produces @trek/shared dist before
client and server stages need it
- Each build stage carries root package.json + package-lock.json so npm
can resolve @trek/shared as a workspace dependency
- Production stage installs via workspace context (npm ci --workspace=server
--omit=dev) so node_modules/@trek/shared symlinks to shared/dist correctly
- Copy server/tsconfig.json into the image so tsconfig-paths/register can
find the MCP SDK path aliases at runtime
- CMD cds into /app/server before starting node so tsconfig-paths baseUrl
resolves and ../node_modules points to /app/node_modules
- Remove mkdir for /app/server (now a real dir); keep symlinks for uploads/data
docker.yml version-bump:
- Replace manual per-workspace cd+npm-version calls with single:
npm version --workspaces --include-workspace-root --no-git-tag-version
(mirrors the version:* scripts in root package.json)
- git add now references root package-lock.json; adds shared/package.json
.dockerignore: add shared/dist
package.json: fix version:prerelease preid (alpha → pre)
* fix(tests): use in-memory SQLite per worker in test mode
vitest pool:forks spawns parallel worker processes that all called
initDb() on the same data/travel.db, causing SQLite "database is locked"
and "duplicate column name" races.
When NODE_ENV=test each fork now gets an isolated :memory: DB so migrations
run independently with no file contention.
* chore(ci): add ACT guards to skip DockerHub steps in local act runs
act sets ACT=true automatically. Guards added:
- docker login: if: ${{ !env.ACT }}
- build outputs: type=docker (local load) when ACT, push-by-digest when CI
- digest export/upload: if: ${{ !env.ACT }}
- merge job: if: ${{ !env.ACT }}
- release-helm job (docker.yml): if: ${{ !env.ACT }}
- version-bump git push (docker.yml): wrapped in [ -z "$ACT" ] shell guard
Run locally with:
./bin/act -j build -W .github/workflows/docker.yml \
-P ubuntu-latest=catthehacker/ubuntu:act-latest
* fix(ci): move ACT guards to step level; add guards to security.yml
env context is invalid in job-level if conditions — moved all ACT
guards down to individual steps. Also guards docker login + scout
in security.yml so act can run the build-only part of that workflow.
* fix(ci): skip git fetch and tag logic in act (no remote access in local containers)
* Revert "fix(ci): skip git fetch and tag logic in act (no remote access in local containers)"
This reverts commit 67cf290cda.
* Revert "fix(ci): move ACT guards to step level; add guards to security.yml"
This reverts commit f92b95e054.
* Revert "chore(ci): add ACT guards to skip DockerHub steps in local act runs"
This reverts commit 797183de08.
* fix(docker): add musl optional deps so alpine builds find native rollup/sharp binaries
npm prunes libc-constrained optional deps to the host libc (glibc) when
generating the lockfile, leaving no musl entry for Alpine containers.
Declaring the x64/arm64 musl variants as explicit root optionalDependencies
forces them into the lockfile so npm ci on Alpine can install them.
Covers shared-builder (tsup/rollup) and client-builder (vite/rollup + sharp
icon generation) for both linux/amd64 and linux/arm64 CI targets.
* fix(docker): copy client dist into server/public so the server resolves static files correctly
The server runs from /app/server and serves static files relative to that
directory, so the client build output must land at /app/server/public, not /app/public.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"plugins": [
|
||||
"prettier-plugin-organize-imports",
|
||||
"@trivago/prettier-plugin-sort-imports"
|
||||
],
|
||||
"importOrder": [
|
||||
"^[a-zA-Z]",
|
||||
"^@/.*"
|
||||
],
|
||||
"importOrderSeparation": true,
|
||||
"importOrderParserPlugins": [
|
||||
"typescript",
|
||||
"decorators-legacy"
|
||||
]
|
||||
}
|
||||
Generated
-7389
File diff suppressed because it is too large
Load Diff
+16
-5
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"name": "trek-server",
|
||||
"name": "@trek/server",
|
||||
"version": "3.0.22",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"start": "node dist/index.js",
|
||||
"start": "node --require tsconfig-paths/register dist/index.js",
|
||||
"dev": "node scripts/dev.mjs",
|
||||
"build": "node scripts/build.mjs",
|
||||
"start:prod": "node dist/index.js",
|
||||
"typecheck": "tsc -p tsconfig.build.json --noEmit",
|
||||
"start:prod": "node --require tsconfig-paths/register dist/index.js",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:unit": "vitest run tests/unit",
|
||||
@@ -18,6 +21,8 @@
|
||||
"test:coverage": "vitest run --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trek/shared": "*",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"@modelcontextprotocol/sdk": "^1.28.0",
|
||||
"@nestjs/common": "^11.1.24",
|
||||
"@nestjs/core": "^11.1.24",
|
||||
@@ -60,6 +65,13 @@
|
||||
"file-type": "^21.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.2",
|
||||
"prettier": "^3.8.3",
|
||||
"prettier-plugin-organize-imports": "^4.3.0",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-flat-gitignore": "^2.3.0",
|
||||
"@nestjs/testing": "^11.1.24",
|
||||
"@swc/core": "^1.15.40",
|
||||
"@types/archiver": "^7.0.0",
|
||||
@@ -82,7 +94,6 @@
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"nodemon": "^3.1.0",
|
||||
"supertest": "^7.2.2",
|
||||
"tsc-alias": "^1.8.17",
|
||||
"tz-lookup": "^6.1.25",
|
||||
"unplugin-swc": "^1.5.9",
|
||||
"vitest": "^3.2.4"
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
// tsc emits JS even with type errors (noEmitOnError:false), but still exits
|
||||
// non-zero to report them. We must run tsc-alias regardless, so run tsc in a
|
||||
// try/catch and always proceed to the path-rewrite step.
|
||||
// Type correctness is enforced separately via `npm run typecheck`.
|
||||
try {
|
||||
execSync('tsc -p tsconfig.build.json', { stdio: 'inherit' });
|
||||
} catch {
|
||||
console.warn('[build] tsc reported type errors — emitting anyway (gated by `npm run typecheck`).');
|
||||
}
|
||||
|
||||
execSync('tsc-alias -p tsconfig.build.json', { stdio: 'inherit' });
|
||||
console.log('[build] dist ready (path aliases rewritten).');
|
||||
console.log('[build] dist ready.');
|
||||
|
||||
+24
-14
@@ -1,22 +1,32 @@
|
||||
import { execSync, spawn } from 'node:child_process';
|
||||
|
||||
// Dev runtime for the co-hosted NestJS + legacy Express server.
|
||||
// NestJS DI needs decorator metadata, which the old tsx/esbuild runtime does not
|
||||
// emit — so dev runs the tsc build with watchers (same toolchain as prod `dist`).
|
||||
// Initial build first so `node --watch dist/index.js` has something to start.
|
||||
console.log('[dev] initial build...');
|
||||
execSync('node scripts/build.mjs', { stdio: 'inherit' });
|
||||
|
||||
const watchers = [
|
||||
['npx', ['tsc', '-w', '-p', 'tsconfig.build.json', '--preserveWatchOutput']],
|
||||
['npx', ['tsc-alias', '-w', '-p', 'tsconfig.build.json']],
|
||||
['node', ['--watch', 'dist/index.js']],
|
||||
];
|
||||
|
||||
const children = watchers.map(([cmd, args]) =>
|
||||
spawn(cmd, args, { stdio: 'inherit', shell: true }),
|
||||
);
|
||||
|
||||
const children = [];
|
||||
const stop = () => { children.forEach((c) => { try { c.kill(); } catch {} }); process.exit(0); };
|
||||
process.on('SIGINT', stop);
|
||||
process.on('SIGTERM', stop);
|
||||
|
||||
// Start tsc -w and wait for its first "Watching for file changes." before launching
|
||||
// node --watch, so the initial tsc compilation doesn't trigger a spurious restart.
|
||||
const tsc = spawn('npx', ['tsc', '-w', '-p', 'tsconfig.build.json', '--preserveWatchOutput'], {
|
||||
stdio: ['ignore', 'pipe', 'inherit'],
|
||||
shell: true,
|
||||
});
|
||||
children.push(tsc);
|
||||
|
||||
let nodeProc = null;
|
||||
let ready = false;
|
||||
|
||||
tsc.stdout.on('data', (chunk) => {
|
||||
process.stdout.write(chunk);
|
||||
if (!ready && chunk.toString().includes('Watching for file changes')) {
|
||||
ready = true;
|
||||
nodeProc = spawn('node', ['--require', 'tsconfig-paths/register', '--watch', 'dist/index.js'], {
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
});
|
||||
children.push(nodeProc);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,12 +6,20 @@ import { runMigrations } from './migrations';
|
||||
import { runSeeds } from './seeds';
|
||||
import { Place, Tag } from '../types';
|
||||
|
||||
const dataDir = path.join(__dirname, '../../data');
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
// In test mode each vitest worker gets an isolated in-memory DB so that
|
||||
// parallel forks can't race on the same file or share migration state.
|
||||
const isTest = process.env.NODE_ENV === 'test';
|
||||
|
||||
const dbPath = path.join(dataDir, 'travel.db');
|
||||
let dbPath: string;
|
||||
if (isTest) {
|
||||
dbPath = ':memory:';
|
||||
} else {
|
||||
const dataDir = path.join(__dirname, '../../data');
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
dbPath = path.join(dataDir, 'travel.db');
|
||||
}
|
||||
|
||||
let _db: Database.Database | null = null;
|
||||
|
||||
|
||||
+11
-12
@@ -18,22 +18,21 @@
|
||||
"allowJs": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"ignoreDeprecations": "6.0",
|
||||
// The MCP SDK's package.json uses a wildcard exports pattern with extension-less targets
|
||||
// (e.g. "./*": "./dist/esm/*") which TypeScript cannot resolve — it only strips .js suffixes.
|
||||
// These paths manually redirect to the CJS dist until the SDK fixes its exports map.
|
||||
"paths": {
|
||||
"@trek/shared": ["../shared/src/index.ts"],
|
||||
"@trek/shared/*": ["../shared/src/*"],
|
||||
"@modelcontextprotocol/sdk/server/mcp": ["./node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js"],
|
||||
"@modelcontextprotocol/sdk/server/streamableHttp": ["./node_modules/@modelcontextprotocol/sdk/dist/cjs/server/streamableHttp.js"],
|
||||
"@modelcontextprotocol/sdk/server/auth/router": ["./node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/router.js"],
|
||||
"@modelcontextprotocol/sdk/server/auth/handlers/authorize": ["./node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/handlers/authorize.js"],
|
||||
"@modelcontextprotocol/sdk/server/auth/handlers/register": ["./node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/handlers/register.js"],
|
||||
"@modelcontextprotocol/sdk/server/auth/provider": ["./node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/provider.js"],
|
||||
"@modelcontextprotocol/sdk/server/auth/clients": ["./node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/clients.js"],
|
||||
"@modelcontextprotocol/sdk/server/auth/errors": ["./node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/errors.js"],
|
||||
"@modelcontextprotocol/sdk/server/auth/types": ["./node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/types.js"],
|
||||
"@modelcontextprotocol/sdk/shared/auth": ["./node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/auth.js"]
|
||||
"@modelcontextprotocol/sdk/server/mcp": ["../node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js"],
|
||||
"@modelcontextprotocol/sdk/server/streamableHttp": ["../node_modules/@modelcontextprotocol/sdk/dist/cjs/server/streamableHttp.js"],
|
||||
"@modelcontextprotocol/sdk/server/auth/router": ["../node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/router.js"],
|
||||
"@modelcontextprotocol/sdk/server/auth/handlers/authorize": ["../node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/handlers/authorize.js"],
|
||||
"@modelcontextprotocol/sdk/server/auth/handlers/register": ["../node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/handlers/register.js"],
|
||||
"@modelcontextprotocol/sdk/server/auth/provider": ["../node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/provider.js"],
|
||||
"@modelcontextprotocol/sdk/server/auth/clients": ["../node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/clients.js"],
|
||||
"@modelcontextprotocol/sdk/server/auth/errors": ["../node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/errors.js"],
|
||||
"@modelcontextprotocol/sdk/server/auth/types": ["../node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/types.js"],
|
||||
"@modelcontextprotocol/sdk/shared/auth": ["../node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/auth.js"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
|
||||
+7
-14
@@ -38,32 +38,25 @@ export default defineConfig({
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
// @trek/shared — Zod contract package (tests resolve it to TS source,
|
||||
// mirroring the tsconfig `paths` the tsx runtime uses).
|
||||
'@trek/shared': new URL('../shared/src/index.ts', import.meta.url).pathname,
|
||||
// MCP SDK's exports map uses extension-less wildcard targets that neither
|
||||
// Node nor Vite can resolve. Point directly at the CJS dist files.
|
||||
// Paths are relative to the monorepo root (packages are hoisted there).
|
||||
'@modelcontextprotocol/sdk/server/mcp': new URL(
|
||||
'./node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js',
|
||||
'../node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js',
|
||||
import.meta.url
|
||||
).pathname,
|
||||
'@modelcontextprotocol/sdk/server/streamableHttp': new URL(
|
||||
'./node_modules/@modelcontextprotocol/sdk/dist/cjs/server/streamableHttp.js',
|
||||
'../node_modules/@modelcontextprotocol/sdk/dist/cjs/server/streamableHttp.js',
|
||||
import.meta.url
|
||||
).pathname,
|
||||
'@modelcontextprotocol/sdk/inMemory': new URL(
|
||||
'./node_modules/@modelcontextprotocol/sdk/dist/cjs/inMemory.js',
|
||||
'../node_modules/@modelcontextprotocol/sdk/dist/cjs/inMemory.js',
|
||||
import.meta.url
|
||||
).pathname,
|
||||
'@modelcontextprotocol/sdk/client/index': new URL(
|
||||
'./node_modules/@modelcontextprotocol/sdk/dist/cjs/client/index.js',
|
||||
'../node_modules/@modelcontextprotocol/sdk/dist/cjs/client/index.js',
|
||||
import.meta.url
|
||||
).pathname,
|
||||
},
|
||||
// The server build emits @trek/shared next to its source (shared/src/*.js,
|
||||
// needed by the prod dist via tsc-alias). Vite's default extension order
|
||||
// prefers .js over .ts, so after a build the tests would load that compiled
|
||||
// CJS instead of the source — and its `require('zod')` is unresolvable from
|
||||
// the shared/ dir on CI (only server deps are installed there). Resolve .ts
|
||||
// first so tests always run the source, whose zod import resolves via Vite.
|
||||
extensions: ['.ts', '.mts', '.mjs', '.js', '.cts', '.cjs', '.tsx', '.jsx', '.json'],
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user