diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df2ce682..a7a1437a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: sudo apt-get install -y clang-21 lld-21 llvm-21-dev \ cmake make gcc g++ \ autoconf automake libtool \ - libzstd-dev zlib1g-dev libsqlite3-dev libcurl4-openssl-dev + libzstd-dev zlib1g-dev libsqlite3-dev libcurl4-openssl-dev libpq-dev sudo ln -sf /usr/bin/clang-21 /usr/bin/clang sudo ln -sf /usr/bin/llvm-config-21 /usr/bin/llvm-config - run: npm install @@ -166,6 +166,24 @@ jobs: build-linux-glibc: runs-on: ubuntu-22.04 + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: test + POSTGRES_DB: chadtest + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + PG_TESTS_ENABLED: 1 + steps: - uses: actions/checkout@v4 @@ -182,7 +200,7 @@ jobs: sudo apt-get install -y clang-21 lld-21 llvm-21-dev \ cmake make gcc g++ \ autoconf automake libtool \ - libzstd-dev zlib1g-dev libsqlite3-dev libcurl4-openssl-dev + libzstd-dev zlib1g-dev libsqlite3-dev libcurl4-openssl-dev libpq-dev sudo ln -sf /usr/bin/clang-21 /usr/bin/clang sudo ln -sf /usr/bin/llvm-config-21 /usr/bin/llvm-config clang --version 2>&1 | head -2 @@ -207,7 +225,7 @@ jobs: - name: Verify vendor libraries run: | fail=0 - for lib in vendor/bdwgc/libgc.a vendor/yyjson/libyyjson.a vendor/libuv/build/libuv.a vendor/picohttpparser/picohttpparser.o c_bridges/lws-bridge.o c_bridges/multipart-bridge.o c_bridges/regex-bridge.o c_bridges/child-process-bridge.o c_bridges/child-process-spawn.o c_bridges/os-bridge.o c_bridges/strlen-cache.o c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o c_bridges/uri-bridge.o c_bridges/dotenv-bridge.o c_bridges/watch-bridge.o c_bridges/arena-bridge.o c_bridges/curl-bridge.o c_bridges/compress-bridge.o c_bridges/yaml-bridge.o c_bridges/string-ops-bridge.o c_bridges/llvm-bridge.o c_bridges/llvm-builder-bridge.o c_bridges/lld-bridge.o; do + for lib in vendor/bdwgc/libgc.a vendor/yyjson/libyyjson.a vendor/libuv/build/libuv.a vendor/picohttpparser/picohttpparser.o c_bridges/lws-bridge.o c_bridges/multipart-bridge.o c_bridges/regex-bridge.o c_bridges/child-process-bridge.o c_bridges/child-process-spawn.o c_bridges/os-bridge.o c_bridges/strlen-cache.o c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o c_bridges/uri-bridge.o c_bridges/dotenv-bridge.o c_bridges/watch-bridge.o c_bridges/arena-bridge.o c_bridges/curl-bridge.o c_bridges/pg-bridge.o c_bridges/compress-bridge.o c_bridges/yaml-bridge.o c_bridges/string-ops-bridge.o c_bridges/llvm-bridge.o c_bridges/llvm-builder-bridge.o c_bridges/lld-bridge.o; do if [ ! -f "$lib" ]; then echo "MISSING: $lib" fail=1 @@ -276,7 +294,7 @@ jobs: cp c_bridges/os-bridge.o c_bridges/strlen-cache.o release/lib/ cp c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o c_bridges/uri-bridge.o release/lib/ cp c_bridges/dotenv-bridge.o release/lib/ - cp c_bridges/watch-bridge.o c_bridges/arena-bridge.o c_bridges/curl-bridge.o c_bridges/compress-bridge.o c_bridges/yaml-bridge.o c_bridges/string-ops-bridge.o c_bridges/llvm-bridge.o c_bridges/llvm-builder-bridge.o c_bridges/lld-bridge.o release/lib/ + cp c_bridges/watch-bridge.o c_bridges/arena-bridge.o c_bridges/curl-bridge.o c_bridges/pg-bridge.o c_bridges/compress-bridge.o c_bridges/yaml-bridge.o c_bridges/string-ops-bridge.o c_bridges/llvm-bridge.o c_bridges/llvm-builder-bridge.o c_bridges/lld-bridge.o release/lib/ tar -czf chadscript-linux-x64.tar.gz -C release chad lib - name: Upload artifact @@ -294,6 +312,9 @@ jobs: build-macos: runs-on: macos-latest + env: + PG_TESTS_ENABLED: 1 + steps: - uses: actions/checkout@v4 @@ -304,10 +325,27 @@ jobs: - name: Install system dependencies run: | - brew install llvm lld cmake autoconf automake libtool zstd + brew install llvm lld cmake autoconf automake libtool zstd libpq postgresql@16 echo "/opt/homebrew/opt/llvm/bin" >> $GITHUB_PATH + echo "/opt/homebrew/opt/postgresql@16/bin" >> $GITHUB_PATH echo "/usr/local/opt/llvm/bin" >> $GITHUB_PATH + - name: Start and provision postgres + run: | + brew services start postgresql@16 + export PATH="/opt/homebrew/opt/postgresql@16/bin:$PATH" + for i in $(seq 1 30); do + if pg_isready -h localhost -p 5432 2>/dev/null; then + echo "postgres ready" + break + fi + sleep 1 + done + psql postgres -c "CREATE USER postgres SUPERUSER PASSWORD 'test';" || echo "postgres user already exists" + psql postgres -c "ALTER USER postgres WITH PASSWORD 'test';" + psql postgres -c "CREATE DATABASE chadtest OWNER postgres;" || echo "chadtest db already exists" + psql -h localhost -p 5432 -U postgres -d chadtest -c "SELECT 1;" postgresql://postgres:test@localhost:5432/chadtest + - name: Install npm dependencies run: npm install @@ -328,7 +366,7 @@ jobs: - name: Verify vendor libraries run: | fail=0 - for lib in vendor/bdwgc/libgc.a vendor/yyjson/libyyjson.a vendor/libuv/build/libuv.a vendor/picohttpparser/picohttpparser.o c_bridges/lws-bridge.o c_bridges/multipart-bridge.o c_bridges/regex-bridge.o c_bridges/child-process-bridge.o c_bridges/child-process-spawn.o c_bridges/os-bridge.o c_bridges/strlen-cache.o c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o c_bridges/uri-bridge.o c_bridges/dotenv-bridge.o c_bridges/watch-bridge.o c_bridges/arena-bridge.o c_bridges/curl-bridge.o c_bridges/compress-bridge.o c_bridges/yaml-bridge.o c_bridges/string-ops-bridge.o c_bridges/llvm-bridge.o c_bridges/llvm-builder-bridge.o c_bridges/lld-bridge.o; do + for lib in vendor/bdwgc/libgc.a vendor/yyjson/libyyjson.a vendor/libuv/build/libuv.a vendor/picohttpparser/picohttpparser.o c_bridges/lws-bridge.o c_bridges/multipart-bridge.o c_bridges/regex-bridge.o c_bridges/child-process-bridge.o c_bridges/child-process-spawn.o c_bridges/os-bridge.o c_bridges/strlen-cache.o c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o c_bridges/uri-bridge.o c_bridges/dotenv-bridge.o c_bridges/watch-bridge.o c_bridges/arena-bridge.o c_bridges/curl-bridge.o c_bridges/pg-bridge.o c_bridges/compress-bridge.o c_bridges/yaml-bridge.o c_bridges/string-ops-bridge.o c_bridges/llvm-bridge.o c_bridges/llvm-builder-bridge.o c_bridges/lld-bridge.o; do if [ ! -f "$lib" ]; then echo "MISSING: $lib" fail=1 @@ -401,7 +439,7 @@ jobs: cp c_bridges/os-bridge.o c_bridges/strlen-cache.o release/lib/ cp c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o c_bridges/uri-bridge.o release/lib/ cp c_bridges/dotenv-bridge.o release/lib/ - cp c_bridges/watch-bridge.o c_bridges/arena-bridge.o c_bridges/curl-bridge.o c_bridges/compress-bridge.o c_bridges/yaml-bridge.o c_bridges/string-ops-bridge.o c_bridges/llvm-bridge.o c_bridges/llvm-builder-bridge.o c_bridges/lld-bridge.o release/lib/ + cp c_bridges/watch-bridge.o c_bridges/arena-bridge.o c_bridges/curl-bridge.o c_bridges/pg-bridge.o c_bridges/compress-bridge.o c_bridges/yaml-bridge.o c_bridges/string-ops-bridge.o c_bridges/llvm-bridge.o c_bridges/llvm-builder-bridge.o c_bridges/lld-bridge.o release/lib/ tar -czf chadscript-macos-arm64.tar.gz -C release chad lib - name: Upload artifact @@ -436,7 +474,7 @@ jobs: wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-21 main" | sudo tee /etc/apt/sources.list.d/llvm-21.list sudo apt-get update - sudo apt-get install -y clang-21 libsqlite3-dev libzstd-dev zlib1g-dev + sudo apt-get install -y clang-21 libsqlite3-dev libzstd-dev zlib1g-dev libpq-dev sudo ln -sf /usr/bin/clang-21 /usr/bin/clang - name: Install system dependencies (macOS) diff --git a/docs/stdlib/postgres.md b/docs/stdlib/postgres.md index 6813c81f..41a6b988 100644 --- a/docs/stdlib/postgres.md +++ b/docs/stdlib/postgres.md @@ -6,7 +6,9 @@ PostgreSQL client via `libpq`. Connect to a Postgres database, run queries, and import { Pool } from "chadscript/postgres"; ``` -`libpq` is required at build time. On macOS: `brew install libpq`. On Debian/Ubuntu: `apt install libpq-dev`. +`libpq` is required at build time. On Debian/Ubuntu: `apt install libpq-dev`. On macOS: `brew install libpq` then add `pg_config` to your `PATH` (libpq is keg-only) — `export PATH="/opt/homebrew/opt/libpq/bin:$PATH"`. Or `brew install postgresql@16` which puts `pg_config` on `PATH` automatically. + +The build script discovers `libpq` via `pg_config --includedir`, so as long as `pg_config` is on `PATH` the bridge compiles cleanly. If `pg_config` is missing, `pg-bridge.o` is silently skipped and `import { Pool } from "chadscript/postgres"` fails to link. ## `new Pool(conninfo)` diff --git a/scripts/build-target-sdk.sh b/scripts/build-target-sdk.sh index d22ad62b..2930613f 100755 --- a/scripts/build-target-sdk.sh +++ b/scripts/build-target-sdk.sh @@ -69,7 +69,7 @@ fi # Copy C bridge object files echo " Copying bridge objects..." -for bridge in child-process-bridge.o os-bridge.o strlen-cache.o time-bridge.o base64-bridge.o url-bridge.o uri-bridge.o regex-bridge.o dotenv-bridge.o watch-bridge.o lws-bridge.o multipart-bridge.o child-process-spawn.o arena-bridge.o curl-bridge.o compress-bridge.o yaml-bridge.o string-ops-bridge.o llvm-bridge.o llvm-builder-bridge.o lld-bridge.o; do +for bridge in child-process-bridge.o os-bridge.o strlen-cache.o time-bridge.o base64-bridge.o url-bridge.o uri-bridge.o regex-bridge.o dotenv-bridge.o watch-bridge.o lws-bridge.o multipart-bridge.o child-process-spawn.o arena-bridge.o curl-bridge.o pg-bridge.o compress-bridge.o yaml-bridge.o string-ops-bridge.o llvm-bridge.o llvm-builder-bridge.o lld-bridge.o; do if [ -f "$C_BRIDGES_DIR/$bridge" ]; then cp "$C_BRIDGES_DIR/$bridge" "$SDK_DIR/bridges/" fi diff --git a/scripts/build-vendor.sh b/scripts/build-vendor.sh index 208a283e..997578ef 100755 --- a/scripts/build-vendor.sh +++ b/scripts/build-vendor.sh @@ -265,14 +265,17 @@ PG_BRIDGE_OBJ="$C_BRIDGES_DIR/pg-bridge.o" if [ ! -f "$PG_BRIDGE_OBJ" ] || [ "$PG_BRIDGE_SRC" -nt "$PG_BRIDGE_OBJ" ]; then PG_CFLAGS="" PG_FOUND=0 - if [ "$(uname)" = "Darwin" ]; then - BREW_PREFIX=$(brew --prefix 2>/dev/null || echo "/opt/homebrew") - LIBPQ_PREFIX=$(brew --prefix libpq 2>/dev/null || echo "$BREW_PREFIX/opt/libpq") - if [ -f "$LIBPQ_PREFIX/include/libpq-fe.h" ]; then - PG_CFLAGS="-I$LIBPQ_PREFIX/include" + # Prefer pg_config — ships with libpq (libpq-dev on debian/ubuntu, libpq on + # brew). Knows the right -I regardless of platform-specific install layout. + if command -v pg_config >/dev/null 2>&1; then + PG_INCDIR=$(pg_config --includedir 2>/dev/null || echo "") + if [ -n "$PG_INCDIR" ] && [ -f "$PG_INCDIR/libpq-fe.h" ]; then + PG_CFLAGS="-I$PG_INCDIR" PG_FOUND=1 fi fi + # Fallback: default cc include path (covers cases where libpq-fe.h is in + # /usr/include directly or the user has set CPATH/CPLUS_INCLUDE_PATH). if [ "$PG_FOUND" = "0" ]; then if echo '#include ' | cc -xc -fsyntax-only - 2>/dev/null; then PG_FOUND=1 @@ -283,7 +286,7 @@ if [ ! -f "$PG_BRIDGE_OBJ" ] || [ "$PG_BRIDGE_SRC" -nt "$PG_BRIDGE_OBJ" ]; then cc -c -O2 -fPIC $PG_CFLAGS "$PG_BRIDGE_SRC" -o "$PG_BRIDGE_OBJ" echo " -> $PG_BRIDGE_OBJ" else - echo "==> pg-bridge skipped (no libpq headers found)" + echo "==> pg-bridge skipped (no libpq headers found — install libpq-dev / libpq)" fi else echo "==> pg-bridge already built, skipping" diff --git a/src/chad-native.ts b/src/chad-native.ts index 5992919d..59ea9941 100644 --- a/src/chad-native.ts +++ b/src/chad-native.ts @@ -22,6 +22,7 @@ registerStdlib("colors.ts", ChadScript.embedFile("../lib/colors.ts")); registerStdlib("events.ts", ChadScript.embedFile("../lib/events.ts")); registerStdlib("glob.ts", ChadScript.embedFile("../lib/glob.ts")); registerStdlib("compress.ts", ChadScript.embedFile("../lib/compress.ts")); +registerStdlib("postgres.ts", ChadScript.embedFile("../lib/postgres.ts")); const skillContent = ChadScript.embedFile("../lib/skill.md"); import { ArgumentParser } from "chadscript/argparse"; diff --git a/tests/fixtures/stdlib/postgres-connect.ts b/tests/fixtures/stdlib/postgres-connect.ts index 397de4a0..705e1d00 100644 --- a/tests/fixtures/stdlib/postgres-connect.ts +++ b/tests/fixtures/stdlib/postgres-connect.ts @@ -1,4 +1,4 @@ -// @test-skip +// @test-requires-env: PG_TESTS_ENABLED import { Client } from "chadscript/postgres"; const c = new Client("host=127.0.0.1 port=5432 user=postgres password=test dbname=chadtest"); diff --git a/tests/fixtures/stdlib/postgres-params-types.ts b/tests/fixtures/stdlib/postgres-params-types.ts index 837ed8a1..9bfa71aa 100644 --- a/tests/fixtures/stdlib/postgres-params-types.ts +++ b/tests/fixtures/stdlib/postgres-params-types.ts @@ -1,4 +1,4 @@ -// @test-skip +// @test-requires-env: PG_TESTS_ENABLED import { Pool } from "chadscript/postgres"; const pool = new Pool("host=127.0.0.1 port=5432 user=postgres password=test dbname=chadtest"); diff --git a/tests/fixtures/stdlib/postgres-pool.ts b/tests/fixtures/stdlib/postgres-pool.ts index 09b60162..68ae39c5 100644 --- a/tests/fixtures/stdlib/postgres-pool.ts +++ b/tests/fixtures/stdlib/postgres-pool.ts @@ -1,4 +1,4 @@ -// @test-skip +// @test-requires-env: PG_TESTS_ENABLED import { Pool } from "chadscript/postgres"; const pool = new Pool("host=127.0.0.1 port=5432 user=postgres password=test dbname=chadtest"); diff --git a/tests/fixtures/stdlib/postgres-query-rowcount.ts b/tests/fixtures/stdlib/postgres-query-rowcount.ts index 4a23ea9f..a943ef79 100644 --- a/tests/fixtures/stdlib/postgres-query-rowcount.ts +++ b/tests/fixtures/stdlib/postgres-query-rowcount.ts @@ -1,4 +1,4 @@ -// @test-skip +// @test-requires-env: PG_TESTS_ENABLED import { Client } from "chadscript/postgres"; const c = new Client("host=127.0.0.1 port=5432 user=postgres password=test dbname=chadtest"); diff --git a/tests/fixtures/stdlib/postgres-query-select.ts b/tests/fixtures/stdlib/postgres-query-select.ts index 92fe8301..803b9c7d 100644 --- a/tests/fixtures/stdlib/postgres-query-select.ts +++ b/tests/fixtures/stdlib/postgres-query-select.ts @@ -1,4 +1,4 @@ -// @test-skip +// @test-requires-env: PG_TESTS_ENABLED import { Client } from "chadscript/postgres"; const c = new Client("host=127.0.0.1 port=5432 user=postgres password=test dbname=chadtest"); diff --git a/tests/test-discovery.ts b/tests/test-discovery.ts index 6990cc0e..750538ed 100644 --- a/tests/test-discovery.ts +++ b/tests/test-discovery.ts @@ -8,6 +8,7 @@ // // @test-description: ... — custom test description // // @test-native-only — skip when running with node compiler // // @test-skip — exclude from auto-discovery +// // @test-requires-env: VAR — skip unless process.env.VAR is set and non-empty // // Defaults (no annotation needed): // expectTestPassed: true — asserts stdout contains TEST_PASSED and exit code 0 @@ -34,6 +35,7 @@ interface ParsedAnnotations { description?: string; skip: boolean; nativeOnly: boolean; + requiresEnv?: string; } function parseAnnotations(filePath: string): ParsedAnnotations { @@ -72,6 +74,11 @@ function parseAnnotations(filePath: string): ParsedAnnotations { if (descMatch) { result.description = descMatch[1].trim(); } + + const requiresEnvMatch = trimmed.match(/^\/\/\s*@test-requires-env:\s*(\S+)/); + if (requiresEnvMatch) { + result.requiresEnv = requiresEnvMatch[1].trim(); + } } return result; @@ -111,6 +118,10 @@ export function discoverTests(fixturesDir: string = "tests/fixtures"): TestCase[ const annotations = parseAnnotations(absPath); if (annotations.skip) continue; + if (annotations.requiresEnv) { + const val = process.env[annotations.requiresEnv]; + if (!val || val.length === 0) continue; + } // Name from relative path without extension: "arrays/array-filter" const relToFixtures = path.relative(fixturesDir, relPath);