Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
38 changes: 37 additions & 1 deletion .github/workflows/install-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,34 @@ concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
alpine-install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Comment thread
cb-muthiahm marked this conversation as resolved.
with:
persist-credentials: false

- 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
Expand All @@ -32,6 +60,8 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
Expand Down Expand Up @@ -72,6 +102,8 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
Expand Down Expand Up @@ -114,6 +146,8 @@ jobs:
runs-on: windows-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
Expand Down Expand Up @@ -148,12 +182,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: |
Expand All @@ -165,5 +200,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
6 changes: 6 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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:

```bash
Expand Down
55 changes: 47 additions & 8 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,37 @@ 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
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"
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 musl Linux." >&2
echo "On Alpine, run: 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=""
Expand Down Expand Up @@ -254,8 +284,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)
Expand All @@ -273,13 +315,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
Expand Down
4 changes: 4 additions & 0 deletions src/commands/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export function registerDocsCommand(program: Command): void {
if (!op.ok) {
if (op.reason === "offline") {
diagnostic(OFFLINE_MSG);
} else if (op.reason === "ambiguous") {
diagnostic(
`Multiple documentation pages match "${resource} ${operation}": ${op.matches?.join(", ")}. Use an exact Docs slug.`
);
} else if (op.operations) {
diagnostic(
`Unknown operation "${operation}" for resource "${resource}". Available: ${formatOpHint(op.operations)}`
Expand Down
7 changes: 4 additions & 3 deletions src/commands/generated/alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ export function registerAlert(parent: Command): void {
.description("API operations for alerts");

cmd
.command("application_alerts-for-subscription")
.command("application-alerts-for-subscription")
.alias("application_alerts-for-subscription")
.argument("[subscription-id]", "The unique identifier of the subscription resource.")
.argument("[json]", "'-' reads a JSON object from stdin.")
.description("List applicable alerts for a subscription")
.option("-d, --data <pairs...>", "Request parameters in key=value format; repeat for multiple fields")
.option("-s, --code-sample <lang>", "Generate code sample (curl, python, nodejs, go, ruby, java, php, dotnet, list)")
.addOption(new Option("--pc-version <version>", "Product catalog version for the code sample (v1 or v2)").hideHelp())
.addHelpText("after", "\n\u001b[1mDOCUMENTATION\u001b[0m\n chargebee docs alert application_alerts-for-subscription\n")
.addHelpText("after", "\n\u001b[1mDOCUMENTATION\u001b[0m\n chargebee docs alert application-alerts-for-subscription\n")
.action(async (id: string | undefined, json: string | undefined, opts: { data?: string[]; codeSample?: string; pcVersion?: string }, command: Command) => {
const resource = takeResourceId(id, json, command);
const params = await loadOperationParams(opts.data ?? [], resource.fromStdin, command, { method: "GET", jsonInput: false, opIdV2: "list_applicable_alerts_for_a_subscription", opIdV1: "", pcVersionFlag: opts.pcVersion });
if (opts.codeSample !== "list") await ensureCatalogAllowed("pc2", "alert application_alerts-for-subscription");
if (opts.codeSample !== "list") await ensureCatalogAllowed("pc2", "alert application-alerts-for-subscription");
if (opts.codeSample) return handleCodeSample({ lang: opts.codeSample, opIdV2: "list_applicable_alerts_for_a_subscription", opIdV1: "", method: "GET", uri: "/subscriptions/{id}/applicable_alerts", dataFlags: opts.data ?? [], params, resourceId: resource.id, pathParamName: "subscription-id", pcVersionFlag: opts.pcVersion });
if (!resource.id) command.error("error: missing required argument 'subscription-id'");
assertResourceId(resource.id, command);
Expand Down
14 changes: 8 additions & 6 deletions src/commands/generated/alert_status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ export function registerAlertStatus(parent: Command): void {
.description("API operations for alert statuses");

cmd
.command("alert_statuses-for-alert")
.command("alert-statuses-for-alert")
.alias("alert_statuses-for-alert")
.argument("[alert-id]", "Uniquely identifies the alert configuration.")
.argument("[json]", "'-' reads a JSON object from stdin.")
.description("List alert statuses for an alert")
.option("-d, --data <pairs...>", "Request parameters in key=value format; repeat for multiple fields")
.option("-s, --code-sample <lang>", "Generate code sample (curl, python, nodejs, go, ruby, java, php, dotnet, list)")
.addOption(new Option("--pc-version <version>", "Product catalog version for the code sample (v1 or v2)").hideHelp())
.addHelpText("after", "\n\u001b[1mDOCUMENTATION\u001b[0m\n chargebee docs alert-status alert_statuses-for-alert\n")
.addHelpText("after", "\n\u001b[1mDOCUMENTATION\u001b[0m\n chargebee docs alert-status alert-statuses-for-alert\n")
.action(async (id: string | undefined, json: string | undefined, opts: { data?: string[]; codeSample?: string; pcVersion?: string }, command: Command) => {
const resource = takeResourceId(id, json, command);
const params = await loadOperationParams(opts.data ?? [], resource.fromStdin, command, { method: "GET", jsonInput: false, opIdV2: "list_alert_statuses_for_an_alert", opIdV1: "", pcVersionFlag: opts.pcVersion });
if (opts.codeSample !== "list") await ensureCatalogAllowed("pc2", "alert-status alert_statuses-for-alert");
if (opts.codeSample !== "list") await ensureCatalogAllowed("pc2", "alert-status alert-statuses-for-alert");
if (opts.codeSample) return handleCodeSample({ lang: opts.codeSample, opIdV2: "list_alert_statuses_for_an_alert", opIdV1: "", method: "GET", uri: "/alerts/{id}/alert_statuses", dataFlags: opts.data ?? [], params, resourceId: resource.id, pathParamName: "alert-id", pcVersionFlag: opts.pcVersion });
if (!resource.id) command.error("error: missing required argument 'alert-id'");
assertResourceId(resource.id, command);
Expand All @@ -40,18 +41,19 @@ export function registerAlertStatus(parent: Command): void {
});

cmd
.command("alert_statuses-for-subscription")
.command("alert-statuses-for-subscription")
.alias("alert_statuses-for-subscription")
.argument("[subscription-id]", "The unique identifier of the subscription resource.")
.argument("[json]", "'-' reads a JSON object from stdin.")
.description("List alert statuses for a subscription")
.option("-d, --data <pairs...>", "Request parameters in key=value format; repeat for multiple fields")
.option("-s, --code-sample <lang>", "Generate code sample (curl, python, nodejs, go, ruby, java, php, dotnet, list)")
.addOption(new Option("--pc-version <version>", "Product catalog version for the code sample (v1 or v2)").hideHelp())
.addHelpText("after", "\n\u001b[1mDOCUMENTATION\u001b[0m\n chargebee docs alert-status alert_statuses-for-subscription\n")
.addHelpText("after", "\n\u001b[1mDOCUMENTATION\u001b[0m\n chargebee docs alert-status alert-statuses-for-subscription\n")
.action(async (id: string | undefined, json: string | undefined, opts: { data?: string[]; codeSample?: string; pcVersion?: string }, command: Command) => {
const resource = takeResourceId(id, json, command);
const params = await loadOperationParams(opts.data ?? [], resource.fromStdin, command, { method: "GET", jsonInput: false, opIdV2: "list_alert_statuses_for_a_subscription", opIdV1: "", pcVersionFlag: opts.pcVersion });
if (opts.codeSample !== "list") await ensureCatalogAllowed("pc2", "alert-status alert_statuses-for-subscription");
if (opts.codeSample !== "list") await ensureCatalogAllowed("pc2", "alert-status alert-statuses-for-subscription");
if (opts.codeSample) return handleCodeSample({ lang: opts.codeSample, opIdV2: "list_alert_statuses_for_a_subscription", opIdV1: "", method: "GET", uri: "/subscriptions/{id}/alert_statuses", dataFlags: opts.data ?? [], params, resourceId: resource.id, pathParamName: "subscription-id", pcVersionFlag: opts.pcVersion });
if (!resource.id) command.error("error: missing required argument 'subscription-id'");
assertResourceId(resource.id, command);
Expand Down
Loading
Loading