@rightkit/release 0.2.1 → 0.2.2
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/package.json +1 -1
- package/release.mjs +1 -1
- package/release.test.mjs +1 -1
- package/right-suite-contract.test.mjs +1 -1
- package/upload-large.mjs +43 -54
- package/upload-large.test.mjs +44 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Portable Right Suite release CLI/SDK: signed installers, updater artifacts, patch/update routing, hardening, R2 publish, and RightApps registration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/release.mjs
CHANGED
|
@@ -9,7 +9,7 @@ const HARDENING_SCAN = path.resolve(TOOL_ROOT, "hardeningscan.mjs");
|
|
|
9
9
|
const UPLOAD_LARGE = path.resolve(TOOL_ROOT, "upload-large.mjs");
|
|
10
10
|
const SIGN_WINDOWS = path.resolve(TOOL_ROOT, "sign-windows.mjs");
|
|
11
11
|
const SIGN_UPDATER = path.resolve(TOOL_ROOT, "sign-updater.mjs");
|
|
12
|
-
const VERSION = "0.2.
|
|
12
|
+
const VERSION = "0.2.2";
|
|
13
13
|
const TIERS = new Set(["patch", "update"]);
|
|
14
14
|
const RIGHTKIT_EXPECTED = new Map([
|
|
15
15
|
["@rightkit/license", "^0.1.3"],
|
package/release.test.mjs
CHANGED
|
@@ -18,7 +18,7 @@ function fixture({ signed = true, publish = false, packageJson } = {}) {
|
|
|
18
18
|
name: "fixture",
|
|
19
19
|
scripts: { "release:patch:win": "right-release --platform win --tier patch" },
|
|
20
20
|
dependencies: { "@rightkit/license": "^0.1.3", "@rightkit/logs": "^0.1.1" },
|
|
21
|
-
devDependencies: { "@rightkit/release": "^0.2.
|
|
21
|
+
devDependencies: { "@rightkit/release": "^0.2.2" },
|
|
22
22
|
},
|
|
23
23
|
null,
|
|
24
24
|
2,
|
|
@@ -8,7 +8,7 @@ const workspace = path.resolve(new URL("../..", import.meta.url).pathname.replac
|
|
|
8
8
|
const pubkey = "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDI5Mzk1RjlGRjQ2NjI2MUQKUldRZEptYjBuMTg1S1VSUXlBdFM4WmtzaHArYko0U2hRMDVlSDJmSExVZG82Q0hoQ2srUlhqanAK";
|
|
9
9
|
const licensePackage = "^0.1.3";
|
|
10
10
|
const logsPackage = "^0.1.1";
|
|
11
|
-
const releasePackage = "^0.2.
|
|
11
|
+
const releasePackage = "^0.2.2";
|
|
12
12
|
const apps = [
|
|
13
13
|
{ key: "viewright", root: "viewright", tauri: "src-tauri/tauri.conf.json", host: "viewright.cc" },
|
|
14
14
|
{ key: "scraperight", root: "scraperight", tauri: "src-tauri/tauri.conf.json", host: "scraperight.cc" },
|
package/upload-large.mjs
CHANGED
|
@@ -1,74 +1,63 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { accessSync, statSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const ACCOUNT_ID = "03ae77ccd7a07bcbb2dcfde47fa7ba3a";
|
|
7
|
+
const BUCKETS = new Map([
|
|
8
|
+
["public", "rightapps-downloads"],
|
|
9
|
+
["private", "rightapps-updates"],
|
|
10
|
+
]);
|
|
8
11
|
|
|
9
|
-
const [, , localFile, r2Key,
|
|
12
|
+
const [, , localFile, r2Key, bucketAlias = "public"] = process.argv;
|
|
10
13
|
if (!localFile || !r2Key) {
|
|
11
14
|
console.error("Usage: node upload-large.mjs <localFile> <r2Key> [public|private]");
|
|
12
15
|
process.exit(1);
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
const
|
|
16
|
-
if (!
|
|
17
|
-
console.error(
|
|
18
|
+
const bucket = BUCKETS.get(bucketAlias);
|
|
19
|
+
if (!bucket) {
|
|
20
|
+
console.error(`upload-large: invalid bucket alias ${bucketAlias}; expected public|private`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
accessSync(localFile);
|
|
26
|
+
} catch {
|
|
27
|
+
console.error(`upload-large: missing local file: ${localFile}`);
|
|
18
28
|
process.exit(1);
|
|
19
29
|
}
|
|
20
30
|
|
|
21
|
-
const headers = { Authorization: `Bearer ${token}` };
|
|
22
31
|
const fileSize = statSync(localFile).size;
|
|
32
|
+
const object = `${bucket}/${r2Key}`;
|
|
33
|
+
const wranglerArgs = ["wrangler@4", "r2", "object", "put", object, "--file", path.resolve(localFile), "--remote"];
|
|
34
|
+
const env = cleanCloudflareEnv(process.env);
|
|
23
35
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
method,
|
|
27
|
-
headers: isJson
|
|
28
|
-
? { ...headers, "Content-Type": "application/json" }
|
|
29
|
-
: { ...headers, "Content-Type": "application/octet-stream" },
|
|
30
|
-
};
|
|
31
|
-
if (body != null) opts.body = isJson ? JSON.stringify(body) : body;
|
|
32
|
-
const resp = await fetch(`${WORKER_BASE}${pathname}`, opts);
|
|
33
|
-
const text = await resp.text();
|
|
34
|
-
if (!resp.ok) throw new Error(`${method} ${pathname} -> ${resp.status}: ${text}`);
|
|
35
|
-
return JSON.parse(text);
|
|
36
|
-
}
|
|
36
|
+
console.log(`Uploading ${path.basename(localFile)} (${(fileSize / 1024 / 1024).toFixed(1)} MB) -> ${object}`);
|
|
37
|
+
console.log(`wrangler r2 object put ${object} --file ${path.resolve(localFile)} --remote`);
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const chunks = [];
|
|
41
|
-
const stream = createReadStream(localFile, { start: offset, end: offset + size - 1 });
|
|
42
|
-
stream.on("data", (c) => chunks.push(c));
|
|
43
|
-
stream.on("end", () => resolve(Buffer.concat(chunks)));
|
|
44
|
-
stream.on("error", reject);
|
|
45
|
-
});
|
|
39
|
+
if (process.env.RIGHT_RELEASE_UPLOAD_DRY_RUN === "1") {
|
|
40
|
+
process.exit(0);
|
|
46
41
|
}
|
|
47
42
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const size = Math.min(PART_SIZE, fileSize - offset);
|
|
55
|
-
const partNumber = i + 1;
|
|
56
|
-
const chunk = await readChunk(offset, size);
|
|
57
|
-
const params = `?uploadId=${encodeURIComponent(uploadId)}&key=${encodeURIComponent(r2Key)}&bucket=${bucket}`;
|
|
58
|
-
const { etag } = await api("PUT", `/admin/multipart/part/${partNumber}${params}`, chunk, false);
|
|
59
|
-
parts.push({ partNumber, etag });
|
|
60
|
-
console.log(` part ${partNumber}/${totalParts}`);
|
|
61
|
-
}
|
|
62
|
-
await api("POST", "/admin/multipart/complete", { uploadId, key: r2Key, bucket, parts });
|
|
63
|
-
console.log(`Done. ${r2Key} uploaded to ${bucket}.`);
|
|
43
|
+
const result = spawnSync("npx", wranglerArgs, {
|
|
44
|
+
env,
|
|
45
|
+
stdio: "inherit",
|
|
46
|
+
shell: process.platform === "win32",
|
|
47
|
+
windowsHide: true,
|
|
48
|
+
});
|
|
64
49
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
50
|
+
if (result.error) {
|
|
51
|
+
console.error(`upload-large: failed to start wrangler: ${result.error.message}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
process.exit(result.status ?? 1);
|
|
55
|
+
|
|
56
|
+
function cleanCloudflareEnv(source) {
|
|
57
|
+
const env = { ...source, CLOUDFLARE_ACCOUNT_ID: source.CLOUDFLARE_ACCOUNT_ID || ACCOUNT_ID };
|
|
58
|
+
delete env.CLOUDFLARE_API_KEY;
|
|
59
|
+
delete env.CLOUDFLARE_EMAIL;
|
|
60
|
+
delete env.CF_API_KEY;
|
|
61
|
+
delete env.CF_EMAIL;
|
|
62
|
+
return env;
|
|
74
63
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import test from "node:test";
|
|
8
|
+
|
|
9
|
+
const uploader = fileURLToPath(new URL("./upload-large.mjs", import.meta.url));
|
|
10
|
+
|
|
11
|
+
function fixtureFile() {
|
|
12
|
+
const dir = mkdtempSync(path.join(os.tmpdir(), "right-upload-test-"));
|
|
13
|
+
const file = path.join(dir, "Artifact.exe");
|
|
14
|
+
writeFileSync(file, "artifact");
|
|
15
|
+
return file;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function run(bucket) {
|
|
19
|
+
return spawnSync(process.execPath, [uploader, fixtureFile(), "fixture/updates/windows/current/Artifact.exe", bucket], {
|
|
20
|
+
encoding: "utf8",
|
|
21
|
+
env: { ...process.env, RIGHT_RELEASE_UPLOAD_DRY_RUN: "1" },
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test("uploads public artifacts through wrangler remote R2 downloads bucket", () => {
|
|
26
|
+
const result = run("public");
|
|
27
|
+
assert.equal(result.status, 0, result.stderr);
|
|
28
|
+
assert.match(result.stdout, /wrangler r2 object put rightapps-downloads\/fixture\/updates\/windows\/current\/Artifact\.exe/);
|
|
29
|
+
assert.match(result.stdout, /--remote/);
|
|
30
|
+
assert.doesNotMatch(result.stdout, /workers\.dev|multipart|heardright-models/i);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("uploads private artifacts through wrangler remote R2 updates bucket", () => {
|
|
34
|
+
const result = run("private");
|
|
35
|
+
assert.equal(result.status, 0, result.stderr);
|
|
36
|
+
assert.match(result.stdout, /wrangler r2 object put rightapps-updates\/fixture\/updates\/windows\/current\/Artifact\.exe/);
|
|
37
|
+
assert.match(result.stdout, /--remote/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("refuses unknown bucket aliases", () => {
|
|
41
|
+
const result = run("random");
|
|
42
|
+
assert.notEqual(result.status, 0);
|
|
43
|
+
assert.match(result.stderr, /expected public\|private/);
|
|
44
|
+
});
|