@rightkit/release 0.2.0

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.
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+
7
+ const EXCLUDE_CREDENTIALS = [
8
+ "ManagedIdentityCredential",
9
+ "WorkloadIdentityCredential",
10
+ "SharedTokenCacheCredential",
11
+ "VisualStudioCredential",
12
+ "VisualStudioCodeCredential",
13
+ "AzurePowerShellCredential",
14
+ "AzureDeveloperCliCredential",
15
+ "InteractiveBrowserCredential",
16
+ ];
17
+ const userEnvCache = new Map();
18
+
19
+ const args = process.argv.slice(2);
20
+ const dryRun = args.includes("--dry-run");
21
+ const files = args.filter((arg) => arg !== "--dry-run").map((arg) => path.resolve(arg));
22
+
23
+ if (!files.length) fail("usage: node sign-windows.mjs [--dry-run] <file>...");
24
+ if (dryRun) {
25
+ console.log(`dry-run: Azure Artifact Signing ${files.join(", ")}`);
26
+ process.exit(0);
27
+ }
28
+ if (process.platform !== "win32") fail("Windows signing must run on Windows");
29
+ for (const file of files) if (!existsSync(file)) fail(`missing file: ${file}`);
30
+
31
+ const signtool = findFirst([env("AZURE_SIGNTOOL_PATH"), env("SIGNTOOL_PATH"), ...signtoolCandidates()]);
32
+ const dlib = findFirst([
33
+ env("AZURE_CODESIGN_DLIB_PATH"),
34
+ env("AZURE_ARTIFACT_SIGNING_DLIB_PATH"),
35
+ path.join(env("LOCALAPPDATA") || "", "AzureArtifactSigningTools", "Microsoft.ArtifactSigning.Client", "bin", "x64", "Azure.CodeSigning.Dlib.dll"),
36
+ "C:\\Program Files\\Microsoft Azure Artifact Signing Client Tools\\x64\\Azure.CodeSigning.Dlib.dll",
37
+ "C:\\Program Files (x86)\\Microsoft\\ArtifactSigningClientTools\\bin\\x64\\Azure.CodeSigning.Dlib.dll",
38
+ ]);
39
+ if (!signtool) fail("signtool.exe not found; set AZURE_SIGNTOOL_PATH or install Windows SDK Build Tools");
40
+ if (!dlib) fail("Azure.CodeSigning.Dlib.dll not found; set AZURE_CODESIGN_DLIB_PATH or install Azure Artifact Signing Client Tools");
41
+
42
+ const tempDir = mkdtempSync(path.join(os.tmpdir(), "right-sign-"));
43
+ const metadata = metadataPath(tempDir);
44
+ const childEnv = signingEnv();
45
+ try {
46
+ for (const file of files) {
47
+ run(signtool, ["sign", "/v", "/fd", "SHA256", "/tr", "http://timestamp.acs.microsoft.com", "/td", "SHA256", "/dlib", dlib, "/dmdf", metadata, file]);
48
+ run(signtool, ["verify", "/pa", "/v", file]);
49
+ }
50
+ } finally {
51
+ rmSync(tempDir, { recursive: true, force: true });
52
+ }
53
+
54
+ function metadataPath(dir) {
55
+ const existing = env("AZURE_ARTIFACT_SIGNING_METADATA") || env("AZURE_SIGNING_METADATA");
56
+ if (existing) {
57
+ if (!existsSync(existing)) fail(`metadata file not found: ${existing}`);
58
+ return existing;
59
+ }
60
+ const Endpoint = env("AZURE_ARTIFACT_SIGNING_ENDPOINT") || env("AZURE_SIGNING_ENDPOINT") || env("AZURE_ENDPOINT");
61
+ const CodeSigningAccountName = env("AZURE_ARTIFACT_SIGNING_ACCOUNT") || env("AZURE_SIGNING_ACCOUNT_NAME") || env("AZURE_ACCOUNT");
62
+ const CertificateProfileName = env("AZURE_ARTIFACT_SIGNING_PROFILE") || env("AZURE_CERTIFICATE_PROFILE_NAME") || env("AZURE_PROFILE");
63
+ if (!Endpoint || !CodeSigningAccountName || !CertificateProfileName) {
64
+ fail("set AZURE_ARTIFACT_SIGNING_ENDPOINT, AZURE_ARTIFACT_SIGNING_ACCOUNT, and AZURE_ARTIFACT_SIGNING_PROFILE");
65
+ }
66
+ const file = path.join(dir, "metadata.json");
67
+ writeFileSync(file, `${JSON.stringify({ Endpoint, CodeSigningAccountName, CertificateProfileName, ExcludeCredentials: EXCLUDE_CREDENTIALS }, null, 2)}\n`);
68
+ return file;
69
+ }
70
+
71
+ function signingEnv() {
72
+ const paths = [path.dirname(findFirst(azureCliCandidates()) ?? ""), env("PATH")].filter(Boolean);
73
+ return { ...process.env, PATH: paths.join(path.delimiter) };
74
+ }
75
+
76
+ function azureCliCandidates() {
77
+ return [
78
+ path.join(env("LOCALAPPDATA") || "", "AzureCLI", "bin", "az.cmd"),
79
+ path.join(env("ProgramFiles(x86)") || "C:\\Program Files (x86)", "Microsoft SDKs", "Azure", "CLI2", "wbin", "az.cmd"),
80
+ path.join(env("ProgramFiles") || "C:\\Program Files", "Microsoft SDKs", "Azure", "CLI2", "wbin", "az.cmd"),
81
+ ];
82
+ }
83
+
84
+ function signtoolCandidates() {
85
+ return [
86
+ ...sdkBuildToolsCandidates(),
87
+ ...windowsKitCandidates(path.join(env("ProgramFiles(x86)") || "C:\\Program Files (x86)", "Windows Kits", "10", "bin")),
88
+ ];
89
+ }
90
+
91
+ function sdkBuildToolsCandidates() {
92
+ const bin = path.join(env("LOCALAPPDATA") || "", "AzureArtifactSigningTools", "Microsoft.Windows.SDK.BuildTools", "bin");
93
+ return windowsKitCandidates(bin);
94
+ }
95
+
96
+ function windowsKitCandidates(bin) {
97
+ if (!existsSync(bin)) return [];
98
+ return readdirSync(bin, { withFileTypes: true })
99
+ .filter((entry) => entry.isDirectory())
100
+ .map((entry) => path.join(bin, entry.name, "x64", "signtool.exe"))
101
+ .sort()
102
+ .reverse();
103
+ }
104
+
105
+ function findFirst(candidates) {
106
+ return candidates.find((candidate) => candidate && existsSync(candidate));
107
+ }
108
+
109
+ function env(name) {
110
+ return process.env[name] || userEnv(name);
111
+ }
112
+
113
+ function userEnv(name) {
114
+ if (userEnvCache.has(name)) return userEnvCache.get(name);
115
+ const result = spawnSync("powershell", [
116
+ "-NoProfile",
117
+ "-Command",
118
+ `[Environment]::GetEnvironmentVariable('${name.replace(/'/g, "''")}','User')`,
119
+ ], { encoding: "utf8", windowsHide: true });
120
+ const value = result.status === 0 ? result.stdout.trim() : "";
121
+ userEnvCache.set(name, value);
122
+ return value;
123
+ }
124
+
125
+ function run(cmd, runArgs) {
126
+ const result = spawnSync(cmd, runArgs, { env: childEnv, stdio: "inherit" });
127
+ if (result.status !== 0) process.exit(result.status ?? 1);
128
+ }
129
+
130
+ function fail(message) {
131
+ console.error(`right-sign-windows: ${message}`);
132
+ process.exit(1);
133
+ }
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ import { createReadStream, statSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+
6
+ const WORKER_BASE = process.env.RIGHT_RELEASE_UPLOAD_WORKER_BASE || "https://heardright-models.adrdsouza.workers.dev";
7
+ const PART_SIZE = 90 * 1024 * 1024;
8
+
9
+ const [, , localFile, r2Key, bucket = "public"] = process.argv;
10
+ if (!localFile || !r2Key) {
11
+ console.error("Usage: node upload-large.mjs <localFile> <r2Key> [public|private]");
12
+ process.exit(1);
13
+ }
14
+
15
+ const token = process.env.ADMIN_UPLOAD_TOKEN || readKeychainToken();
16
+ if (!token) {
17
+ console.error("ADMIN_UPLOAD_TOKEN not set and no Keychain token found");
18
+ process.exit(1);
19
+ }
20
+
21
+ const headers = { Authorization: `Bearer ${token}` };
22
+ const fileSize = statSync(localFile).size;
23
+
24
+ async function api(method, pathname, body, isJson = true) {
25
+ const opts = {
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
+ }
37
+
38
+ async function readChunk(offset, size) {
39
+ return new Promise((resolve, reject) => {
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
+ });
46
+ }
47
+
48
+ console.log(`Uploading ${path.basename(localFile)} (${(fileSize / 1024 / 1024).toFixed(1)} MB) -> ${bucket}/${r2Key}`);
49
+ const { uploadId } = await api("POST", "/admin/multipart/init", { key: r2Key, bucket });
50
+ const parts = [];
51
+ const totalParts = Math.ceil(fileSize / PART_SIZE);
52
+ for (let i = 0; i < totalParts; i++) {
53
+ const offset = i * PART_SIZE;
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}.`);
64
+
65
+ function readKeychainToken() {
66
+ if (process.platform !== "darwin") return "";
67
+ for (const service of ["rightapps-admin-upload-token", "heardright-admin-upload-token"]) {
68
+ const result = spawnSync("security", ["find-generic-password", "-a", process.env.USER || "", "-s", service, "-w"], {
69
+ encoding: "utf8",
70
+ });
71
+ if (result.status === 0 && result.stdout.trim()) return result.stdout.trim();
72
+ }
73
+ return "";
74
+ }