Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -677,8 +677,8 @@ jobs:
# probe (crash-recovery, ~125s) plus setup.
#
# Each leg rebuilds the bundle rather than sharing one via upload-artifact:
# dist/ is ~403MB (libchdb.so 334MB + maple 69MB), and build-local-binary.sh
# takes ~14s including the libchdb download. Uploading once and downloading
# dist/ carries the native chdb npm sidecar plus maple, and build-local-binary.sh
# is already quick after bun install. Uploading once and downloading
# seven times is strictly slower.
name: Local archive native (${{ matrix.probe }})
needs: changes
Expand Down
25 changes: 13 additions & 12 deletions .github/workflows/local-binary-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ name: Local Binary Release

# Builds the distributable `maple` local binary (a single Bun-compiled
# executable: CLI + OTLP-ingest/query server + bundled SPA) for macOS and Linux,
# then attaches a 2-file bundle (`maple` + `libchdb.so`) per platform to a
# then attaches a bundle (`maple` + npm `chdb` runtime sidecar) per platform to a
# GitHub release.
#
# Why native runners (no cross-compilation): the bundle ships a prebuilt,
# platform-specific `libchdb.so` (downloaded from chdb-io/chdb-core releases),
# which `maple` dlopens via bun:ffi at runtime. We fetch the engine matching the
# host, so each target is built on a matching native runner.
# Why native runners (no cross-compilation): the bundle ships the platform-specific
# `@chdb/lib-*` npm package installed for the host, so each target is built on a
# matching native runner.
#
# Each build job publishes its own tarball immediately after packaging — no
# separate aggregation step — so a slow or queued runner can never block the
Expand Down Expand Up @@ -91,8 +90,8 @@ jobs:

- name: Build local binary bundle
# Builds the SPA, inlines it into the binary, compiles apps/cli with
# `bun build --compile`, and downloads the matching libchdb beside it.
# Output bundle lands in ./dist (maple + libchdb.so). MAPLE_BUILD_VERSION
# `bun build --compile`, and copies the matching npm chdb runtime beside it.
# Output bundle lands in ./dist (maple + node_modules). MAPLE_BUILD_VERSION
# bakes the resolved tag into `maple --version`.
env:
MAPLE_BUILD_VERSION: ${{ steps.version.outputs.version }}
Expand All @@ -109,13 +108,15 @@ jobs:

- name: Ad-hoc codesign (macOS)
# bun-compiled binaries run locally once Gatekeeper quarantine is
# cleared; ad-hoc sign both files for good measure. Production
# cleared; ad-hoc sign the executable and native sidecar for good measure. Production
# distribution should layer Developer ID signing + notarization on
# top (requires APPLE_* secrets not configured here).
if: ${{ matrix.os == 'macos' }}
run: |
set -euo pipefail
codesign --force --sign - dist/libchdb.so
while IFS= read -r -d '' f; do
codesign --force --sign - "$f"
done < <(find dist/node_modules/@chdb -type f \( -name '*.node' -o -name 'libchdb.so' -o -name '*.dylib' \) -print0)
codesign --force --sign - dist/maple
codesign --verify --verbose dist/maple

Expand All @@ -130,8 +131,8 @@ jobs:
rm -rf "$staging"
mkdir -p "$staging"
cp dist/maple "$staging/maple"
cp dist/libchdb.so "$staging/libchdb.so"
# Smoke-check the relocatable 2-file bundle (maple + libchdb.so).
cp -R dist/node_modules "$staging/node_modules"
# Smoke-check the relocatable bundle (maple + node_modules/chdb).
( cd "$staging" && ./maple --help >/dev/null 2>&1 ) || \
echo "::warning::'maple --help' smoke check did not exit cleanly on $target"
tarball="$RUNNER_TEMP/${name}.tar.gz"
Expand Down Expand Up @@ -181,7 +182,7 @@ jobs:
'Local Maple (`maple start`) bundles.' \
'**Install (macOS / Linux):**' \
'curl -fsSL https://maple.dev/cli/install | sh' \
'Or download a bundle below. Each archive contains `maple` + `libchdb.so` — keep both in the same directory. macOS: run `xattr -dr com.apple.quarantine maple libchdb.so` after extracting.' \
'Or download a bundle below. Each archive contains `maple` + `node_modules/chdb` — keep the sidecar next to the binary. macOS: run `xattr -dr com.apple.quarantine maple node_modules` after extracting.' \
> "$RUNNER_TEMP/release-notes.md"
gh release create "$TAG" --repo "$REPO" \
--title "$TAG" \
Expand Down
1 change: 1 addition & 0 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@maple-dev/effect-sdk": "workspace:*",
"@maple/domain": "workspace:*",
"@maple/query-engine": "workspace:*",
"chdb": "3.3.0",
"effect": "catalog:effect",
"protobufjs": "^8.6.1"
},
Expand Down
29 changes: 14 additions & 15 deletions apps/cli/src/core/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
// (driven by `maple update`, see commands/update.ts).
//
// We mirror scripts/install.sh's conventions (target triples, release URLs,
// 2-file bundle, checksum, macOS quarantine clear) rather than shelling out to
// runtime sidecar, checksum, macOS quarantine clear) rather than shelling out to
// it: the installer uses `cp`, which fails with ETXTBSY when overwriting the
// *running* executable on Linux. Self-update instead downloads into a temp dir
// on the same filesystem as the install dir and `rename()`s over the targets —
// rename swaps the directory entry, so the running process keeps its old inode
// while new invocations pick up the new binary. Keep the triple/URL logic here
// while new invocations pick up the new binary and sidecar. Keep the triple/URL logic here
// in sync with install.sh.
import { Clock, Duration, Effect, Option, Schema, Stream } from "effect"
import { FileSystem } from "effect/FileSystem"
Expand Down Expand Up @@ -152,7 +152,7 @@ export const fetchLatestTag = (timeoutMs = 5000): Effect.Effect<string, UpdateEr
}),
)

