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
70 changes: 37 additions & 33 deletions bun.lock

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions cli/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env node
import { Command } from "commander"
import { cliConfig } from "lib/cli-config"
import { registerStaticAssetLoaders } from "lib/shared/register-static-asset-loaders"
import { captureTelemetryEvent } from "lib/telemetry"
import { perfectCli } from "perfect-cli"
import { getVersion } from "./../lib/getVersion"
import { registerAdd } from "./add/register"
Expand Down Expand Up @@ -48,6 +50,19 @@ export const program = new Command()

program.name("tsci").description("CLI for developing tscircuit packages")

const getCommandPath = (command: Command) => {
const names: string[] = []
let currentCommand: Command | null = command

while (currentCommand) {
const name = currentCommand.name()
if (name && name !== program.name()) names.unshift(name)
currentCommand = currentCommand.parent ?? null
}

return names
}

registerStaticAssetLoaders()

registerInit(program)
Expand Down Expand Up @@ -97,6 +112,17 @@ registerImport(program)
registerConvert(program)
registerSimulate(program)

program.hook("preAction", async (_thisCommand, actionCommand) => {
const commandPath = getCommandPath(actionCommand)

await captureTelemetryEvent("tsci_command", {
command: commandPath[0] ?? actionCommand.name(),
command_path: commandPath.join(" "),
authenticated: Boolean(cliConfig.get("accountId")),
status: "started",
})
})

// Manually handle --version, -v, and -V flags
if (
process.argv.includes("--version") ||
Expand Down
1 change: 1 addition & 0 deletions lib/cli-config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface CliConfig {
tscircuitHandle?: string
registryApiUrl?: string
alwaysCloneWithAuthorName?: boolean
telemetryAnonymousId?: string
}

export const getCliConfig = (
Expand Down
18 changes: 17 additions & 1 deletion lib/telemetry/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { randomUUID } from "node:crypto"
import type Conf from "conf"
import { cliConfig, type CliConfig } from "lib/cli-config"
import { getVersion } from "lib/getVersion"

const POSTHOG_HOST = "https://us.i.posthog.com"
Expand All @@ -24,6 +26,19 @@ const isTruthy = (value: string | undefined) =>
const joinUrl = (host: string, path: string) =>
`${host.replace(/\/$/, "")}${path}`

export const getTelemetryDistinctId = (config: Conf<CliConfig> = cliConfig) => {
const accountId = config.get("accountId")
if (accountId) return `account:${accountId}`

let anonymousId = config.get("telemetryAnonymousId")
if (!anonymousId) {
anonymousId = randomUUID()
config.set("telemetryAnonymousId", anonymousId)
}

return `anonymous:${anonymousId}`
}

export const getTelemetryConfigFromEnv = (
env: NodeJS.ProcessEnv = process.env,
): TelemetryConfig => {
Expand All @@ -41,7 +56,7 @@ export const getTelemetryConfigFromEnv = (
enabled: true,
projectApiKey: TSCI_POSTHOG_PROJECT_API_KEY,
host: POSTHOG_HOST,
distinctId: `tscircuit-cli-${randomUUID()}`,
distinctId: getTelemetryDistinctId(),
}
}

Expand Down Expand Up @@ -71,6 +86,7 @@ export const captureTelemetryEvent = async (
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(1_000),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each cli command taking 1s is wayyyy too long. An abortsignal needs to be sent at the end of every command

})
} catch {
// Telemetry must never make CLI commands fail.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"semver": "^7.6.3",
"stepts": "^0.0.3",
"tempy": "^3.1.0",
"tscircuit": "0.0.1772-libonly",
"tscircuit": "0.0.1779-libonly",
"tsx": "^4.7.1",
"typed-ky": "^0.0.4",
"zod": "^3.23.8"
Expand Down
2 changes: 1 addition & 1 deletion tests/cli/build/build-ci-keep-tscircuit-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ test("build --ci keeps tscircuit in devDependencies", async () => {
expect(packageJson.dependencies.tscircuit).toBeUndefined()
expect(packageJson.dependencies.lodash).toBe("^4.17.21")
expect(packageJson.devDependencies.tscircuit).toBe("^0.0.101")
}, 60_000)
}, 100_000)
39 changes: 39 additions & 0 deletions tests/lib/telemetry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { rm } from "node:fs/promises"
import { temporaryDirectory } from "tempy"
import { expect, test } from "bun:test"
import { getCliConfig } from "lib/cli-config"
import { getTelemetryDistinctId } from "lib/telemetry"

test("telemetry distinct id is stable for anonymous users", async () => {
const configDir = temporaryDirectory()
const config = getCliConfig({ configDir })

try {
const firstDistinctId = getTelemetryDistinctId(config)
const secondDistinctId = getTelemetryDistinctId(config)

expect(firstDistinctId).toBe(secondDistinctId)
expect(firstDistinctId).toStartWith("anonymous:")
expect(config.get("telemetryAnonymousId")).toBe(
firstDistinctId.replace("anonymous:", ""),
)
} finally {
config.clear()
await rm(configDir, { recursive: true, force: true })
}
})

test("telemetry distinct id uses account id for authenticated users", async () => {
const configDir = temporaryDirectory()
const config = getCliConfig({ configDir })

try {
config.set("accountId", "account-123")

expect(getTelemetryDistinctId(config)).toBe("account:account-123")
expect(config.get("telemetryAnonymousId")).toBeUndefined()
} finally {
config.clear()
await rm(configDir, { recursive: true, force: true })
}
})
Loading