@rightkit/release 0.2.10 → 0.2.11

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.
@@ -16,6 +16,7 @@ const commands = new Map([
16
16
  ["sign-windows", "sign-windows.mjs"],
17
17
  ["sign-updater", "sign-updater.mjs"],
18
18
  ["create-mac-updater", "create-mac-updater.mjs"],
19
+ ["mirror-root-artifact", "mirror-root-artifact.mjs"],
19
20
  ]);
20
21
 
21
22
  const args = process.argv.slice(2);
@@ -105,6 +106,8 @@ Commands:
105
106
  hardening <artifact...> Run the Right Suite hardening scan
106
107
  lsclean <AppName.app> Clear macOS LaunchServices duplicates
107
108
  generate-dmg-background <options> Generate the branded multi-resolution DMG background
109
+ mirror-root-artifact --file <path> --package-root <dir>
110
+ Persist a worktree artifact in the primary repo root
108
111
 
109
112
  Direct flags are treated as: right-release release <flags>.
110
113
  Publishing requires an explicit tier. Unsigned/local smoke builds stay app-local unless declared in right-release.config.mjs.`);
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import { copyFileSync, mkdirSync, realpathSync, renameSync, rmSync } from "node:fs";
3
+ import { execFileSync } from "node:child_process";
4
+ import path from "node:path";
5
+
6
+ const args = parseArgs(process.argv.slice(2));
7
+ if (!args.file || !args.packageRoot) {
8
+ console.error("Usage: right-release mirror-root-artifact --file <artifact> --package-root <dir> [--name <filename>]");
9
+ process.exit(2);
10
+ }
11
+
12
+ const packageRoot = realpathSync(path.resolve(args.packageRoot));
13
+ const source = path.resolve(packageRoot, args.file);
14
+ const name = args.name || path.basename(source);
15
+ const repoRoot = git(packageRoot, "rev-parse", "--show-toplevel");
16
+ const commonDir = git(packageRoot, "rev-parse", "--path-format=absolute", "--git-common-dir");
17
+ const canonicalRepoRoot = path.basename(commonDir) === ".git" ? path.dirname(commonDir) : repoRoot;
18
+ const relativePackageRoot = path.relative(repoRoot, packageRoot);
19
+ const destinations = new Set([
20
+ path.join(packageRoot, name),
21
+ path.join(canonicalRepoRoot, relativePackageRoot, name),
22
+ ]);
23
+
24
+ for (const destination of destinations) {
25
+ if (path.resolve(destination) === source) continue;
26
+ atomicCopy(source, destination);
27
+ }
28
+
29
+ const canonicalArtifact = path.join(canonicalRepoRoot, relativePackageRoot, name);
30
+ console.log(`right-release: repository-root artifact -> ${canonicalArtifact}`);
31
+
32
+ function git(cwd, ...commandArgs) {
33
+ return execFileSync("git", commandArgs, { cwd, encoding: "utf8" }).trim();
34
+ }
35
+
36
+ function atomicCopy(from, to) {
37
+ mkdirSync(path.dirname(to), { recursive: true });
38
+ const temporary = `${to}.tmp-${process.pid}`;
39
+ rmSync(temporary, { force: true });
40
+ try {
41
+ copyFileSync(from, temporary);
42
+ renameSync(temporary, to);
43
+ } finally {
44
+ rmSync(temporary, { force: true });
45
+ }
46
+ }
47
+
48
+ function parseArgs(values) {
49
+ const parsed = {};
50
+ for (let i = 0; i < values.length; i += 1) {
51
+ const key = values[i];
52
+ if (!key.startsWith("--")) continue;
53
+ parsed[key.slice(2).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())] = values[++i];
54
+ }
55
+ return parsed;
56
+ }
@@ -0,0 +1,57 @@
1
+ import assert from "node:assert/strict";
2
+ import { execFileSync } from "node:child_process";
3
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import test from "node:test";
7
+
8
+ const helper = new URL("./mirror-root-artifact.mjs", import.meta.url);
9
+
10
+ test("mirrors a nested package artifact from a linked worktree into both package roots", () => {
11
+ const temp = mkdtempSync(path.join(os.tmpdir(), "right-release-mirror-"));
12
+ const main = path.join(temp, "suite");
13
+ const worktree = path.join(temp, "release-worktree");
14
+ try {
15
+ mkdirSync(path.join(main, "apps", "desktop"), { recursive: true });
16
+ git(temp, "init", "suite");
17
+ git(main, "config", "user.email", "test@example.com");
18
+ git(main, "config", "user.name", "Right Release Test");
19
+ writeFileSync(path.join(main, "tracked"), "fixture\n");
20
+ git(main, "add", "tracked");
21
+ git(main, "commit", "-m", "fixture");
22
+ git(main, "worktree", "add", worktree, "-b", "release-test");
23
+
24
+ const packageRoot = path.join(worktree, "apps", "desktop");
25
+ const source = path.join(packageRoot, "target", "App_1.0.0.dmg");
26
+ mkdirSync(path.dirname(source), { recursive: true });
27
+ writeFileSync(source, "signed-dmg-fixture");
28
+
29
+ execFileSync(process.execPath, [helper.pathname, "--file", source, "--package-root", packageRoot], { encoding: "utf8" });
30
+
31
+ assert.equal(readFileSync(path.join(packageRoot, "App_1.0.0.dmg"), "utf8"), "signed-dmg-fixture");
32
+ assert.equal(readFileSync(path.join(main, "apps", "desktop", "App_1.0.0.dmg"), "utf8"), "signed-dmg-fixture");
33
+ } finally {
34
+ rmSync(temp, { recursive: true, force: true });
35
+ }
36
+ });
37
+
38
+ test("copies a main-worktree artifact into its package root", () => {
39
+ const temp = mkdtempSync(path.join(os.tmpdir(), "right-release-mirror-main-"));
40
+ try {
41
+ git(temp, "init", "app");
42
+ const root = path.join(temp, "app");
43
+ const source = path.join(root, "target", "App_2.0.0.dmg");
44
+ mkdirSync(path.dirname(source), { recursive: true });
45
+ writeFileSync(source, "main-worktree-dmg");
46
+
47
+ execFileSync(process.execPath, [helper.pathname, "--file", source, "--package-root", root], { encoding: "utf8" });
48
+
49
+ assert.equal(readFileSync(path.join(root, "App_2.0.0.dmg"), "utf8"), "main-worktree-dmg");
50
+ } finally {
51
+ rmSync(temp, { recursive: true, force: true });
52
+ }
53
+ });
54
+
55
+ function git(cwd, ...args) {
56
+ return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
57
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/release",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
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
@@ -10,7 +10,7 @@ const HARDENING_SCAN = path.resolve(TOOL_ROOT, "hardeningscan.mjs");
10
10
  const UPLOAD_LARGE = path.resolve(TOOL_ROOT, "upload-large.mjs");
11
11
  const SIGN_WINDOWS = path.resolve(TOOL_ROOT, "sign-windows.mjs");
12
12
  const SIGN_UPDATER = path.resolve(TOOL_ROOT, "sign-updater.mjs");
13
- const VERSION = "0.2.10";
13
+ const VERSION = "0.2.11";
14
14
  const TIERS = new Set(["patch", "update"]);
15
15
  const RIGHTKIT_EXPECTED = new Map([
16
16
  ["@rightkit/license", "^0.1.5"],
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.5", "@rightkit/logs": "^0.1.3" },
21
- devDependencies: { "@rightkit/release": "^0.2.10" },
21
+ devDependencies: { "@rightkit/release": "^0.2.11" },
22
22
  },
23
23
  null,
24
24
  2,
@@ -57,7 +57,7 @@ function lockFixture() {
57
57
  name: "fixture-lock",
58
58
  scripts: { "release:patch:mac": "right-release --platform mac --tier patch" },
59
59
  dependencies: { "@rightkit/license": "^0.1.5", "@rightkit/logs": "^0.1.3" },
60
- devDependencies: { "@rightkit/release": "^0.2.10" },
60
+ devDependencies: { "@rightkit/release": "^0.2.11" },
61
61
  },
62
62
  null,
63
63
  2,
@@ -1,5 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
- import { readFileSync } from "node:fs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import test from "node:test";
@@ -8,7 +8,7 @@ const workspace = path.resolve(new URL("../../../..", import.meta.url).pathname.
8
8
  const pubkey = "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDI5Mzk1RjlGRjQ2NjI2MUQKUldRZEptYjBuMTg1S1VSUXlBdFM4WmtzaHArYko0U2hRMDVlSDJmSExVZG82Q0hoQ2srUlhqanAK";
9
9
  const licensePackage = "^0.1.5";
10
10
  const logsPackage = "^0.1.3";
11
- const releasePackage = "^0.2.10";
11
+ const releasePackage = "^0.2.11";
12
12
  const apps = [
13
13
  { key: "viewright", root: "viewright", tauri: "src-tauri/tauri.conf.json", releaseFiles: ["scripts/build-mac-notarized.sh"] },
14
14
  { key: "scraperight", root: "scraperight", tauri: "src-tauri/tauri.conf.json", releaseFiles: ["package.sh"] },
@@ -44,6 +44,7 @@ for (const app of apps) {
44
44
  assert.equal(pkg.scripts["publish:update:win"], "right-release publish --platform win --tier update");
45
45
  assert.equal(pkg.scripts["deps:check"], "right-release deps --check");
46
46
  assert.equal(pkg.scripts["deps:update"], "right-release deps --update");
47
+ assert.ok(!Object.entries(pkg.scripts).some(([name, command]) => /mac|dmg/i.test(name) && /unsigned|--no-sign/i.test(command)), `${app.key} must not expose an unsigned macOS DMG mode`);
47
48
  assert.ok(!JSON.stringify(pkg.scripts).includes("../tools/right-release"), `${app.key} scripts must not depend on the parent Claude workspace`);
48
49
  assert.ok(!JSON.stringify(pkg.scripts).includes("../tools/rightkit/packages/release"), `${app.key} scripts must not depend on the parent Claude workspace`);
49
50
 
@@ -66,6 +67,9 @@ for (const app of apps) {
66
67
  }
67
68
  }
68
69
  assert.match(config.targets.mac.package.args.join(" "), /mac:dmg:notarized|--notarize/, `${app.key} publish packaging must use the notarized macOS lane`);
70
+ for (const installer of config.targets.mac.installer.artifacts) {
71
+ assert.equal(path.dirname(installer.file), ".", `${app.key} macOS installer must be copied to the app package root before upload`);
72
+ }
69
73
  assert.ok(config.targets.win.sign.files.length);
70
74
  const winUpdaterFiles = new Set(config.targets.win.updater.artifacts.map((artifact) => artifact.file));
71
75
  for (const signed of config.targets.win.sign.files) assert.ok(winUpdaterFiles.has(signed), `${signed} must be both Azure-signed and updater-signed`);
@@ -79,6 +83,10 @@ for (const app of apps) {
79
83
  for (const releaseFile of app.releaseFiles) {
80
84
  const source = readFileSync(path.join(root, releaseFile), "utf8");
81
85
  assert.doesNotMatch(source, /(?:\.\.\/)+tools\/right-release|tools\/right-release\//, `${app.key} ${releaseFile} must consume the installed package`);
86
+ assert.match(source, /right-release["']?,?\s*["']mirror-root-artifact|right-release mirror-root-artifact/, `${app.key} ${releaseFile} must mirror the final DMG to the canonical package root`);
87
+ }
88
+ for (const forbidden of ["scripts/build-mac-unsigned.sh", "package-unsigned.sh"]) {
89
+ assert.equal(existsSync(path.join(root, forbidden)), false, `${app.key} must not ship ${forbidden}`);
82
90
  }
83
91
  });
84
92
  }