on master help sign in
Ship the native host and a one-step installer in the release zip
59dc7aaClaude
.github/workflows/release.yml+1−1
@@ -54,7 +54,7 @@ jobs:
54 VERSION: ${{ steps.version.outputs.version }}
54 VERSION: ${{ steps.version.outputs.version }}
55 run: |
55 run: |
56 asset="diagonal-extension-${VERSION}.zip"
56 asset="diagonal-extension-${VERSION}.zip"
57− notes="Built from ${GITHUB_SHA::7}. Unzip and load the folder at brave://extensions (Developer mode → Load unpacked)."
57+ notes="Built from ${GITHUB_SHA::7}. Unzip, load the folder at brave://extensions (Developer mode → Load unpacked), then install the native host once from Terminal: bash <that folder>/install-host.command"
58 if gh release view "$VERSION" >/dev/null 2>&1; then
58 if gh release view "$VERSION" >/dev/null 2>&1; then
59 # Two commits in the same minute share a version: refresh the asset on the existing release.
59 # Two commits in the same minute share a version: refresh the asset on the existing release.
60 gh release upload "$VERSION" "$asset" --clobber
60 gh release upload "$VERSION" "$asset" --clobber
README.md+21−8
@@ -22,17 +22,30 @@ The model sees a tab's title, address and `<meta name="description">`, and nothi
22
22
23## Install
23## Install
24
24
25−### 1. The extension
25+### From a release (no clone needed)
26
26
27−The built extension is in `dist/` (or `diagonal-extension/` next to this folder in the delivery).
27+1. Download `diagonal-extension-<version>.zip` from the [latest release](https://github.com/iko-soy/diagonal/releases/latest) and unzip it somewhere it can stay, for example `~/Applications/Diagonal`.
28+2. Open `brave://extensions`, turn on **Developer mode**, click **Load unpacked** and pick that folder.
29+3. Install the native host once, from Terminal:
28
30
29−1. Open `brave://extensions` and turn on **Developer mode**.
31+ ```sh
30−2. Click **Load unpacked** and pick the `dist/` folder.
32+ bash ~/Applications/Diagonal/install-host.command
31−3. Check that the ID shown is `mpnodlalikgeehnlnofdkpgapkmbkjdf`. It is pinned by the `key` in `manifest.json`, so it stays the same across reloads and machines.
33+ ```
34+
35+ (Type `bash `, then drag `install-host.command` from the folder into the Terminal window.) If it says python3 is not set up, run `xcode-select --install` and try again.
36+4. Open Diagonal's settings and click **Run self-test**. Brave does not need a restart.
37+
38+The zip carries the host (`host/`), `extension-id` and `install-host.command`, which is the same script as `scripts/install-manifest.sh`. When you update to a newer release, rerun step 3.
32
39
33−To rebuild from source: `npm ci && npm run build`.
40+### From source
41+
42+#### 1. The extension
43+
44+1. `npm ci && npm run build` writes the unpacked extension to `dist/`.
45+2. Open `brave://extensions`, turn on **Developer mode**, click **Load unpacked** and pick `dist/`.
46+3. Check that the ID shown is `mpnodlalikgeehnlnofdkpgapkmbkjdf`. It is pinned by the `key` in `manifest.json`, so it stays the same across reloads and machines.
34
47
35−### 2. The native host
48+#### 2. The native host
36
49
37Without Nix:
50Without Nix:
38
51
@@ -56,7 +69,7 @@ programs.diagonal.enable = true;
56
69
57`nix build .#extension` builds `dist/` into `./result`; `nix build .#diagonal-host` builds the host.
70`nix build .#extension` builds `dist/` into `./result`; `nix build .#diagonal-host` builds the host.
58
71
59−### 3. Check it
72+#### 3. Check it
60
73
61Open Diagonal's settings page and click **Run self-test**. Expect `fmAvailable: true`, `schemasOk: true` and a title for the three fixture tabs. From a terminal, `diagonal-host --selftest` runs the same checks.
74Open Diagonal's settings page and click **Run self-test**. Expect `fmAvailable: true`, `schemasOk: true` and a title for the three fixture tabs. From a terminal, `diagonal-host --selftest` runs the same checks.
62
75
scripts/build.mjs+7−1
@@ -1,6 +1,6 @@
1// Bundles src/ into dist/ (the unpacked extension). `--watch` rebuilds on change.
1// Bundles src/ into dist/ (the unpacked extension). `--watch` rebuilds on change.
2import * as esbuild from "esbuild";
2import * as esbuild from "esbuild";
3−import { cpSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3+import { chmodSync, cpSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4import { dirname, join } from "node:path";
4import { dirname, join } from "node:path";
5import { fileURLToPath } from "node:url";
5import { fileURLToPath } from "node:url";
6
6
@@ -35,6 +35,12 @@ function copyStatic() {
35 cpSync(join(root, "src/options/options.html"), join(dist, "options.html"));
35 cpSync(join(root, "src/options/options.html"), join(dist, "options.html"));
36 cpSync(join(root, "src/shared/ui.css"), join(dist, "ui.css"));
36 cpSync(join(root, "src/shared/ui.css"), join(dist, "ui.css"));
37 cpSync(join(root, "icons"), join(dist, "icons"), { recursive: true, filter: (p) => !p.endsWith(".svg") });
37 cpSync(join(root, "icons"), join(dist, "icons"), { recursive: true, filter: (p) => !p.endsWith(".svg") });
38+ // The native host and its installer ride along, so the release zip is all a user needs.
39+ mkdirSync(join(dist, "host"));
40+ for (const f of ["diagonal-host.py", "prompts.py", "validate.py", "emoji.txt"]) cpSync(join(root, "host", f), join(dist, "host", f));
41+ cpSync(join(root, "extension-id"), join(dist, "extension-id"));
42+ cpSync(join(root, "scripts/install-manifest.sh"), join(dist, "install-host.command"));
43+ chmodSync(join(dist, "install-host.command"), 0o755);
38}
44}
39
45
40if (watch) {
46if (watch) {
scripts/install-manifest.sh+10−5
@@ -1,11 +1,14 @@
1#!/usr/bin/env bash
1#!/usr/bin/env bash
2# Non-Nix installer for the native host (section 14).
2# Non-Nix installer for the native host (section 14).
3# scripts/install-manifest.sh [--beta] [--nightly] [--all-channels]
3# scripts/install-manifest.sh [--beta] [--nightly] [--all-channels]
4+# The release zip ships this same script as install-host.command next to host/ and extension-id,
5+# so `bash install-host.command` works from the unzipped extension folder too.
4# Copies host/ to ~/.local/share/diagonal-host, links ~/.local/bin/diagonal-host to it, writes the Brave
6# Copies host/ to ~/.local/share/diagonal-host, links ~/.local/bin/diagonal-host to it, writes the Brave
5# host manifest with that absolute path and the pinned extension ID, then installs the fm schemas
7# host manifest with that absolute path and the pinned extension ID, then installs the fm schemas
6# and runs the self-test. Safe to re-run.
8# and runs the self-test. Safe to re-run.
7set -euo pipefail
9set -euo pipefail
8−cd "$(dirname "$0")/.."
10+HERE="$(cd "$(dirname "$0")" && pwd)"
11+if [[ -d "$HERE/host" ]]; then cd "$HERE"; else cd "$HERE/.."; fi
9
12
10CHANNELS=("Brave-Browser")
13CHANNELS=("Brave-Browser")
11for arg in "$@"; do
14for arg in "$@"; do
@@ -24,12 +27,12 @@ if [[ -z "$ID" ]]; then
24fi
27fi
25
28
26PYTHON=$(command -v python3 || true)
29PYTHON=$(command -v python3 || true)
27−if [[ -z "$PYTHON" ]]; then
30+# Brave starts the host with a minimal PATH, so the shebang names this python3 explicitly.
28− echo "python3 not found on PATH" >&2
31+# On a Mac without the Command Line Tools, /usr/bin/python3 is a stub that only offers to install them.
32+if [[ -z "$PYTHON" ]] || ! PYTHON=$("$PYTHON" -c 'import sys; print(sys.executable)' 2>/dev/null); then
33+ echo "python3 is not set up. Run: xcode-select --install then run this installer again." >&2
29 exit 1
34 exit 1
30fi
35fi
31−# Brave starts the host with a minimal PATH, so the shebang names this python3 explicitly.
32−PYTHON=$("$PYTHON" -c 'import sys; print(sys.executable)')
33
36
34SHARE="$HOME/.local/share/diagonal-host"
37SHARE="$HOME/.local/share/diagonal-host"
35BIN="$HOME/.local/bin/diagonal-host"
38BIN="$HOME/.local/bin/diagonal-host"
@@ -37,6 +40,8 @@ mkdir -p "$SHARE" "$(dirname "$BIN")"
37cp host/prompts.py host/validate.py host/emoji.txt "$SHARE/"
40cp host/prompts.py host/validate.py host/emoji.txt "$SHARE/"
38sed -e "1s|.*|#!$PYTHON|" -e "s|^ALLOWED_ORIGIN = \".*\"|ALLOWED_ORIGIN = \"chrome-extension://$ID/\"|" host/diagonal-host.py > "$SHARE/diagonal-host.py"
41sed -e "1s|.*|#!$PYTHON|" -e "s|^ALLOWED_ORIGIN = \".*\"|ALLOWED_ORIGIN = \"chrome-extension://$ID/\"|" host/diagonal-host.py > "$SHARE/diagonal-host.py"
39chmod 755 "$SHARE/diagonal-host.py"
42chmod 755 "$SHARE/diagonal-host.py"
43+# Files unzipped from a browser download carry the quarantine flag; the host must not.
44+xattr -dr com.apple.quarantine "$SHARE" 2>/dev/null || true
40ln -sf "$SHARE/diagonal-host.py" "$BIN"
45ln -sf "$SHARE/diagonal-host.py" "$BIN"
41echo "host: $BIN -> $SHARE/diagonal-host.py"
46echo "host: $BIN -> $SHARE/diagonal-host.py"
42
47
src/background/host.ts+1−1
@@ -111,7 +111,7 @@ export function explain(error: HostError | undefined, extensionId = ""): string
111 if (!error) return "";
111 if (!error) return "";
112 switch (error.code) {
112 switch (error.code) {
113 case "HOST_NOT_FOUND":
113 case "HOST_NOT_FOUND":
114− return `Native host not installed. Expected manifest at ${HOST_MANIFEST_PATH}`;
114+ return `Native host not installed. In Terminal, run: bash <the Diagonal folder>/install-host.command (expected manifest at ${HOST_MANIFEST_PATH})`;
115 case "HOST_FORBIDDEN":
115 case "HOST_FORBIDDEN":
116 case "FORBIDDEN_ORIGIN":
116 case "FORBIDDEN_ORIGIN":
117 return `The host does not allow this extension. Its manifest needs "allowed_origins": ["chrome-extension://${extensionId}/"]`;
117 return `The host does not allow this extension. Its manifest needs "allowed_origins": ["chrome-extension://${extensionId}/"]`;