summaryrefslogtreecommitdiffhomepage
path: root/script
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-07-31 01:25:24 -0400
committerDax Raad <[email protected]>2025-07-31 01:25:24 -0400
commit2d9ed06367516daa184ecf70bb0b2451c06da8d1 (patch)
treee735b78999853aa52d77240a4be7d32e0d09556e /script
parent50be2aee39289b7e05299e61d53478736ab914cc (diff)
downloadopencode-2d9ed06367516daa184ecf70bb0b2451c06da8d1.tar.gz
opencode-2d9ed06367516daa184ecf70bb0b2451c06da8d1.zip
ci: scripts
Diffstat (limited to 'script')
-rwxr-xr-xscript/hooks15
-rw-r--r--script/hooks.bat16
-rwxr-xr-xscript/publish.ts28
-rwxr-xr-xscript/release42
-rwxr-xr-xscript/stats.ts204
5 files changed, 305 insertions, 0 deletions
diff --git a/script/hooks b/script/hooks
new file mode 100755
index 000000000..4597c6f41
--- /dev/null
+++ b/script/hooks
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+if [ ! -d ".git" ]; then
+ exit 0
+fi
+
+mkdir -p .git/hooks
+
+cat > .git/hooks/pre-push << 'EOF'
+#!/bin/sh
+bun run typecheck
+EOF
+
+chmod +x .git/hooks/pre-push
+echo "✅ Pre-push hook installed"
diff --git a/script/hooks.bat b/script/hooks.bat
new file mode 100644
index 000000000..52d690100
--- /dev/null
+++ b/script/hooks.bat
@@ -0,0 +1,16 @@
+@echo off
+
+if not exist ".git" (
+ exit /b 0
+)
+
+if not exist ".git\hooks" (
+ mkdir ".git\hooks"
+)
+
+(
+ echo #!/bin/sh
+ echo bun run typecheck
+) > ".git\hooks\pre-push"
+
+echo ✅ Pre-push hook installed
diff --git a/script/publish.ts b/script/publish.ts
new file mode 100755
index 000000000..13716b014
--- /dev/null
+++ b/script/publish.ts
@@ -0,0 +1,28 @@
+#!/usr/bin/env bun
+
+import { $ } from "bun"
+
+const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true"
+const version = snapshot
+ ? `0.0.0-${new Date().toISOString().slice(0, 16).replace(/[-:T]/g, "")}`
+ : process.env["OPENCODE_VERSION"]
+if (!version) {
+ throw new Error("OPENCODE_VERSION is required")
+}
+process.env["OPENCODE_VERSION"] = version
+
+await import(`../packages/opencode/script/publish.ts`)
+await import(`../packages/sdk/stainless/generate.ts`)
+await import(`../packages/sdk/js/script/publish.ts`)
+
+if (!snapshot) {
+ await $`git commit -am "Release v${version}"`
+ await $`git tag v${version}`
+ await $`git push origin HEAD --tags`
+}
+if (snapshot) {
+ await $`git commit --allow-empty -m "Snapshot release v${version}"`
+ await $`git tag v${version}`
+ await $`git push origin v${version}`
+ await $`git reset --soft HEAD~1`
+}
diff --git a/script/release b/script/release
new file mode 100755
index 000000000..631cd5a7b
--- /dev/null
+++ b/script/release
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+
+# Parse command line arguments
+minor=false
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ --minor) minor=true; shift 1;;
+ *) echo "Unknown parameter: $1"; exit 1;;
+ esac
+done
+
+# Get the latest release from GitHub
+latest_tag=$(gh release list --limit 1 --json tagName --jq '.[0].tagName')
+
+# If there is no tag, exit the script
+if [ -z "$latest_tag" ]; then
+ echo "No tags found"
+ exit 1
+fi
+
+echo "Latest tag: $latest_tag"
+
+# Remove the 'v' prefix and split into major, minor, and patch numbers
+version_without_v=${latest_tag#v}
+IFS='.' read -ra VERSION <<< "$version_without_v"
+
+if [ "$minor" = true ]; then
+ # Increment the minor version and reset patch to 0
+ minor_number=${VERSION[1]}
+ let "minor_number++"
+ new_version="${VERSION[0]}.$minor_number.0"
+else
+ # Increment the patch version
+ patch_number=${VERSION[2]}
+ let "patch_number++"
+ new_version="${VERSION[0]}.${VERSION[1]}.$patch_number"
+fi
+
+echo "New version: $new_version"
+
+gh workflow run publish.yml -f version="$new_version"
+
diff --git a/script/stats.ts b/script/stats.ts
new file mode 100755
index 000000000..bce211855
--- /dev/null
+++ b/script/stats.ts
@@ -0,0 +1,204 @@
+#!/usr/bin/env bun
+
+interface Asset {
+ name: string
+ download_count: number
+}
+
+interface Release {
+ tag_name: string
+ name: string
+ assets: Asset[]
+}
+
+interface NpmDownloadsRange {
+ start: string
+ end: string
+ package: string
+ downloads: Array<{
+ downloads: number
+ day: string
+ }>
+}
+
+async function fetchNpmDownloads(packageName: string): Promise<number> {
+ try {
+ // Use a range from 2020 to current year + 5 years to ensure it works forever
+ const currentYear = new Date().getFullYear()
+ const endYear = currentYear + 5
+ const response = await fetch(`https://api.npmjs.org/downloads/range/2020-01-01:${endYear}-12-31/${packageName}`)
+ if (!response.ok) {
+ console.warn(`Failed to fetch npm downloads for ${packageName}: ${response.status}`)
+ return 0
+ }
+ const data: NpmDownloadsRange = await response.json()
+ return data.downloads.reduce((total, day) => total + day.downloads, 0)
+ } catch (error) {
+ console.warn(`Error fetching npm downloads for ${packageName}:`, error)
+ return 0
+ }
+}
+
+async function fetchReleases(): Promise<Release[]> {
+ const releases: Release[] = []
+ let page = 1
+ const per = 100
+
+ while (true) {
+ const url = `https://api.github.com/repos/sst/opencode/releases?page=${page}&per_page=${per}`
+
+ const response = await fetch(url)
+ if (!response.ok) {
+ throw new Error(`GitHub API error: ${response.status} ${response.statusText}`)
+ }
+
+ const batch: Release[] = await response.json()
+ if (batch.length === 0) break
+
+ releases.push(...batch)
+ console.log(`Fetched page ${page} with ${batch.length} releases`)
+
+ if (batch.length < per) break
+ page++
+ await new Promise((resolve) => setTimeout(resolve, 1000))
+ }
+
+ return releases
+}
+
+function calculate(releases: Release[]) {
+ let total = 0
+ const stats = []
+
+ for (const release of releases) {
+ let downloads = 0
+ const assets = []
+
+ for (const asset of release.assets) {
+ downloads += asset.download_count
+ assets.push({
+ name: asset.name,
+ downloads: asset.download_count,
+ })
+ }
+
+ total += downloads
+ stats.push({
+ tag: release.tag_name,
+ name: release.name,
+ downloads,
+ assets,
+ })
+ }
+
+ return { total, stats }
+}
+
+async function save(githubTotal: number, npmDownloads: number) {
+ const file = "STATS.md"
+ const date = new Date().toISOString().split("T")[0]
+ const total = githubTotal + npmDownloads
+
+ let previousGithub = 0
+ let previousNpm = 0
+ let previousTotal = 0
+ let content = ""
+
+ try {
+ content = await Bun.file(file).text()
+ const lines = content.trim().split("\n")
+
+ for (let i = lines.length - 1; i >= 0; i--) {
+ const line = lines[i].trim()
+ if (line.startsWith("|") && !line.includes("Date") && !line.includes("---")) {
+ const match = line.match(
+ /\|\s*[\d-]+\s*\|\s*([\d,]+)\s*(?:\([^)]*\))?\s*\|\s*([\d,]+)\s*(?:\([^)]*\))?\s*\|\s*([\d,]+)\s*(?:\([^)]*\))?\s*\|/,
+ )
+ if (match) {
+ previousGithub = parseInt(match[1].replace(/,/g, ""))
+ previousNpm = parseInt(match[2].replace(/,/g, ""))
+ previousTotal = parseInt(match[3].replace(/,/g, ""))
+ break
+ }
+ }
+ }
+ } catch {
+ content =
+ "# Download Stats\n\n| Date | GitHub Downloads | npm Downloads | Total |\n|------|------------------|---------------|-------|\n"
+ }
+
+ const githubChange = githubTotal - previousGithub
+ const npmChange = npmDownloads - previousNpm
+ const totalChange = total - previousTotal
+
+ const githubChangeStr =
+ githubChange > 0
+ ? ` (+${githubChange.toLocaleString()})`
+ : githubChange < 0
+ ? ` (${githubChange.toLocaleString()})`
+ : " (+0)"
+ const npmChangeStr =
+ npmChange > 0 ? ` (+${npmChange.toLocaleString()})` : npmChange < 0 ? ` (${npmChange.toLocaleString()})` : " (+0)"
+ const totalChangeStr =
+ totalChange > 0
+ ? ` (+${totalChange.toLocaleString()})`
+ : totalChange < 0
+ ? ` (${totalChange.toLocaleString()})`
+ : " (+0)"
+ const line = `| ${date} | ${githubTotal.toLocaleString()}${githubChangeStr} | ${npmDownloads.toLocaleString()}${npmChangeStr} | ${total.toLocaleString()}${totalChangeStr} |\n`
+
+ if (!content.includes("# Download Stats")) {
+ content =
+ "# Download Stats\n\n| Date | GitHub Downloads | npm Downloads | Total |\n|------|------------------|---------------|-------|\n"
+ }
+
+ await Bun.write(file, content + line)
+ await Bun.spawn(["bunx", "prettier", "--write", file]).exited
+
+ console.log(
+ `\nAppended stats to ${file}: GitHub ${githubTotal.toLocaleString()}${githubChangeStr}, npm ${npmDownloads.toLocaleString()}${npmChangeStr}, Total ${total.toLocaleString()}${totalChangeStr}`,
+ )
+}
+
+console.log("Fetching GitHub releases for sst/opencode...\n")
+
+const releases = await fetchReleases()
+console.log(`\nFetched ${releases.length} releases total\n`)
+
+const { total: githubTotal, stats } = calculate(releases)
+
+console.log("Fetching npm all-time downloads for opencode-ai...\n")
+const npmDownloads = await fetchNpmDownloads("opencode-ai")
+console.log(`Fetched npm all-time downloads: ${npmDownloads.toLocaleString()}\n`)
+
+await save(githubTotal, npmDownloads)
+
+const totalDownloads = githubTotal + npmDownloads
+
+console.log("=".repeat(60))
+console.log(`TOTAL DOWNLOADS: ${totalDownloads.toLocaleString()}`)
+console.log(` GitHub: ${githubTotal.toLocaleString()}`)
+console.log(` npm: ${npmDownloads.toLocaleString()}`)
+console.log("=".repeat(60))
+
+console.log("\nDownloads by release:")
+console.log("-".repeat(60))
+
+stats
+ .sort((a, b) => b.downloads - a.downloads)
+ .forEach((release) => {
+ console.log(`${release.tag.padEnd(15)} ${release.downloads.toLocaleString().padStart(10)} downloads`)
+
+ if (release.assets.length > 1) {
+ release.assets
+ .sort((a, b) => b.downloads - a.downloads)
+ .forEach((asset) => {
+ console.log(` └─ ${asset.name.padEnd(25)} ${asset.downloads.toLocaleString().padStart(8)}`)
+ })
+ }
+ })
+
+console.log("-".repeat(60))
+console.log(`GitHub Total: ${githubTotal.toLocaleString()} downloads across ${releases.length} releases`)
+console.log(`npm Total: ${npmDownloads.toLocaleString()} downloads`)
+console.log(`Combined Total: ${totalDownloads.toLocaleString()} downloads`)