Skip to content

Commit 4c17255

Browse files
committed
update docker-compose for local testing of functions
1 parent 4b6f35c commit 4c17255

4 files changed

Lines changed: 247 additions & 17 deletions

File tree

Makefile

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: install build clean lint generate dev dev-build dev-down docker-build
1+
.PHONY: install build clean lint generate dev dev-fn dev-down dev-logs docker-build
22

33
install:
44
node --experimental-strip-types scripts/generate.ts
@@ -16,15 +16,24 @@ lint:
1616
generate:
1717
pnpm run generate
1818

19-
dev-build:
20-
docker compose build
19+
# --- Local development ---
20+
# Infrastructure (postgres, db-setup, graphql-server, mailpit) runs in Docker.
21+
# Functions run as local Node processes for fast edit-run cycles.
2122

2223
dev:
23-
docker compose up
24+
docker compose up -d
25+
26+
dev-fn:
27+
node --experimental-strip-types scripts/dev.ts
2428

2529
dev-down:
2630
docker compose down
2731

32+
dev-logs:
33+
docker compose logs -f
34+
35+
# --- Docker image builds ---
36+
2837
docker-build:
2938
pnpm run docker:build
3039

docker-compose.yml

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,97 @@
1+
# Infrastructure services for local development and integration testing.
2+
#
3+
# Functions run as local Node processes (via `make dev-fn` or `pnpm dev:fn`),
4+
# NOT inside Docker. This keeps the edit-run cycle fast — no rebuild needed.
5+
#
6+
# Usage:
7+
# docker compose up -d # Start infra (postgres, graphql, mailpit)
8+
# docker compose down # Stop everything
9+
# docker compose logs -f # Follow logs
10+
111
services:
212
postgres:
313
image: ghcr.io/launchql/pgvector-postgis:16.10
414
environment:
5-
POSTGRES_DB: launchql
15+
POSTGRES_DB: constructive
616
POSTGRES_USER: postgres
717
POSTGRES_PASSWORD: password
818
ports:
919
- "5432:5432"
1020
volumes:
1121
- pgdata:/var/lib/postgresql/data
22+
healthcheck:
23+
test: ["CMD-SHELL", "pg_isready -U postgres"]
24+
interval: 5s
25+
timeout: 5s
26+
retries: 5
1227

13-
job-service:
14-
build:
15-
context: .
16-
dockerfile: Dockerfile.dev
17-
command: node job/service/dist/run.js
28+
db-setup:
29+
image: ghcr.io/constructive-io/constructive:latest
30+
depends_on:
31+
postgres:
32+
condition: service_healthy
1833
environment:
1934
PGHOST: postgres
35+
PGPORT: "5432"
2036
PGUSER: postgres
2137
PGPASSWORD: password
22-
PGDATABASE: launchql
23-
CONSTRUCTIVE_FUNCTIONS: all
24-
CONSTRUCTIVE_JOBS_ENABLED: "true"
38+
PGDATABASE: constructive
39+
entrypoint: ["bash", "-c"]
40+
command:
41+
- |
42+
cd /app
43+
echo "Creating database $$PGDATABASE"
44+
createdb -h "$$PGHOST" -U "$$PGUSER" "$$PGDATABASE" || true
45+
46+
echo "Applying bootstrap roles"
47+
pgpm admin-users bootstrap --yes
48+
pgpm admin-users add --test --yes
49+
50+
echo "Deploying packages"
51+
pgpm deploy --yes --database "$$PGDATABASE" --package constructive
52+
pgpm deploy --yes --database "$$PGDATABASE" --package constructive-services
53+
pgpm deploy --yes --database "$$PGDATABASE" --package constructive-prod
54+
55+
echo "Deploying metaschema and jobs"
56+
pgpm deploy --yes --database "$$PGDATABASE" --package metaschema
57+
pgpm deploy --yes --database "$$PGDATABASE" --package pgpm-database-jobs
58+
59+
echo "Done"
60+
61+
graphql-server:
62+
image: ghcr.io/constructive-io/constructive:latest
2563
depends_on:
26-
- postgres
64+
db-setup:
65+
condition: service_completed_successfully
66+
entrypoint: ["constructive", "server", "--host", "0.0.0.0", "--port", "3000", "--origin", "*"]
67+
environment:
68+
NODE_ENV: development
69+
PORT: "3000"
70+
SERVER_HOST: "0.0.0.0"
71+
SERVER_TRUST_PROXY: "true"
72+
SERVER_ORIGIN: "*"
73+
SERVER_STRICT_AUTH: "false"
74+
# Postgres connection
75+
PGHOST: postgres
76+
PGPORT: "5432"
77+
PGUSER: postgres
78+
PGPASSWORD: password
79+
PGDATABASE: constructive
80+
# API configuration — header-based routing (X-Api-Name, X-Database-Id)
81+
API_ENABLE_SERVICES: "true"
82+
API_EXPOSED_SCHEMAS: "metaschema_public,services_public,constructive_auth_public"
83+
API_IS_PUBLIC: "false"
84+
API_META_SCHEMAS: "metaschema_public,services_public,metaschema_modules_public,constructive_auth_public"
85+
API_ANON_ROLE: "administrator"
86+
API_ROLE_NAME: "administrator"
87+
ports:
88+
- "3002:3000"
89+
90+
mailpit:
91+
image: axllent/mailpit:latest
2792
ports:
28-
- "8080:8080"
29-
- "8081:8081"
30-
- "8082:8082"
93+
- "1025:1025" # SMTP
94+
- "8025:8025" # Web UI
3195

