mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
chore(metrics): classify release downloads for PostHog v2
This commit is contained in:
5
.github/workflows/download-stats.yml
vendored
5
.github/workflows/download-stats.yml
vendored
@@ -28,7 +28,8 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
|
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
|
||||||
POSTHOG_HOST: https://us.i.posthog.com
|
POSTHOG_HOST: https://us.i.posthog.com
|
||||||
POSTHOG_EVENT: download
|
POSTHOG_LEGACY_EVENT: download
|
||||||
|
POSTHOG_V2_EVENT: release_asset_snapshot
|
||||||
POSTHOG_DISTINCT_ID: openwork-download
|
POSTHOG_DISTINCT_ID: openwork-download
|
||||||
GITHUB_REPO: different-ai/openwork
|
GITHUB_REPO: different-ai/openwork
|
||||||
run: node scripts/stats.mjs
|
run: node scripts/stats.mjs
|
||||||
@@ -37,6 +38,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
git config --local user.email "action@github.com"
|
git config --local user.email "action@github.com"
|
||||||
git config --local user.name "GitHub Action"
|
git config --local user.name "GitHub Action"
|
||||||
git add STATS.md
|
git add STATS.md STATS_V2.md
|
||||||
git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)"
|
git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)"
|
||||||
git push
|
git push
|
||||||
|
|||||||
2
STATS.md
2
STATS.md
@@ -1,5 +1,7 @@
|
|||||||
# Download Stats
|
# Download Stats
|
||||||
|
|
||||||
|
Legacy cumulative release-asset totals. For classified v2 buckets, see `STATS_V2.md`.
|
||||||
|
|
||||||
| Date | GitHub Downloads | Total |
|
| Date | GitHub Downloads | Total |
|
||||||
|------|------------------|-------|
|
|------|------------------|-------|
|
||||||
| 2026-01-24 | 15,879 (+15,879) | 15,879 (+15,879) |
|
| 2026-01-24 | 15,879 (+15,879) | 15,879 (+15,879) |
|
||||||
|
|||||||
6
STATS_V2.md
Normal file
6
STATS_V2.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Download Stats V2
|
||||||
|
|
||||||
|
Classified GitHub release asset snapshots. `Manual installs` counts installer downloads (`.dmg`, `.msi`, `.deb`, `.rpm`). `Updater` counts updater artifacts (`latest.json`, macOS updater bundles, updater signatures). `Other` captures signatures, sidecars, and uncategorized assets.
|
||||||
|
|
||||||
|
| Date | Manual Installs | Updater | Other | All Release Assets |
|
||||||
|
|------|-----------------|---------|-------|--------------------|
|
||||||
@@ -1,13 +1,41 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import fs from "node:fs/promises"
|
||||||
|
import { resolve } from "node:path"
|
||||||
|
import { pathToFileURL } from "node:url"
|
||||||
|
|
||||||
const POSTHOG_KEY = process.env.POSTHOG_KEY || process.env.POSTHOG_API_KEY
|
const POSTHOG_KEY = process.env.POSTHOG_KEY || process.env.POSTHOG_API_KEY
|
||||||
const POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com"
|
const POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com"
|
||||||
const POSTHOG_EVENT = process.env.POSTHOG_EVENT || "download"
|
const POSTHOG_LEGACY_EVENT = process.env.POSTHOG_LEGACY_EVENT || process.env.POSTHOG_EVENT || "download"
|
||||||
|
const POSTHOG_V2_EVENT = process.env.POSTHOG_V2_EVENT || "release_asset_snapshot"
|
||||||
const POSTHOG_DISTINCT_ID = process.env.POSTHOG_DISTINCT_ID || "openwork-download"
|
const POSTHOG_DISTINCT_ID = process.env.POSTHOG_DISTINCT_ID || "openwork-download"
|
||||||
const GITHUB_REPO = process.env.GITHUB_REPO || "different-ai/openwork"
|
const GITHUB_REPO = process.env.GITHUB_REPO || "different-ai/openwork"
|
||||||
const STATS_FILE = process.env.STATS_FILE || "STATS.md"
|
const STATS_FILE = process.env.STATS_FILE || "STATS.md"
|
||||||
|
const STATS_V2_FILE = process.env.STATS_V2_FILE || "STATS_V2.md"
|
||||||
|
|
||||||
async function sendToPostHog(event, properties) {
|
const LEGACY_HEADER = [
|
||||||
|
"# Download Stats",
|
||||||
|
"",
|
||||||
|
"Legacy cumulative release-asset totals. For classified v2 buckets, see `STATS_V2.md`.",
|
||||||
|
"",
|
||||||
|
"| Date | GitHub Downloads | Total |",
|
||||||
|
"|------|------------------|-------|",
|
||||||
|
].join("\n")
|
||||||
|
|
||||||
|
const V2_HEADER = [
|
||||||
|
"# Download Stats V2",
|
||||||
|
"",
|
||||||
|
"Classified GitHub release asset snapshots. `Manual installs` counts installer downloads (`.dmg`, `.msi`, `.deb`, `.rpm`). `Updater` counts updater artifacts (`latest.json`, macOS updater bundles, updater signatures). `Other` captures signatures, sidecars, and uncategorized assets.",
|
||||||
|
"",
|
||||||
|
"| Date | Manual Installs | Updater | Other | All Release Assets |",
|
||||||
|
"|------|-----------------|---------|-------|--------------------|",
|
||||||
|
].join("\n")
|
||||||
|
|
||||||
|
const MANUAL_INSTALL_SUFFIXES = [".dmg", ".msi", ".deb", ".rpm", ".pkg", ".appimage", ".exe"]
|
||||||
|
const UPDATER_SUFFIXES = ["latest.json", ".blockmap", ".app.tar.gz", ".app.tar.gz.sig"]
|
||||||
|
const V2_BUCKETS = ["manual_install", "updater", "other", "all"]
|
||||||
|
|
||||||
|
async function sendToPostHog(event, properties, distinctId = POSTHOG_DISTINCT_ID) {
|
||||||
if (!POSTHOG_KEY) {
|
if (!POSTHOG_KEY) {
|
||||||
console.warn("POSTHOG_KEY not set, skipping PostHog event")
|
console.warn("POSTHOG_KEY not set, skipping PostHog event")
|
||||||
return
|
return
|
||||||
@@ -19,7 +47,7 @@ async function sendToPostHog(event, properties) {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
distinct_id: POSTHOG_DISTINCT_ID,
|
distinct_id: distinctId,
|
||||||
api_key: POSTHOG_KEY,
|
api_key: POSTHOG_KEY,
|
||||||
event,
|
event,
|
||||||
properties,
|
properties,
|
||||||
@@ -31,6 +59,28 @@ async function sendToPostHog(event, properties) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDesktopRelease(release) {
|
||||||
|
return typeof release?.tag_name === "string" && /^v\d/.test(release.tag_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function classifyAsset(release, asset) {
|
||||||
|
const name = String(asset?.name || "").toLowerCase()
|
||||||
|
|
||||||
|
if (!name) return "other"
|
||||||
|
|
||||||
|
if (isDesktopRelease(release)) {
|
||||||
|
if (UPDATER_SUFFIXES.some((suffix) => name === suffix || name.endsWith(suffix))) {
|
||||||
|
return "updater"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MANUAL_INSTALL_SUFFIXES.some((suffix) => name.endsWith(suffix))) {
|
||||||
|
return "manual_install"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "other"
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchReleases() {
|
async function fetchReleases() {
|
||||||
const releases = []
|
const releases = []
|
||||||
let page = 1
|
let page = 1
|
||||||
@@ -67,8 +117,13 @@ async function fetchReleases() {
|
|||||||
return releases
|
return releases
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculate(releases) {
|
export function calculate(releases) {
|
||||||
let total = 0
|
let legacyTotal = 0
|
||||||
|
const buckets = {
|
||||||
|
manual_install: 0,
|
||||||
|
updater: 0,
|
||||||
|
other: 0,
|
||||||
|
}
|
||||||
const stats = []
|
const stats = []
|
||||||
|
|
||||||
for (const release of releases) {
|
for (const release of releases) {
|
||||||
@@ -76,14 +131,19 @@ function calculate(releases) {
|
|||||||
const assets = []
|
const assets = []
|
||||||
|
|
||||||
for (const asset of release.assets ?? []) {
|
for (const asset of release.assets ?? []) {
|
||||||
downloads += asset.download_count
|
const count = Number(asset.download_count) || 0
|
||||||
|
const bucket = classifyAsset(release, asset)
|
||||||
|
|
||||||
|
downloads += count
|
||||||
|
legacyTotal += count
|
||||||
|
buckets[bucket] += count
|
||||||
assets.push({
|
assets.push({
|
||||||
name: asset.name,
|
name: asset.name,
|
||||||
downloads: asset.download_count,
|
downloads: count,
|
||||||
|
bucket,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
total += downloads
|
|
||||||
stats.push({
|
stats.push({
|
||||||
tag: release.tag_name,
|
tag: release.tag_name,
|
||||||
name: release.name,
|
name: release.name,
|
||||||
@@ -92,86 +152,184 @@ function calculate(releases) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return { total, stats }
|
return {
|
||||||
|
total: legacyTotal,
|
||||||
|
legacyTotal,
|
||||||
|
buckets: {
|
||||||
|
...buckets,
|
||||||
|
all: legacyTotal,
|
||||||
|
},
|
||||||
|
stats,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save(githubTotal) {
|
export function parseCountCell(cell) {
|
||||||
const date = new Date().toISOString().split("T")[0]
|
const match = String(cell || "").match(/-?[\d,]+/)
|
||||||
const total = githubTotal
|
if (!match) return 0
|
||||||
|
return parseInt(match[0].replace(/,/g, ""), 10)
|
||||||
|
}
|
||||||
|
|
||||||
let previousGithub = 0
|
export function parseLastDataRow(content, expectedColumns) {
|
||||||
let previousTotal = 0
|
const lines = String(content || "")
|
||||||
let content = ""
|
.trim()
|
||||||
|
.split("\n")
|
||||||
|
|
||||||
try {
|
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
||||||
const file = await import("node:fs/promises")
|
const line = lines[i].trim()
|
||||||
content = await file.readFile(STATS_FILE, "utf8")
|
if (!line.startsWith("|")) continue
|
||||||
const lines = content.trim().split("\n")
|
|
||||||
|
|
||||||
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
const cells = line
|
||||||
const line = lines[i].trim()
|
.split("|")
|
||||||
if (line.startsWith("|") && !line.includes("Date") && !line.includes("---")) {
|
.map((cell) => cell.trim())
|
||||||
const match = line.match(
|
.filter(Boolean)
|
||||||
/\|\s*[\d-]+\s*\|\s*([\d,]+)\s*(?:\([^)]*\))?\s*\|\s*([\d,]+)\s*(?:\([^)]*\))?\s*\|/,
|
|
||||||
)
|
if (cells.length !== expectedColumns) continue
|
||||||
if (match) {
|
if (cells[0] === "Date") continue
|
||||||
previousGithub = parseInt(match[1].replace(/,/g, ""), 10)
|
if (cells.every((cell) => /^-+$/.test(cell))) continue
|
||||||
previousTotal = parseInt(match[2].replace(/,/g, ""), 10)
|
|
||||||
break
|
return cells
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
content =
|
|
||||||
"# Download Stats\n\n| Date | GitHub Downloads | Total |\n|------|------------------|-------|\n"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatChange(change) {
|
||||||
|
if (change > 0) return ` (+${change.toLocaleString()})`
|
||||||
|
if (change < 0) return ` (${change.toLocaleString()})`
|
||||||
|
return " (+0)"
|
||||||
|
}
|
||||||
|
|
||||||
|
function withTrailingNewline(content) {
|
||||||
|
return content.endsWith("\n") ? content : `${content}\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadOrInitialize(filePath, header) {
|
||||||
|
try {
|
||||||
|
return await fs.readFile(filePath, "utf8")
|
||||||
|
} catch {
|
||||||
|
return `${header}\n`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveLegacyStats(githubTotal) {
|
||||||
|
const date = new Date().toISOString().split("T")[0]
|
||||||
|
let content = await loadOrInitialize(STATS_FILE, LEGACY_HEADER)
|
||||||
|
|
||||||
|
if (!content.includes("| Date | GitHub Downloads | Total |")) {
|
||||||
|
content = `${LEGACY_HEADER}\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
const previous = parseLastDataRow(content, 3)
|
||||||
|
const previousGithub = previous ? parseCountCell(previous[1]) : 0
|
||||||
|
const previousTotal = previous ? parseCountCell(previous[2]) : 0
|
||||||
|
|
||||||
const githubChange = githubTotal - previousGithub
|
const githubChange = githubTotal - previousGithub
|
||||||
const totalChange = total - previousTotal
|
const totalChange = githubTotal - previousTotal
|
||||||
|
const line = `| ${date} | ${githubTotal.toLocaleString()}${formatChange(githubChange)} | ${githubTotal.toLocaleString()}${formatChange(totalChange)} |\n`
|
||||||
|
|
||||||
const githubChangeStr =
|
await fs.writeFile(STATS_FILE, `${withTrailingNewline(content)}${line}`, "utf8")
|
||||||
githubChange > 0
|
|
||||||
? ` (+${githubChange.toLocaleString()})`
|
|
||||||
: githubChange < 0
|
|
||||||
? ` (${githubChange.toLocaleString()})`
|
|
||||||
: " (+0)"
|
|
||||||
const totalChangeStr =
|
|
||||||
totalChange > 0
|
|
||||||
? ` (+${totalChange.toLocaleString()})`
|
|
||||||
: totalChange < 0
|
|
||||||
? ` (${totalChange.toLocaleString()})`
|
|
||||||
: " (+0)"
|
|
||||||
const line = `| ${date} | ${githubTotal.toLocaleString()}${githubChangeStr} | ${total.toLocaleString()}${totalChangeStr} |\n`
|
|
||||||
|
|
||||||
if (!content.includes("# Download Stats")) {
|
|
||||||
content =
|
|
||||||
"# Download Stats\n\n| Date | GitHub Downloads | Total |\n|------|------------------|-------|\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = await import("node:fs/promises")
|
|
||||||
await file.writeFile(STATS_FILE, content + line, "utf8")
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`\nAppended stats to ${STATS_FILE}: GitHub ${githubTotal.toLocaleString()}${githubChangeStr}, Total ${total.toLocaleString()}${totalChangeStr}`,
|
`\nAppended legacy stats to ${STATS_FILE}: GitHub ${githubTotal.toLocaleString()}${formatChange(githubChange)}, Total ${githubTotal.toLocaleString()}${formatChange(totalChange)}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
date,
|
||||||
|
totals: {
|
||||||
|
github: githubTotal,
|
||||||
|
total: githubTotal,
|
||||||
|
},
|
||||||
|
deltas: {
|
||||||
|
github: githubChange,
|
||||||
|
total: totalChange,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Fetching GitHub releases for ${GITHUB_REPO}...\n`)
|
async function saveV2Stats(totals) {
|
||||||
|
const date = new Date().toISOString().split("T")[0]
|
||||||
|
let content = await loadOrInitialize(STATS_V2_FILE, V2_HEADER)
|
||||||
|
|
||||||
const releases = await fetchReleases()
|
if (!content.includes("| Date | Manual Installs | Updater | Other | All Release Assets |")) {
|
||||||
console.log(`\nFetched ${releases.length} releases total\n`)
|
content = `${V2_HEADER}\n`
|
||||||
|
}
|
||||||
|
|
||||||
const { total: githubTotal } = calculate(releases)
|
const previous = parseLastDataRow(content, 5)
|
||||||
|
const previousTotals = {
|
||||||
|
manual_install: previous ? parseCountCell(previous[1]) : 0,
|
||||||
|
updater: previous ? parseCountCell(previous[2]) : 0,
|
||||||
|
other: previous ? parseCountCell(previous[3]) : 0,
|
||||||
|
all: previous ? parseCountCell(previous[4]) : 0,
|
||||||
|
}
|
||||||
|
|
||||||
await save(githubTotal)
|
const deltas = {
|
||||||
|
manual_install: totals.manual_install - previousTotals.manual_install,
|
||||||
|
updater: totals.updater - previousTotals.updater,
|
||||||
|
other: totals.other - previousTotals.other,
|
||||||
|
all: totals.all - previousTotals.all,
|
||||||
|
}
|
||||||
|
|
||||||
await sendToPostHog(POSTHOG_EVENT, {
|
const line = `| ${date} | ${totals.manual_install.toLocaleString()}${formatChange(deltas.manual_install)} | ${totals.updater.toLocaleString()}${formatChange(deltas.updater)} | ${totals.other.toLocaleString()}${formatChange(deltas.other)} | ${totals.all.toLocaleString()}${formatChange(deltas.all)} |`
|
||||||
count: githubTotal,
|
|
||||||
source: "github",
|
|
||||||
repo: GITHUB_REPO,
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log("=".repeat(60))
|
await fs.writeFile(STATS_V2_FILE, `${withTrailingNewline(content)}${line}\n`, "utf8")
|
||||||
console.log(`TOTAL DOWNLOADS: ${githubTotal.toLocaleString()}`)
|
|
||||||
console.log(` GitHub: ${githubTotal.toLocaleString()}`)
|
console.log(
|
||||||
console.log("=".repeat(60))
|
`Appended classified stats to ${STATS_V2_FILE}: manual ${totals.manual_install.toLocaleString()}${formatChange(deltas.manual_install)}, updater ${totals.updater.toLocaleString()}${formatChange(deltas.updater)}, other ${totals.other.toLocaleString()}${formatChange(deltas.other)}, all ${totals.all.toLocaleString()}${formatChange(deltas.all)}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
date,
|
||||||
|
totals,
|
||||||
|
deltas,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendClassifiedSnapshot(snapshot) {
|
||||||
|
for (const bucket of V2_BUCKETS) {
|
||||||
|
await sendToPostHog(
|
||||||
|
POSTHOG_V2_EVENT,
|
||||||
|
{
|
||||||
|
metric_version: 2,
|
||||||
|
bucket,
|
||||||
|
total: snapshot.totals[bucket],
|
||||||
|
delta: snapshot.deltas[bucket],
|
||||||
|
source: "github",
|
||||||
|
repo: GITHUB_REPO,
|
||||||
|
date: snapshot.date,
|
||||||
|
},
|
||||||
|
`${POSTHOG_DISTINCT_ID}-${bucket}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function main() {
|
||||||
|
console.log(`Fetching GitHub releases for ${GITHUB_REPO}...\n`)
|
||||||
|
|
||||||
|
const releases = await fetchReleases()
|
||||||
|
console.log(`\nFetched ${releases.length} releases total\n`)
|
||||||
|
|
||||||
|
const { total: githubTotal, buckets } = calculate(releases)
|
||||||
|
const legacySnapshot = await saveLegacyStats(githubTotal)
|
||||||
|
const v2Snapshot = await saveV2Stats(buckets)
|
||||||
|
|
||||||
|
await sendToPostHog(POSTHOG_LEGACY_EVENT, {
|
||||||
|
count: githubTotal,
|
||||||
|
source: "github",
|
||||||
|
repo: GITHUB_REPO,
|
||||||
|
date: legacySnapshot.date,
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendClassifiedSnapshot(v2Snapshot)
|
||||||
|
|
||||||
|
console.log("=".repeat(60))
|
||||||
|
console.log(`TOTAL DOWNLOADS: ${githubTotal.toLocaleString()}`)
|
||||||
|
console.log(` Legacy all assets: ${githubTotal.toLocaleString()}`)
|
||||||
|
console.log(` Manual installs: ${buckets.manual_install.toLocaleString()}`)
|
||||||
|
console.log(` Updater: ${buckets.updater.toLocaleString()}`)
|
||||||
|
console.log(` Other: ${buckets.other.toLocaleString()}`)
|
||||||
|
console.log("=".repeat(60))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
||||||
|
await main()
|
||||||
|
}
|
||||||
|
|||||||
67
scripts/stats.test.mjs
Normal file
67
scripts/stats.test.mjs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import test from "node:test"
|
||||||
|
import assert from "node:assert/strict"
|
||||||
|
|
||||||
|
import { calculate, classifyAsset, parseCountCell, parseLastDataRow } from "./stats.mjs"
|
||||||
|
|
||||||
|
test("classifyAsset buckets manual installer assets", () => {
|
||||||
|
const release = { tag_name: "v0.11.135" }
|
||||||
|
|
||||||
|
assert.equal(classifyAsset(release, { name: "openwork-desktop-darwin-aarch64.dmg" }), "manual_install")
|
||||||
|
assert.equal(classifyAsset(release, { name: "openwork-desktop-windows-x64.msi" }), "manual_install")
|
||||||
|
assert.equal(classifyAsset(release, { name: "openwork-desktop-linux-amd64.deb" }), "manual_install")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("classifyAsset buckets updater and non-desktop assets separately", () => {
|
||||||
|
const desktopRelease = { tag_name: "v0.11.135" }
|
||||||
|
const sidecarRelease = { tag_name: "openwork-orchestrator-v0.11.135" }
|
||||||
|
|
||||||
|
assert.equal(classifyAsset(desktopRelease, { name: "latest.json" }), "updater")
|
||||||
|
assert.equal(classifyAsset(desktopRelease, { name: "openwork-desktop-darwin-aarch64.app.tar.gz" }), "updater")
|
||||||
|
assert.equal(classifyAsset(desktopRelease, { name: "openwork-desktop-darwin-aarch64.app.tar.gz.sig" }), "updater")
|
||||||
|
assert.equal(classifyAsset(desktopRelease, { name: "openwork-desktop-linux-aarch64.rpm.sig" }), "other")
|
||||||
|
assert.equal(classifyAsset(sidecarRelease, { name: "openwork-server-darwin-arm64" }), "other")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("calculate aggregates legacy total and v2 buckets", () => {
|
||||||
|
const releases = [
|
||||||
|
{
|
||||||
|
tag_name: "v0.11.135",
|
||||||
|
assets: [
|
||||||
|
{ name: "openwork-desktop-darwin-aarch64.dmg", download_count: 10 },
|
||||||
|
{ name: "openwork-desktop-darwin-aarch64.app.tar.gz", download_count: 4 },
|
||||||
|
{ name: "latest.json", download_count: 6 },
|
||||||
|
{ name: "openwork-desktop-linux-aarch64.rpm.sig", download_count: 3 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag_name: "openwork-orchestrator-v0.11.135",
|
||||||
|
assets: [{ name: "openwork-server-darwin-arm64", download_count: 8 }],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const result = calculate(releases)
|
||||||
|
|
||||||
|
assert.equal(result.total, 31)
|
||||||
|
assert.deepEqual(result.buckets, {
|
||||||
|
manual_install: 10,
|
||||||
|
updater: 10,
|
||||||
|
other: 11,
|
||||||
|
all: 31,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("table helpers parse formatted rows with deltas", () => {
|
||||||
|
const content = [
|
||||||
|
"# Download Stats V2",
|
||||||
|
"",
|
||||||
|
"| Date | Manual Installs | Updater | Other | All Release Assets |",
|
||||||
|
"|------|-----------------|---------|-------|--------------------|",
|
||||||
|
"| 2026-03-07 | 1,234 (+12) | 567 (+5) | 89 (+1) | 1,890 (+18) |",
|
||||||
|
].join("\n")
|
||||||
|
|
||||||
|
const row = parseLastDataRow(content, 5)
|
||||||
|
|
||||||
|
assert.deepEqual(row, ["2026-03-07", "1,234 (+12)", "567 (+5)", "89 (+1)", "1,890 (+18)"])
|
||||||
|
assert.equal(parseCountCell(row[1]), 1234)
|
||||||
|
assert.equal(parseCountCell(row[4]), 1890)
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user