123 lines
7.1 KiB
TypeScript
123 lines
7.1 KiB
TypeScript
// The zsh module's shape (novox/hq ADR 0203, ADR 0204, to-be 41 WP3): it claims the mesh's
|
|
// node-login-shell and declares no seat; its ~/.zshrc block goes at the start and holds the three
|
|
// slots in order; its ~/.zshenv block sources the path node-environment fixes and nothing else; its
|
|
// environment is a contribution, never an export in its own lines.
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
|
|
const m = JSON.parse(readFileSync(new URL("../module.json", import.meta.url), "utf8"));
|
|
const resource = (id: string) => m.resources.find((r: { id: string }) => r.id === id);
|
|
// A file's text is its template's, in the module's files/ (novox/hq ADR 0349 §2).
|
|
const text = (r: { id: string; template?: string }): string => {
|
|
assert.ok(r.template, `${r.id} names no template`);
|
|
return readFileSync(new URL(`../${r.template}`, import.meta.url), "utf8");
|
|
};
|
|
|
|
test("no file carries its text inline: each names its template, and the build packs files/ into one archive", () => {
|
|
for (const r of m.resources) {
|
|
assert.equal(r.content, undefined, `${r.id} keeps its text inline`);
|
|
if (r.type === "file") text(r);
|
|
}
|
|
const packs = m.build.artifacts.filter((a: { kind: string; from?: string }) => a.kind === "archive" && a.from === "files");
|
|
assert.deepEqual(packs.map((a: { name: string }) => a.name), ["files"]);
|
|
});
|
|
|
|
test("it claims node-login-shell serving execute, and declares no seat", () => {
|
|
assert.equal(m.seats, undefined);
|
|
assert.deepEqual(m.claims, [{ name: "node-login-shell", scope: "node", serves: ["execute"] }]);
|
|
});
|
|
|
|
test("the .zshrc block is at the start and holds the three slots in order, after the defaults' header", () => {
|
|
const rc = resource("rc");
|
|
assert.equal(rc.path, "${machine:account-home}/.zshrc");
|
|
assert.equal(rc.into, "block");
|
|
assert.equal(rc.at, "start");
|
|
const c = text(rc);
|
|
const first = c.indexOf("${shell:zsh:first}");
|
|
const normal = c.indexOf("${shell:zsh:normal}");
|
|
const last = c.indexOf("${shell:zsh:last}");
|
|
assert.ok(c.startsWith("# The mesh's block"), "it opens by saying whose it is");
|
|
assert.ok(first > 0 && first < normal && normal < last, "first, normal, last, in that order");
|
|
assert.ok(c.indexOf("set_terminal_title") > first && c.indexOf("sudo-disksize") < normal, "the defaults sit between first and normal");
|
|
for (const slot of ["first", "normal", "last"]) assert.equal(c.split(`\${shell:zsh:${slot}}`).length, 2, `${slot} appears once`);
|
|
});
|
|
|
|
test("the .zshrc block carries the shared defaults and none of the operator's own lines", () => {
|
|
const c = text(resource("rc"));
|
|
for (const want of ["precmd_functions+=(set_terminal_title)", "alias egrep='egrep --color=auto'", "alias ll='ls -alhF'", "alias drun=", "disksize()", "sudo-disksize()"]) {
|
|
assert.ok(c.includes(want), want);
|
|
}
|
|
assert.equal((c.match(/^bindkey /gm) ?? []).length, 7);
|
|
for (const not of ["export ", "p10k", "powerlevel10k", "nvm", ".dotnet", "MY_KV_PATH", "MY_LIB_PATH", "CLAUDE_CODE_DISABLE_TERMINAL_TITLE", "killport", "findport", ".zshrc.local", "zsh-autosuggestions", "zsh-syntax-highlighting"]) {
|
|
assert.ok(!c.includes(not), `the block holds no ${not}`);
|
|
}
|
|
});
|
|
|
|
test("the .zshenv block sources exactly the environment file node-environment fixes", () => {
|
|
const env = resource("env");
|
|
assert.equal(env.path, "${machine:account-home}/.zshenv");
|
|
assert.equal(env.into, "block");
|
|
assert.equal(env.at, "start");
|
|
const sourced = [...text(env).matchAll(/^\s*(?:source|\.)\s+"?([^"\s]+)"?/gm)].map((x) => x[1]);
|
|
assert.deepEqual(sourced, ["${machine:account-home}/.config/mesh/environment.sh"]);
|
|
});
|
|
|
|
test("its environment is a contribution: the editor, the configuration home, and three PATH entries at the start", () => {
|
|
// The contribution is typed items of the environment collection, each with its value in a template (ADR 0348, 0349).
|
|
const items = m.items.filter((i: { collection: string }) => i.collection === "environment");
|
|
const value = (i: { template: string }) => readFileSync(new URL(`../${i.template}`, import.meta.url), "utf8").trim();
|
|
const variables = Object.fromEntries(
|
|
items.filter((i: { attributes: { kind: string } }) => i.attributes.kind === "variable").map((i: { attributes: { name: string }; template: string }) => [i.attributes.name, value(i)]),
|
|
);
|
|
assert.deepEqual(variables, { EDITOR: "vim", VISUAL: "vim", XDG_CONFIG_HOME: "${machine:account-home}/.config" });
|
|
const path = items
|
|
.filter((i: { attributes: { kind: string } }) => i.attributes.kind === "path")
|
|
.sort((a: { attributes: { order: string } }, b: { attributes: { order: string } }) => Number(a.attributes.order) - Number(b.attributes.order))
|
|
.map((i: { attributes: { at: string }; template: string }) => ({ entry: value(i), at: i.attributes.at }));
|
|
assert.deepEqual(path, [
|
|
{ entry: "${machine:account-home}/.local/bin", at: "start" },
|
|
{ entry: "${machine:account-home}/scripts", at: "start" },
|
|
{ entry: "${machine:account-home}/scripts/bin", at: "start" },
|
|
]);
|
|
assert.equal(m.environment, undefined);
|
|
});
|
|
|
|
test("it keeps the package and sets the login shell through the user shape", () => {
|
|
assert.deepEqual(resource("package"), { id: "package", type: "package", package: "zsh" });
|
|
assert.deepEqual(resource("login"), { id: "login", type: "user", name: "${machine:account}", shell: "/usr/bin/zsh" });
|
|
assert.ok(m.capabilities.includes("package-manager"));
|
|
});
|
|
|
|
test("the tools bundle registers the seat's verb under node-login-shell", () => {
|
|
const src = readFileSync(new URL("../tools/index.ts", import.meta.url), "utf8");
|
|
assert.match(src, /registerModuleTools\("node-login-shell", seatVerbs\)/);
|
|
assert.doesNotMatch(src, /runuser|"login-shell"/);
|
|
});
|
|
|
|
test("execute is a per-machine config value, served unless a machine withholds it (ADR 0268, ADR 0348 §4)", () => {
|
|
assert.equal(m.settings, undefined);
|
|
assert.deepEqual(Object.keys(m.config), ["execute"]);
|
|
assert.equal(m.config.execute.type, "string");
|
|
assert.deepEqual(m.config.execute.enum, ["serve", "withhold"]);
|
|
assert.equal(m.config.execute.trusted, true, "it decides what runs, so it is set at the terminal or on an approval");
|
|
assert.equal(m.config.execute.default, "serve");
|
|
assert.match(m.config.execute.description, /withhold/);
|
|
const dir = resource("state");
|
|
assert.deepEqual(dir, { id: "state", type: "directory", place: ".", owner: "root", mode: "0755" });
|
|
const conf = resource("tools-conf");
|
|
assert.equal(conf.type, "file");
|
|
assert.equal(conf.path, "${dir:state}/tools.conf");
|
|
assert.equal(conf.owner, "root", "the account the runtime runs as may read it, never change it");
|
|
assert.equal(conf.mode, "0644");
|
|
assert.match(text(conf), /^execute=\$\{config:execute\}$/m);
|
|
const [bundle] = m.build.artifacts;
|
|
assert.deepEqual(bundle.env, { ZSH_TOOLS_CONF: conf.path }, "the word names the file exactly, so its change restarts the runtime");
|
|
});
|
|
|
|
test("the tools bundle registers execute only where the machine's setting serves it", () => {
|
|
const src = readFileSync(new URL("../tools/index.ts", import.meta.url), "utf8");
|
|
assert.match(src, /readExecuteSetting\(env\)/);
|
|
assert.match(src, /if \(!setting\.served\)[\s\S]{0,200}return \[\]/);
|
|
});
|