HotCodePush

Node.js client

Install @hotcodepush/node, create a client with an API token, release a bundle, upload a file, handle errors and retry safely with idempotency keys.

@hotcodepush/node is the typed Node.js client for the REST API: one class, HotCodePush, whose resources mirror the API's paths, so /v1/apps/{appId}/channels is apps.channels. Every endpoint of the Swagger reference is a method with typed options and a typed answer. The CLI and the MCP server are built on it.

The source is on GitHub, MIT.

Install

npm install @hotcodepush/node

The package is ES modules only and needs Node.js 22 or later.

Authenticate

import { HotCodePush } from "@hotcodepush/node";

const hotCodePush = new HotCodePush({ token: process.env.HOTCODEPUSH_TOKEN });

token is an API token from the console's Settings › API tokens page, sent as Authorization: Bearer on every request. The session token the CLI stores after hotcodepush login works in its place.

OptionDefaultWhat it is
baseUrlhttps://api.hotcodepush.comNames the API's origin.
clientnode/<the package's version>Names the client version the API checks, X-HotCodePush-Client.
tokennoneHolds the API token, or a session token.
userAgentthe runtime'sSets the User-Agent header, which a session keeps and the console's sessions page shows.

A method that needs a signed-in session says so in its doc comment. With an API token it throws HotCodePushError with the code forbidden. The hub lists what a token cannot do.

Usage

Every method takes one options object holding the path parameters, the query parameters or the body, and answers what the API answers. A paged list takes limit, offset and, where the API embeds linked resources, relations, and has a count beside it. Wherever the API takes a {userId}, me names the caller.

Release a bundle

import { HotCodePush } from "@hotcodepush/node";

const hotCodePush = new HotCodePush({ token: process.env.HOTCODEPUSH_TOKEN });
const appId = process.env.HOTCODEPUSH_APP_ID;
const channelId = process.env.HOTCODEPUSH_CHANNEL_ID;

const [bundle] = await hotCodePush.apps.bundles.list({ appId, limit: 1 });
const release = await hotCodePush.apps.channels.releases.create({
  appId,
  channelId,
  bundleId: bundle.id,
  rolloutPercentage: 10,
});
await hotCodePush.apps.releases.update({ appId, releaseId: release.id, rolloutPercentage: 100 });
await hotCodePush.apps.channels.rollbacks.create({ appId, channelId });

A release creates from a ready bundle, or from what another channel serves with fromChannelId in place of bundleId. A rollback is a new release of the channel's previous bundle. Rollouts and auto-pause covers the percentage and the progression. Uploading a bundle is the CLI's job, hotcodepush bundle upload, and the client holds the same calls, apps.files.upload, apps.bundles.create, apps.bundles.pack.upload and apps.bundles.complete, for a pipeline of your own.

Upload a file

import { openAsBlob } from "node:fs";
import { HotCodePush } from "@hotcodepush/node";

const hotCodePush = new HotCodePush({ token: process.env.HOTCODEPUSH_TOKEN });

await hotCodePush.apps.files.upload({
  appId: process.env.HOTCODEPUSH_APP_ID,
  body: await openAsBlob("dist/index.html.gz"),
  sha256: "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
});

apps.files.upload and apps.bundles.pack.upload stream their body, a Blob or a ReadableStream with its contentLength, and never buffer it. A file goes up gzip-compressed, and sha256 is the hash of its uncompressed content. A Blob above 64 MiB goes up in parts of 10 MiB through the uploads resources, while a stream goes up in one request and is not retried.

Errors

A response outside 2xx throws HotCodePushError, which carries the API's code, message and details verbatim and the HTTP status. A response outside the API's error shape throws the client's own unexpected_response with the status.

import { HotCodePush, HotCodePushError } from "@hotcodepush/node";

const hotCodePush = new HotCodePush({ token: process.env.HOTCODEPUSH_TOKEN });

try {
  await hotCodePush.apps.channels.create({ appId: process.env.HOTCODEPUSH_APP_ID, name: "staging" });
} catch (error) {
  if (error instanceof HotCodePushError && error.code === "channel_name_taken") {
    console.log("staging exists already");
  } else {
    throw error;
  }
}

Retries and idempotency

The client retries only where the API makes a repeat safe:

What the client does
TimeoutAn attempt has 60 seconds, an upload 10 minutes.
RetriesA GET, DELETE or PUT, and a transition a repeat leaves unchanged, such as pause, resume or a bundle's complete, is sent up to 3 times on a network error, a 408, a 429 or a 5xx, waiting 500 ms and then 1 second, or on a 429 or a 503 what Retry-After asks, up to 60 seconds. A PATCH is never retried.
IdempotencyA creating POST sends an Idempotency-Key, a UUID generated per call and kept across its retries, so a retried create answers the first result.

Pass idempotencyKey to reuse a key when you retry the call yourself. The API answers the first result for 24 hours.

await hotCodePush.apps.channels.create({
  appId,
  name: "staging",
  idempotencyKey: `create-staging-${appId}`,
});

organizations.checkouts.create and organizations.customerPortalSessions.create carry no key and are never retried.

Reference

The client covers every endpoint of the API: organizations, members, invitations, apps, channels, bundles, releases, devices, statistics, webhooks and your account. The method list is in the package's README, and each method maps to an operation of the Swagger reference.

Last updated on

On this page