Skip to content

Commit 7894a79

Browse files
tae2089claude
andcommitted
ci: add GitHub Actions release workflow + npm distribution
- .github/workflows/release.yml: build on tag push for 5 platforms (darwin-arm64, darwin-amd64, linux-amd64, linux-arm64, windows-amd64) - npm/package.json: code-context-graph npm package - npm/install.js: postinstall downloads binary from GitHub Releases Install via: npm install -g code-context-graph (or bun install -g) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 11671fb commit 7894a79

3 files changed

Lines changed: 266 additions & 0 deletions

File tree

.github/workflows/release.yml

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
build:
13+
strategy:
14+
matrix:
15+
include:
16+
- os: macos-latest
17+
goos: darwin
18+
goarch: arm64
19+
artifact: ccg-darwin-arm64
20+
- os: macos-13
21+
goos: darwin
22+
goarch: amd64
23+
artifact: ccg-darwin-amd64
24+
- os: ubuntu-latest
25+
goos: linux
26+
goarch: amd64
27+
artifact: ccg-linux-amd64
28+
- os: ubuntu-latest
29+
goos: linux
30+
goarch: arm64
31+
artifact: ccg-linux-arm64
32+
- os: windows-latest
33+
goos: windows
34+
goarch: amd64
35+
artifact: ccg-windows-amd64.exe
36+
37+
runs-on: ${{ matrix.os }}
38+
39+
steps:
40+
- uses: actions/checkout@v4
41+
42+
- uses: actions/setup-go@v5
43+
with:
44+
go-version: '1.23'
45+
46+
- name: Install cross-compiler (Linux ARM64)
47+
if: matrix.goos == 'linux' && matrix.goarch == 'arm64'
48+
run: |
49+
sudo apt-get update
50+
sudo apt-get install -y gcc-aarch64-linux-gnu
51+
echo "CC=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
52+
53+
- name: Build
54+
env:
55+
GOOS: ${{ matrix.goos }}
56+
GOARCH: ${{ matrix.goarch }}
57+
CGO_ENABLED: 1
58+
run: |
59+
go build -tags "fts5" -ldflags "-s -w" -o ${{ matrix.artifact }} ./cmd/ccg/
60+
61+
- name: Compress (Unix)
62+
if: matrix.goos != 'windows'
63+
run: |
64+
tar czf ${{ matrix.artifact }}.tar.gz ${{ matrix.artifact }}
65+
66+
- name: Compress (Windows)
67+
if: matrix.goos == 'windows'
68+
run: |
69+
Compress-Archive -Path ${{ matrix.artifact }} -DestinationPath ccg-windows-amd64.zip
70+
71+
- name: Upload artifact
72+
uses: actions/upload-artifact@v4
73+
with:
74+
name: ${{ matrix.artifact }}
75+
path: |
76+
*.tar.gz
77+
*.zip
78+
79+
release:
80+
needs: build
81+
runs-on: ubuntu-latest
82+
83+
steps:
84+
- uses: actions/checkout@v4
85+
86+
- uses: actions/download-artifact@v4
87+
with:
88+
path: artifacts
89+
merge-multiple: true
90+
91+
- name: Create Release
92+
uses: softprops/action-gh-release@v2
93+
with:
94+
generate_release_notes: true
95+
files: |
96+
artifacts/*.tar.gz
97+
artifacts/*.zip
98+
99+
publish-npm:
100+
needs: release
101+
runs-on: ubuntu-latest
102+
103+
steps:
104+
- uses: actions/checkout@v4
105+
106+
- uses: actions/setup-node@v4
107+
with:
108+
node-version: '20'
109+
registry-url: 'https://registry.npmjs.org'
110+
111+
- name: Set version from tag
112+
run: |
113+
VERSION=${GITHUB_REF#refs/tags/v}
114+
cd npm
115+
npm version $VERSION --no-git-tag-version
116+
117+
- name: Publish to npm
118+
working-directory: npm
119+
env:
120+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
121+
run: npm publish --access public

npm/install.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env node
2+
3+
const { execSync } = require("child_process");
4+
const fs = require("fs");
5+
const path = require("path");
6+
const https = require("https");
7+
const http = require("http");
8+
9+
const pkg = require("./package.json");
10+
const version = pkg.version;
11+
const repo = "tae2089/code-context-graph";
12+
13+
const PLATFORMS = {
14+
"darwin-arm64": "ccg-darwin-arm64",
15+
"darwin-x64": "ccg-darwin-amd64",
16+
"linux-x64": "ccg-linux-amd64",
17+
"linux-arm64": "ccg-linux-arm64",
18+
"win32-x64": "ccg-windows-amd64",
19+
};
20+
21+
function getPlatformKey() {
22+
const platform = process.platform;
23+
const arch = process.arch;
24+
return `${platform}-${arch}`;
25+
}
26+
27+
function getBinaryName() {
28+
const key = getPlatformKey();
29+
const name = PLATFORMS[key];
30+
if (!name) {
31+
console.error(`Unsupported platform: ${key}`);
32+
console.error(`Supported: ${Object.keys(PLATFORMS).join(", ")}`);
33+
process.exit(1);
34+
}
35+
return name;
36+
}
37+
38+
function getDownloadUrl(binaryName) {
39+
const isWindows = process.platform === "win32";
40+
const ext = isWindows ? ".zip" : ".tar.gz";
41+
return `https://github.com/${repo}/releases/download/v${version}/${binaryName}${ext}`;
42+
}
43+
44+
function download(url) {
45+
return new Promise((resolve, reject) => {
46+
const follow = (url) => {
47+
const client = url.startsWith("https") ? https : http;
48+
client.get(url, (res) => {
49+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
50+
follow(res.headers.location);
51+
return;
52+
}
53+
if (res.statusCode !== 200) {
54+
reject(new Error(`HTTP ${res.statusCode} for ${url}`));
55+
return;
56+
}
57+
const chunks = [];
58+
res.on("data", (chunk) => chunks.push(chunk));
59+
res.on("end", () => resolve(Buffer.concat(chunks)));
60+
res.on("error", reject);
61+
}).on("error", reject);
62+
};
63+
follow(url);
64+
});
65+
}
66+
67+
async function install() {
68+
const binaryName = getBinaryName();
69+
const url = getDownloadUrl(binaryName);
70+
const binDir = path.join(__dirname, "bin");
71+
const isWindows = process.platform === "win32";
72+
const binPath = path.join(binDir, isWindows ? "ccg.exe" : "ccg");
73+
74+
// Skip if binary already exists
75+
if (fs.existsSync(binPath)) {
76+
console.log(`ccg already installed at ${binPath}`);
77+
return;
78+
}
79+
80+
console.log(`Downloading ccg v${version} for ${getPlatformKey()}...`);
81+
console.log(` ${url}`);
82+
83+
try {
84+
const data = await download(url);
85+
fs.mkdirSync(binDir, { recursive: true });
86+
87+
if (isWindows) {
88+
// Write zip and extract
89+
const zipPath = path.join(binDir, "ccg.zip");
90+
fs.writeFileSync(zipPath, data);
91+
execSync(`powershell -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${binDir}' -Force"`, { stdio: "ignore" });
92+
fs.unlinkSync(zipPath);
93+
} else {
94+
// Write tar.gz and extract
95+
const tarPath = path.join(binDir, "ccg.tar.gz");
96+
fs.writeFileSync(tarPath, data);
97+
execSync(`tar xzf "${tarPath}" -C "${binDir}"`, { stdio: "ignore" });
98+
fs.unlinkSync(tarPath);
99+
100+
// Rename platform-specific binary to 'ccg'
101+
const extracted = path.join(binDir, binaryName);
102+
if (fs.existsSync(extracted) && extracted !== binPath) {
103+
fs.renameSync(extracted, binPath);
104+
}
105+
fs.chmodSync(binPath, 0o755);
106+
}
107+
108+
console.log(`ccg v${version} installed successfully.`);
109+
} catch (err) {
110+
console.error(`Failed to install ccg: ${err.message}`);
111+
console.error(`You can build manually: CGO_ENABLED=1 go build -tags "fts5" -o ccg ./cmd/ccg/`);
112+
process.exit(1);
113+
}
114+
}
115+
116+
install();

npm/package.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "code-context-graph",
3+
"version": "0.0.0",
4+
"description": "Local code analysis tool — parse codebases into knowledge graphs with 15 language support, annotation search, and Cypher queries",
5+
"keywords": [
6+
"code-analysis",
7+
"knowledge-graph",
8+
"tree-sitter",
9+
"mcp",
10+
"cypher",
11+
"apache-age",
12+
"annotations"
13+
],
14+
"repository": {
15+
"type": "git",
16+
"url": "https://github.com/tae2089/code-context-graph"
17+
},
18+
"license": "MIT",
19+
"bin": {
20+
"ccg": "bin/ccg"
21+
},
22+
"scripts": {
23+
"postinstall": "node install.js"
24+
},
25+
"files": [
26+
"install.js",
27+
"bin/"
28+
]
29+
}

0 commit comments

Comments
 (0)