3296
volumes:
3397
pgdata:

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
"build": "pnpm -r run build",
2323
"docker:build": "node --experimental-strip-types scripts/docker-build.ts --all",
2424
"lint": "pnpm -r --if-present run lint",
25+
"dev": "docker compose up -d",
26+
"dev:fn": "node --experimental-strip-types scripts/dev.ts",
27+
"dev:down": "docker compose down",
2528
"test": "jest",
2629
"test:unit": "jest --testPathPatterns='functions/.+/__tests__'",
2730
"test:integration": "jest --testPathPatterns='tests/integration'"

scripts/dev.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// dev: starts functions and job-service as local Node processes
2+
// with env vars pointing to Docker infrastructure services.
3+
//
4+
// Usage:
5+
// node --experimental-strip-types scripts/dev.ts
6+
// node --experimental-strip-types scripts/dev.ts --only=send-email-link
7+
8+
const path = require('path') as typeof import('path');
9+
const { spawn } = require('child_process') as typeof import('child_process');
10+
const { ChildProcess } = require('child_process') as typeof import('child_process');
11+
12+
const ROOT: string = process.cwd();
13+
14+
// --- Shared env vars for all processes ---
15+
16+
const sharedEnv: Record<string, string> = {
17+
...process.env as Record<string, string>,
18+
NODE_ENV: 'development',
19+
LOG_LEVEL: 'info',
20+
// Postgres (matches docker-compose postgres service)
21+
PGHOST: 'localhost',
22+
PGPORT: '5432',
23+
PGUSER: 'postgres',
24+
PGPASSWORD: 'password',
25+
PGDATABASE: 'constructive',
26+
// GraphQL (matches docker-compose graphql-server on port 3002)
27+
GRAPHQL_URL: 'http://localhost:3002/graphql',
28+
META_GRAPHQL_URL: 'http://localhost:3002/graphql',
29+
GRAPHQL_API_NAME: 'private',
30+
DEFAULT_DATABASE_ID: 'dbe',
31+
// Mailpit SMTP (matches docker-compose mailpit on port 1025)
32+
MAILGUN_API_KEY: 'test-key',
33+
MAILGUN_KEY: 'test-key',
34+
MAILGUN_DOMAIN: 'mg.constructive.io',
35+
MAILGUN_FROM: 'no-reply@mg.constructive.io',
36+
MAILGUN_REPLY: 'info@mg.constructive.io',
37+
SMTP_HOST: 'localhost',
38+
SMTP_PORT: '1025',
39+
LOCAL_APP_PORT: '3000',
40+
SEND_EMAIL_LINK_DRY_RUN: 'true',
41+
};
42+
43+
// --- Process definitions ---
44+
45+
interface ProcessDef {
46+
name: string;
47+
script: string;
48+
port: number;
49+
}
50+
51+
const allProcesses: ProcessDef[] = [
52+
{
53+
name: 'job-service',
54+
script: path.resolve(ROOT, 'job/service/dist/run.js'),
55+
port: 8080,
56+
},
57+
{
58+
name: 'simple-email',
59+
script: path.resolve(ROOT, 'generated/simple-email/dist/index.js'),
60+
port: 8081,
61+
},
62+
{
63+
name: 'send-email-link',
64+
script: path.resolve(ROOT, 'generated/send-email-link/dist/index.js'),
65+
port: 8082,
66+
},
67+
];
68+
69+
// --- CLI args ---
70+
71+
const args = process.argv.slice(2);
72+
const onlyArg = args.find((a: string) => a.startsWith('--only='));
73+
const onlyName: string | undefined = onlyArg?.split('=')[1];
74+
75+
// --- Job-service specific env ---
76+
77+
function getJobServiceEnv(): Record<string, string> {
78+
return {
79+
JOBS_SCHEMA: 'app_jobs',
80+
JOBS_SUPPORT_ANY: 'false',
81+
JOBS_SUPPORTED: 'send-email-link',
82+
HOSTNAME: 'knative-job-service-local',
83+
INTERNAL_JOBS_CALLBACK_PORT: '8080',
84+
INTERNAL_JOBS_CALLBACK_URL: 'http://localhost:8080/callback',
85+
JOBS_CALLBACK_HOST: 'localhost',
86+
INTERNAL_GATEWAY_URL: 'http://localhost:8082',
87+
INTERNAL_GATEWAY_DEVELOPMENT_MAP: JSON.stringify({
88+
'send-email-link': 'http://localhost:8082',
89+
'simple-email': 'http://localhost:8081',
90+
}),
91+
};
92+
}
93+
94+
// --- Main ---
95+
96+
const children: ReturnType<typeof spawn>[] = [];
97+
98+
function startProcess(def: ProcessDef): void {
99+
const extraEnv = def.name === 'job-service' ? getJobServiceEnv() : {};
100+
const env = { ...sharedEnv, ...extraEnv, PORT: String(def.port) };
101+
102+
console.log(`Starting ${def.name} on port ${def.port}...`);
103+
104+
const child = spawn('node', [def.script], {
105+
env,
106+
stdio: ['ignore', 'pipe', 'pipe'],
107+
cwd: ROOT,
108+
});
109+
110+
child.stdout?.on('data', (data: Buffer) => {
111+
for (const line of data.toString().trimEnd().split('\n')) {
112+
console.log(`[${def.name}] ${line}`);
113+
}
114+
});
115+
116+
child.stderr?.on('data', (data: Buffer) => {
117+
for (const line of data.toString().trimEnd().split('\n')) {
118+
console.error(`[${def.name}] ${line}`);
119+
}
120+
});
121+
122+
child.on('exit', (code: number | null) => {
123+
console.log(`[${def.name}] exited with code ${code}`);
124+
});
125+
126+
children.push(child);
127+
}
128+
129+
function shutdown(): void {
130+
console.log('\nShutting down...');
131+
for (const child of children) {
132+
child.kill('SIGTERM');
133+
}
134+
process.exit(0);
135+
}
136+
137+
process.on('SIGINT', shutdown);
138+
process.on('SIGTERM', shutdown);
139+
140+
const processes = onlyName
141+
? allProcesses.filter((p) => p.name === onlyName)
142+
: allProcesses;
143+
144+
if (processes.length === 0) {
145+
console.error(onlyName ? `Unknown process "${onlyName}".` : 'No processes to start.');
146+
console.log('Available:', allProcesses.map((p) => p.name).join(', '));
147+
process.exit(1);
148+
}
149+
150+
console.log(`Starting: ${processes.map((p) => `${p.name} (:${p.port})`).join(', ')}`);
151+
152+
for (const def of processes) {
153+
startProcess(def);
154+
}

0 commit comments

Comments
 (0)