Files
Termix/scripts/plugin-pack.test.ts
a89ccc6b07 v26.10.3 (#1692)
* feat: shell controller, TabStage, layout and theme plugins with repair view

* feat: move the classic layout into its own bundled plugin

* feat: move color themes into a bundled themes plugin

* fix: plugin tests and type checks work with a linked sdk

* fix: home tab, layout switch and missing layout fallbacks for looks

* chore: add classic layout and themes to the readme

* chore: drop example plugin links from the plugin guide

* feat: plugin filters by type, paged browse, hide plugins needing newer termix

* feat: move desktop, status, import, audit and branding features into plugins

* feat: host lock api so a plugin can load hosts from a config file (#1581)

* chore: add hosts config to the plugin table

* feat: login policy hook for plugins and optional https redirect (#1584)

* chore: add login security to the plugin table

* feat: api key socket tickets so a master can open remote sessions (#1654)

* feat: sync moves to a plugin, desktop gets multiple accounts (#1580, #1654)

* chore: add sync to the plugin table

* feat: move the sync screens and wizard out of core into the plugin (#1580)

* fix: add a neutral linked desktop hook for plugins (#1580)

* fix: show the linked account's name and admin flag in the app (#1580)

* feat: ssh transport providers, core ssh relay and plugin folders api

* feat: support FIDO2 security keys through ssh agent

* feat: add companion panels beside host tabs

* feat: mTLS client certificates for desktop server links

* feat: native web view for plugins in the desktop app

* fix: detect the desktop link window by Electron's default user agent

Fixes #1675

* fix: changelog for desktop link fix

* fix: keep text primary keys in the plugin data export

* fix: ship sharp binaries for Windows arm64 builds

* feat: client cert on the sync link types (sdk 1.0.18)

* chore: update translations

* feat: themed title bar for the desktop app (#1690)

* chore: changelog for the themed title bar

* fix: draw the title bar in the sidebar color

* feat: per host layout for the terminal panel bar

* chore: sdk 1.0.19 for the panel bar layout

* chore: bundle all 43 plugins for 26.10.3

* chore: update translations

---------

Co-authored-by: SirHephaistos <60616553+SirHephaistos@users.noreply.github.com>
Co-authored-by: Giovanny Aranda <garanda21@gmail.com>
2026-10-20 16:03:29 -05:00

171 lines
5.5 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import * as tar from "tar";
import { pack } from "../packages/plugin-sdk/cli/commands/pack.mjs";
const cleanups: Array<() => void> = [];
function tempDir(prefix: string): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
cleanups.push(() => fs.rmSync(dir, { recursive: true, force: true }));
return dir;
}
function writeFile(root: string, rel: string, content: string) {
const full = path.join(root, rel);
fs.mkdirSync(path.dirname(full), { recursive: true });
fs.writeFileSync(full, content);
}
function fixturePlugin(manifest: Record<string, unknown> = {}): string {
const dir = path.join(tempDir("termix-pack-"), "pack-fixture");
writeFile(
dir,
"manifest.json",
JSON.stringify({
id: "pack-fixture",
name: "Pack Fixture",
version: "1.2.3",
description: "Fixture for the pack tests.",
author: { name: "Termix Tests" },
license: "MIT",
category: "Productivity",
engine: { termix: ">=2.9.0", api: "1" },
capabilities: [],
...manifest,
}),
);
writeFile(dir, "dist/backend.js", "export async function activate() {}\n");
writeFile(dir, "dist/frontend.js", "export function activate() {}\n");
writeFile(dir, "locales/en.json", "{}\n");
writeFile(dir, "migrations/sqlite/0001_init.sql", "SELECT 1;\n");
writeFile(dir, "migrations/postgres/0001_init.sql", "SELECT 1;\n");
writeFile(dir, "migrations/mysql/0001_init.sql", "SELECT 1;\n");
writeFile(dir, "README.md", "# Fixture\n");
writeFile(dir, "icon.svg", "<svg/>\n");
writeFile(dir, "LICENSE", "Apache-2.0\n");
writeFile(dir, "NOTICE", "Third-party notices\n");
writeFile(dir, "src/backend/index.ts", "left out of the archive\n");
return dir;
}
function sha(file: string): string {
return crypto
.createHash("sha256")
.update(fs.readFileSync(file))
.digest("hex");
}
async function entries(file: string): Promise<string[]> {
const names: string[] = [];
await tar.t({ file, onReadEntry: (entry) => names.push(entry.path) });
return names;
}
afterEach(() => {
vi.restoreAllMocks();
while (cleanups.length) cleanups.pop()?.();
});
describe("termix-plugin pack", () => {
it("gives the same sha256 when rebuilt with new mtimes", async () => {
vi.spyOn(console, "log").mockImplementation(() => {});
const dir = fixturePlugin();
const first = await pack({ cwd: dir, args: ["--out", tempDir("a-")] });
const later = new Date(Date.now() + 86_400_000);
for (const rel of ["manifest.json", "dist/backend.js", "README.md"]) {
fs.utimesSync(path.join(dir, rel), later, later);
}
const second = await pack({ cwd: dir, args: ["--out", tempDir("b-")] });
expect(path.basename(first)).toBe("pack-fixture-1.2.3.tmxplug");
expect(sha(first)).toBe(sha(second));
});
it("holds only the shipped files, sorted, with fixed metadata", async () => {
vi.spyOn(console, "log").mockImplementation(() => {});
const dir = fixturePlugin();
const file = await pack({ cwd: dir, args: ["--out", tempDir("c-")] });
const names = await entries(file);
expect(names).toEqual([
"LICENSE",
"NOTICE",
"README.md",
"dist/backend.js",
"dist/frontend.js",
"icon.svg",
"locales/en.json",
"manifest.json",
"migrations/mysql/0001_init.sql",
"migrations/postgres/0001_init.sql",
"migrations/sqlite/0001_init.sql",
]);
const meta: Array<{ mode: number; mtime: number; uid: number }> = [];
await tar.t({
file,
onReadEntry: (entry) =>
meta.push({
mode: entry.mode ?? -1,
mtime: entry.mtime?.getTime() ?? -1,
uid: entry.uid ?? -1,
}),
});
expect(new Set(meta.map((m) => JSON.stringify(m)))).toEqual(
new Set([JSON.stringify({ mode: 0o644, mtime: 0, uid: 0 })]),
);
const gz = fs.readFileSync(file);
expect(gz.readUInt32LE(4)).toBe(0);
expect(gz[9]).toBe(0x03);
const out = tempDir("x-");
await tar.x({ file, cwd: out });
expect(fs.readFileSync(path.join(out, "dist/backend.js"), "utf8")).toBe(
"export async function activate() {}\n",
);
});
it("refuses a manifest with errors", async () => {
vi.spyOn(console, "log").mockImplementation(() => {});
vi.spyOn(console, "error").mockImplementation(() => {});
const dir = fixturePlugin({ capabilities: ["not:a-capability"] });
const out = tempDir("d-");
await expect(pack({ cwd: dir, args: ["--out", out] })).rejects.toThrow(
/manifest is not valid/,
);
expect(fs.readdirSync(out)).toEqual([]);
});
});
describe("termix-plugin pack themes", () => {
it("ships the CSS files the manifest's themes declare", async () => {
vi.spyOn(console, "log").mockImplementation(() => {});
vi.spyOn(console, "warn").mockImplementation(() => {});
const dir = fixturePlugin({
capabilities: ["ui:theme"],
contributes: {
themes: [
{
id: "paper",
label: "Paper",
mode: "light",
css: "styles/paper.css",
},
],
},
});
writeFile(dir, "styles/paper.css", ".paper { --background: #fff; }\n");
writeFile(dir, "styles/draft.css", ".draft {}\n");
const file = await pack({ cwd: dir, args: ["--out", tempDir("t-")] });
const names = await entries(file);
expect(names).toContain("styles/paper.css");
expect(names).not.toContain("styles/draft.css");
});
});