@ggcode-cli/ggcode 1.0.7

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/ggcode.js ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawnSync } = require("child_process");
4
+ const pkg = require("../package.json");
5
+ const { ensureInstalled } = require("../lib/install");
6
+
7
+ async function main() {
8
+ const binary = await ensureInstalled(process.env.GGCODE_INSTALL_VERSION, pkg.version, true);
9
+ const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });
10
+ if (result.error) {
11
+ throw result.error;
12
+ }
13
+ process.exit(result.status === null ? 1 : result.status);
14
+ }
15
+
16
+ main().catch((err) => {
17
+ console.error(`ggcode npm wrapper failed: ${err.message}`);
18
+ process.exit(1);
19
+ });
package/lib/install.js ADDED
@@ -0,0 +1,186 @@
1
+ const crypto = require("crypto");
2
+ const fs = require("fs");
3
+ const https = require("https");
4
+ const os = require("os");
5
+ const path = require("path");
6
+ const { execFileSync } = require("child_process");
7
+
8
+ const OWNER = "topcheer";
9
+ const REPO = "ggcode";
10
+ const BINARY = process.platform === "win32" ? "ggcode.exe" : "ggcode";
11
+
12
+ function normalizeVersion(version) {
13
+ const envVersion = process.env.GGCODE_INSTALL_VERSION;
14
+ const selected = (envVersion || version || "").trim();
15
+ if (!selected || selected === "latest" || selected.startsWith("0.0.0-")) {
16
+ return "latest";
17
+ }
18
+ return selected.startsWith("v") ? selected : `v${selected}`;
19
+ }
20
+
21
+ function resolveTarget() {
22
+ const platform = process.platform;
23
+ let arch = process.arch;
24
+ if (arch === "x64") {
25
+ arch = "x86_64";
26
+ } else if (arch === "arm64") {
27
+ arch = "arm64";
28
+ } else {
29
+ throw new Error(`Unsupported architecture: ${process.arch}`);
30
+ }
31
+
32
+ let ext = ".tar.gz";
33
+ if (platform === "win32") {
34
+ ext = ".zip";
35
+ } else if (platform !== "linux" && platform !== "darwin") {
36
+ throw new Error(`Unsupported platform: ${platform}`);
37
+ }
38
+
39
+ return {
40
+ platform,
41
+ archiveName: `ggcode_${platform}_${arch}${ext}`,
42
+ archiveExt: ext,
43
+ binaryName: BINARY,
44
+ };
45
+ }
46
+
47
+ function releaseBase(version) {
48
+ if (version === "latest") {
49
+ return `https://github.com/${OWNER}/${REPO}/releases/latest/download`;
50
+ }
51
+ return `https://github.com/${OWNER}/${REPO}/releases/download/${version}`;
52
+ }
53
+
54
+ function cacheRoot() {
55
+ if (process.platform === "win32") {
56
+ return path.join(process.env.LOCALAPPDATA || os.tmpdir(), "ggcode", "npm");
57
+ }
58
+ return path.join(os.homedir(), ".cache", "ggcode", "npm");
59
+ }
60
+
61
+ function installRoot(version, target) {
62
+ return path.join(cacheRoot(), version, `${target.platform}-${process.arch}`);
63
+ }
64
+
65
+ function binaryPath(version, target) {
66
+ return path.join(installRoot(version, target), target.binaryName);
67
+ }
68
+
69
+ async function ensureInstalled(version, packageVersion, quiet) {
70
+ const resolvedVersion = normalizeVersion(version || packageVersion);
71
+ const target = resolveTarget();
72
+ const dest = binaryPath(resolvedVersion, target);
73
+ if (fs.existsSync(dest)) {
74
+ return dest;
75
+ }
76
+
77
+ const base = releaseBase(resolvedVersion);
78
+ const archiveURL = `${base}/${target.archiveName}`;
79
+ const checksumsURL = `${base}/checksums.txt`;
80
+ const archive = await downloadBuffer(archiveURL);
81
+ const checksums = await downloadText(checksumsURL);
82
+ verifyChecksum(target.archiveName, archive, checksums);
83
+
84
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ggcode-npm-"));
85
+ const archivePath = path.join(tempDir, target.archiveName);
86
+ const extractDir = path.join(tempDir, "extract");
87
+ fs.mkdirSync(extractDir, { recursive: true });
88
+ fs.writeFileSync(archivePath, archive);
89
+
90
+ if (!quiet) {
91
+ process.stderr.write(`Downloading ggcode ${resolvedVersion} from GitHub Releases...\n`);
92
+ }
93
+
94
+ extractArchive(target, archivePath, extractDir);
95
+ const extracted = findBinary(extractDir, target.binaryName);
96
+ if (!extracted) {
97
+ throw new Error(`Could not find ${target.binaryName} inside ${target.archiveName}`);
98
+ }
99
+
100
+ const root = installRoot(resolvedVersion, target);
101
+ fs.mkdirSync(root, { recursive: true });
102
+ fs.copyFileSync(extracted, dest);
103
+ if (process.platform !== "win32") {
104
+ fs.chmodSync(dest, 0o755);
105
+ }
106
+ return dest;
107
+ }
108
+
109
+ function verifyChecksum(assetName, archive, checksumsText) {
110
+ const lines = checksumsText.split(/\r?\n/);
111
+ let expected = null;
112
+ for (const line of lines) {
113
+ const parts = line.trim().split(/\s+/);
114
+ if (parts.length >= 2 && parts[parts.length - 1] === assetName) {
115
+ expected = parts[0];
116
+ break;
117
+ }
118
+ }
119
+ if (!expected) {
120
+ throw new Error(`Checksum for ${assetName} not found`);
121
+ }
122
+ const actual = crypto.createHash("sha256").update(archive).digest("hex");
123
+ if (actual.toLowerCase() !== expected.toLowerCase()) {
124
+ throw new Error(`Checksum mismatch for ${assetName}`);
125
+ }
126
+ }
127
+
128
+ function extractArchive(target, archivePath, extractDir) {
129
+ if (target.archiveExt === ".zip") {
130
+ const command = `Expand-Archive -LiteralPath '${archivePath.replace(/'/g, "''")}' -DestinationPath '${extractDir.replace(/'/g, "''")}' -Force`;
131
+ execFileSync("powershell", ["-NoProfile", "-Command", command], { stdio: "ignore" });
132
+ return;
133
+ }
134
+ execFileSync("tar", ["-xzf", archivePath, "-C", extractDir], { stdio: "ignore" });
135
+ }
136
+
137
+ function findBinary(dir, binaryName) {
138
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
139
+ for (const entry of entries) {
140
+ const fullPath = path.join(dir, entry.name);
141
+ if (entry.isDirectory()) {
142
+ const nested = findBinary(fullPath, binaryName);
143
+ if (nested) {
144
+ return nested;
145
+ }
146
+ continue;
147
+ }
148
+ if (entry.isFile() && path.basename(entry.name) === binaryName) {
149
+ return fullPath;
150
+ }
151
+ }
152
+ return null;
153
+ }
154
+
155
+ async function downloadText(url) {
156
+ return (await downloadBuffer(url)).toString("utf8");
157
+ }
158
+
159
+ function downloadBuffer(url) {
160
+ return new Promise((resolve, reject) => {
161
+ const get = (target) => {
162
+ https
163
+ .get(target, (res) => {
164
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
165
+ get(res.headers.location);
166
+ return;
167
+ }
168
+ if (res.statusCode !== 200) {
169
+ reject(new Error(`${target} returned ${res.statusCode}`));
170
+ return;
171
+ }
172
+ const chunks = [];
173
+ res.on("data", (chunk) => chunks.push(chunk));
174
+ res.on("end", () => resolve(Buffer.concat(chunks)));
175
+ })
176
+ .on("error", reject);
177
+ };
178
+ get(url);
179
+ });
180
+ }
181
+
182
+ module.exports = {
183
+ ensureInstalled,
184
+ normalizeVersion,
185
+ resolveTarget,
186
+ };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@ggcode-cli/ggcode",
3
+ "version": "1.0.7",
4
+ "description": "Thin npm wrapper that installs the ggcode GitHub Release binary",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/topcheer/ggcode.git"
9
+ },
10
+ "homepage": "https://github.com/topcheer/ggcode",
11
+ "bugs": {
12
+ "url": "https://github.com/topcheer/ggcode/issues"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public",
16
+ "provenance": true
17
+ },
18
+ "bin": {
19
+ "ggcode": "bin/ggcode.js"
20
+ },
21
+ "files": [
22
+ "bin",
23
+ "lib",
24
+ "scripts"
25
+ ],
26
+ "scripts": {
27
+ "postinstall": "node ./scripts/postinstall.js"
28
+ },
29
+ "keywords": [
30
+ "ai",
31
+ "cli",
32
+ "coding",
33
+ "ggcode"
34
+ ]
35
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ const pkg = require("../package.json");
4
+ const { ensureInstalled } = require("../lib/install");
5
+
6
+ ensureInstalled(process.env.GGCODE_INSTALL_VERSION, pkg.version, false).catch((err) => {
7
+ console.warn(`ggcode postinstall warning: ${err.message}`);
8
+ console.warn("The wrapper will try again on first run.");
9
+ });