Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

pi/agent/skills/optional/quickshell/bin/qs-scaffold.mjs

Raw
#!/usr/bin/env node
// qs-scaffold.mjs: scaffold a new Quickshell config from an example.
// Cross-platform: no shell, no POSIX assumptions. Pure Node builtins.
//
// Usage:
//   qs-scaffold <name> [--from <example-name>]
//
// Default example: 02-per-monitor-bar (canonical per-monitor shape).

import {
	copyFileSync,
	existsSync,
	mkdirSync,
	readdirSync,
	statSync,
	writeFileSync,
} from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));
const SKILL_ROOT = join(__dirname, "..");
const EXAMPLES_DIR = join(SKILL_ROOT, "examples");

const DEFAULT_EXAMPLE = "02-per-monitor-bar";

function usage() {
	process.stderr.write("usage: qs-scaffold <name> [--from <example-name>]\n");
	process.stderr.write(
		"  example-name: e.g. 01-hello-bar, 07-composite-bar (omit the .qml)\n",
	);
}

function parseArgs(argv) {
	const out = { name: null, from: DEFAULT_EXAMPLE };
	for (let i = 2; i < argv.length; i++) {
		const a = argv[i];
		if (a === "-h" || a === "--help") {
			usage();
			process.exit(0);
		}
		if (a === "--from") {
			const v = argv[++i];
			if (!v) {
				process.stderr.write("qs-scaffold: --from requires a value\n");
				process.exit(1);
			}
			out.from = v;
			continue;
		}
		if (a.startsWith("--")) {
			process.stderr.write(`qs-scaffold: unknown flag ${a}\n`);
			process.exit(1);
		}
		if (!out.name) {
			out.name = a;
			continue;
		}
		process.stderr.write(`qs-scaffold: unexpected positional arg ${a}\n`);
		process.exit(1);
	}
	return out;
}

function xdgConfigHome() {
	if (process.env.XDG_CONFIG_HOME) return process.env.XDG_CONFIG_HOME;
	if (process.env.HOME) return join(process.env.HOME, ".config");
	// Windows fallback.
	if (process.env.APPDATA) return process.env.APPDATA;
	process.stderr.write(
		"qs-scaffold: cannot determine config dir (set XDG_CONFIG_HOME or HOME)\n",
	);
	process.exit(1);
}

const { name, from } = parseArgs(process.argv);
if (!name) {
	usage();
	process.exit(1);
}

function requirePathSegment(label, value) {
	if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value)) {
		process.stderr.write(
			`qs-scaffold: ${label} must be a single path segment: ${value}\n`,
		);
		process.exit(1);
	}
}

requirePathSegment("name", name);
requirePathSegment("example name", from);

// Validate example dir.
const exampleDir = join(EXAMPLES_DIR, from);
if (!existsSync(exampleDir) || !statSync(exampleDir).isDirectory()) {
	process.stderr.write(
		`qs-scaffold: example not found: ${from} (looked in ${EXAMPLES_DIR})\n`,
	);
	process.exit(1);
}

const configRoot = join(xdgConfigHome(), "quickshell");
const target = join(configRoot, name);

if (existsSync(target)) {
	process.stderr.write(`qs-scaffold: target already exists: ${target}\n`);
	process.exit(1);
}

mkdirSync(target, { recursive: true });

// Copy the example dir's QML + README. Skip hidden / non-source files.
for (const entry of readdirSync(exampleDir, { withFileTypes: true })) {
	if (!entry.isFile()) continue;
	if (entry.name.startsWith(".")) continue;
	if (!(entry.name.endsWith(".qml") || entry.name.endsWith(".md"))) continue;
	copyFileSync(join(exampleDir, entry.name), join(target, entry.name));
}

// Touch .qmlls.ini for LSP.
writeFileSync(join(target, ".qmlls.ini"), "");

process.stdout.write(`Scaffolded ${target}\n`);
process.stdout.write(`Run: qs -c ${name}\n`);