@thaddeus.run/lazythad 0.1.0-alpha
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/README.md +19 -0
- package/bin/lazythad.cjs +20 -0
- package/download.cjs +141 -0
- package/install.cjs +15 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @thaddeus.run/lazythad
|
|
2
|
+
|
|
3
|
+
A lazygit-style terminal UI for [Thaddeus](https://thaddeus.run) (Rust ·
|
|
4
|
+
[ratatui](https://ratatui.rs)) — browse a server's repos, the op log, the signed
|
|
5
|
+
why, vetoes, and reputation. Distributed as a prebuilt binary. Installs the
|
|
6
|
+
`lazythad` command.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npm i -g @thaddeus.run/lazythad@alpha
|
|
10
|
+
lazythad http://localhost:4000
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Installing fetches the `lazythad` binary for your platform from the
|
|
14
|
+
[GitHub releases](https://github.com/thaddeus-run/thaddeus/releases) (with a
|
|
15
|
+
download-on-first-run fallback). The npm package is just the launcher.
|
|
16
|
+
|
|
17
|
+
Prefer a single command?
|
|
18
|
+
`curl -fsSL https://raw.githubusercontent.com/thaddeus-run/thaddeus/main/install.sh | sh`
|
|
19
|
+
installs both `thaddeus` and `lazythad` and sets up your `PATH`.
|
package/bin/lazythad.cjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Launcher: ensure the prebuilt `lazythad` binary exists (download on first run
|
|
3
|
+
// if the postinstall was skipped), then exec it with the passed args.
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const { spawnSync } = require('node:child_process');
|
|
7
|
+
const { ensureBinary } = require('../download.cjs');
|
|
8
|
+
|
|
9
|
+
ensureBinary('lazythad')
|
|
10
|
+
.then((bin) => {
|
|
11
|
+
const res = spawnSync(bin, process.argv.slice(2), { stdio: 'inherit' });
|
|
12
|
+
if (res.error) {
|
|
13
|
+
throw res.error;
|
|
14
|
+
}
|
|
15
|
+
process.exit(res.status === null ? 1 : res.status);
|
|
16
|
+
})
|
|
17
|
+
.catch((err) => {
|
|
18
|
+
console.error(`lazythad: ${err.message}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
package/download.cjs
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Resolve and fetch the prebuilt binary for this platform from the GitHub
|
|
2
|
+
// Release matching this package's version, verified against the release's
|
|
3
|
+
// SHA256SUMS. Shared by the postinstall (install.cjs) and the launcher
|
|
4
|
+
// (bin/*.cjs), so the tool works even when install scripts are disabled — the
|
|
5
|
+
// launcher downloads on first run.
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const https = require('node:https');
|
|
11
|
+
const crypto = require('node:crypto');
|
|
12
|
+
|
|
13
|
+
const REPO = 'thaddeus-run/thaddeus';
|
|
14
|
+
const MAX_REDIRECTS = 10;
|
|
15
|
+
|
|
16
|
+
// The release asset name for the current platform, e.g. `thaddeus-darwin-arm64`.
|
|
17
|
+
function assetFor(tool) {
|
|
18
|
+
const os = { linux: 'linux', darwin: 'darwin', win32: 'windows' }[
|
|
19
|
+
process.platform
|
|
20
|
+
];
|
|
21
|
+
const arch = { x64: 'x64', arm64: 'arm64' }[process.arch];
|
|
22
|
+
if (!os || !arch) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`unsupported platform ${process.platform}-${process.arch}; ` +
|
|
25
|
+
'build from source or use the install script'
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
const ext = os === 'windows' ? '.exe' : '';
|
|
29
|
+
return { asset: `${tool}-${os}-${arch}${ext}`, ext };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// GET a URL, following redirects up to a bounded depth (a redirect cycle would
|
|
33
|
+
// otherwise exhaust the stack). `onResponse(res)` consumes the final 200 body.
|
|
34
|
+
function request(url, onResponse, redirects = 0) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
if (redirects > MAX_REDIRECTS) {
|
|
37
|
+
reject(new Error(`too many redirects fetching ${url}`));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
https
|
|
41
|
+
.get(
|
|
42
|
+
url,
|
|
43
|
+
{ headers: { 'user-agent': 'thaddeus-npm-installer' } },
|
|
44
|
+
(res) => {
|
|
45
|
+
if (
|
|
46
|
+
res.statusCode >= 300 &&
|
|
47
|
+
res.statusCode < 400 &&
|
|
48
|
+
res.headers.location
|
|
49
|
+
) {
|
|
50
|
+
res.resume();
|
|
51
|
+
resolve(request(res.headers.location, onResponse, redirects + 1));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (res.statusCode !== 200) {
|
|
55
|
+
res.resume();
|
|
56
|
+
reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
onResponse(res, resolve, reject);
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
.on('error', reject);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Download a URL to `dest`.
|
|
67
|
+
function download(url, dest) {
|
|
68
|
+
return request(url, (res, resolve, reject) => {
|
|
69
|
+
const file = fs.createWriteStream(dest);
|
|
70
|
+
res.pipe(file);
|
|
71
|
+
file.on('finish', () => file.close(() => resolve()));
|
|
72
|
+
file.on('error', reject);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Fetch a URL's text body (used for SHA256SUMS).
|
|
77
|
+
function fetchText(url) {
|
|
78
|
+
return request(url, (res, resolve) => {
|
|
79
|
+
let body = '';
|
|
80
|
+
res.setEncoding('utf8');
|
|
81
|
+
res.on('data', (chunk) => (body += chunk));
|
|
82
|
+
res.on('end', () => resolve(body));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function sha256(file) {
|
|
87
|
+
return crypto
|
|
88
|
+
.createHash('sha256')
|
|
89
|
+
.update(fs.readFileSync(file))
|
|
90
|
+
.digest('hex');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// The expected hex digest for `asset` from a `SHA256SUMS` body (lines are
|
|
94
|
+
// `<hash> <name>` or `<hash> *<name>`); null if the asset is not listed.
|
|
95
|
+
function expectedHash(sums, asset) {
|
|
96
|
+
for (const line of sums.split('\n')) {
|
|
97
|
+
const m = line.trim().match(/^([0-9a-f]{64})\s+\*?(.+)$/i);
|
|
98
|
+
if (m && m[2] === asset) {
|
|
99
|
+
return m[1].toLowerCase();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Ensure the binary exists locally (idempotent) and return its path. The
|
|
106
|
+
// download is checksum-verified against the release's SHA256SUMS before it is
|
|
107
|
+
// trusted; a mismatch or a missing entry is fatal.
|
|
108
|
+
async function ensureBinary(tool) {
|
|
109
|
+
const { version } = require('./package.json');
|
|
110
|
+
const { asset, ext } = assetFor(tool);
|
|
111
|
+
const binDir = path.join(__dirname, 'bin');
|
|
112
|
+
const binPath = path.join(binDir, `${tool}${ext}`);
|
|
113
|
+
if (fs.existsSync(binPath)) {
|
|
114
|
+
return binPath;
|
|
115
|
+
}
|
|
116
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
117
|
+
const releaseBase = `https://github.com/${REPO}/releases/download/v${version}`;
|
|
118
|
+
const tmp = `${binPath}.download`;
|
|
119
|
+
try {
|
|
120
|
+
await download(`${releaseBase}/${asset}`, tmp);
|
|
121
|
+
const sums = await fetchText(`${releaseBase}/SHA256SUMS`);
|
|
122
|
+
const expected = expectedHash(sums, asset);
|
|
123
|
+
if (!expected) {
|
|
124
|
+
throw new Error(`no checksum for ${asset} in SHA256SUMS`);
|
|
125
|
+
}
|
|
126
|
+
const actual = sha256(tmp);
|
|
127
|
+
if (actual !== expected) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`checksum mismatch for ${asset}: expected ${expected}, got ${actual}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
fs.chmodSync(tmp, 0o755);
|
|
133
|
+
fs.renameSync(tmp, binPath);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
fs.rmSync(tmp, { force: true });
|
|
136
|
+
throw new Error(`failed to install ${asset}: ${err.message}`);
|
|
137
|
+
}
|
|
138
|
+
return binPath;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = { ensureBinary };
|
package/install.cjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// postinstall: prefetch the binary so the first `lazythad` run is instant.
|
|
2
|
+
// Never fail the install — the launcher downloads on first run as a fallback
|
|
3
|
+
// (e.g. when install scripts are disabled or the machine is offline).
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const { ensureBinary } = require('./download.cjs');
|
|
7
|
+
|
|
8
|
+
ensureBinary('lazythad').then(
|
|
9
|
+
(bin) => console.log(`lazythad: ready (${bin})`),
|
|
10
|
+
(err) =>
|
|
11
|
+
console.warn(
|
|
12
|
+
`lazythad: could not prefetch the binary (${err.message}); ` +
|
|
13
|
+
'it will download on first run.'
|
|
14
|
+
)
|
|
15
|
+
);
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thaddeus.run/lazythad",
|
|
3
|
+
"version": "0.1.0-alpha",
|
|
4
|
+
"description": "lazythad — a lazygit-style terminal UI for Thaddeus. Installs the prebuilt `lazythad` binary for your platform.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"lazygit",
|
|
7
|
+
"ratatui",
|
|
8
|
+
"source-control",
|
|
9
|
+
"thaddeus",
|
|
10
|
+
"tui"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://thaddeus.run",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/thaddeus-run/thaddeus/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/thaddeus-run/thaddeus.git",
|
|
20
|
+
"directory": "npm/lazythad"
|
|
21
|
+
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"lazythad": "bin/lazythad.cjs"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"bin/lazythad.cjs",
|
|
27
|
+
"download.cjs",
|
|
28
|
+
"install.cjs"
|
|
29
|
+
],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"postinstall": "node install.cjs"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18"
|
|
38
|
+
}
|
|
39
|
+
}
|