Add Hyprland skill and configuration files; enhance installation process with GPU detection and SDDM support
This commit is contained in:
@@ -18,17 +18,27 @@ npm install
|
||||
npm start install # interactive: choose packages, installs them, backs up conflicting files, symlinks into $HOME
|
||||
npm start update # pacman -Syu + yay -Syu, then re-links (stow --restow) any new files
|
||||
npm start remove # removes symlinks (stow -D), restores the most recent backup (does NOT uninstall packages)
|
||||
npx tsc --noEmit # typecheck (no test suite or lint script exists)
|
||||
npx tsc --noEmit # typecheck
|
||||
npm test # runs test/ via Node's built-in test runner (node:test) through tsx
|
||||
./docker/test.sh "npm test" # run the full suite, including stow-dependent tests skipped on a bare host
|
||||
```
|
||||
|
||||
There is no build step — `tsx` runs the TypeScript directly.
|
||||
|
||||
## Testing
|
||||
|
||||
`test/*.test.ts` uses Node's built-in `node:test` + `node:assert` (via `tsx --test`) — no test framework dependency was added. `test/gpu.test.ts` and `test/packages.test.ts` are pure unit tests. `test/stow.test.ts` exercises the real `stow` binary and real filesystem calls against a throwaway `$HOME` (a `mkdtempSync` dir, swapped in via `process.env.HOME` before `stow.ts` is dynamically imported, since `os.homedir()` reads `$HOME` and the module's path constants are computed at import time); each case is skipped when `stow` isn't installed, which is the case on a bare host — run `./docker/test.sh "npm test"` to execute those for real inside a container that has `stow`.
|
||||
|
||||
`docker/test.sh` (see `docker/Dockerfile`) spins up a disposable Arch container with `stow`/`nodejs`/`npm`/`git`/`sudo` preinstalled and the repo copied to a writable path inside it, so `npm install`, Stow symlinks, and backups never touch the host. Run it with no args for an interactive shell, or pass a command string to run non-interactively.
|
||||
|
||||
## Architecture
|
||||
|
||||
- `src/packages.ts` — the package model. Each entry has a `tier` (`required` installs with no prompt; `recommended` is offered via checkbox), an optional `group` for mutually-exclusive alternatives (e.g. `waybar` vs `ags` both have `group: "bar"`, only one gets picked via a select prompt), and an optional `dir` when the Stow directory name differs from the package name (e.g. package `rofi-wayland` → dir `rofi`). `toDirs()` converts chosen package names to their Stow directory names.
|
||||
- `src/stow.ts` — all filesystem/process side effects live here: bootstrapping `yay` and oh-my-zsh/zinit if missing, installing packages via `yay`, backing up real (non-symlink) files that would collide with a Stow target into `~/.dotfiles-backup/<timestamp>/`, and wrapping `stow`/`--restow`/`-D` (always invoked with an explicit `-d <PACKAGES_DIR>` so behavior doesn't depend on cwd). Also reads/writes `~/.config/dotfiles/state.json`, which records which package dirs were chosen at install time — `update` and `remove` act on this list rather than re-prompting.
|
||||
- `src/gpu.ts` — `detectGpuVendors()` shells out to `lspci` and matches VGA/3D controller lines against NVIDIA/AMD/Intel keywords (word-boundary regexes — a naive `/amd|ati/i` used to false-positive on the "ati" inside "Corporation"); `driverPackagesFor()` maps detected vendors to their Arch driver packages. `install.ts` shows every detected vendor's packages in a pre-checked confirm prompt (installing all of them on hybrid/Optimus laptops), and if NVIDIA is kept, `stow.ts`'s `writeNvidiaEnvConfig()` writes `~/.config/hypr/conf.d/nvidia.lua` (machine-generated, not stowed) with the Wayland env vars Hyprland's NVIDIA docs recommend.
|
||||
- `src/stow.ts` — all filesystem/process side effects live here: bootstrapping `yay` and oh-my-zsh/zinit if missing, installing packages via `yay`, enabling the `sddm` systemd service (`ensureSddmEnabled`), backing up real (non-symlink) files that would collide with a Stow target into `~/.dotfiles-backup/<timestamp>/`, and wrapping `stow`/`--restow`/`-D` (always invoked with an explicit `-d <PACKAGES_DIR>` so behavior doesn't depend on cwd). Also reads/writes `~/.config/dotfiles/state.json`, which records which package dirs were chosen at install time — `update` and `remove` act on this list rather than re-prompting.
|
||||
- `src/install.ts`, `src/update.ts`, `src/remove.ts` — the three subcommands, each a thin sequence of calls into `stow.ts`, using `@clack/prompts` for interactive selection in `install.ts`.
|
||||
- `src/cli.ts` — dispatches `install|update|remove` via Node's built-in `node:util.parseArgs`.
|
||||
- `packages/hyprland/.config/hypr/hyprland.lua` — Hyprland's config, in its **current Lua format** (`hl.config`/`hl.bind`/`hl.monitor`/`hl.on`/`hl.env`), not the old `hyprland.conf` ini/hyprlang syntax deprecated since Hyprland 0.55. Easy detail to get wrong when editing — check `wiki.hypr.land` (or the raw `hyprland-wiki` GitHub markdown) for current syntax rather than assuming the old ini style.
|
||||
|
||||
### Adding a new app
|
||||
|
||||
|
||||
@@ -35,6 +35,13 @@ Which packages/dirs were chosen at install time is recorded in
|
||||
`~/.config/dotfiles/state.json`, so `update`/`remove` know what to act on
|
||||
without re-prompting.
|
||||
|
||||
`install` also installs SDDM and enables its systemd service (so Hyprland
|
||||
actually starts at next boot), and auto-detects installed GPU(s) via `lspci`
|
||||
to propose NVIDIA/AMD/Intel driver packages (confirm/override before they're
|
||||
installed). If NVIDIA is kept, the required Wayland env vars are written to
|
||||
`~/.config/hypr/conf.d/nvidia.lua` (machine-generated, not stowed) and
|
||||
sourced from `hyprland.lua`.
|
||||
|
||||
## Wallpaper
|
||||
|
||||
Default wallpaper by rahremix (via github.com/PsychedelicPalimpsest/dotfiles).
|
||||
|
||||
+2
-1
@@ -3,7 +3,8 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "tsx src/cli.ts"
|
||||
"start": "tsx src/cli.ts",
|
||||
"test": "tsx --test \"test/**/*.test.ts\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.9.0"
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
monitor=,preferred,auto,1
|
||||
|
||||
exec-once = waybar
|
||||
exec-once = mako
|
||||
exec-once = hyprpaper
|
||||
|
||||
$mainMod = SUPER
|
||||
|
||||
bind = $mainMod, RETURN, exec, kitty
|
||||
bind = $mainMod, D, exec, wofi --show drun
|
||||
bind = $mainMod, Q, killactive
|
||||
bind = $mainMod, M, exit
|
||||
|
||||
input {
|
||||
kb_layout = us
|
||||
}
|
||||
|
||||
general {
|
||||
gaps_in = 4
|
||||
gaps_out = 8
|
||||
border_size = 2
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = 1 })
|
||||
|
||||
hl.on("hyprland.start", function()
|
||||
hl.exec_cmd("waybar")
|
||||
hl.exec_cmd("mako")
|
||||
hl.exec_cmd("hyprpaper")
|
||||
end)
|
||||
|
||||
local mainMod = "SUPER"
|
||||
|
||||
hl.bind(mainMod .. " + T", hl.dsp.exec_cmd("kitty"))
|
||||
hl.bind(mainMod .. " + D", hl.dsp.exec_cmd("wofi --show drun"))
|
||||
hl.bind(mainMod .. " + Q", hl.dsp.window.close())
|
||||
hl.bind(mainMod .. " + M", hl.dsp.exit())
|
||||
|
||||
hl.config({
|
||||
input = { kb_layout = "us" },
|
||||
general = { gaps_in = 4, gaps_out = 8, border_size = 2 },
|
||||
})
|
||||
|
||||
-- optional, machine-generated, only present when install detected an NVIDIA GPU
|
||||
pcall(require, "conf.d.nvidia")
|
||||
@@ -1,3 +1,7 @@
|
||||
splash = false
|
||||
preload = ~/.config/hypr/wallpapers/wallpaper.png
|
||||
wallpaper = ,~/.config/hypr/wallpapers/wallpaper.png
|
||||
|
||||
wallpaper {
|
||||
monitor =
|
||||
path = ~/.config/hypr/wallpapers/wallpaper.png
|
||||
fit_mode = cover
|
||||
}
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
|
||||
export type GpuVendor = "nvidia" | "amd" | "intel";
|
||||
|
||||
// ponytail: card-generation nuance (e.g. Nouveau for pre-Turing NVIDIA cards) isn't
|
||||
// handled — add if an unsupported-card report comes in.
|
||||
const DRIVER_PACKAGES: Record<GpuVendor, string[]> = {
|
||||
nvidia: ["nvidia-open-dkms", "nvidia-utils", "egl-wayland"],
|
||||
amd: ["mesa", "vulkan-radeon", "libva-mesa-driver"],
|
||||
intel: ["mesa", "vulkan-intel", "intel-media-driver"],
|
||||
};
|
||||
|
||||
const VENDOR_PATTERNS: [RegExp, GpuVendor][] = [
|
||||
[/nvidia/i, "nvidia"],
|
||||
[/\b(amd|ati)\b/i, "amd"],
|
||||
[/intel/i, "intel"],
|
||||
];
|
||||
|
||||
export function detectGpuVendors(): GpuVendor[] {
|
||||
let output: string;
|
||||
try {
|
||||
output = execFileSync("lspci", [], { encoding: "utf8" });
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
|
||||
const controllerLines = output
|
||||
.split("\n")
|
||||
.filter((line) => /VGA compatible controller|3D controller/i.test(line));
|
||||
|
||||
const found = new Set<GpuVendor>();
|
||||
for (const line of controllerLines) {
|
||||
for (const [pattern, vendor] of VENDOR_PATTERNS) {
|
||||
if (pattern.test(line)) found.add(vendor);
|
||||
}
|
||||
}
|
||||
return [...found];
|
||||
}
|
||||
|
||||
export function driverPackagesFor(vendors: GpuVendor[]): string[] {
|
||||
return vendors.flatMap((vendor) => DRIVER_PACKAGES[vendor]);
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
import * as p from "@clack/prompts";
|
||||
import { groups, packages, toDirs } from "./packages.js";
|
||||
import { detectGpuVendors, driverPackagesFor } from "./gpu.js";
|
||||
import {
|
||||
backupConflicts,
|
||||
ensureSddmEnabled,
|
||||
ensureYay,
|
||||
ensureZsh,
|
||||
installPackages,
|
||||
stow,
|
||||
writeNvidiaEnvConfig,
|
||||
writeState,
|
||||
} from "./stow.js";
|
||||
|
||||
@@ -36,10 +39,33 @@ export async function install() {
|
||||
if (p.isCancel(picks)) return p.cancel("Install cancelled.");
|
||||
for (const name of picks as string[]) chosen.add(name);
|
||||
|
||||
const gpuVendors = detectGpuVendors();
|
||||
const gpuOptions = gpuVendors.flatMap((vendor) =>
|
||||
driverPackagesFor([vendor]).map((pkg) => ({ value: pkg, label: `${vendor}: ${pkg}` })),
|
||||
);
|
||||
let gpuPicks: string[] = [];
|
||||
if (gpuOptions.length > 0) {
|
||||
const picked = await p.multiselect({
|
||||
message: "Detected GPU(s) — confirm driver packages to install",
|
||||
options: gpuOptions,
|
||||
initialValues: gpuOptions.map((o) => o.value),
|
||||
required: false,
|
||||
});
|
||||
if (p.isCancel(picked)) return p.cancel("Install cancelled.");
|
||||
gpuPicks = picked as string[];
|
||||
for (const name of gpuPicks) chosen.add(name);
|
||||
}
|
||||
|
||||
installPackages([...chosen]);
|
||||
|
||||
ensureSddmEnabled();
|
||||
|
||||
if (chosen.has("zsh")) ensureZsh();
|
||||
|
||||
if (gpuVendors.includes("nvidia") && driverPackagesFor(["nvidia"]).some((pkg) => gpuPicks.includes(pkg))) {
|
||||
writeNvidiaEnvConfig();
|
||||
}
|
||||
|
||||
const dirs = toDirs([...chosen]);
|
||||
const backupDir = backupConflicts(dirs);
|
||||
if (backupDir) p.log.info(`Backed up conflicting files to ${backupDir}`);
|
||||
|
||||
@@ -10,6 +10,8 @@ export type Pkg = {
|
||||
export const packages: Pkg[] = [
|
||||
{ name: "hyprland", tier: "required" },
|
||||
{ name: "xdg-desktop-portal-hyprland", tier: "required" },
|
||||
{ name: "sddm", tier: "required" },
|
||||
{ name: "pciutils", tier: "required" },
|
||||
{ name: "waybar", tier: "recommended", group: "bar" },
|
||||
{ name: "ags", tier: "recommended", group: "bar", aur: true },
|
||||
{ name: "wofi", tier: "recommended", group: "launcher" },
|
||||
|
||||
+18
@@ -63,6 +63,24 @@ export function ensureZsh() {
|
||||
}
|
||||
}
|
||||
|
||||
export function ensureSddmEnabled() {
|
||||
try {
|
||||
execFileSync("systemctl", ["is-enabled", "--quiet", "sddm"]);
|
||||
} catch {
|
||||
run("sudo", ["systemctl", "enable", "sddm.service"]);
|
||||
}
|
||||
}
|
||||
|
||||
// Machine-generated, not stowed — only written when install detects an NVIDIA GPU.
|
||||
export function writeNvidiaEnvConfig() {
|
||||
const dir = join(homedir(), ".config", "hypr", "conf.d");
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(
|
||||
join(dir, "nvidia.lua"),
|
||||
'hl.env("LIBVA_DRIVER_NAME", "nvidia")\nhl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia")\n',
|
||||
);
|
||||
}
|
||||
|
||||
// Move real (non-symlink) files that would collide with a stow target out of the way.
|
||||
export function backupConflicts(dirs: string[]): string | null {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { mkdtempSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { detectGpuVendors, driverPackagesFor, type GpuVendor } from "../src/gpu.js";
|
||||
|
||||
function withFakeLspci(output: string, fn: () => void) {
|
||||
const dir = mkdtempSync(join(tmpdir(), "fake-lspci-"));
|
||||
writeFileSync(join(dir, "lspci"), `#!/bin/sh\ncat <<'EOF'\n${output}\nEOF\n`, { mode: 0o755 });
|
||||
const originalPath = process.env.PATH;
|
||||
process.env.PATH = `${dir}:${originalPath}`;
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
process.env.PATH = originalPath;
|
||||
}
|
||||
}
|
||||
|
||||
test("detects an NVIDIA + Intel machine", () => {
|
||||
withFakeLspci(
|
||||
`00:02.0 VGA compatible controller: Intel Corporation blah (rev 0a)
|
||||
01:00.0 VGA compatible controller: NVIDIA Corporation GA104 [GeForce RTX 3070] (rev a1)
|
||||
01:00.1 Audio device: NVIDIA Corporation GA104 High Definition Audio Controller (rev a1)`,
|
||||
() => {
|
||||
assert.deepStrictEqual(detectGpuVendors().sort(), ["intel", "nvidia"]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("detects an Intel + NVIDIA hybrid/Optimus laptop", () => {
|
||||
withFakeLspci(
|
||||
`00:02.0 VGA compatible controller: Intel Corporation TigerLake-LP GT2 [Iris Xe Graphics]
|
||||
01:00.0 3D controller: NVIDIA Corporation GA107M [GeForce RTX 3050 Mobile]`,
|
||||
() => {
|
||||
assert.deepStrictEqual(detectGpuVendors().sort(), ["intel", "nvidia"]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("detects an AMD-only machine without false-positiving on 'Corporation'", () => {
|
||||
// Regression test: /amd|ati/i used to match the "ati" inside "Corporation",
|
||||
// flagging every vendor line (including Intel/NVIDIA ones) as AMD.
|
||||
withFakeLspci(
|
||||
`03:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Navi 23 [Radeon RX 6600]`,
|
||||
() => {
|
||||
assert.deepStrictEqual(detectGpuVendors(), ["amd"]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("does not false-positive AMD on an NVIDIA-only 'Corporation' line", () => {
|
||||
withFakeLspci(
|
||||
`01:00.0 VGA compatible controller: NVIDIA Corporation GA104 [GeForce RTX 3070]`,
|
||||
() => {
|
||||
assert.deepStrictEqual(detectGpuVendors(), ["nvidia"]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("returns no vendors when lspci reports no controllers", () => {
|
||||
withFakeLspci("", () => {
|
||||
assert.deepStrictEqual(detectGpuVendors(), []);
|
||||
});
|
||||
});
|
||||
|
||||
test("returns no vendors when lspci is missing entirely", () => {
|
||||
const originalPath = process.env.PATH;
|
||||
process.env.PATH = "/nonexistent";
|
||||
try {
|
||||
assert.deepStrictEqual(detectGpuVendors(), []);
|
||||
} finally {
|
||||
process.env.PATH = originalPath;
|
||||
}
|
||||
});
|
||||
|
||||
test("driverPackagesFor maps each vendor to its package list", () => {
|
||||
assert.deepStrictEqual(driverPackagesFor(["amd"]), ["mesa", "vulkan-radeon", "libva-mesa-driver"]);
|
||||
assert.deepStrictEqual(driverPackagesFor([]), []);
|
||||
const vendors: GpuVendor[] = ["nvidia", "intel"];
|
||||
assert.deepStrictEqual(driverPackagesFor(vendors), [
|
||||
"nvidia-open-dkms",
|
||||
"nvidia-utils",
|
||||
"egl-wayland",
|
||||
"mesa",
|
||||
"vulkan-intel",
|
||||
"intel-media-driver",
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { groups, packages, toDirs } from "../src/packages.js";
|
||||
|
||||
test("toDirs maps a package name to its dir when set", () => {
|
||||
assert.deepStrictEqual(toDirs(["rofi-wayland"]), ["rofi"]);
|
||||
});
|
||||
|
||||
test("toDirs falls back to the package name when no dir is set", () => {
|
||||
assert.deepStrictEqual(toDirs(["waybar", "kitty"]), ["waybar", "kitty"]);
|
||||
});
|
||||
|
||||
test("toDirs falls back to the raw name for unknown packages", () => {
|
||||
assert.deepStrictEqual(toDirs(["not-a-real-package"]), ["not-a-real-package"]);
|
||||
});
|
||||
|
||||
test("groups contains exactly the distinct group values declared on packages, no duplicates", () => {
|
||||
const expected = [...new Set(packages.filter((p) => p.group).map((p) => p.group!))];
|
||||
assert.deepStrictEqual(groups, expected);
|
||||
assert.strictEqual(new Set(groups).size, groups.length, "groups should have no duplicates");
|
||||
});
|
||||
|
||||
test("every grouped package's group appears in the groups list", () => {
|
||||
for (const pkg of packages.filter((p) => p.group)) {
|
||||
assert.ok(groups.includes(pkg.group!), `${pkg.name}'s group "${pkg.group}" missing from groups`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
// Integration-style tests for src/stow.ts. These exercise the real `stow` binary and
|
||||
// real filesystem calls, so each test:
|
||||
// - runs against a throwaway $HOME (a fresh mkdtempSync dir), never the real one
|
||||
// - is skipped when `stow` isn't installed (confirmed missing on a bare host) —
|
||||
// run the full suite for real via `./docker/test.sh "npm test"`
|
||||
import { test, after } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { existsSync, lstatSync, mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
function hasStow(): boolean {
|
||||
try {
|
||||
execFileSync("which", ["stow"], { stdio: "ignore" });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const skip = !hasStow();
|
||||
|
||||
// os.homedir() reads $HOME on POSIX, so setting this before importing stow.js
|
||||
// (whose module-level consts are computed from homedir() at import time) sandboxes
|
||||
// every path the module touches, with no changes to production code.
|
||||
const fakeHome = mkdtempSync(join(tmpdir(), "dotfiles-test-home-"));
|
||||
process.env.HOME = fakeHome;
|
||||
after(() => rmSync(fakeHome, { recursive: true, force: true }));
|
||||
|
||||
const {
|
||||
backupConflicts,
|
||||
restoreLatestBackup,
|
||||
restow,
|
||||
stow,
|
||||
unstow,
|
||||
writeState,
|
||||
readState,
|
||||
} = skip ? ({} as any) : await import("../src/stow.js");
|
||||
|
||||
const dirs = ["hyprland", "waybar", "kitty", "hyprpaper"];
|
||||
const hyprlandTarget = join(fakeHome, ".config", "hypr", "hyprland.lua");
|
||||
const wallpaperTarget = join(fakeHome, ".config", "hypr", "wallpapers", "wallpaper.png");
|
||||
|
||||
test("seeds a pre-existing conflicting file", { skip }, () => {
|
||||
mkdirSync(join(fakeHome, ".config", "hypr"), { recursive: true });
|
||||
writeFileSync(hyprlandTarget, "old config\n");
|
||||
assert.strictEqual(readFileSync(hyprlandTarget, "utf8").trim(), "old config");
|
||||
});
|
||||
|
||||
test("backupConflicts moves the pre-existing file out of the way", { skip }, () => {
|
||||
const backupDir = backupConflicts(dirs);
|
||||
assert.ok(backupDir, "expected a backup dir since a conflict existed");
|
||||
assert.ok(!existsSync(hyprlandTarget), "conflicting file should have been moved out of the way");
|
||||
});
|
||||
|
||||
test("stow symlinks the chosen packages into $HOME", { skip }, () => {
|
||||
stow(dirs);
|
||||
assert.ok(lstatSync(hyprlandTarget).isSymbolicLink(), "hyprland.lua should now be a symlink");
|
||||
assert.ok(existsSync(wallpaperTarget), "wallpaper.png should be reachable through the stowed dir");
|
||||
});
|
||||
|
||||
test("writeState/readState round-trip the chosen dirs", { skip }, () => {
|
||||
writeState({ dirs });
|
||||
assert.deepStrictEqual(readState().dirs, dirs);
|
||||
});
|
||||
|
||||
test("restow keeps the symlinks intact", { skip }, () => {
|
||||
restow(dirs);
|
||||
assert.ok(lstatSync(hyprlandTarget).isSymbolicLink(), "restow should keep the symlink");
|
||||
assert.ok(existsSync(wallpaperTarget), "restow should keep wallpaper reachable");
|
||||
});
|
||||
|
||||
test("unstow removes the symlinks", { skip }, () => {
|
||||
unstow(dirs);
|
||||
assert.ok(
|
||||
!existsSync(hyprlandTarget) || !lstatSync(hyprlandTarget).isSymbolicLink(),
|
||||
"unstow should remove the symlink",
|
||||
);
|
||||
assert.ok(!existsSync(wallpaperTarget), "unstow should remove the wallpaper symlink");
|
||||
});
|
||||
|
||||
test("restoreLatestBackup brings back the original file content", { skip }, () => {
|
||||
const restored = restoreLatestBackup();
|
||||
assert.ok(restored, "expected a backup to restore");
|
||||
assert.strictEqual(readFileSync(hyprlandTarget, "utf8").trim(), "old config", "original content should be restored");
|
||||
});
|
||||
+1
-1
@@ -7,5 +7,5 @@
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": ["src", "test"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user