@that-company/dat 0.1.30 → 0.1.32
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 +23 -0
- package/bin/dat.js +1 -1
- package/package.json +5 -2
- package/scripts/prepublish.js +114 -0
package/README.md
CHANGED
|
@@ -28,3 +28,26 @@ curl -fsSL https://thatcompany.ai/install.sh | sh
|
|
|
28
28
|
- `DAT_INSTALL_BASE`: full release asset base URL
|
|
29
29
|
- `DAT_CHANNEL`: release path under `/releases`, default `download/v<package version>`
|
|
30
30
|
- `DAT_SKIP_INSTALL=1`: skip binary download during npm install
|
|
31
|
+
|
|
32
|
+
## Release
|
|
33
|
+
|
|
34
|
+
The npm package version must match an already-published `dat` GitHub Release.
|
|
35
|
+
The installer intentionally downloads from `download/v<package version>` instead
|
|
36
|
+
of `latest/download`, so npm installs are reproducible and the package version
|
|
37
|
+
has one meaning.
|
|
38
|
+
|
|
39
|
+
Release order:
|
|
40
|
+
|
|
41
|
+
1. Publish the `dat` binary release to `theSalted/dat-releases`.
|
|
42
|
+
2. Set this package version to the same `X.Y.Z`.
|
|
43
|
+
3. Run `npm run verify:release`.
|
|
44
|
+
4. Publish with `npm run release`.
|
|
45
|
+
5. Verify `npm view @that-company/dat version` and a temp global install:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
npm install -g --prefix /tmp/dat-npm-check @that-company/dat@latest
|
|
49
|
+
/tmp/dat-npm-check/bin/dat --version
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The `prepublishOnly` hook runs the same release guard if someone uses
|
|
53
|
+
`npm publish --access public` directly.
|
package/bin/dat.js
CHANGED
|
@@ -8,7 +8,7 @@ const { join } = require("node:path");
|
|
|
8
8
|
const binary = join(__dirname, "..", "vendor", process.platform === "win32" ? "dat.exe" : "dat");
|
|
9
9
|
|
|
10
10
|
if (!existsSync(binary)) {
|
|
11
|
-
console.error("dat binary is missing. Reinstall with: npm rebuild -g @
|
|
11
|
+
console.error("dat binary is missing. Reinstall with: npm rebuild -g @that-company/dat");
|
|
12
12
|
process.exit(1);
|
|
13
13
|
}
|
|
14
14
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@that-company/dat",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"description": "Installer for the D.A.T. CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"dat": "bin/dat.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"postinstall": "node scripts/install.js"
|
|
9
|
+
"postinstall": "node scripts/install.js",
|
|
10
|
+
"prepublishOnly": "node scripts/prepublish.js",
|
|
11
|
+
"release": "npm run verify:release && npm publish --access public",
|
|
12
|
+
"verify:release": "node scripts/prepublish.js"
|
|
10
13
|
},
|
|
11
14
|
"files": [
|
|
12
15
|
"bin",
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { get, request } = require("node:https");
|
|
5
|
+
const { URL } = require("node:url");
|
|
6
|
+
|
|
7
|
+
const pkg = require("../package.json");
|
|
8
|
+
|
|
9
|
+
const RELEASE_ASSETS = ["dat-darwin-arm64", "dat-darwin-x64", "dat-linux-arm64", "dat-linux-x64"];
|
|
10
|
+
const REPO = process.env.DAT_RELEASE_REPO || "theSalted/dat-releases";
|
|
11
|
+
const CHANNEL = process.env.DAT_CHANNEL || `download/v${pkg.version}`;
|
|
12
|
+
const RELEASE_BASE = trimTrailingSlash(
|
|
13
|
+
process.env.DAT_INSTALL_BASE || `https://github.com/${REPO}/releases/${CHANNEL}`,
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
main().catch((error) => {
|
|
17
|
+
console.error(`dat npm release check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
async function main() {
|
|
22
|
+
if (!/^\d+\.\d+\.\d+$/.test(pkg.version)) {
|
|
23
|
+
throw new Error(`package version must be plain X.Y.Z, got ${pkg.version}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const version = await downloadText(`${RELEASE_BASE}/VERSION`);
|
|
27
|
+
if (version.trim() !== pkg.version) {
|
|
28
|
+
throw new Error(`release VERSION is ${version.trim()}, but package.json is ${pkg.version}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const sums = await downloadText(`${RELEASE_BASE}/SHA256SUMS`);
|
|
32
|
+
for (const asset of RELEASE_ASSETS) {
|
|
33
|
+
if (!hasChecksumEntry(sums, asset)) {
|
|
34
|
+
throw new Error(`SHA256SUMS has no entry for ${asset}`);
|
|
35
|
+
}
|
|
36
|
+
await assertHeadOk(`${RELEASE_BASE}/${asset}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.error(`release check ok: @that-company/dat ${pkg.version} matches ${RELEASE_BASE}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function hasChecksumEntry(sums, asset) {
|
|
43
|
+
return sums
|
|
44
|
+
.split(/\r?\n/)
|
|
45
|
+
.some((line) => {
|
|
46
|
+
const parts = line.trim().split(/\s+/);
|
|
47
|
+
return parts.length === 2 && parts[1] === asset && /^[0-9a-f]{64}$/i.test(parts[0]);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function trimTrailingSlash(value) {
|
|
52
|
+
return value.endsWith("/") ? trimTrailingSlash(value.slice(0, -1)) : value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function downloadText(url, redirects = 0) {
|
|
56
|
+
if (redirects > 5) {
|
|
57
|
+
return Promise.reject(new Error(`too many redirects while downloading ${url}`));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const req = get(new URL(url), (res) => {
|
|
62
|
+
const status = res.statusCode || 0;
|
|
63
|
+
const location = res.headers.location;
|
|
64
|
+
if (status >= 300 && status < 400 && location) {
|
|
65
|
+
res.resume();
|
|
66
|
+
downloadText(new URL(location, url).toString(), redirects + 1).then(resolve, reject);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (status !== 200) {
|
|
70
|
+
res.resume();
|
|
71
|
+
reject(new Error(`GET ${url} -> HTTP ${status}`));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
res.setEncoding("utf8");
|
|
75
|
+
let body = "";
|
|
76
|
+
res.on("data", (chunk) => {
|
|
77
|
+
body += chunk;
|
|
78
|
+
});
|
|
79
|
+
res.on("end", () => resolve(body));
|
|
80
|
+
});
|
|
81
|
+
req.on("error", reject);
|
|
82
|
+
req.setTimeout(30_000, () => {
|
|
83
|
+
req.destroy(new Error(`timeout downloading ${url}`));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function assertHeadOk(url, redirects = 0) {
|
|
89
|
+
if (redirects > 5) {
|
|
90
|
+
return Promise.reject(new Error(`too many redirects while checking ${url}`));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
const req = request(new URL(url), { method: "HEAD" }, (res) => {
|
|
95
|
+
const status = res.statusCode || 0;
|
|
96
|
+
const location = res.headers.location;
|
|
97
|
+
res.resume();
|
|
98
|
+
if (status >= 300 && status < 400 && location) {
|
|
99
|
+
assertHeadOk(new URL(location, url).toString(), redirects + 1).then(resolve, reject);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (status !== 200) {
|
|
103
|
+
reject(new Error(`HEAD ${url} -> HTTP ${status}`));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
resolve();
|
|
107
|
+
});
|
|
108
|
+
req.on("error", reject);
|
|
109
|
+
req.setTimeout(30_000, () => {
|
|
110
|
+
req.destroy(new Error(`timeout checking ${url}`));
|
|
111
|
+
});
|
|
112
|
+
req.end();
|
|
113
|
+
});
|
|
114
|
+
}
|