@rightkit/release 0.2.14 → 0.2.15
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 +15 -6
- package/publish-update.test.mjs +20 -1
- package/release-token.mjs +54 -0
- package/release-token.test.mjs +21 -0
- package/right-suite-contract.test.mjs +2 -2
- package/rightkit-versions.json +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
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
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import { pruneReleaseObjects } from "./prune-r2.mjs";
|
|
8
|
+
import { loadReleaseToken } from "./release-token.mjs";
|
|
8
9
|
|
|
9
10
|
const TOOL_ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
const UPLOAD = path.join(TOOL_ROOT, "upload-large.mjs");
|
|
@@ -37,8 +38,19 @@ const target = config?.targets?.[platform];
|
|
|
37
38
|
const artifacts = target?.updater?.artifacts ?? [];
|
|
38
39
|
const installers = target?.installer?.artifacts ?? [];
|
|
39
40
|
|
|
41
|
+
if (tier === "patch" && !installers.length) fail(`${config?.app ?? "app"} ${platform} patch has no installer.artifacts`);
|
|
42
|
+
if (tier === "update" && !artifacts.length) fail(`${config?.app ?? "app"} ${platform} update has no updater.artifacts`);
|
|
43
|
+
|
|
44
|
+
let releaseToken = null;
|
|
45
|
+
if (!dryRun && (tier === "update" || artifacts.length > 0)) {
|
|
46
|
+
try {
|
|
47
|
+
releaseToken = await loadReleaseToken();
|
|
48
|
+
} catch (error) {
|
|
49
|
+
fail(error.message);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
if (tier === "patch") {
|
|
41
|
-
if (!installers.length) fail(`${config?.app ?? "app"} ${platform} patch has no installer.artifacts`);
|
|
42
54
|
for (const installer of installers) {
|
|
43
55
|
assertCurrentKey(installer.key, "installer");
|
|
44
56
|
const file = path.resolve(root, installer.file);
|
|
@@ -145,8 +157,6 @@ if (tier === "patch") {
|
|
|
145
157
|
process.exit(0);
|
|
146
158
|
}
|
|
147
159
|
|
|
148
|
-
if (!artifacts.length) fail(`${config?.app ?? "app"} ${platform} update has no updater.artifacts`);
|
|
149
|
-
|
|
150
160
|
const platforms = {};
|
|
151
161
|
const registrations = new Map();
|
|
152
162
|
const updaterManifestArtifacts = new Map();
|
|
@@ -262,12 +272,11 @@ function patchInstallerKey(artifact) {
|
|
|
262
272
|
}
|
|
263
273
|
|
|
264
274
|
async function register(route, body) {
|
|
265
|
-
|
|
266
|
-
if (!token) fail("RIGHTAPPS_ADMIN_TOKEN is required to register an update");
|
|
275
|
+
if (!releaseToken) fail("RIGHTAPPS_RELEASE_TOKEN is required to register an update");
|
|
267
276
|
const response = await fetch(`${API_BASE}${route}`, {
|
|
268
277
|
method: "POST",
|
|
269
278
|
headers: {
|
|
270
|
-
authorization: `Bearer ${
|
|
279
|
+
authorization: `Bearer ${releaseToken}`,
|
|
271
280
|
"content-type": "application/json",
|
|
272
281
|
"x-store-slug": process.env.RIGHTAPPS_STORE_SLUG || "rightapps",
|
|
273
282
|
},
|
package/publish-update.test.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import test from "node:test";
|
|
|
8
8
|
|
|
9
9
|
const publisher = fileURLToPath(new URL("./publish-update.mjs", import.meta.url));
|
|
10
10
|
|
|
11
|
-
function fixture() {
|
|
11
|
+
function fixture({ materialize = false } = {}) {
|
|
12
12
|
const dir = mkdtempSync(path.join(os.tmpdir(), "right-publish-test-"));
|
|
13
13
|
const config = path.join(dir, "right-release.config.mjs");
|
|
14
14
|
writeFileSync(config, `export default ${JSON.stringify({
|
|
@@ -27,6 +27,11 @@ function fixture() {
|
|
|
27
27
|
},
|
|
28
28
|
},
|
|
29
29
|
})};\n`);
|
|
30
|
+
if (materialize) {
|
|
31
|
+
writeFileSync(path.join(dir, "Fixture-Setup.exe"), "signed installer");
|
|
32
|
+
writeFileSync(path.join(dir, "Fixture.exe"), "signed updater");
|
|
33
|
+
writeFileSync(path.join(dir, "Fixture.exe.sig"), "updater signature");
|
|
34
|
+
}
|
|
30
35
|
return config;
|
|
31
36
|
}
|
|
32
37
|
|
|
@@ -181,3 +186,17 @@ test("refuses versioned updater object keys for gated feature updates", () => {
|
|
|
181
186
|
assert.notEqual(result.status, 0);
|
|
182
187
|
assert.match(result.stderr, /current R2 object/);
|
|
183
188
|
});
|
|
189
|
+
|
|
190
|
+
test("requires the durable release credential before the first patch upload", () => {
|
|
191
|
+
const config = fixture({ materialize: true });
|
|
192
|
+
const env = { ...process.env, RIGHT_RELEASE_TIER: "patch", RIGHTAPPS_ADMIN_TOKEN: "expired-admin-session" };
|
|
193
|
+
delete env.RIGHTAPPS_RELEASE_TOKEN;
|
|
194
|
+
delete env.RIGHTAPPS_RELEASE_TOKEN_FILE;
|
|
195
|
+
const result = spawnSync(process.execPath, [publisher, "--config", config, "--platform", "win"], {
|
|
196
|
+
encoding: "utf8",
|
|
197
|
+
env,
|
|
198
|
+
});
|
|
199
|
+
assert.notEqual(result.status, 0);
|
|
200
|
+
assert.match(result.stderr, /RIGHTAPPS_RELEASE_TOKEN/);
|
|
201
|
+
assert.doesNotMatch(result.stdout, /UPLOAD /, "credential preflight must run before R2 mutation");
|
|
202
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
const KEYCHAIN_SERVICE = "rightkit-rightapps-release-token";
|
|
9
|
+
|
|
10
|
+
export function defaultWindowsReleaseTokenFile(env = process.env) {
|
|
11
|
+
const appData = env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
|
12
|
+
return path.join(appData, "RightKit", "rightapps-release.token");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function readTokenFile(file) {
|
|
16
|
+
const token = (await readFile(file, "utf8")).trim();
|
|
17
|
+
if (!token) throw new Error(`release token file is empty: ${file}`);
|
|
18
|
+
return token;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function loadReleaseToken({ env = process.env, platform = process.platform } = {}) {
|
|
22
|
+
const direct = env.RIGHTAPPS_RELEASE_TOKEN?.trim();
|
|
23
|
+
if (direct) return direct;
|
|
24
|
+
|
|
25
|
+
if (env.RIGHTAPPS_RELEASE_TOKEN_FILE) {
|
|
26
|
+
return readTokenFile(path.resolve(env.RIGHTAPPS_RELEASE_TOKEN_FILE));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (platform === "win32") {
|
|
30
|
+
try {
|
|
31
|
+
return await readTokenFile(defaultWindowsReleaseTokenFile(env));
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw new Error(`RIGHTAPPS_RELEASE_TOKEN is unavailable (${error.message})`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (platform === "darwin") {
|
|
38
|
+
try {
|
|
39
|
+
const { stdout } = await execFileAsync("security", [
|
|
40
|
+
"find-generic-password",
|
|
41
|
+
"-a", env.USER || os.userInfo().username,
|
|
42
|
+
"-s", KEYCHAIN_SERVICE,
|
|
43
|
+
"-w",
|
|
44
|
+
], { windowsHide: true });
|
|
45
|
+
const token = stdout.trim();
|
|
46
|
+
if (!token) throw new Error("Keychain item is empty");
|
|
47
|
+
return token;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
throw new Error(`RIGHTAPPS_RELEASE_TOKEN is unavailable (macOS Keychain service ${KEYCHAIN_SERVICE}: ${error.message})`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw new Error("RIGHTAPPS_RELEASE_TOKEN or RIGHTAPPS_RELEASE_TOKEN_FILE is required");
|
|
54
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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 test from "node:test";
|
|
6
|
+
import { defaultWindowsReleaseTokenFile, loadReleaseToken } from "./release-token.mjs";
|
|
7
|
+
|
|
8
|
+
test("loads an explicit release token without exposing admin-session fallback", async () => {
|
|
9
|
+
assert.equal(await loadReleaseToken({ env: { RIGHTAPPS_RELEASE_TOKEN: " durable-token ", RIGHTAPPS_ADMIN_TOKEN: "admin-session" } }), "durable-token");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("loads the release token from an explicit protected-file path", async () => {
|
|
13
|
+
const dir = mkdtempSync(path.join(os.tmpdir(), "right-release-token-"));
|
|
14
|
+
const file = path.join(dir, "token");
|
|
15
|
+
writeFileSync(file, "file-token\n");
|
|
16
|
+
assert.equal(await loadReleaseToken({ env: { RIGHTAPPS_RELEASE_TOKEN_FILE: file }, platform: "linux" }), "file-token");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("uses the portable Windows token location", () => {
|
|
20
|
+
assert.equal(defaultWindowsReleaseTokenFile({ APPDATA: "C:\\Users\\fixture\\AppData\\Roaming" }), path.join("C:\\Users\\fixture\\AppData\\Roaming", "RightKit", "rightapps-release.token"));
|
|
21
|
+
});
|
|
@@ -19,10 +19,10 @@ const apps = [
|
|
|
19
19
|
test("RightKit exposes one current version manifest", () => {
|
|
20
20
|
assert.equal(existsSync(versionsPath), true, "rightkit-versions.json must be the single current-version source");
|
|
21
21
|
assert.match(versions.packageManager, /^pnpm@\d+\.\d+\.\d+$/);
|
|
22
|
-
assert.equal(versions.npm["@rightkit/release"], "0.2.
|
|
22
|
+
assert.equal(versions.npm["@rightkit/release"], "0.2.15");
|
|
23
23
|
assert.equal(versions.npm["@rightkit/license"], "0.1.5");
|
|
24
24
|
assert.equal(versions.npm["@rightkit/logs"], "0.1.3");
|
|
25
|
-
assert.equal(versions.npm["@rightkit/updates"], "0.
|
|
25
|
+
assert.equal(versions.npm["@rightkit/updates"], "0.2.1");
|
|
26
26
|
assert.equal(versions.cargo["rightkit-license"], "0.1.1");
|
|
27
27
|
assert.equal(versions.cargo["rightkit-logs"], "0.1.0");
|
|
28
28
|
for (const [crate, version] of Object.entries(versions.cargo)) {
|
package/rightkit-versions.json
CHANGED