From 38369a9b55aa1a858d845a6f0e97c4129933eed6 Mon Sep 17 00:00:00 2001 From: Muthiah Muralidharan Date: Mon, 12 Oct 2026 11:27:46 +0530 Subject: [PATCH 1/3] fix(install): verify binary before replacing installation --- install.sh | 23 +++++++++++++------ src/tests/integration/install-sh.test.ts | 28 ++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/install.sh b/install.sh index b2fb96e..c2080bb 100755 --- a/install.sh +++ b/install.sh @@ -254,8 +254,20 @@ main() { verify_checksum "$TMP" "$ASSET" "$SUMS_URL" fi - chmod 0755 "$TMP" - mv "$TMP" "$BIN_DIR/$BIN_NAME" + # Probe the candidate from the destination filesystem before replacing an + # existing installation. A downloaded binary can have a valid checksum yet + # fail to start on this host (for example, a libc mismatch). + local STAGED NEW_VERSION + STAGED=$(mktemp "$BIN_DIR/.chargebee.XXXXXX") + trap 'rm -f "$TMP" "${STAGED:-}"' EXIT + cp "$TMP" "$STAGED" + chmod 0755 "$STAGED" + if ! NEW_VERSION=$("$STAGED" --version 2>/dev/null) || [[ -z "${NEW_VERSION//[[:space:]]/}" ]]; then + echo "Error: downloaded $ASSET cannot start on this system" >&2 + exit 1 + fi + NEW_VERSION=${NEW_VERSION%%$'\n'*} + mv -f "$STAGED" "$BIN_DIR/$BIN_NAME" local TARGET_HOME TARGET_HOME=$(target_home) @@ -273,13 +285,10 @@ main() { (mkdir -p -m 0700 "$MARKER_DIR" && printf 'github\n' > "$MARKER_DIR/install-method") || true own_as_target_user "$MARKER_ROOT" - local NEW_VERSION - NEW_VERSION=$("$BIN_DIR/$BIN_NAME" --version 2>/dev/null | head -1 || true) - if [[ -n "$PREV_VERSION" ]]; then - echo "Updated: ${PREV_VERSION} → ${NEW_VERSION:-done}" + echo "Updated: ${PREV_VERSION} → ${NEW_VERSION}" else - echo "Installed: ${NEW_VERSION:-chargebee} → $BIN_DIR/$BIN_NAME" + echo "Installed: ${NEW_VERSION} → $BIN_DIR/$BIN_NAME" fi # Warn if BIN_DIR isn't in PATH diff --git a/src/tests/integration/install-sh.test.ts b/src/tests/integration/install-sh.test.ts index 2ecb40d..dcc4b75 100644 --- a/src/tests/integration/install-sh.test.ts +++ b/src/tests/integration/install-sh.test.ts @@ -198,6 +198,34 @@ main "$@" }); describe.skipIf(process.platform === "win32")("install.sh snapshot mode", () => { + it("rejects a candidate that cannot start and preserves the installed binary", () => { + const root = mkdtempSync(join(tmpdir(), "cb-install-sh-invalid-")); + try { + const binDir = join(root, "bin"); + const configDir = join(root, "config"); + mkdirSync(binDir, { recursive: true }); + const installed = join(binDir, "chargebee"); + writeFileSync(installed, "#!/bin/sh\necho 1.0.0\n", { mode: 0o755 }); + const invalid = join(root, "invalid-binary"); + writeFileSync(invalid, "not an executable binary\n"); + + const result = runInstall({ + CHARGEBEE_CLI_INSTALL_FILE: invalid, + CHARGEBEE_CLI_BIN_DIR: binDir, + CHARGEBEE_CONFIG_DIR: configDir, + HOME: root, + PATH: `${binDir}:${SYSTEM_PATH}`, + }); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("cannot start on this system"); + expect(result.stdout).not.toContain("Updated:"); + expect(readFileSync(installed, "utf8")).toContain("echo 1.0.0"); + expect(existsSync(join(configDir, "install-method"))).toBe(false); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + it("copies CHARGEBEE_CLI_INSTALL_FILE into CHARGEBEE_CLI_BIN_DIR and writes github marker", () => { const root = mkdtempSync(join(tmpdir(), "cb-install-sh-")); try { From 0d125b379009b5b8640e84091768dfe1fc643691 Mon Sep 17 00:00:00 2001 From: Muthiah Muralidharan Date: Mon, 12 Oct 2026 12:03:20 +0530 Subject: [PATCH 2/3] fix(install): ship and select Linux musl binaries --- .github/workflows/ci.yml | 6 ++ .github/workflows/install-smoke.yml | 30 +++++++++- .github/workflows/release-please.yml | 6 ++ README.md | 2 + install.sh | 26 ++++++++- src/lib/update/index.ts | 8 ++- src/tests/integration/install-sh.test.ts | 71 ++++++++++++++++++++++++ src/tests/unit/lib/update/index.test.ts | 22 +++++++- src/tools/build.ts | 7 ++- 9 files changed, 171 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e8c3db..3f13e42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,6 +105,8 @@ jobs: - bun-darwin-x64 - bun-linux-arm64 - bun-linux-x64 + - bun-linux-arm64-musl + - bun-linux-x64-musl - bun-windows-x64 include: - target: bun-darwin-arm64 @@ -115,6 +117,10 @@ jobs: outfile: chargebee-cli-linux-arm64 - target: bun-linux-x64 outfile: chargebee-cli-linux-x64 + - target: bun-linux-arm64-musl + outfile: chargebee-cli-linux-arm64-musl + - target: bun-linux-x64-musl + outfile: chargebee-cli-linux-x64-musl - target: bun-windows-x64 outfile: chargebee-cli-windows-x64.exe diff --git a/.github/workflows/install-smoke.yml b/.github/workflows/install-smoke.yml index 8dee5e9..a415e46 100644 --- a/.github/workflows/install-smoke.yml +++ b/.github/workflows/install-smoke.yml @@ -24,6 +24,32 @@ concurrency: cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: + alpine-install: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: "1.4.2" + + - run: bun install --frozen-lockfile + + - name: Build and install the musl binary on Alpine + run: | + bun run build --target=bun-linux-x64-musl + docker run --rm -v "$PWD:/work" -w /work alpine:3.20 sh -c ' + set -eu + apk add --no-cache bash libstdc++ libgcc + export CI=1 CHARGEBEE_CLI_NO_ONBOARDING=1 + export CHARGEBEE_CLI_INSTALL_FILE=/work/dist/chargebee-cli-linux-x64-musl + export CHARGEBEE_CLI_BIN_DIR=/tmp/cb-bin CHARGEBEE_CONFIG_DIR=/tmp/cb-config + bash install.sh + export PATH=/tmp/cb-bin:$PATH + bash scripts/install-smoke-assert.sh + test "$(cat /tmp/cb-config/install-method)" = github + ' + github-install: strategy: fail-fast: false @@ -148,12 +174,13 @@ jobs: required-checks: name: Install required checks if: ${{ always() }} - needs: [github-install, npm-install, windows-install] + needs: [alpine-install, github-install, npm-install, windows-install] runs-on: ubuntu-latest steps: - name: Verify required job results env: GITHUB_INSTALL_RESULT: ${{ needs.github-install.result }} + ALPINE_INSTALL_RESULT: ${{ needs.alpine-install.result }} NPM_INSTALL_RESULT: ${{ needs.npm-install.result }} WINDOWS_INSTALL_RESULT: ${{ needs.windows-install.result }} run: | @@ -165,5 +192,6 @@ jobs: fi } require_result github-install "$GITHUB_INSTALL_RESULT" success + require_result alpine-install "$ALPINE_INSTALL_RESULT" success require_result npm-install "$NPM_INSTALL_RESULT" success require_result windows-install "$WINDOWS_INSTALL_RESULT" success diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 2f08549..58f8938 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -75,6 +75,8 @@ jobs: - bun-darwin-x64 - bun-linux-arm64 - bun-linux-x64 + - bun-linux-arm64-musl + - bun-linux-x64-musl - bun-windows-x64 include: - target: bun-darwin-arm64 @@ -85,6 +87,10 @@ jobs: outfile: chargebee-cli-linux-arm64 - target: bun-linux-x64 outfile: chargebee-cli-linux-x64 + - target: bun-linux-arm64-musl + outfile: chargebee-cli-linux-arm64-musl + - target: bun-linux-x64-musl + outfile: chargebee-cli-linux-x64-musl - target: bun-windows-x64 outfile: chargebee-cli-windows-x64.exe steps: diff --git a/README.md b/README.md index 9751817..ca51c8e 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ The Chargebee CLI is available for macOS, Linux, and Windows. curl -fsSL https://raw.githubusercontent.com/chargebee/cli/main/install.sh | bash ``` +On Alpine Linux, install the runtime libraries first: `apk add --no-cache libstdc++ libgcc`. The installer then selects the musl build automatically. Other Linux distributions use the standard Linux build. + The installer can also add the Chargebee skill for detected coding agents. To choose agents manually, decline the prompt and run: ```bash diff --git a/install.sh b/install.sh index c2080bb..5e799e8 100755 --- a/install.sh +++ b/install.sh @@ -186,7 +186,31 @@ main() { mkdir -p "$BIN_DIR" fi - local ASSET="chargebee-cli-${OS}-${ARCH}" + local LIBC_SUFFIX="" + if [[ "$OS" == "linux" ]]; then + local LIBC="${CHARGEBEE_CLI_LIBC:-}" + if [[ -z "$LIBC" ]]; then + if ls /lib/ld-musl-*.so.1 >/dev/null 2>&1 || (ldd --version 2>&1 | grep -qi musl); then + LIBC="musl" + else + LIBC="glibc" + fi + fi + case "$LIBC" in + musl) + LIBC_SUFFIX="-musl" + if [[ -z "$(find /lib /usr/lib -name 'libstdc++.so.6' -print -quit 2>/dev/null)" ]] || + [[ -z "$(find /lib /usr/lib -name 'libgcc_s.so.1' -print -quit 2>/dev/null)" ]]; then + echo "The Chargebee CLI needs libstdc++ and libgcc on Alpine:" >&2 + echo " apk add --no-cache libstdc++ libgcc" >&2 + exit 1 + fi + ;; + glibc) ;; + *) echo "Error: CHARGEBEE_CLI_LIBC must be musl or glibc" >&2; exit 1 ;; + esac + fi + local ASSET="chargebee-cli-${OS}-${ARCH}${LIBC_SUFFIX}" # Show current version if already installed local PREV_VERSION="" diff --git a/src/lib/update/index.ts b/src/lib/update/index.ts index c4d15ae..dfe6e76 100644 --- a/src/lib/update/index.ts +++ b/src/lib/update/index.ts @@ -340,9 +340,13 @@ function reportUpdated(currentVersion: string, probe: () => ReturnType { diff --git a/src/tests/integration/install-sh.test.ts b/src/tests/integration/install-sh.test.ts index dcc4b75..b08878a 100644 --- a/src/tests/integration/install-sh.test.ts +++ b/src/tests/integration/install-sh.test.ts @@ -518,6 +518,77 @@ printf '200' }); }); +describe.skipIf(process.platform === "win32")("install.sh Linux libc selection", () => { + function fakeLinux(root: string, withLibraries: boolean): { bin: string; log: string } { + const bin = join(root, "fake-bin"); + const log = join(root, "curl.log"); + mkdirSync(bin); + writeFakeCurl(bin, log, '{"tag_name":"v9.9.9"}'); + writeFileSync(join(bin, "uname"), "#!/bin/sh\nif [ \"$1\" = -s ]; then echo Linux; else echo x86_64; fi\n", { mode: 0o755 }); + writeFileSync(join(bin, "ldd"), "#!/bin/sh\necho musl libc\n", { mode: 0o755 }); + writeFileSync(join(bin, "find"), withLibraries + ? "#!/bin/sh\necho /usr/lib/$4\n" + : "#!/bin/sh\nexit 0\n", { mode: 0o755 }); + return { bin, log }; + } + + it("downloads the musl asset when Linux uses musl", () => { + const root = mkdtempSync(join(tmpdir(), "cb-install-musl-")); + try { + const { bin, log } = fakeLinux(root, true); + const result = runInstall({ + PATH: `${bin}:${SYSTEM_PATH}`, + HOME: root, + CHARGEBEE_CLI_BIN_DIR: join(root, "bin"), + CHARGEBEE_CONFIG_DIR: join(root, "config"), + CHARGEBEE_CLI_SKIP_CHECKSUM: "1", + }); + expect(result.exitCode).toBe(0); + expect(readFileSync(log, "utf8")).toContain("chargebee-cli-linux-x64-musl"); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it("honors the glibc override", () => { + const root = mkdtempSync(join(tmpdir(), "cb-install-glibc-")); + try { + const { bin, log } = fakeLinux(root, false); + const result = runInstall({ + PATH: `${bin}:${SYSTEM_PATH}`, + HOME: root, + CHARGEBEE_CLI_BIN_DIR: join(root, "bin"), + CHARGEBEE_CONFIG_DIR: join(root, "config"), + CHARGEBEE_CLI_SKIP_CHECKSUM: "1", + CHARGEBEE_CLI_LIBC: "glibc", + }); + expect(result.exitCode).toBe(0); + expect(readFileSync(log, "utf8")).toContain("chargebee-cli-linux-x64"); + expect(readFileSync(log, "utf8")).not.toContain("chargebee-cli-linux-x64-musl"); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it("explains missing Alpine runtime libraries before downloading", () => { + const root = mkdtempSync(join(tmpdir(), "cb-install-musl-missing-")); + try { + const { bin, log } = fakeLinux(root, false); + const result = runInstall({ + PATH: `${bin}:${SYSTEM_PATH}`, + HOME: root, + CHARGEBEE_CLI_BIN_DIR: join(root, "bin"), + CHARGEBEE_CONFIG_DIR: join(root, "config"), + }); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("apk add --no-cache libstdc++ libgcc"); + expect(existsSync(log)).toBe(false); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); +}); + describe.skipIf(process.platform === "win32")("install.sh checksum verification", () => { function setup(): { root: string; fakeBin: string; binDir: string; configDir: string; log: string } { const root = mkdtempSync(join(tmpdir(), "cb-install-sh-checksum-")); diff --git a/src/tests/unit/lib/update/index.test.ts b/src/tests/unit/lib/update/index.test.ts index c989961..115a526 100644 --- a/src/tests/unit/lib/update/index.test.ts +++ b/src/tests/unit/lib/update/index.test.ts @@ -107,8 +107,10 @@ describe("githubAssetName", () => { it("matches the release asset naming used by install.sh / install.ps1", () => { expect(githubAssetName("darwin", "arm64")).toBe("chargebee-cli-darwin-arm64"); expect(githubAssetName("darwin", "x64")).toBe("chargebee-cli-darwin-x64"); - expect(githubAssetName("linux", "x64")).toBe("chargebee-cli-linux-x64"); - expect(githubAssetName("linux", "arm64")).toBe("chargebee-cli-linux-arm64"); + expect(githubAssetName("linux", "x64", "glibc")).toBe("chargebee-cli-linux-x64"); + expect(githubAssetName("linux", "arm64", "glibc")).toBe("chargebee-cli-linux-arm64"); + expect(githubAssetName("linux", "x64", "musl")).toBe("chargebee-cli-linux-x64-musl"); + expect(githubAssetName("linux", "arm64", "musl")).toBe("chargebee-cli-linux-arm64-musl"); expect(githubAssetName("win32", "x64")).toBe("chargebee-cli-windows-x64.exe"); }); }); @@ -469,6 +471,8 @@ describe("update action (mocked spawn + fetch)", () => { "chargebee-cli-darwin-x64", "chargebee-cli-linux-arm64", "chargebee-cli-linux-x64", + "chargebee-cli-linux-arm64-musl", + "chargebee-cli-linux-x64-musl", "chargebee-cli-windows-x64.exe", ] .map((name) => `${ASSET_HASH} ${name}`) @@ -805,6 +809,20 @@ describe("update action (mocked spawn + fetch)", () => { expect(readInstallMethod()).toBe("github"); }); + it("keeps a musl installation on the musl release asset", async () => { + const previousLibc = process.env.CHARGEBEE_CLI_LIBC; + process.env.CHARGEBEE_CLI_LIBC = "musl"; + try { + await runUpdate(); + const assetReq = fetched.find((f) => f.url.includes("/releases/download/")); + expect(assetReq?.url).toContain(`chargebee-cli-linux-${process.arch}-musl`); + expect(readFileSync(execPath, "utf8")).toBe(ASSET_BYTES); + } finally { + if (previousLibc === undefined) delete process.env.CHARGEBEE_CLI_LIBC; + else process.env.CHARGEBEE_CLI_LIBC = previousLibc; + } + }); + it("prefers the release's API asset URL (works for private repos) with an octet-stream Accept", async () => { stubFetch([ { diff --git a/src/tools/build.ts b/src/tools/build.ts index f346ef9..6e85369 100644 --- a/src/tools/build.ts +++ b/src/tools/build.ts @@ -3,7 +3,7 @@ * Compiles the standalone binary. * * Default: one binary for the host platform at `./chargebee-cli`. `--all` - * cross-compiles the five release artifacts into `dist/`; `--target=` builds + * cross-compiles the seven release artifacts into `dist/`; `--target=` builds * just one of them (CI fans these out across runners). * * The version is baked in via `process.env.VERSION` — without it the binary @@ -20,6 +20,8 @@ const TARGETS = [ { target: "bun-darwin-x64", outfile: `dist/${BINARY}-darwin-x64` }, { target: "bun-linux-arm64", outfile: `dist/${BINARY}-linux-arm64` }, { target: "bun-linux-x64", outfile: `dist/${BINARY}-linux-x64` }, + { target: "bun-linux-arm64-musl", outfile: `dist/${BINARY}-linux-arm64-musl` }, + { target: "bun-linux-x64-musl", outfile: `dist/${BINARY}-linux-x64-musl` }, { target: "bun-windows-x64", outfile: `dist/${BINARY}-windows-x64.exe` }, ] as const; @@ -36,6 +38,9 @@ async function compile(outfile: string, target?: string): Promise { outfile, "--define", `process.env.VERSION='${version}'`, + ...(target?.startsWith("bun-linux-") + ? ["--define", `process.env.CHARGEBEE_CLI_LIBC='${target.endsWith("-musl") ? "musl" : "glibc"}'`] + : []), ...(target ? [`--target=${target}`] : []), ]; const proc = Bun.spawn(["bun", ...args], { stdout: "inherit", stderr: "inherit" }); From 8ea80933b4c4d8ea6064f5a30faff3f4306d0f70 Mon Sep 17 00:00:00 2001 From: Muthiah Muralidharan Date: Mon, 12 Oct 2026 16:34:04 +0530 Subject: [PATCH 3/3] fix(install): prefer active libc when selecting Linux asset --- README.md | 2 +- install.sh | 12 ++++-- src/tests/integration/install-sh.test.ts | 49 +++++++++++++++++++++++- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ca51c8e..5011c29 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The Chargebee CLI is available for macOS, Linux, and Windows. curl -fsSL https://raw.githubusercontent.com/chargebee/cli/main/install.sh | bash ``` -On Alpine Linux, install the runtime libraries first: `apk add --no-cache libstdc++ libgcc`. The installer then selects the musl build automatically. Other Linux distributions use the standard Linux build. +On musl-based Linux distributions, install the `libstdc++` and `libgcc` runtime libraries with your package manager first. On Alpine, run `apk add --no-cache libstdc++ libgcc`. The installer selects the musl build automatically; glibc-based distributions use the standard Linux build. The installer can also add the Chargebee skill for detected coding agents. To choose agents manually, decline the prompt and run: diff --git a/install.sh b/install.sh index 5e799e8..df8729a 100755 --- a/install.sh +++ b/install.sh @@ -190,7 +190,13 @@ main() { if [[ "$OS" == "linux" ]]; then local LIBC="${CHARGEBEE_CLI_LIBC:-}" if [[ -z "$LIBC" ]]; then - if ls /lib/ld-musl-*.so.1 >/dev/null 2>&1 || (ldd --version 2>&1 | grep -qi musl); then + local LDD_VERSION + LDD_VERSION=$(ldd --version 2>&1 || true) + if echo "$LDD_VERSION" | grep -qi musl; then + LIBC="musl" + elif echo "$LDD_VERSION" | grep -Eqi 'glibc|GNU C Library|GNU libc'; then + LIBC="glibc" + elif ls /lib/ld-musl-*.so.1 >/dev/null 2>&1; then LIBC="musl" else LIBC="glibc" @@ -201,8 +207,8 @@ main() { LIBC_SUFFIX="-musl" if [[ -z "$(find /lib /usr/lib -name 'libstdc++.so.6' -print -quit 2>/dev/null)" ]] || [[ -z "$(find /lib /usr/lib -name 'libgcc_s.so.1' -print -quit 2>/dev/null)" ]]; then - echo "The Chargebee CLI needs libstdc++ and libgcc on Alpine:" >&2 - echo " apk add --no-cache libstdc++ libgcc" >&2 + echo "The Chargebee CLI needs libstdc++ and libgcc on musl Linux." >&2 + echo "On Alpine, run: apk add --no-cache libstdc++ libgcc" >&2 exit 1 fi ;; diff --git a/src/tests/integration/install-sh.test.ts b/src/tests/integration/install-sh.test.ts index b08878a..d5eda76 100644 --- a/src/tests/integration/install-sh.test.ts +++ b/src/tests/integration/install-sh.test.ts @@ -519,13 +519,21 @@ printf '200' }); describe.skipIf(process.platform === "win32")("install.sh Linux libc selection", () => { - function fakeLinux(root: string, withLibraries: boolean): { bin: string; log: string } { + function fakeLinux( + root: string, + withLibraries: boolean, + lddVersion = "musl libc", + hasMuslLoader = false, + ): { bin: string; log: string } { const bin = join(root, "fake-bin"); const log = join(root, "curl.log"); mkdirSync(bin); writeFakeCurl(bin, log, '{"tag_name":"v9.9.9"}'); writeFileSync(join(bin, "uname"), "#!/bin/sh\nif [ \"$1\" = -s ]; then echo Linux; else echo x86_64; fi\n", { mode: 0o755 }); - writeFileSync(join(bin, "ldd"), "#!/bin/sh\necho musl libc\n", { mode: 0o755 }); + writeFileSync(join(bin, "ldd"), `#!/bin/sh\necho '${lddVersion}'\n`, { mode: 0o755 }); + if (hasMuslLoader) { + writeFileSync(join(bin, "ls"), "#!/bin/sh\necho /lib/ld-musl-x86_64.so.1\n", { mode: 0o755 }); + } writeFileSync(join(bin, "find"), withLibraries ? "#!/bin/sh\necho /usr/lib/$4\n" : "#!/bin/sh\nexit 0\n", { mode: 0o755 }); @@ -570,6 +578,43 @@ describe.skipIf(process.platform === "win32")("install.sh Linux libc selection", } }); + it("selects glibc when ldd reports glibc despite an installed musl loader", () => { + const root = mkdtempSync(join(tmpdir(), "cb-install-glibc-with-musl-")); + try { + const { bin, log } = fakeLinux(root, false, "ldd (GNU libc) 2.39", true); + const result = runInstall({ + PATH: `${bin}:${SYSTEM_PATH}`, + HOME: root, + CHARGEBEE_CLI_BIN_DIR: join(root, "bin"), + CHARGEBEE_CONFIG_DIR: join(root, "config"), + CHARGEBEE_CLI_SKIP_CHECKSUM: "1", + }); + expect(result.exitCode).toBe(0); + expect(readFileSync(log, "utf8")).toContain("chargebee-cli-linux-x64"); + expect(readFileSync(log, "utf8")).not.toContain("chargebee-cli-linux-x64-musl"); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it("uses the musl loader when ldd cannot identify the active libc", () => { + const root = mkdtempSync(join(tmpdir(), "cb-install-unknown-libc-")); + try { + const { bin, log } = fakeLinux(root, true, "unknown libc", true); + const result = runInstall({ + PATH: `${bin}:${SYSTEM_PATH}`, + HOME: root, + CHARGEBEE_CLI_BIN_DIR: join(root, "bin"), + CHARGEBEE_CONFIG_DIR: join(root, "config"), + CHARGEBEE_CLI_SKIP_CHECKSUM: "1", + }); + expect(result.exitCode).toBe(0); + expect(readFileSync(log, "utf8")).toContain("chargebee-cli-linux-x64-musl"); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + it("explains missing Alpine runtime libraries before downloading", () => { const root = mkdtempSync(join(tmpdir(), "cb-install-musl-missing-")); try {