@rightkit/release 0.2.1 → 0.2.3
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/publish-update.mjs +85 -29
- package/publish-update.test.mjs +18 -7
- package/release.mjs +1 -1
- package/release.test.mjs +13 -2
- 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.3",
|
|
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/publish-update.mjs
CHANGED
|
@@ -33,24 +33,80 @@ const root = path.dirname(configPath);
|
|
|
33
33
|
const config = (await import(pathToFileURL(configPath))).default;
|
|
34
34
|
const target = config?.targets?.[platform];
|
|
35
35
|
const artifacts = target?.updater?.artifacts ?? [];
|
|
36
|
-
if (!artifacts.length) fail(`${config?.app ?? "app"} ${platform} has no updater.artifacts`);
|
|
37
36
|
const installers = target?.installer?.artifacts ?? [];
|
|
38
37
|
|
|
39
38
|
if (tier === "patch") {
|
|
40
39
|
if (!installers.length) fail(`${config?.app ?? "app"} ${platform} patch has no installer.artifacts`);
|
|
40
|
+
if (!artifacts.length) fail(`${config?.app ?? "app"} ${platform} patch has no updater.artifacts for the free app-update manifest`);
|
|
41
41
|
for (const installer of installers) {
|
|
42
42
|
assertCurrentKey(installer.key, "installer");
|
|
43
43
|
const file = path.resolve(root, installer.file);
|
|
44
44
|
if (!dryRun) await access(file).catch(() => fail(`missing signed installer: ${installer.file}`));
|
|
45
45
|
}
|
|
46
|
+
const platforms = {};
|
|
47
|
+
const registrations = new Map();
|
|
48
|
+
const uploadedKeys = new Set();
|
|
49
|
+
for (const artifact of artifacts) {
|
|
50
|
+
assertCurrentKey(artifact.key, "updater");
|
|
51
|
+
const installerKey = patchInstallerKey(artifact);
|
|
52
|
+
assertCurrentKey(installerKey, "installer");
|
|
53
|
+
const file = path.resolve(root, artifact.file);
|
|
54
|
+
const signatureFile = path.resolve(root, artifact.signature);
|
|
55
|
+
if (!dryRun) {
|
|
56
|
+
await access(file).catch(() => fail(`missing patch updater payload: ${artifact.file}`));
|
|
57
|
+
await access(signatureFile).catch(() => fail(`missing patch updater signature: ${artifact.signature}`));
|
|
58
|
+
}
|
|
59
|
+
if (!uploadedKeys.has(installerKey)) {
|
|
60
|
+
console.log(`${dryRun ? "[dry-run] " : ""}UPLOAD public/${installerKey}`);
|
|
61
|
+
if (!dryRun) await run(process.execPath, [UPLOAD, file, installerKey, "public"], root);
|
|
62
|
+
uploadedKeys.add(installerKey);
|
|
63
|
+
}
|
|
64
|
+
const signature = dryRun ? `<signature:${artifact.signature}>` : (await readFile(signatureFile, "utf8")).trim();
|
|
65
|
+
const artifactKey = artifact.artifactKey || installerKey.replaceAll("/", "__");
|
|
66
|
+
const metadata = dryRun ? { sha256: null, sizeBytes: null } : await fileMetadata(file);
|
|
67
|
+
platforms[artifact.platform] = {
|
|
68
|
+
signature,
|
|
69
|
+
url: `${PUBLIC_BASE}/${installerKey}`,
|
|
70
|
+
};
|
|
71
|
+
registrations.set(installerKey, { artifactKey, path: installerKey, ...metadata });
|
|
72
|
+
}
|
|
73
|
+
for (const installer of installers) {
|
|
74
|
+
const file = path.resolve(root, installer.file);
|
|
75
|
+
if (!uploadedKeys.has(installer.key)) {
|
|
76
|
+
console.log(`${dryRun ? "[dry-run] " : ""}UPLOAD public/${installer.key}`);
|
|
77
|
+
if (!dryRun) await run(process.execPath, [UPLOAD, file, installer.key, "public"], root);
|
|
78
|
+
uploadedKeys.add(installer.key);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const version = config.version || target.updater.version;
|
|
82
|
+
if (!version) fail(`${config.app} config must expose version or targets.${platform}.updater.version`);
|
|
83
|
+
const body = {
|
|
84
|
+
appKey: config.app,
|
|
85
|
+
version,
|
|
86
|
+
channel: config.channel || "stable",
|
|
87
|
+
platform: null,
|
|
88
|
+
manifest: {
|
|
89
|
+
version,
|
|
90
|
+
tier,
|
|
91
|
+
notes: process.env.RIGHT_RELEASE_NOTES || `${config.app} ${version}`,
|
|
92
|
+
pub_date: new Date().toISOString(),
|
|
93
|
+
platforms,
|
|
94
|
+
},
|
|
95
|
+
artifacts: [...registrations.values()],
|
|
96
|
+
};
|
|
97
|
+
console.log(`${dryRun ? "[dry-run] " : ""}POST ${API_BASE}/v1/admin/apps/patches`);
|
|
98
|
+
console.log(JSON.stringify(body));
|
|
99
|
+
if (!dryRun) await register("/v1/admin/apps/patches", body);
|
|
100
|
+
process.exit(0);
|
|
46
101
|
}
|
|
47
102
|
|
|
103
|
+
if (!artifacts.length) fail(`${config?.app ?? "app"} ${platform} update has no updater.artifacts`);
|
|
104
|
+
|
|
48
105
|
const platforms = {};
|
|
49
106
|
const registrations = new Map();
|
|
50
107
|
const uploadedKeys = new Set();
|
|
51
108
|
for (const artifact of artifacts) {
|
|
52
109
|
assertCurrentKey(artifact.key, "updater");
|
|
53
|
-
const bucket = tier === "patch" ? "public" : "private";
|
|
54
110
|
const file = path.resolve(root, artifact.file);
|
|
55
111
|
const signatureFile = path.resolve(root, artifact.signature);
|
|
56
112
|
if (!dryRun) {
|
|
@@ -58,8 +114,8 @@ for (const artifact of artifacts) {
|
|
|
58
114
|
await access(signatureFile).catch(() => fail(`missing updater signature: ${artifact.signature}`));
|
|
59
115
|
}
|
|
60
116
|
if (!uploadedKeys.has(artifact.key)) {
|
|
61
|
-
console.log(`${dryRun ? "[dry-run] " : ""}UPLOAD
|
|
62
|
-
if (!dryRun) await run(process.execPath, [UPLOAD, file, artifact.key,
|
|
117
|
+
console.log(`${dryRun ? "[dry-run] " : ""}UPLOAD private/${artifact.key}`);
|
|
118
|
+
if (!dryRun) await run(process.execPath, [UPLOAD, file, artifact.key, "private"], root);
|
|
63
119
|
uploadedKeys.add(artifact.key);
|
|
64
120
|
}
|
|
65
121
|
|
|
@@ -68,19 +124,11 @@ for (const artifact of artifacts) {
|
|
|
68
124
|
const metadata = dryRun ? { sha256: null, sizeBytes: null } : await fileMetadata(file);
|
|
69
125
|
platforms[artifact.platform] = {
|
|
70
126
|
signature,
|
|
71
|
-
url:
|
|
127
|
+
url: `${DOWNLOAD_BASE}/${artifact.key}`,
|
|
72
128
|
};
|
|
73
129
|
registrations.set(artifact.key, { artifactKey, path: artifact.key, ...metadata });
|
|
74
130
|
}
|
|
75
131
|
|
|
76
|
-
if (tier === "patch") {
|
|
77
|
-
for (const installer of installers) {
|
|
78
|
-
const file = path.resolve(root, installer.file);
|
|
79
|
-
console.log(`${dryRun ? "[dry-run] " : ""}UPLOAD public/${installer.key}`);
|
|
80
|
-
if (!dryRun) await run(process.execPath, [UPLOAD, file, installer.key, "public"], root);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
132
|
const version = config.version || target.updater.version;
|
|
85
133
|
if (!version) fail(`${config.app} config must expose version or targets.${platform}.updater.version`);
|
|
86
134
|
const body = {
|
|
@@ -97,25 +145,11 @@ const body = {
|
|
|
97
145
|
},
|
|
98
146
|
artifacts: [...registrations.values()],
|
|
99
147
|
};
|
|
100
|
-
const route =
|
|
148
|
+
const route = "/v1/admin/apps/releases";
|
|
101
149
|
console.log(`${dryRun ? "[dry-run] " : ""}POST ${API_BASE}${route}`);
|
|
102
150
|
console.log(JSON.stringify(body));
|
|
103
151
|
|
|
104
|
-
if (!dryRun)
|
|
105
|
-
const token = process.env.RIGHTAPPS_ADMIN_TOKEN;
|
|
106
|
-
if (!token) fail("RIGHTAPPS_ADMIN_TOKEN is required to register an update");
|
|
107
|
-
const response = await fetch(`${API_BASE}${route}`, {
|
|
108
|
-
method: "POST",
|
|
109
|
-
headers: {
|
|
110
|
-
authorization: `Bearer ${token}`,
|
|
111
|
-
"content-type": "application/json",
|
|
112
|
-
"x-store-slug": process.env.RIGHTAPPS_STORE_SLUG || "rightapps",
|
|
113
|
-
},
|
|
114
|
-
body: JSON.stringify(body),
|
|
115
|
-
});
|
|
116
|
-
if (!response.ok) fail(`registration failed: ${response.status} ${await response.text()}`);
|
|
117
|
-
console.log(await response.text());
|
|
118
|
-
}
|
|
152
|
+
if (!dryRun) await register(route, body);
|
|
119
153
|
|
|
120
154
|
async function fileMetadata(file) {
|
|
121
155
|
const bytes = await readFile(file);
|
|
@@ -142,6 +176,28 @@ function assertCurrentKey(key, kind) {
|
|
|
142
176
|
}
|
|
143
177
|
}
|
|
144
178
|
|
|
179
|
+
function patchInstallerKey(artifact) {
|
|
180
|
+
if (artifact.patchKey) return artifact.patchKey;
|
|
181
|
+
if (!artifact.key.includes("/updates/")) fail(`patch updater key must be convertible to installers/current, got: ${artifact.key}`);
|
|
182
|
+
return artifact.key.replace("/updates/", "/installers/");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function register(route, body) {
|
|
186
|
+
const token = process.env.RIGHTAPPS_ADMIN_TOKEN;
|
|
187
|
+
if (!token) fail("RIGHTAPPS_ADMIN_TOKEN is required to register an update");
|
|
188
|
+
const response = await fetch(`${API_BASE}${route}`, {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: {
|
|
191
|
+
authorization: `Bearer ${token}`,
|
|
192
|
+
"content-type": "application/json",
|
|
193
|
+
"x-store-slug": process.env.RIGHTAPPS_STORE_SLUG || "rightapps",
|
|
194
|
+
},
|
|
195
|
+
body: JSON.stringify(body),
|
|
196
|
+
});
|
|
197
|
+
if (!response.ok) fail(`registration failed: ${response.status} ${await response.text()}`);
|
|
198
|
+
console.log(await response.text());
|
|
199
|
+
}
|
|
200
|
+
|
|
145
201
|
function fail(message) {
|
|
146
202
|
console.error(`publish-update: ${message}`);
|
|
147
203
|
process.exit(1);
|
package/publish-update.test.mjs
CHANGED
|
@@ -59,18 +59,19 @@ function run(tier) {
|
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
test("
|
|
62
|
+
test("patch registers a free updater manifest backed by public installer-path objects", () => {
|
|
63
63
|
const result = run("patch");
|
|
64
64
|
assert.equal(result.status, 0, result.stderr);
|
|
65
|
-
const
|
|
65
|
+
const patchPayloadUpload = result.stdout.indexOf("UPLOAD public/fixture/installers/windows/current/Fixture.exe");
|
|
66
66
|
const installerUpload = result.stdout.indexOf("UPLOAD public/fixture/installers/windows/current/Fixture-Setup.exe");
|
|
67
67
|
const registration = result.stdout.indexOf("/v1/admin/apps/patches");
|
|
68
|
-
assert.ok(
|
|
69
|
-
assert.ok(installerUpload >= 0, "patch must upload
|
|
70
|
-
assert.ok(
|
|
68
|
+
assert.ok(patchPayloadUpload >= 0, "patch updater payload must upload to the public installer path");
|
|
69
|
+
assert.ok(installerUpload >= 0, "patch download installer must upload to the public installer path");
|
|
70
|
+
assert.ok(patchPayloadUpload < installerUpload, "patch should upload app-update payloads before replacing the public download installer");
|
|
71
71
|
assert.ok(installerUpload < registration, "patch should register only after uploads complete");
|
|
72
|
-
assert.
|
|
73
|
-
assert.
|
|
72
|
+
assert.doesNotMatch(result.stdout, /UPLOAD public\/fixture\/updates\//);
|
|
73
|
+
assert.doesNotMatch(result.stdout, /UPLOAD private\//);
|
|
74
|
+
assert.match(result.stdout, /pub-6c73208d46c245a9b4881d5e02f6b618\.r2\.dev\/fixture\/installers\/windows\/current\/Fixture\.exe/);
|
|
74
75
|
assert.match(result.stdout, /POST .*\/v1\/admin\/apps\/patches/);
|
|
75
76
|
assert.match(result.stdout, /"tier":"patch"/);
|
|
76
77
|
});
|
|
@@ -99,3 +100,13 @@ test("refuses versioned R2 object keys that would accumulate old releases", () =
|
|
|
99
100
|
assert.notEqual(result.status, 0);
|
|
100
101
|
assert.match(result.stderr, /current R2 object/);
|
|
101
102
|
});
|
|
103
|
+
|
|
104
|
+
test("refuses versioned updater object keys for gated feature updates", () => {
|
|
105
|
+
const config = versionedFixture();
|
|
106
|
+
const result = spawnSync(process.execPath, [publisher, "--config", config, "--platform", "win", "--dry-run"], {
|
|
107
|
+
encoding: "utf8",
|
|
108
|
+
env: { ...process.env, RIGHT_RELEASE_TIER: "update" },
|
|
109
|
+
});
|
|
110
|
+
assert.notEqual(result.status, 0);
|
|
111
|
+
assert.match(result.stderr, /current R2 object/);
|
|
112
|
+
});
|
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.3";
|
|
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.3" },
|
|
22
22
|
},
|
|
23
23
|
null,
|
|
24
24
|
2,
|
|
@@ -115,7 +115,7 @@ test("publish runs the signed package step before the updater publication step",
|
|
|
115
115
|
assert.ok(publishAt > packageAt, result.stdout);
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
-
test("Windows
|
|
118
|
+
test("Windows patch signs the installer then minisigns the free updater payload", () => {
|
|
119
119
|
const result = run(fixture({ publish: true }), "--tier=patch", "--upload");
|
|
120
120
|
assert.equal(result.status, 0, result.stderr);
|
|
121
121
|
const azureAt = result.stdout.indexOf("sign-windows.mjs");
|
|
@@ -125,3 +125,14 @@ test("Windows re-signs the updater artifact after Azure code signing", () => {
|
|
|
125
125
|
assert.ok(updaterAt > azureAt, result.stdout);
|
|
126
126
|
assert.ok(publishAt > updaterAt, result.stdout);
|
|
127
127
|
});
|
|
128
|
+
|
|
129
|
+
test("Windows update re-signs the updater artifact after Azure code signing", () => {
|
|
130
|
+
const result = run(fixture({ publish: true }), "--tier=update", "--upload");
|
|
131
|
+
assert.equal(result.status, 0, result.stderr);
|
|
132
|
+
const azureAt = result.stdout.indexOf("sign-windows.mjs");
|
|
133
|
+
const updaterAt = result.stdout.indexOf("sign-updater.mjs");
|
|
134
|
+
const publishAt = result.stdout.indexOf("publish-update.mjs");
|
|
135
|
+
assert.ok(azureAt >= 0, result.stdout);
|
|
136
|
+
assert.ok(updaterAt > azureAt, result.stdout);
|
|
137
|
+
assert.ok(publishAt > updaterAt, result.stdout);
|
|
138
|
+
});
|
|
@@ -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.3";
|
|
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
|
+
});
|