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 CHANGED
@@ -24,10 +24,8 @@ pm2 flush <name> # clear logs
24
24
 
25
25
  ### Service Loader Runtime
26
26
 
27
- Services run under **Bun** by default. Services that need Node.js (e.g. `node-pty`) place a `service/runtime` file containing `node` the loader spawns them via `npx tsx` instead.
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
- bun biome check --write . # lint + format
73
- bunx tsc --noEmit # typecheck
74
- bun next build # production build
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. `bun biome check .` — lint + format clean
85
- 2. `bunx tsc --noEmit` — no type errors
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" bun next dev --port "$$NEXT_PORT" 2>&1 | sed 's/^/[next] /' & \
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" bun run src/lib/service-loader.ts 2>&1 | sed 's/^/[svc] /' & \
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" bun run src/lib/ws-server.ts 2>&1 | sed 's/^/[ws] /' & \
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)" bun run packages/os-sdk/src/db/migrate.ts; \
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)" bun run packages/os-sdk/src/db/migrate.ts; \
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
- bun install; \
127
+ pnpm install; \
128
128
  echo "[prod] Building Next.js..."; \
129
- bun next build; \
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" bun run packages/os-sdk/src/db/migrate.ts; \
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
- bun next build; \
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
- @bun run packages/os-cli/src/index.ts qa assert health
232
- @bun run packages/os-cli/src/index.ts qa assert auth
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
- bun install; \
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; \
@@ -11,21 +11,21 @@ module.exports = {
11
11
  {
12
12
  name: 'os-dev',
13
13
  script: 'bash',
14
- args: '-c "bun next build && exec bun next start --port 8888"',
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: 'bun',
21
- args: 'run src/lib/service-loader.ts',
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: 'bun',
28
- args: 'run src/lib/ws-server.ts',
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.13",
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": "bun run src/lib/service-loader.ts",
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.13";
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 + bun run packages/npx-cli/src/server.ts
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("bun", ["run", serverTs, ...args], {
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.13';
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 + bun run packages/npx-cli/src/server.ts\n'));
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('bun', ['run', serverTs, ...args], {
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 using the existing migration orchestrator.
66
+ * Run Drizzle migrations inline no subprocess, works inside compiled binary.
66
67
  */
67
- export async function runMigrations(projectRoot: string): Promise<void> {
68
+ export async function runMigrations(_projectRoot: string): Promise<void> {
68
69
  const dbUrl = getDatabaseUrl();
69
- execSync('bun run packages/os-sdk/src/db/migrate.ts', {
70
- cwd: projectRoot,
71
- stdio: 'pipe',
72
- env: { ...process.env, DATABASE_URL: dbUrl } as NodeJS.ProcessEnv,
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 `bun build --compile`.
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 Bun binary.
45
- * When compiled with `bun build --compile`, import.meta.url will not
46
- * end with `.ts` and process.argv[0] points to the binary itself.
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('bun next build', {
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
- execSync('bun install', { cwd: projectRoot, stdio: 'pipe' });
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
 
@@ -34,6 +34,10 @@
34
34
  }
35
35
  },
36
36
  "devDependencies": {
37
+ "@types/pg": "^8.20.0",
37
38
  "drizzle-kit": "^0.31.10"
39
+ },
40
+ "dependencies": {
41
+ "pg": "^8.20.0"
38
42
  }
39
43
  }
@@ -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 url = new URL(getDatabaseUrl());
29
- const adminSql = new SQL({
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(adminSql);
39
- const result = await provisionSchema(adminSql, config);
27
+ await initCatalog(client);
28
+ const result = await provisionSchema(client, config);
40
29
  return { created: result.created };
41
30
  } finally {
42
- await adminSql.close();
31
+ await client.end();
43
32
  }
44
33
  }
@@ -1,6 +1,6 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env node
2
2
  /**
3
3
  * Thin wrapper to run the khal-os CLI from source (no build needed).
4
- * Usage: bun scripts/app-cli.ts app list
4
+ * Usage: tsx scripts/app-cli.ts app list
5
5
  */
6
6
  import '../packages/os-cli/src/index.ts';
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
- # Build khal-os standalone binaries via bun build --compile.
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
- # Map our target names to bun's --target values
13
- declare -A BUN_TARGETS=(
14
- [linux-x64]="bun-linux-x64"
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 bun_target="${BUN_TARGETS[$target]}"
38
+ local pkg_target="${PKG_TARGETS[$target]}"
26
39
  local asset="${ASSET_NAMES[$target]}"
27
40
 
28
- echo "Building ${asset} (${bun_target})..."
29
- bun build --compile \
30
- --target="${bun_target}" \
31
- "${ENTRY}" \
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}"
@@ -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 "bun" "bun" "--version"
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: bun install -g 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 Bun
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
- # 3. Install PM2
37
+ # 2. Install PM2
48
38
  # -------------------------
49
39
  if ! command -v pm2 &>/dev/null; then
50
40
  echo " Installing pm2..."
51
- bun install -g pm2
41
+ pnpm add -g pm2
52
42
  fi
53
43
  echo " pm2 $(pm2 --version) ${GREEN}✔${RESET}"
54
44
 
55
45
  # -------------------------
56
- # 4. Install NATS server
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
- # 5. Check PostgreSQL
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
- # 6. Install dependencies
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
- # 7. Create database
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
- # 8. Run migrations
93
+ # 7. Run migrations
102
94
  # -------------------------
103
95
  echo " ${DIM}Running migrations...${RESET}"
104
- DATABASE_URL="$DB_URL" bun run packages/os-sdk/src/db/migrate.ts 2>&1 | tail -3
96
+ DATABASE_URL="$DB_URL" pnpm tsx packages/os-sdk/src/db/migrate.ts 2>&1 | tail -3
105
97
 
106
98
  # -------------------------
107
- # 9. Build
99
+ # 8. Build
108
100
  # -------------------------
109
101
  echo " ${DIM}Building Next.js...${RESET}"
110
- bun next build 2>&1 | tail -3
102
+ pnpm next build 2>&1 | tail -3
111
103
 
112
104
  # -------------------------
113
- # 10. Generate .env if missing
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
- # 11. Start PM2
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.dir, '../components/apps');
9
- const PACKAGE_ROOT = resolve(import.meta.dir, '../../packages');
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: 'bun' | 'node';
18
- process: ReturnType<typeof Bun.spawn> | null;
20
+ runtime: 'node';
21
+ process: ChildProcess | null;
19
22
  retries: number;
20
23
  running: boolean;
21
24
  }
22
25
 
23
- function detectRuntime(serviceDir: string): 'bun' | 'node' {
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 'bun';
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 = service.runtime === 'node' ? 'node/tsx' : 'bun';
114
+ const runtimeLabel = 'node/tsx';
112
115
  console.log(`[service-loader] starting ${service.name} (${relPath}) [${runtimeLabel}]`);
113
116
 
114
- // node-pty and other native addons may not work under Bun;
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 = Bun.spawn(cmd, {
119
- stdout: 'inherit',
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.exited.then((code) => {
127
+ proc.on('exit', (code) => {
128
128
  service.running = false;
129
129
  service.process = null;
130
130