@kroszborg/sugi 0.1.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.
Files changed (3) hide show
  1. package/bin.js +27 -0
  2. package/install.js +160 -0
  3. package/package.json +37 -0
package/bin.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+ const { spawnSync } = require('child_process');
6
+ const fs = require('fs');
7
+
8
+ const binName = process.platform === 'win32' ? 'sugi.exe' : 'sugi';
9
+ const binPath = path.join(__dirname, 'bin', binName);
10
+
11
+ if (!fs.existsSync(binPath)) {
12
+ console.error('sugi: binary not found at ' + binPath);
13
+ console.error('Try reinstalling: npm install -g sugi');
14
+ process.exit(1);
15
+ }
16
+
17
+ const result = spawnSync(binPath, process.argv.slice(2), {
18
+ stdio: 'inherit',
19
+ env: process.env,
20
+ });
21
+
22
+ if (result.error) {
23
+ console.error('sugi: failed to start:', result.error.message);
24
+ process.exit(1);
25
+ }
26
+
27
+ process.exit(result.status ?? 0);
package/install.js ADDED
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+ // npm binary wrapper for sugi
3
+ // Downloads the correct pre-built binary from GitHub Releases on postinstall.
4
+ // Falls back to `go build` if no release exists yet (local / dev install).
5
+
6
+ 'use strict';
7
+
8
+ const https = require('https');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const os = require('os');
12
+ const { execSync, spawnSync } = require('child_process');
13
+
14
+ const VERSION = require('./package.json').version;
15
+ const REPO = 'Kroszborg/sugi';
16
+ const BIN_DIR = path.join(__dirname, 'bin');
17
+
18
+ function platformInfo() {
19
+ const platform = process.platform;
20
+ const arch = process.arch;
21
+
22
+ const osMap = { darwin: 'macOS', linux: 'Linux', win32: 'Windows' };
23
+ const archMap = { x64: 'x86_64', arm64: 'arm64' };
24
+
25
+ const osName = osMap[platform];
26
+ const cpu = archMap[arch];
27
+
28
+ if (!osName || !cpu) {
29
+ throw new Error(`Unsupported platform: ${platform}/${arch}`);
30
+ }
31
+
32
+ const ext = platform === 'win32' ? '.zip' : '.tar.gz';
33
+ const binName = platform === 'win32' ? 'sugi.exe' : 'sugi';
34
+
35
+ return { osName, cpu, ext, binName };
36
+ }
37
+
38
+ function downloadFile(url, dest) {
39
+ return new Promise((resolve, reject) => {
40
+ const file = fs.createWriteStream(dest);
41
+ const get = (u) => {
42
+ https.get(u, { headers: { 'User-Agent': 'sugi-npm-installer' } }, (res) => {
43
+ if (res.statusCode === 301 || res.statusCode === 302) {
44
+ file.close();
45
+ const newFile = fs.createWriteStream(dest);
46
+ return get(res.headers.location);
47
+ }
48
+ if (res.statusCode !== 200) {
49
+ file.close();
50
+ return reject(new Error(`HTTP ${res.statusCode} for ${u}`));
51
+ }
52
+ res.pipe(file);
53
+ file.on('finish', () => { file.close(); resolve(); });
54
+ }).on('error', (e) => { file.close(); reject(e); });
55
+ };
56
+ get(url);
57
+ });
58
+ }
59
+
60
+ function tryGoBuild() {
61
+ // Check if go is available
62
+ const goCheck = spawnSync('go', ['version'], { stdio: 'pipe' });
63
+ if (goCheck.status !== 0) {
64
+ return false;
65
+ }
66
+
67
+ if (!fs.existsSync(BIN_DIR)) fs.mkdirSync(BIN_DIR, { recursive: true });
68
+
69
+ const binName = process.platform === 'win32' ? 'sugi.exe' : 'sugi';
70
+ const binDest = path.join(BIN_DIR, binName);
71
+
72
+ // If we're inside an npm-installed package, try go install as a fallback
73
+ console.log('sugi: building from source via `go install`…');
74
+ const result = spawnSync(
75
+ 'go',
76
+ ['install', `github.com/${REPO}/cmd/sugi@latest`],
77
+ { stdio: 'inherit', env: process.env }
78
+ );
79
+
80
+ if (result.status === 0) {
81
+ // Find the installed binary in GOPATH/bin or GOBIN
82
+ const goEnv = spawnSync('go', ['env', 'GOBIN', 'GOPATH'], { stdio: 'pipe' });
83
+ if (goEnv.status === 0) {
84
+ const [gobin, gopath] = goEnv.stdout.toString().trim().split('\n');
85
+ const candidates = [
86
+ gobin && path.join(gobin.trim(), binName),
87
+ gopath && path.join(gopath.trim(), 'bin', binName),
88
+ ].filter(Boolean);
89
+
90
+ for (const src of candidates) {
91
+ if (fs.existsSync(src)) {
92
+ fs.copyFileSync(src, binDest);
93
+ fs.chmodSync(binDest, 0o755);
94
+ console.log(`sugi: installed to ${binDest}`);
95
+ return true;
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ return false;
102
+ }
103
+
104
+ async function main() {
105
+ const { osName, cpu, ext, binName } = platformInfo();
106
+
107
+ const assetName = `sugi_${VERSION}_${osName}_${cpu}${ext}`;
108
+ const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${assetName}`;
109
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sugi-'));
110
+ const archivePath = path.join(tmpDir, assetName);
111
+
112
+ console.log(`sugi: downloading ${assetName}…`);
113
+
114
+ let downloadOk = false;
115
+ try {
116
+ await downloadFile(url, archivePath);
117
+ downloadOk = true;
118
+ } catch (e) {
119
+ console.error(`sugi: download failed — ${e.message}`);
120
+ }
121
+
122
+ if (!downloadOk) {
123
+ console.log(`sugi: falling back to go install…`);
124
+ const ok = tryGoBuild();
125
+ if (!ok) {
126
+ console.error(`sugi: install failed. Install manually:`);
127
+ console.error(` go install github.com/${REPO}/cmd/sugi@latest`);
128
+ console.error(` or download from: https://github.com/${REPO}/releases`);
129
+ process.exit(1);
130
+ }
131
+ fs.rmSync(tmpDir, { recursive: true, force: true });
132
+ return;
133
+ }
134
+
135
+ if (!fs.existsSync(BIN_DIR)) fs.mkdirSync(BIN_DIR, { recursive: true });
136
+ const binDest = path.join(BIN_DIR, binName);
137
+
138
+ try {
139
+ if (ext === '.tar.gz') {
140
+ execSync(`tar -xzf "${archivePath}" -C "${BIN_DIR}" sugi`, { stdio: 'inherit' });
141
+ } else {
142
+ execSync(
143
+ `powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${BIN_DIR}' -Force"`,
144
+ { stdio: 'inherit' }
145
+ );
146
+ }
147
+ fs.chmodSync(binDest, 0o755);
148
+ console.log(`sugi: installed to ${binDest}`);
149
+ } catch (e) {
150
+ console.error(`sugi: extraction failed — ${e.message}`);
151
+ process.exit(1);
152
+ } finally {
153
+ fs.rmSync(tmpDir, { recursive: true, force: true });
154
+ }
155
+ }
156
+
157
+ main().catch((e) => {
158
+ console.error(e.message);
159
+ process.exit(1);
160
+ });
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@kroszborg/sugi",
3
+ "version": "0.1.0",
4
+ "description": "Terminal UI git client — GitHub/GitLab PRs, AI commit messages, commit graph",
5
+ "keywords": [
6
+ "git",
7
+ "tui",
8
+ "terminal",
9
+ "github",
10
+ "gitlab",
11
+ "ai",
12
+ "cli",
13
+ "lazygit"
14
+ ],
15
+ "homepage": "https://github.com/Kroszborg/sugi",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/Kroszborg/sugi.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/Kroszborg/sugi/issues"
22
+ },
23
+ "license": "MIT",
24
+ "bin": {
25
+ "sugi": "bin.js"
26
+ },
27
+ "scripts": {
28
+ "postinstall": "node install.js"
29
+ },
30
+ "files": [
31
+ "bin.js",
32
+ "install.js"
33
+ ],
34
+ "engines": {
35
+ "node": ">=16"
36
+ }
37
+ }