Skip to content
This repository was archived by the owner on Sep 10, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [macos-latest, ubuntu-20.04, windows-latest]
platform: [macos-latest, ubuntu-22.04, windows-latest]
runs-on: ${{ matrix.platform }}

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-20.04'
if: matrix.platform == 'ubuntu-22.04'
# You can remove libayatana-appindicator3-dev if you don't use the system tray feature.
run: |
sudo apt-get update
Expand All @@ -47,13 +47,27 @@ jobs:
- name: Release Drafter
uses: release-drafter/release-drafter@v5.21.1

- name: Validate public search build inputs
env:
TAURI_MEILI_HOST: ${{ secrets.TAURI_MEILI_HOST }}
TAURI_MEILI_SEARCH_KEY: ${{ secrets.TAURI_MEILI_SEARCH_KEY }}
run: node scripts/validate-public-search-config.mjs

- name: Build the app
uses: tauri-apps/tauri-action@v0

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Both of these are INLINED INTO THE SHIPPED BUNDLE by Vite --
# vite.config.ts sets envPrefix: ["VITE_", "TAURI_"]. They are build
# inputs, not secrets: whoever holds a release artifact can read them
# out of it with `strings`.
#
# TAURI_MEILI_SEARCH_KEY must therefore be a Meilisearch SEARCH key,
# scoped read-only to the indexes this app searches. Never the master
# key, which can create keys, write documents, and read every index.
TAURI_MEILI_HOST: ${{ secrets.TAURI_MEILI_HOST }}
TAURI_MEILI_API_KEY: ${{ secrets.TAURI_MEILI_API_KEY }}
TAURI_MEILI_SEARCH_KEY: ${{ secrets.TAURI_MEILI_SEARCH_KEY }}

with:
tagName: ${{ github.ref_name }} # This only works if your workflow triggers on new tags.
Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/verify_frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ on:
paths:
- 'src/**'
- 'tests/**'
- 'scripts/**'
- 'package.json'
- '.github/workflows/release.yml'
- '.github/workflows/verify_frontend.yml'
branches:
- main
Expand All @@ -13,7 +16,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
os: [ubuntu-22.04, windows-latest, macOS-latest]

steps:
- uses: actions/checkout@v3
Expand All @@ -34,11 +37,12 @@ jobs:
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev
if: matrix.os == 'ubuntu-latest'
if: matrix.os == 'ubuntu-22.04'

- name: npm install, build and test
run: |
npm install
npm run test:release-config
npm run build
npm run tauri build
npm run test run -- -t e
npm run test -- run
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"format": "prettier src --ext .ts,.tsx,.js,.jsx -w ",
"tauri": "tauri",
"start": "tauri dev",
"test": "vitest"
"test": "vitest",
"test:release-config": "node scripts/validate-public-search-config.check.mjs"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.0",
Expand Down
36 changes: 36 additions & 0 deletions scripts/validate-public-search-config.check.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";

const validator = fileURLToPath(
new URL("./validate-public-search-config.mjs", import.meta.url),
);

function validate(overrides) {
const result = spawnSync(process.execPath, [validator], {
encoding: "utf8",
env: {
...process.env,
TAURI_MEILI_HOST: "https://search.example.test",
TAURI_MEILI_SEARCH_KEY: "public-search-key",
...overrides,
},
});

if (result.error) {
assert.fail(`configuration validator could not run: ${result.error.message}`);
}

return result;
}

const missingHost = validate({ TAURI_MEILI_HOST: "" });
assert.notEqual(missingHost.status, 0, "an empty search host must stop the release");
assert.match(missingHost.stderr, /TAURI_MEILI_HOST/);

const missingKey = validate({ TAURI_MEILI_SEARCH_KEY: "" });
assert.notEqual(missingKey.status, 0, "an empty search key must stop the release");
assert.match(missingKey.stderr, /TAURI_MEILI_SEARCH_KEY/);

const configured = validate({});
assert.equal(configured.status, 0, configured.stderr);
11 changes: 11 additions & 0 deletions scripts/validate-public-search-config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const requiredInputs = ["TAURI_MEILI_HOST", "TAURI_MEILI_SEARCH_KEY"];
const missingInputs = requiredInputs.filter(
(name) => !process.env[name] || !process.env[name].trim(),
);

if (missingInputs.length > 0) {
console.error(
`::error::Missing required public search build input(s): ${missingInputs.join(", ")}`,
);
process.exitCode = 1;
}
21 changes: 18 additions & 3 deletions src/components/search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,28 @@ import { Button } from "../buttons";
import SearchBox from "./SearchBox";
import styles from "./Search.module.scss";

const { TAURI_MEILI_HOST: host = "", TAURI_MEILI_API_KEY: apiKey = "" } =
// vite.config.ts sets envPrefix: ["VITE_", "TAURI_"], so anything read here
// through import.meta.env is INLINED INTO THE BUNDLE at build time. It is not a
// secret once the app is distributed: `strings` over the binary returns it, and
// so does opening the JavaScript.
//
// There is no way to hide a credential in a client that queries a search server
// directly, so this must be a value the client is ALLOWED to hold — a
// Meilisearch search key, scoped read-only to the indexes this app searches.
// The name says so on purpose. The master key must never be put here: it can
// create keys, write documents and read every index on the instance.
//
// If search ever needs a credential the client may not hold, the request has to
// go through something that holds it instead — a proxy, or a Tauri command
// talking to a server — because the Rust binary is no better a hiding place
// than the JavaScript is.
const { TAURI_MEILI_HOST: host = "", TAURI_MEILI_SEARCH_KEY: searchKey = "" } =
import.meta.env;

let searchClient: InstantMeiliSearchInstance;
if (host && apiKey) {
if (host && searchKey) {
try {
searchClient = instantMeiliSearch(host, apiKey, {
searchClient = instantMeiliSearch(host, searchKey, {
primaryKey: "_id",
});
} catch (error) {
Expand Down
18 changes: 0 additions & 18 deletions tests/components/modules/DiceModule.test.tsx

This file was deleted.

1 change: 0 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,5 @@ export default defineConfig({
globals: true,
// for access to the DOM
environment: "jsdom",
testNamePattern: "./tests/*.test.tsx",
},
});
Loading