khal-os 1.260326.13 → 1.260326.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +6 -8
- package/Makefile +12 -12
- package/ecosystem.config.cjs +5 -5
- package/package.json +4 -2
- package/packages/npx-cli/dist/cli.js +3 -3
- package/packages/npx-cli/src/_version.ts +1 -1
- package/packages/npx-cli/src/cli.ts +2 -2
- package/packages/npx-cli/src/provision.ts +22 -7
- package/packages/npx-cli/src/server.ts +13 -6
- package/packages/os-sdk/package.json +4 -0
- package/packages/os-sdk/src/db/provision.ts +6 -17
- package/scripts/app-cli.ts +2 -2
- package/scripts/build-binaries.sh +27 -11
- package/scripts/preflight.sh +2 -3
- package/scripts/setup.sh +15 -23
- package/src/lib/service-loader.ts +15 -15
- package/src/lib/ws-bridge.ts +8 -13
- package/src/lib/ws-server.ts +73 -56
- package/bun.lock +0 -1249
- package/packages/os-sdk/bun.lock +0 -334
package/CLAUDE.md
CHANGED
|
@@ -24,10 +24,8 @@ pm2 flush <name> # clear logs
|
|
|
24
24
|
|
|
25
25
|
### Service Loader Runtime
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
All services run under **Node.js + tsx**. The service-loader spawns each discovered service via `npx tsx`.
|
|
28
28
|
|
|
29
|
-
- `node-pty` does NOT work under Bun (PTY gets SIGHUP immediately). The terminal service uses `runtime: node`.
|
|
30
|
-
- `import.meta.dir` is Bun-only. Use `import.meta.dirname ?? dirname(fileURLToPath(import.meta.url))` for cross-runtime compat.
|
|
31
29
|
- `import pty from 'node-pty'` fails under tsx (CJS compat). Use `import * as pty from 'node-pty'`.
|
|
32
30
|
|
|
33
31
|
### Caddy (reverse proxy)
|
|
@@ -69,9 +67,9 @@ Headless Chrome needs to render `/desktop` without logging in. When `OS_SECRET`
|
|
|
69
67
|
## Lint / Build
|
|
70
68
|
|
|
71
69
|
```bash
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
pnpm biome check --write . # lint + format
|
|
71
|
+
npx tsc --noEmit # typecheck
|
|
72
|
+
pnpm next build # production build
|
|
75
73
|
```
|
|
76
74
|
|
|
77
75
|
- Biome config: `biome.json` — `**/service/**` override allows console.log
|
|
@@ -81,8 +79,8 @@ bun next build # production build
|
|
|
81
79
|
|
|
82
80
|
Before claiming any work is complete, **run verification first**:
|
|
83
81
|
|
|
84
|
-
1. `
|
|
85
|
-
2. `
|
|
82
|
+
1. `pnpm biome check .` — lint + format clean
|
|
83
|
+
2. `npx tsc --noEmit` — no type errors
|
|
86
84
|
|
|
87
85
|
Never say "tests pass", "build succeeds", or "should work now" without showing command output.
|
|
88
86
|
These hooks run automatically on Stop (after each response that edits .ts/.tsx files).
|
package/Makefile
CHANGED
|
@@ -53,11 +53,11 @@ dev:
|
|
|
53
53
|
bash $(SLOT_SCRIPT) update-pid "$$SLOT" "$$$$"; \
|
|
54
54
|
$(MAKE) db-create DB="$$DB_NAME" 2>&1 | sed 's/^/[db] /' || { echo "[dev] DB creation failed"; exit 1; }; \
|
|
55
55
|
echo "[dev] Starting processes..."; \
|
|
56
|
-
DATABASE_URL="$$DB_URL" PORT="$$NEXT_PORT"
|
|
56
|
+
DATABASE_URL="$$DB_URL" PORT="$$NEXT_PORT" pnpm next dev --port "$$NEXT_PORT" 2>&1 | sed 's/^/[next] /' & \
|
|
57
57
|
PID_NEXT=$$!; \
|
|
58
|
-
DATABASE_URL="$$DB_URL"
|
|
58
|
+
DATABASE_URL="$$DB_URL" pnpm tsx src/lib/service-loader.ts 2>&1 | sed 's/^/[svc] /' & \
|
|
59
59
|
PID_SVC=$$!; \
|
|
60
|
-
WS_BRIDGE_PORT="$$WS_PORT" DATABASE_URL="$$DB_URL"
|
|
60
|
+
WS_BRIDGE_PORT="$$WS_PORT" DATABASE_URL="$$DB_URL" pnpm tsx src/lib/ws-server.ts 2>&1 | sed 's/^/[ws] /' & \
|
|
61
61
|
PID_WS=$$!; \
|
|
62
62
|
wait $$PID_NEXT $$PID_SVC $$PID_WS
|
|
63
63
|
|
|
@@ -95,7 +95,7 @@ db-create:
|
|
|
95
95
|
echo "Creating database $(DB)..."; \
|
|
96
96
|
PGPASSWORD=$(PG_PASS) createdb -h $(PG_HOST) -p $(PG_PORT) -U $(PG_USER) "$(DB)" 2>/dev/null || echo "Database $(DB) already exists"; \
|
|
97
97
|
echo "Running migrations..."; \
|
|
98
|
-
DATABASE_URL="postgresql://$(PG_USER):$(PG_PASS)@$(PG_HOST):$(PG_PORT)/$(DB)"
|
|
98
|
+
DATABASE_URL="postgresql://$(PG_USER):$(PG_PASS)@$(PG_HOST):$(PG_PORT)/$(DB)" pnpm tsx packages/os-sdk/src/db/migrate.ts; \
|
|
99
99
|
echo "Database $(DB) ready."
|
|
100
100
|
|
|
101
101
|
## Drop a database (DB=<name>)
|
|
@@ -111,7 +111,7 @@ db-migrate:
|
|
|
111
111
|
@set -euo pipefail; \
|
|
112
112
|
if [ -z "$(DB)" ]; then echo "Usage: make db-migrate DB=<name>"; exit 1; fi; \
|
|
113
113
|
echo "Running migrations on $(DB)..."; \
|
|
114
|
-
DATABASE_URL="postgresql://$(PG_USER):$(PG_PASS)@$(PG_HOST):$(PG_PORT)/$(DB)"
|
|
114
|
+
DATABASE_URL="postgresql://$(PG_USER):$(PG_PASS)@$(PG_HOST):$(PG_PORT)/$(DB)" pnpm tsx packages/os-sdk/src/db/migrate.ts; \
|
|
115
115
|
echo "Migrations complete."
|
|
116
116
|
|
|
117
117
|
# =====================
|
|
@@ -124,9 +124,9 @@ db-migrate:
|
|
|
124
124
|
prod:
|
|
125
125
|
@set -euo pipefail; \
|
|
126
126
|
echo "[prod] Installing dependencies..."; \
|
|
127
|
-
|
|
127
|
+
pnpm install; \
|
|
128
128
|
echo "[prod] Building Next.js..."; \
|
|
129
|
-
|
|
129
|
+
pnpm next build; \
|
|
130
130
|
echo "[prod] Starting PM2 processes..."; \
|
|
131
131
|
if pm2 describe os-services >/dev/null 2>&1; then \
|
|
132
132
|
pm2 restart ecosystem.config.cjs; \
|
|
@@ -213,9 +213,9 @@ deploy:
|
|
|
213
213
|
echo "[deploy] Installing dependencies..."; \
|
|
214
214
|
pnpm install; \
|
|
215
215
|
echo "[deploy] Running database migrations..."; \
|
|
216
|
-
DATABASE_URL="postgresql://$(PG_USER):$(PG_PASS)@$(PG_HOST):$(PG_PORT)/genie-os"
|
|
216
|
+
DATABASE_URL="postgresql://$(PG_USER):$(PG_PASS)@$(PG_HOST):$(PG_PORT)/genie-os" pnpm tsx packages/os-sdk/src/db/migrate.ts; \
|
|
217
217
|
echo "[deploy] Building Next.js..."; \
|
|
218
|
-
|
|
218
|
+
pnpm next build; \
|
|
219
219
|
echo "[deploy] Restarting PM2 processes..."; \
|
|
220
220
|
pm2 restart ecosystem.config.cjs 2>/dev/null || pm2 start ecosystem.config.cjs; \
|
|
221
221
|
echo "[deploy] Waiting for startup..."; \
|
|
@@ -228,8 +228,8 @@ deploy:
|
|
|
228
228
|
## Run QA assertions against the running instance
|
|
229
229
|
test:
|
|
230
230
|
@echo "Running QA assertions..."
|
|
231
|
-
@
|
|
232
|
-
@
|
|
231
|
+
@pnpm tsx packages/os-cli/src/index.ts qa assert health
|
|
232
|
+
@pnpm tsx packages/os-cli/src/index.ts qa assert auth
|
|
233
233
|
@echo ""
|
|
234
234
|
@echo "All assertions passed."
|
|
235
235
|
|
|
@@ -251,7 +251,7 @@ preflight:
|
|
|
251
251
|
prod-backend:
|
|
252
252
|
@set -euo pipefail; \
|
|
253
253
|
echo "[prod-backend] Installing dependencies..."; \
|
|
254
|
-
|
|
254
|
+
pnpm install; \
|
|
255
255
|
echo "[prod-backend] Starting backend services..."; \
|
|
256
256
|
if pm2 describe os-services >/dev/null 2>&1; then \
|
|
257
257
|
pm2 restart os-services os-ws-bridge; \
|
package/ecosystem.config.cjs
CHANGED
|
@@ -11,21 +11,21 @@ module.exports = {
|
|
|
11
11
|
{
|
|
12
12
|
name: 'os-dev',
|
|
13
13
|
script: 'bash',
|
|
14
|
-
args: '-c "
|
|
14
|
+
args: '-c "pnpm next build && exec pnpm next start --port 8888"',
|
|
15
15
|
cwd: __dirname,
|
|
16
16
|
autorestart: true,
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
name: 'os-services',
|
|
20
|
-
script: '
|
|
21
|
-
args: '
|
|
20
|
+
script: 'npx',
|
|
21
|
+
args: 'tsx src/lib/service-loader.ts',
|
|
22
22
|
cwd: __dirname,
|
|
23
23
|
autorestart: true,
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
name: 'os-ws-bridge',
|
|
27
|
-
script: '
|
|
28
|
-
args: '
|
|
27
|
+
script: 'npx',
|
|
28
|
+
args: 'tsx src/lib/ws-server.ts',
|
|
29
29
|
cwd: __dirname,
|
|
30
30
|
autorestart: true,
|
|
31
31
|
env: { WS_BRIDGE_PORT: '4280' },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "khal-os",
|
|
3
|
-
"version": "1.260326.
|
|
3
|
+
"version": "1.260326.15",
|
|
4
4
|
"bin": {
|
|
5
5
|
"khal-os": "./packages/npx-cli/dist/cli.js"
|
|
6
6
|
},
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"lint": "biome check .",
|
|
13
13
|
"lint:fix": "biome check --write .",
|
|
14
14
|
"knip": "knip",
|
|
15
|
-
"services": "
|
|
15
|
+
"services": "tsx src/lib/service-loader.ts",
|
|
16
16
|
"tauri": "cd tauri && cargo run",
|
|
17
17
|
"tauri:build": "cd tauri && cargo build --release",
|
|
18
18
|
"prepare": "husky"
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"react-dom": "19.2.3",
|
|
53
53
|
"tailwind-merge": "^3.4.0",
|
|
54
54
|
"uuid": "^13.0.0",
|
|
55
|
+
"ws": "^8.20.0",
|
|
55
56
|
"zod": "^4.3.6",
|
|
56
57
|
"zustand": "^5.0.11"
|
|
57
58
|
},
|
|
@@ -64,6 +65,7 @@
|
|
|
64
65
|
"@types/react": "^19",
|
|
65
66
|
"@types/react-dom": "^19",
|
|
66
67
|
"@types/uuid": "^11.0.0",
|
|
68
|
+
"@types/ws": "^8.18.1",
|
|
67
69
|
"git-cliff": "^2.12.0",
|
|
68
70
|
"husky": "^9.1.7",
|
|
69
71
|
"knip": "^5.85.0",
|
|
@@ -44,7 +44,7 @@ var exports__version = {};
|
|
|
44
44
|
__export(exports__version, {
|
|
45
45
|
KHALOS_BUILD_VERSION: () => KHALOS_BUILD_VERSION
|
|
46
46
|
});
|
|
47
|
-
var KHALOS_BUILD_VERSION = "1.260326.
|
|
47
|
+
var KHALOS_BUILD_VERSION = "1.260326.15";
|
|
48
48
|
|
|
49
49
|
// src/cli.ts
|
|
50
50
|
import { execSync, spawn } from "node:child_process";
|
|
@@ -793,13 +793,13 @@ function runFromSource(args) {
|
|
|
793
793
|
if (!existsSync(serverTs)) {
|
|
794
794
|
console.error(source_default.red(`
|
|
795
795
|
No binary available for your platform and source not found.`));
|
|
796
|
-
console.error(source_default.dim(` Install from source: git clone +
|
|
796
|
+
console.error(source_default.dim(` Install from source: git clone + npx tsx packages/npx-cli/src/server.ts
|
|
797
797
|
`));
|
|
798
798
|
process.exit(1);
|
|
799
799
|
}
|
|
800
800
|
console.log(source_default.dim(` No binary available — running from source...
|
|
801
801
|
`));
|
|
802
|
-
const child = spawn("
|
|
802
|
+
const child = spawn("npx", ["tsx", serverTs, ...args], {
|
|
803
803
|
cwd: projectRoot,
|
|
804
804
|
stdio: "inherit",
|
|
805
805
|
env: process.env
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const KHALOS_BUILD_VERSION = '1.260326.
|
|
1
|
+
export const KHALOS_BUILD_VERSION = '1.260326.15';
|
|
@@ -349,12 +349,12 @@ function runFromSource(args: string[]): void {
|
|
|
349
349
|
|
|
350
350
|
if (!existsSync(serverTs)) {
|
|
351
351
|
console.error(chalk.red('\n No binary available for your platform and source not found.'));
|
|
352
|
-
console.error(chalk.dim(' Install from source: git clone +
|
|
352
|
+
console.error(chalk.dim(' Install from source: git clone + npx tsx packages/npx-cli/src/server.ts\n'));
|
|
353
353
|
process.exit(1);
|
|
354
354
|
}
|
|
355
355
|
|
|
356
356
|
console.log(chalk.dim(' No binary available — running from source...\n'));
|
|
357
|
-
const child = spawn('
|
|
357
|
+
const child = spawn('npx', ['tsx', serverTs, ...args], {
|
|
358
358
|
cwd: projectRoot,
|
|
359
359
|
stdio: 'inherit',
|
|
360
360
|
env: process.env,
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { execSync } from 'node:child_process';
|
|
9
9
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
|
+
import chalk from 'chalk';
|
|
11
12
|
import { DATA_DIR, PG_PORT } from './services';
|
|
12
13
|
|
|
13
14
|
const DB_NAME = 'khal-os';
|
|
@@ -62,15 +63,29 @@ export async function createDatabase(): Promise<void> {
|
|
|
62
63
|
// ---------------------------------------------------------------------------
|
|
63
64
|
|
|
64
65
|
/**
|
|
65
|
-
* Run Drizzle migrations
|
|
66
|
+
* Run Drizzle migrations inline — no subprocess, works inside compiled binary.
|
|
66
67
|
*/
|
|
67
|
-
export async function runMigrations(
|
|
68
|
+
export async function runMigrations(_projectRoot: string): Promise<void> {
|
|
68
69
|
const dbUrl = getDatabaseUrl();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
// Set DATABASE_URL for the migration module to pick up
|
|
71
|
+
process.env.DATABASE_URL = dbUrl;
|
|
72
|
+
try {
|
|
73
|
+
// Import and run migrations directly — bundled into the binary
|
|
74
|
+
const { runAllMigrations } = await import('@khal-os/sdk');
|
|
75
|
+
await runAllMigrations();
|
|
76
|
+
} catch {
|
|
77
|
+
// If the SDK import fails (e.g., in minimal binary mode), try direct postgres
|
|
78
|
+
console.log(` ${chalk.yellow('⚠')} Migration via SDK failed, trying direct connection...`);
|
|
79
|
+
try {
|
|
80
|
+
const postgres = (await import('postgres')).default;
|
|
81
|
+
const sql = postgres(dbUrl, { max: 1 });
|
|
82
|
+
await sql`SELECT 1`;
|
|
83
|
+
await sql.end();
|
|
84
|
+
console.log(` ${chalk.dim('Database reachable — migrations may need manual run')}`);
|
|
85
|
+
} catch {
|
|
86
|
+
console.log(` ${chalk.yellow('⚠')} Could not connect to database — skipping migrations`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
74
89
|
}
|
|
75
90
|
|
|
76
91
|
// ---------------------------------------------------------------------------
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* khal-os server — standalone binary entry point.
|
|
3
3
|
*
|
|
4
|
-
* This file compiles to a standalone binary via
|
|
4
|
+
* This file compiles to a standalone binary via esbuild + pkg.
|
|
5
5
|
* It IS the khal-os server: starts everything, serves everything.
|
|
6
6
|
*
|
|
7
7
|
* Modes:
|
|
@@ -41,9 +41,9 @@ import {
|
|
|
41
41
|
// ---------------------------------------------------------------------------
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Detect if we are running as a compiled
|
|
45
|
-
* When compiled
|
|
46
|
-
*
|
|
44
|
+
* Detect if we are running as a compiled binary.
|
|
45
|
+
* When compiled, import.meta.url will not end with `.ts` and
|
|
46
|
+
* process.argv[0] points to the binary itself.
|
|
47
47
|
*/
|
|
48
48
|
function isCompiledBinary(): boolean {
|
|
49
49
|
return !import.meta.url.endsWith('.ts');
|
|
@@ -123,7 +123,7 @@ function printStatus(services: ServiceStatus[]): void {
|
|
|
123
123
|
|
|
124
124
|
async function buildNextJs(projectRoot: string, env: Record<string, string | undefined>): Promise<void> {
|
|
125
125
|
console.log(`\n ${chalk.dim('Building Next.js...')}`);
|
|
126
|
-
execSync('
|
|
126
|
+
execSync('pnpm next build', {
|
|
127
127
|
cwd: projectRoot,
|
|
128
128
|
env: { ...process.env, ...env, NEXT_PRIVATE_BUILD_WORKER: 'false' } as NodeJS.ProcessEnv,
|
|
129
129
|
stdio: 'inherit',
|
|
@@ -135,9 +135,16 @@ async function buildNextJs(projectRoot: string, env: Record<string, string | und
|
|
|
135
135
|
// ---------------------------------------------------------------------------
|
|
136
136
|
|
|
137
137
|
async function ensureDeps(projectRoot: string): Promise<void> {
|
|
138
|
+
// Compiled binary has everything bundled — no need to install deps
|
|
139
|
+
if (isCompiledBinary()) return;
|
|
140
|
+
|
|
138
141
|
if (!existsSync(join(projectRoot, 'node_modules'))) {
|
|
139
142
|
console.log(` ${chalk.dim('Installing dependencies...')}`);
|
|
140
|
-
|
|
143
|
+
try {
|
|
144
|
+
execSync('pnpm install', { cwd: projectRoot, stdio: 'pipe' });
|
|
145
|
+
} catch {
|
|
146
|
+
console.log(` ${chalk.yellow('⚠')} Could not install deps — continuing without`);
|
|
147
|
+
}
|
|
141
148
|
}
|
|
142
149
|
}
|
|
143
150
|
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Each app calls provisionAppSchema() on boot to create its isolated
|
|
5
5
|
* PostgreSQL schema + role via pgserve. Idempotent, safe for restarts.
|
|
6
|
-
*
|
|
7
|
-
* Requires Bun runtime (uses Bun.SQL for the pgserve admin connection).
|
|
8
6
|
*/
|
|
9
7
|
|
|
8
|
+
import pg from 'pg';
|
|
10
9
|
import { getDatabaseUrl } from '../config';
|
|
11
10
|
|
|
12
11
|
export interface AppSchemaConfig {
|
|
@@ -20,25 +19,15 @@ export async function provisionAppSchema(config: AppSchemaConfig): Promise<{ cre
|
|
|
20
19
|
const pgserveModule = 'pgserve';
|
|
21
20
|
// biome-ignore lint/suspicious/noExplicitAny: optional runtime dependency, no types guaranteed
|
|
22
21
|
const { initCatalog, provisionSchema } = (await import(pgserveModule)) as any;
|
|
23
|
-
// Bun.SQL is only available at runtime under Bun — indirect import to skip tsc module resolution
|
|
24
|
-
const bunModule = 'bun';
|
|
25
|
-
// biome-ignore lint/suspicious/noExplicitAny: Bun-only runtime API, no type declarations available
|
|
26
|
-
const { SQL } = (await import(bunModule)) as any;
|
|
27
22
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
hostname: url.hostname,
|
|
31
|
-
port: Number(url.port),
|
|
32
|
-
database: url.pathname.slice(1),
|
|
33
|
-
username: url.username,
|
|
34
|
-
password: url.password,
|
|
35
|
-
});
|
|
23
|
+
const client = new pg.Client({ connectionString: getDatabaseUrl() });
|
|
24
|
+
await client.connect();
|
|
36
25
|
|
|
37
26
|
try {
|
|
38
|
-
await initCatalog(
|
|
39
|
-
const result = await provisionSchema(
|
|
27
|
+
await initCatalog(client);
|
|
28
|
+
const result = await provisionSchema(client, config);
|
|
40
29
|
return { created: result.created };
|
|
41
30
|
} finally {
|
|
42
|
-
await
|
|
31
|
+
await client.end();
|
|
43
32
|
}
|
|
44
33
|
}
|
package/scripts/app-cli.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
-
# Build khal-os standalone binaries via
|
|
4
|
+
# Build khal-os standalone binaries via esbuild + pkg.
|
|
5
5
|
# Usage: bash scripts/build-binaries.sh [--target <platform>]
|
|
6
6
|
#
|
|
7
7
|
# Targets: linux-x64, macos-arm64, all (default)
|
|
8
8
|
|
|
9
9
|
ENTRY="packages/npx-cli/src/server.ts"
|
|
10
|
+
BUNDLE="dist/server.cjs"
|
|
10
11
|
DIST="dist"
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
[
|
|
15
|
-
[macos-arm64]="bun-darwin-arm64"
|
|
13
|
+
declare -A PKG_TARGETS=(
|
|
14
|
+
[linux-x64]="node22-linux-x64"
|
|
15
|
+
[macos-arm64]="node22-macos-arm64"
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
declare -A ASSET_NAMES=(
|
|
@@ -20,16 +20,28 @@ declare -A ASSET_NAMES=(
|
|
|
20
20
|
[macos-arm64]="khal-os-macos-arm64"
|
|
21
21
|
)
|
|
22
22
|
|
|
23
|
+
bundle() {
|
|
24
|
+
echo "Bundling ${ENTRY} → ${BUNDLE}..."
|
|
25
|
+
npx esbuild "${ENTRY}" \
|
|
26
|
+
--bundle \
|
|
27
|
+
--platform=node \
|
|
28
|
+
--target=node22 \
|
|
29
|
+
--format=cjs \
|
|
30
|
+
--outfile="${BUNDLE}" \
|
|
31
|
+
--external:@nats-io/* \
|
|
32
|
+
--external:node-pty
|
|
33
|
+
echo " -> ${BUNDLE}"
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
build_target() {
|
|
24
37
|
local target="$1"
|
|
25
|
-
local
|
|
38
|
+
local pkg_target="${PKG_TARGETS[$target]}"
|
|
26
39
|
local asset="${ASSET_NAMES[$target]}"
|
|
27
40
|
|
|
28
|
-
echo "Building ${asset} (${
|
|
29
|
-
|
|
30
|
-
--
|
|
31
|
-
"${
|
|
32
|
-
--outfile "${DIST}/${asset}"
|
|
41
|
+
echo "Building ${asset} (${pkg_target})..."
|
|
42
|
+
npx @yao-pkg/pkg "${BUNDLE}" \
|
|
43
|
+
--targets "${pkg_target}" \
|
|
44
|
+
--output "${DIST}/${asset}"
|
|
33
45
|
|
|
34
46
|
echo " -> ${DIST}/${asset}"
|
|
35
47
|
}
|
|
@@ -71,10 +83,14 @@ fi
|
|
|
71
83
|
echo ""
|
|
72
84
|
echo "=== khal-os binary build ==="
|
|
73
85
|
echo "Entry: ${ENTRY}"
|
|
86
|
+
echo "Bundle: ${BUNDLE}"
|
|
74
87
|
echo "Output: ${DIST}/"
|
|
75
88
|
echo "Targets: ${TARGETS[*]}"
|
|
76
89
|
echo ""
|
|
77
90
|
|
|
91
|
+
# Bundle TypeScript to single CJS file
|
|
92
|
+
bundle
|
|
93
|
+
|
|
78
94
|
# Build each target
|
|
79
95
|
for target in "${TARGETS[@]}"; do
|
|
80
96
|
build_target "${target}"
|
package/scripts/preflight.sh
CHANGED
|
@@ -36,7 +36,7 @@ echo "◆ Khal OS — Preflight Check"
|
|
|
36
36
|
echo ""
|
|
37
37
|
echo " Core tools:"
|
|
38
38
|
check "node" "node" "--version"
|
|
39
|
-
check "
|
|
39
|
+
check "tsx" "tsx" "--version"
|
|
40
40
|
check "pnpm" "pnpm" "--version"
|
|
41
41
|
check "git" "git" "--version"
|
|
42
42
|
|
|
@@ -64,9 +64,8 @@ echo " ${GREEN}✔${RESET} $PASS passed ${RED}✘${RESET} $FAIL missing"
|
|
|
64
64
|
if [ "$FAIL" -gt 0 ]; then
|
|
65
65
|
echo ""
|
|
66
66
|
echo " ${DIM}Install missing tools:${RESET}"
|
|
67
|
-
echo " bun: curl -fsSL https://bun.sh/install | bash"
|
|
68
67
|
echo " pnpm: corepack enable && corepack prepare pnpm@latest --activate"
|
|
69
|
-
echo " pm2:
|
|
68
|
+
echo " pm2: pnpm add -g pm2"
|
|
70
69
|
echo " psql: sudo apt install postgresql-client (Linux)"
|
|
71
70
|
echo " brew install postgresql (macOS)"
|
|
72
71
|
echo ""
|
package/scripts/setup.sh
CHANGED
|
@@ -24,17 +24,7 @@ echo "${BOLD}◆ Khal OS — Developer Setup${RESET}"
|
|
|
24
24
|
echo ""
|
|
25
25
|
|
|
26
26
|
# -------------------------
|
|
27
|
-
# 1. Install
|
|
28
|
-
# -------------------------
|
|
29
|
-
if ! command -v bun &>/dev/null; then
|
|
30
|
-
echo " Installing bun..."
|
|
31
|
-
curl -fsSL https://bun.sh/install | bash
|
|
32
|
-
export PATH="$HOME/.bun/bin:$PATH"
|
|
33
|
-
fi
|
|
34
|
-
echo " bun $(bun --version) ${GREEN}✔${RESET}"
|
|
35
|
-
|
|
36
|
-
# -------------------------
|
|
37
|
-
# 2. Install pnpm via corepack
|
|
27
|
+
# 1. Install pnpm via corepack
|
|
38
28
|
# -------------------------
|
|
39
29
|
if ! command -v pnpm &>/dev/null; then
|
|
40
30
|
echo " Installing pnpm via corepack..."
|
|
@@ -44,16 +34,16 @@ fi
|
|
|
44
34
|
echo " pnpm $(pnpm --version) ${GREEN}✔${RESET}"
|
|
45
35
|
|
|
46
36
|
# -------------------------
|
|
47
|
-
#
|
|
37
|
+
# 2. Install PM2
|
|
48
38
|
# -------------------------
|
|
49
39
|
if ! command -v pm2 &>/dev/null; then
|
|
50
40
|
echo " Installing pm2..."
|
|
51
|
-
|
|
41
|
+
pnpm add -g pm2
|
|
52
42
|
fi
|
|
53
43
|
echo " pm2 $(pm2 --version) ${GREEN}✔${RESET}"
|
|
54
44
|
|
|
55
45
|
# -------------------------
|
|
56
|
-
#
|
|
46
|
+
# 3. Install NATS server
|
|
57
47
|
# -------------------------
|
|
58
48
|
if ! command -v nats-server &>/dev/null; then
|
|
59
49
|
echo " Installing nats-server..."
|
|
@@ -62,7 +52,7 @@ fi
|
|
|
62
52
|
echo " nats-server $(nats-server --version 2>&1 | grep -oE 'v[0-9.]+' | head -1) ${GREEN}✔${RESET}"
|
|
63
53
|
|
|
64
54
|
# -------------------------
|
|
65
|
-
#
|
|
55
|
+
# 4. Check PostgreSQL
|
|
66
56
|
# -------------------------
|
|
67
57
|
if ! command -v pg_ctl &>/dev/null && ! command -v psql &>/dev/null; then
|
|
68
58
|
echo ""
|
|
@@ -79,13 +69,15 @@ echo " postgresql $(psql --version 2>&1 | grep -oE '[0-9]+\.[0-9]+' | head -
|
|
|
79
69
|
echo ""
|
|
80
70
|
|
|
81
71
|
# -------------------------
|
|
82
|
-
#
|
|
72
|
+
# 5. Install dependencies
|
|
83
73
|
# -------------------------
|
|
84
74
|
echo " ${DIM}Installing project dependencies...${RESET}"
|
|
85
75
|
pnpm install --no-frozen-lockfile 2>&1 | tail -1
|
|
86
76
|
|
|
77
|
+
# tsx should be installed via pnpm install (devDependency)
|
|
78
|
+
|
|
87
79
|
# -------------------------
|
|
88
|
-
#
|
|
80
|
+
# 6. Create database
|
|
89
81
|
# -------------------------
|
|
90
82
|
echo " ${DIM}Setting up database...${RESET}"
|
|
91
83
|
PG_PORT="${PG_PORT:-5433}"
|
|
@@ -98,19 +90,19 @@ PGPASSWORD="$PG_USER" createdb -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" "$DB_NAM
|
|
|
98
90
|
|| echo " ${DIM}Database ${DB_NAME} already exists${RESET}"
|
|
99
91
|
|
|
100
92
|
# -------------------------
|
|
101
|
-
#
|
|
93
|
+
# 7. Run migrations
|
|
102
94
|
# -------------------------
|
|
103
95
|
echo " ${DIM}Running migrations...${RESET}"
|
|
104
|
-
DATABASE_URL="$DB_URL"
|
|
96
|
+
DATABASE_URL="$DB_URL" pnpm tsx packages/os-sdk/src/db/migrate.ts 2>&1 | tail -3
|
|
105
97
|
|
|
106
98
|
# -------------------------
|
|
107
|
-
#
|
|
99
|
+
# 8. Build
|
|
108
100
|
# -------------------------
|
|
109
101
|
echo " ${DIM}Building Next.js...${RESET}"
|
|
110
|
-
|
|
102
|
+
pnpm next build 2>&1 | tail -3
|
|
111
103
|
|
|
112
104
|
# -------------------------
|
|
113
|
-
#
|
|
105
|
+
# 9. Generate .env if missing
|
|
114
106
|
# -------------------------
|
|
115
107
|
if [ ! -f .env ]; then
|
|
116
108
|
echo " ${DIM}Generating .env from .env.example...${RESET}"
|
|
@@ -123,7 +115,7 @@ if [ ! -f .env ]; then
|
|
|
123
115
|
fi
|
|
124
116
|
|
|
125
117
|
# -------------------------
|
|
126
|
-
#
|
|
118
|
+
# 10. Start PM2
|
|
127
119
|
# -------------------------
|
|
128
120
|
echo " ${DIM}Starting PM2 processes...${RESET}"
|
|
129
121
|
pm2 start ecosystem.config.cjs 2>/dev/null \
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
|
+
|
|
3
|
+
import { type ChildProcess, spawn } from 'node:child_process';
|
|
2
4
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
3
|
-
import { relative, resolve } from 'node:path';
|
|
5
|
+
import { dirname, relative, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
4
7
|
import { runAllMigrations } from '@khal-os/sdk/db/migrate';
|
|
5
8
|
import { ensureO11yStreams } from '@khal-os/sdk/service/o11y-streams';
|
|
6
9
|
import { connect } from '@nats-io/transport-node';
|
|
7
10
|
|
|
8
|
-
const ROOT = resolve(import.meta.
|
|
9
|
-
const PACKAGE_ROOT = resolve(import.meta.
|
|
11
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../components/apps');
|
|
12
|
+
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../packages');
|
|
10
13
|
const MAX_RETRIES = 5;
|
|
11
14
|
const RETRY_DELAY_MS = 2000;
|
|
12
15
|
const HEALTH_INTERVAL_MS = 30_000;
|
|
@@ -14,19 +17,19 @@ const HEALTH_INTERVAL_MS = 30_000;
|
|
|
14
17
|
interface ManagedService {
|
|
15
18
|
name: string;
|
|
16
19
|
path: string;
|
|
17
|
-
runtime: '
|
|
18
|
-
process:
|
|
20
|
+
runtime: 'node';
|
|
21
|
+
process: ChildProcess | null;
|
|
19
22
|
retries: number;
|
|
20
23
|
running: boolean;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
function detectRuntime(serviceDir: string): '
|
|
26
|
+
function detectRuntime(serviceDir: string): 'node' {
|
|
24
27
|
const runtimeFile = resolve(serviceDir, 'runtime');
|
|
25
28
|
if (existsSync(runtimeFile)) {
|
|
26
29
|
const content = readFileSync(runtimeFile, 'utf8').trim();
|
|
27
30
|
if (content === 'node') return 'node';
|
|
28
31
|
}
|
|
29
|
-
return '
|
|
32
|
+
return 'node';
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
function discoverPackageServices(knownNames: Set<string>): ManagedService[] {
|
|
@@ -108,23 +111,20 @@ function discoverServices(): ManagedService[] {
|
|
|
108
111
|
|
|
109
112
|
function spawnService(service: ManagedService): void {
|
|
110
113
|
const relPath = relative(process.cwd(), service.path);
|
|
111
|
-
const runtimeLabel =
|
|
114
|
+
const runtimeLabel = 'node/tsx';
|
|
112
115
|
console.log(`[service-loader] starting ${service.name} (${relPath}) [${runtimeLabel}]`);
|
|
113
116
|
|
|
114
|
-
|
|
115
|
-
// services can opt into Node via a `service/runtime` file containing "node"
|
|
116
|
-
const cmd = service.runtime === 'node' ? ['npx', 'tsx', service.path] : ['bun', 'run', service.path];
|
|
117
|
+
const cmd = ['npx', 'tsx', service.path];
|
|
117
118
|
|
|
118
|
-
const proc =
|
|
119
|
-
|
|
120
|
-
stderr: 'inherit',
|
|
119
|
+
const proc = spawn(cmd[0], cmd.slice(1), {
|
|
120
|
+
stdio: ['ignore', 'inherit', 'inherit'],
|
|
121
121
|
env: { ...process.env },
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
service.process = proc;
|
|
125
125
|
service.running = true;
|
|
126
126
|
|
|
127
|
-
proc.
|
|
127
|
+
proc.on('exit', (code) => {
|
|
128
128
|
service.running = false;
|
|
129
129
|
service.process = null;
|
|
130
130
|
|