/** Directory holding the running `maple` binary and its sibling `libchdb.so`
/** Directory holding the running `maple` binary and its npm runtime sidecar
* (the symlink on PATH resolves here). */
const resolveInstallDir = (): string => dirname(realpathSync(process.execPath))

Expand Down Expand Up @@ -298,10 +298,10 @@ const clearQuarantine = (paths: ReadonlyArray<string>): Effect.Effect<void, neve
)

/**
* Swap both bundle files into place. Each rename is atomic but the PAIR is
* not; the previous files are parked inside `tmpDir` first so a failure after
* the first swap restores the matched old pair instead of leaving a new
* executable beside an old native library. A hard crash mid-swap can still
* Swap the executable and runtime sidecar into place. Each rename is atomic but
* the PAIR is not; the previous entries are parked inside `tmpDir` first so a
* failure after the first swap restores the matched old pair instead of leaving
* a new executable beside an old npm runtime. A hard crash mid-swap can still
* mismatch — rerunning `maple update` replaces both.
*/
const swapBundlePair = (
Expand All @@ -313,14 +313,14 @@ const swapBundlePair = (
const fs = yield* FileSystem
const previousDir = join(tmpDir, "previous")
const mapleDst = join(installDir, "maple")
const libDst = join(installDir, "libchdb.so")
const modulesDst = join(installDir, "node_modules")
const restorePrevious = Effect.gen(function* () {
for (const [parked, dst] of [
[join(previousDir, "maple"), mapleDst],
[join(previousDir, "libchdb.so"), libDst],
[join(previousDir, "node_modules"), modulesDst],
] as const) {
if (yield* fs.exists(parked)) {
yield* fs.remove(dst, { force: true }).pipe(Effect.ignore)
yield* fs.remove(dst, { recursive: true, force: true }).pipe(Effect.ignore)
yield* fs.rename(parked, dst)
}
}
Expand All @@ -329,8 +329,8 @@ const swapBundlePair = (
yield* fs.makeDirectory(previousDir, { recursive: true })
if (yield* fs.exists(mapleDst)) yield* fs.rename(mapleDst, join(previousDir, "maple"))
yield* fs.rename(join(srcDir, "maple"), mapleDst)
if (yield* fs.exists(libDst)) yield* fs.rename(libDst, join(previousDir, "libchdb.so"))
yield* fs.rename(join(srcDir, "libchdb.so"), libDst)
if (yield* fs.exists(modulesDst)) yield* fs.rename(modulesDst, join(previousDir, "node_modules"))
yield* fs.rename(join(srcDir, "node_modules"), modulesDst)
yield* fs.chmod(mapleDst, 0o755)
}).pipe(Effect.tapError(() => restorePrevious))
})
Expand Down Expand Up @@ -409,9 +409,8 @@ export const performUpdate = (
Effect.mapError((e) => mapFsError(e, installDir)),
)

if (process.platform === "darwin") {
yield* clearQuarantine([join(installDir, "maple"), join(installDir, "libchdb.so")])
}
if (process.platform === "darwin")
yield* clearQuarantine([join(installDir, "maple"), join(installDir, "node_modules")])
}),
)

Expand Down
Loading