@aranova/tracking-cli
v0.10.0
Published
CLI for the Aranova tracking package — generates typed service unions from your dashboard taxonomy
Readme
@aranova/tracking-cli
CLI for the Aranova tracking packages. Generates a typed service union from a
business's dashboard-managed service taxonomy (so createSalesClient calls get
compile-time-checked service values) plus the business's registered Google
Ads / Meta tag IDs and R2 runtime config (ARANOVA_TRACKING_CONFIG) — ad-platform
config stays backend-driven instead of hand-typed.
Install it as a devDependency alongside @aranova/tracking-react or
@aranova/tracking-next:
npm install --save-dev @aranova/tracking-cligen
Fetches the active services, registered ad-platform IDs, and R2 tracking config reference for one site of your business and writes a typed module.
npx @aranova/tracking-cli gen [--dev] [--endpoint <url>] [--api-key <key>] [--site <hostname>] [--non-interactive] [--out <path>] [--check] [--env-file <path>]| Flag | Default | Notes |
| ------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| --dev | off | Use the local dev backend (http://localhost:6969). Pre-flights reachability and fails fast if it's down. |
| --endpoint <url> | prod (https://aranovainternal-production.up.railway.app/tracking) | Overrides both --dev and the prod default. |
| --api-key <key> | ARANOVA_TRACKING_SECRET_KEY (else ARANOVA_TRACKING_API_KEY) from .env | Use a secret key (aranv_sk_…). Public keys are Origin-enforced and will 403 from Node. |
| --out <path> | src/aranova-services.ts | - writes to stdout. |
| --check | off | Don't write — compare the existing --out file against the freshly generated module. Exits 1 with a line diff if it's stale or missing, 0 if up to date. Great for CI. |
| --env-file <path> | — | Extra dotenv file to load (after .env and .env.local). |
| --site <hostname> | ARANOVA_TRACKING_SITE, else inferred | Which of the business's sites to generate for. Inferred from the existing file's siteId, then from the repo's public key; on a TTY you get a picker. |
| --non-interactive | off | Never prompt (CI / agents). A single site auto-resolves; an unconfirmed multi-site business exits 2 with the roster and a --site hint. |
Requires Node ≥ 20.
The key is read from .env via dotenv, so the typical invocation is just:
# .env (never commit a secret key)
ARANOVA_TRACKING_SECRET_KEY=aranv_sk_xxxxxxxxxxxxxxxxxxxxxxxxxx
npx @aranova/tracking-cli gen --devOutput
// AUTO-GENERATED by `@aranova/tracking-cli gen` — do not edit by hand.
// Re-run the command to refresh after changing services in the dashboard.
export const ARANOVA_SERVICES = [
{ key: "detail", label: "Detail" },
{ key: "oil", label: "Oil" },
{ key: "tires", label: "Tires" },
] as const;
export type AranovaService = (typeof ARANOVA_SERVICES)[number]["key"];
export const ARANOVA_SERVICE_LABELS: Record<AranovaService, string> = {
detail: "Detail",
oil: "Oil",
tires: "Tires",
};
// Legacy compatibility export. New installs use ARANOVA_TRACKING_CONFIG.
export const ARANOVA_GTAG_IDS = {
production: "AW-111111111",
test: "AW-18151802025",
} as const;
export const ARANOVA_TRACKING_CONFIG = {
businessId: "<business>",
environment: "production",
cdnBaseUrl: process.env.NEXT_PUBLIC_ARANOVA_CDN_BASE_URL || "https://demos.aranova.io",
} as const;
export const ARANOVA_CALENDARS = [
{ key: "consultations", label: "Consultations", timeZone: "America/Toronto" },
] as const;
export type AranovaCalendarKey = (typeof ARANOVA_CALENDARS)[number]["key"];
export const ARANOVA_CALENDAR_LABELS: Record<AranovaCalendarKey, string> = {
consultations: "Consultations",
};ARANOVA_SERVICES carries each service's human-readable label (handy for a
dropdown), AranovaService is the union of keys, and ARANOVA_SERVICE_LABELS is
a typed key→label lookup. ARANOVA_GTAG_IDS and ARANOVA_CONFIG_URL remain
generated only so legacy imports keep compiling; do not wire them into new
installs. Use ARANOVA_TRACKING_CONFIG with createTracking,
createSalesClient (via getTrackingConfigRuntime), and
<AdPlatformTracking> so R2 remains the live authority and dashboard
tombstones clear old Google commands without a redeploy.
ARANOVA_TRACKING_CONFIG is environment-independent: it carries the business
identity and reads its origin from an env var, defaulting to the production CDN.
Generating against a local backend therefore cannot bake a localhost origin into
this committed file, and gen --check compares dashboard content rather than
whichever environment you last generated against. To point a local run at your own
object storage, set the variable — no code change:
NEXT_PUBLIC_ARANOVA_CDN_BASE_URL=http://localhost:9100/aranova-demosLeave it unset in production. The emitted variable name follows the SDK package
found in your package.json (NEXT_PUBLIC_ for tracking-next, REACT_APP_ for
tracking-react); the plain-script tracking-browser build has no bundler to
inline a variable, so it gets the production origin and overrides via
init({ trackingConfig: { …, cdnBaseUrl } }).
ARANOVA_CALENDARS lists the calendar syncs registered for the business, for
createCalendarReadClient<AranovaCalendarKey>(). Appointment types are
deliberately absent — those are your site's own vocabulary, not dashboard
config. When a business has no calendars the union is never; leave the generic
unbound (it defaults to string) until one exists.
Bind the generated union when constructing the sales client:
import { createSalesClient } from '@aranova/tracking-next';
import type { AranovaService } from './aranova-services';
const sales = createSalesClient<AranovaService>({
apiKey: process.env.ARANOVA_TRACKING_SECRET_KEY!,
endpoint: process.env.ARANOVA_TRACKING_ENDPOINT!,
});
await sales.record({ service: 'tires', /* 'tirez' is a compile error */ ... });Generated types can lag the dashboard safely — the backend validates service
on every write and returns 422 for an unknown key. Re-run gen to refresh.
A common setup is a package.json script:
"scripts": { "gen:services": "aranova-tracking gen --dev" }See docs/tracking-package/sales-tracking.md
for the full sales/conversions guide, and
calendar.md
for calendar bookings.
list
Print the active services to stdout — a quick way to see a business's taxonomy without generating a file.
npx @aranova/tracking-cli list [--dev] [--endpoint <url>] [--api-key <key>] [--json] [--env-file <path>]KEY LABEL
oil Oil
tires TiresPass --json for a machine-readable [{ "key", "label" }] array. list shares
the --dev / --endpoint / --api-key / --env-file flags with gen.
gtags
Print the registered Google Ads tag IDs to stdout:
npx @aranova/tracking-cli gtags [--dev] [--endpoint <url>] [--api-key <key>] [--site <hostname>] [--json] [--env-file <path>]LABEL GTAG_ID
production AW-111111111
test AW-18151802025list, gtags and meta-pixels resolve a site the same way gen does.
sites
Print the business's site roster (secret key) — the hostnames --site accepts:
npx @aranova/tracking-cli sites [--dev] [--endpoint <url>] [--api-key <key>] [--json] [--env-file <path>]HOSTNAME ROLE PRIMARY PUBLIC_KEY GTAG_IDS GOALS PUBLISHED
www.example.com primary yes yes production=AW-111,test=AW-222 3 7
shop.example.com secondary no yes - 1 2CI drift check
"scripts": { "gen:services:check": "aranova-tracking gen --check" }gen --check regenerates in memory and compares against the committed file,
exiting non-zero with a diff if they differ — so CI fails when the generated
types lag the dashboard, without rewriting the file.
Exit codes
gen exits non-zero with a clear message on: missing/invalid key (401), a key
not permitted for the business (403), an unreachable --dev backend, a network
failure, or an unwritable --out path. It exits 2 — printing the site roster
and a --site hint, writing nothing — when the site cannot be resolved (unknown
--site, or a multi-site business with nothing to confirm the default in a
non-interactive run). With --check it also exits non-zero when
the --out file is stale or missing (it is never written in --check mode). An
empty service list is not an error — it emits an empty array + empty labels map
and warns. An empty/partial gtag map (or a backend without the /gtags route)
is also not an error — it emits {} as const / the partial map with a note.
