greenlight-cc 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +147 -0
- package/package.json +26 -0
package/bin/index.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const https = require("https");
|
|
9
|
+
const zlib = require("zlib");
|
|
10
|
+
const { spawn } = require("child_process");
|
|
11
|
+
|
|
12
|
+
const pkg = require("../package.json");
|
|
13
|
+
const VERSION = pkg.version;
|
|
14
|
+
const REPO = "atlantic-blue/greenlight";
|
|
15
|
+
|
|
16
|
+
const ARCH_MAP = { x64: "amd64", arm64: "arm64" };
|
|
17
|
+
const PLATFORM_MAP = { darwin: "darwin", linux: "linux" };
|
|
18
|
+
|
|
19
|
+
function getDownloadUrl() {
|
|
20
|
+
const platform = PLATFORM_MAP[os.platform()];
|
|
21
|
+
const arch = ARCH_MAP[os.arch()];
|
|
22
|
+
|
|
23
|
+
if (!platform) {
|
|
24
|
+
console.error(
|
|
25
|
+
`Unsupported platform: ${os.platform()}. Greenlight supports macOS and Linux.`
|
|
26
|
+
);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!arch) {
|
|
31
|
+
console.error(
|
|
32
|
+
`Unsupported architecture: ${os.arch()}. Greenlight supports x64 and arm64.`
|
|
33
|
+
);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const filename = `greenlight_${VERSION}_${platform}_${arch}.tar.gz`;
|
|
38
|
+
return `https://github.com/${REPO}/releases/download/v${VERSION}/${filename}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function fetch(url) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
https
|
|
44
|
+
.get(url, (res) => {
|
|
45
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
46
|
+
return fetch(res.headers.location).then(resolve, reject);
|
|
47
|
+
}
|
|
48
|
+
if (res.statusCode !== 200) {
|
|
49
|
+
reject(
|
|
50
|
+
new Error(
|
|
51
|
+
`Download failed (HTTP ${res.statusCode}). ` +
|
|
52
|
+
`Make sure v${VERSION} exists at https://github.com/${REPO}/releases`
|
|
53
|
+
)
|
|
54
|
+
);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
resolve(res);
|
|
58
|
+
})
|
|
59
|
+
.on("error", reject);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function extractTarGz(stream, destDir) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const gunzip = zlib.createGunzip();
|
|
66
|
+
const chunks = [];
|
|
67
|
+
let totalSize = 0;
|
|
68
|
+
|
|
69
|
+
stream
|
|
70
|
+
.pipe(gunzip)
|
|
71
|
+
.on("data", (chunk) => {
|
|
72
|
+
chunks.push(chunk);
|
|
73
|
+
totalSize += chunk.length;
|
|
74
|
+
})
|
|
75
|
+
.on("end", () => {
|
|
76
|
+
const buffer = Buffer.concat(chunks, totalSize);
|
|
77
|
+
let offset = 0;
|
|
78
|
+
|
|
79
|
+
while (offset < buffer.length) {
|
|
80
|
+
// tar header is 512 bytes
|
|
81
|
+
if (offset + 512 > buffer.length) break;
|
|
82
|
+
const header = buffer.subarray(offset, offset + 512);
|
|
83
|
+
|
|
84
|
+
// Check for end-of-archive (two consecutive zero blocks)
|
|
85
|
+
if (header.every((b) => b === 0)) break;
|
|
86
|
+
|
|
87
|
+
const name = header.subarray(0, 100).toString("utf8").replace(/\0/g, "");
|
|
88
|
+
const sizeOctal = header.subarray(124, 136).toString("utf8").replace(/\0/g, "").trim();
|
|
89
|
+
const size = parseInt(sizeOctal, 8) || 0;
|
|
90
|
+
const typeFlag = header[156];
|
|
91
|
+
|
|
92
|
+
offset += 512; // move past header
|
|
93
|
+
|
|
94
|
+
if (typeFlag === 48 || typeFlag === 0) {
|
|
95
|
+
// Regular file (type '0' or null)
|
|
96
|
+
const content = buffer.subarray(offset, offset + size);
|
|
97
|
+
const filePath = path.join(destDir, path.basename(name));
|
|
98
|
+
fs.writeFileSync(filePath, content);
|
|
99
|
+
fs.chmodSync(filePath, 0o755);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Advance past file data, padded to 512-byte boundary
|
|
103
|
+
offset += Math.ceil(size / 512) * 512;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
resolve();
|
|
107
|
+
})
|
|
108
|
+
.on("error", reject);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function run() {
|
|
113
|
+
const url = getDownloadUrl();
|
|
114
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "greenlight-"));
|
|
115
|
+
const binaryPath = path.join(tempDir, "greenlight");
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const stream = await fetch(url);
|
|
119
|
+
await extractTarGz(stream, tempDir);
|
|
120
|
+
|
|
121
|
+
if (!fs.existsSync(binaryPath)) {
|
|
122
|
+
console.error("Failed to extract greenlight binary from archive.");
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const args = process.argv.slice(2);
|
|
127
|
+
const child = spawn(binaryPath, args, { stdio: "inherit" });
|
|
128
|
+
|
|
129
|
+
child.on("close", (code) => {
|
|
130
|
+
// Clean up temp directory
|
|
131
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
132
|
+
process.exit(code);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
child.on("error", (err) => {
|
|
136
|
+
console.error(`Failed to run greenlight: ${err.message}`);
|
|
137
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
138
|
+
process.exit(1);
|
|
139
|
+
});
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.error(err.message);
|
|
142
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
run();
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "greenlight-cc",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "TDD-first development system for Claude Code",
|
|
5
|
+
"bin": {
|
|
6
|
+
"greenlight-cc": "bin/index.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"claude-code",
|
|
13
|
+
"tdd",
|
|
14
|
+
"testing",
|
|
15
|
+
"ai",
|
|
16
|
+
"developer-tools"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/atlantic-blue/greenlight.git"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=16.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|