chore: add inquirer, add blank theme

This commit is contained in:
2026-06-01 14:21:03 +02:00
parent eea32cf96a
commit 31507041a5
33 changed files with 3151 additions and 56 deletions
+73
View File
@@ -0,0 +1,73 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "create-ezpplauncher-theme",
"dependencies": {
"@inquirer/prompts": "^8.5.2",
},
"devDependencies": {
"@types/node": "^25.6.0",
"typescript": "^6.0.3",
},
},
},
"packages": {
"@inquirer/ansi": ["@inquirer/ansi@2.0.7", "", {}, "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q=="],
"@inquirer/checkbox": ["@inquirer/checkbox@5.2.1", "", { "dependencies": { "@inquirer/ansi": "^2.0.7", "@inquirer/core": "^11.2.1", "@inquirer/figures": "^2.0.7", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw=="],
"@inquirer/confirm": ["@inquirer/confirm@6.1.1", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ=="],
"@inquirer/core": ["@inquirer/core@11.2.1", "", { "dependencies": { "@inquirer/ansi": "^2.0.7", "@inquirer/figures": "^2.0.7", "@inquirer/type": "^4.0.7", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA=="],
"@inquirer/editor": ["@inquirer/editor@5.2.2", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/external-editor": "^3.0.3", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg=="],
"@inquirer/expand": ["@inquirer/expand@5.1.1", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g=="],
"@inquirer/external-editor": ["@inquirer/external-editor@3.0.3", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA=="],
"@inquirer/figures": ["@inquirer/figures@2.0.7", "", {}, "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw=="],
"@inquirer/input": ["@inquirer/input@5.1.2", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg=="],
"@inquirer/number": ["@inquirer/number@4.1.1", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA=="],
"@inquirer/password": ["@inquirer/password@5.1.1", "", { "dependencies": { "@inquirer/ansi": "^2.0.7", "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg=="],
"@inquirer/prompts": ["@inquirer/prompts@8.5.2", "", { "dependencies": { "@inquirer/checkbox": "^5.2.1", "@inquirer/confirm": "^6.1.1", "@inquirer/editor": "^5.2.2", "@inquirer/expand": "^5.1.1", "@inquirer/input": "^5.1.2", "@inquirer/number": "^4.1.1", "@inquirer/password": "^5.1.1", "@inquirer/rawlist": "^5.3.1", "@inquirer/search": "^4.2.1", "@inquirer/select": "^5.2.1" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g=="],
"@inquirer/rawlist": ["@inquirer/rawlist@5.3.1", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og=="],
"@inquirer/search": ["@inquirer/search@4.2.1", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/figures": "^2.0.7", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g=="],
"@inquirer/select": ["@inquirer/select@5.2.1", "", { "dependencies": { "@inquirer/ansi": "^2.0.7", "@inquirer/core": "^11.2.1", "@inquirer/figures": "^2.0.7", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw=="],
"@inquirer/type": ["@inquirer/type@4.0.7", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g=="],
"@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
"chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="],
"cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
"fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="],
"fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="],
"fast-wrap-ansi": ["fast-wrap-ansi@0.2.2", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q=="],
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
}
}
+66 -24
View File
@@ -1,29 +1,71 @@
#!/usr/bin/env node
import fs from 'fs';
import path from 'path';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
import fs from "fs";
import path from "path";
import { execSync } from "child_process";
import { fileURLToPath } from "url";
import { input, select } from "@inquirer/prompts";
const projectTypes = [
{
name: "blank",
value: "blank",
description: "start with a blank project",
},
{
name: "example",
value: "example",
description: "start with an example project, to see how theming works",
},
];
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectName = process.argv[2];
if (!projectName) {
console.error('❌ Please provide a project name');
async function main() {
let projectName = process.argv[2];
if (!projectName) {
projectName = await input({
message: "Please enter a project name:",
pattern: /^[a-zA-Z0-9-]+$/,
validate: (value) => {
if (!value)
return "Project name is required";
if (!/^[a-zA-Z0-9-]+$/.test(value))
return "Project name can only contain letters, numbers, and dashes";
if (fs.existsSync(value))
return "Directory already exists";
return true;
},
});
if (!projectName) {
console.error("❌ Project name is required");
process.exit(1);
}
const targetDir = path.resolve(process.cwd(), projectName);
const templateDir = path.resolve(__dirname, '../templates/base');
if (fs.existsSync(targetDir)) {
console.error('❌ Directory already exists');
}
}
const templateType = await select({
message: "Please select a template type:",
choices: projectTypes,
});
const targetDir = path.resolve(process.cwd(), projectName);
const templateDir = path.resolve(__dirname, `../templates/${templateType}`);
if (!fs.existsSync(templateDir)) {
console.error("❌ Template directory not found");
process.exit(1);
}
if (fs.existsSync(targetDir)) {
console.error("❌ Directory already exists");
process.exit(1);
}
fs.cpSync(templateDir, targetDir, { recursive: true });
const pkgPath = path.join(targetDir, "package.json");
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
pkg.name = projectName;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
const themePath = path.join(targetDir, "theme.json");
const theme = JSON.parse(fs.readFileSync(themePath, "utf-8"));
theme.name = projectName;
fs.writeFileSync(themePath, JSON.stringify(theme, null, 2));
console.log("📦 Installing dependencies...");
execSync("npm install", { cwd: targetDir, stdio: "inherit" });
console.log(`\n✅ Theme created!\n`);
}
fs.cpSync(templateDir, targetDir, { recursive: true });
const pkgPath = path.join(targetDir, 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
pkg.name = projectName;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
const themePath = path.join(targetDir, 'theme.json');
const theme = JSON.parse(fs.readFileSync(themePath, 'utf-8'));
theme.name = projectName;
fs.writeFileSync(themePath, JSON.stringify(theme, null, 2));
console.log('📦 Installing dependencies...');
execSync('npm install', { cwd: targetDir, stdio: 'inherit' });
console.log(`\n✅ Theme created!\n`);
main().catch((err) => {
console.error(err);
process.exit(1);
});
+3
View File
@@ -11,5 +11,8 @@
"devDependencies": {
"@types/node": "^25.6.0",
"typescript": "^6.0.3"
},
"dependencies": {
"@inquirer/prompts": "^8.5.2"
}
}
+76 -30
View File
@@ -1,38 +1,84 @@
#!/usr/bin/env node
import fs from 'fs';
import path from 'path';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
import fs from "fs";
import path from "path";
import { execSync } from "child_process";
import { fileURLToPath } from "url";
import { input, select } from "@inquirer/prompts";
const projectTypes = [
{
name: "blank",
value: "blank",
description: "start with a blank project",
},
{
name: "example",
value: "example",
description: "start with an example project, to see how theming works",
},
];
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectName = process.argv[2];
if (!projectName) {
console.error('❌ Please provide a project name');
async function main() {
let projectName: string | undefined = process.argv[2];
if (!projectName) {
projectName = await input({
message: "Please enter a project name:",
pattern: /^[a-zA-Z0-9-]+$/,
validate: (value) => {
if (!value) return "Project name is required";
if (!/^[a-zA-Z0-9-]+$/.test(value))
return "Project name can only contain letters, numbers, and dashes";
if (fs.existsSync(value)) return "Directory already exists";
return true;
},
});
if (!projectName) {
console.error("❌ Project name is required");
process.exit(1);
}
}
const templateType = await select({
message: "Please select a template type:",
choices: projectTypes,
});
const targetDir = path.resolve(process.cwd(), projectName);
const templateDir = path.resolve(__dirname, `../templates/${templateType}`);
if(!fs.existsSync(templateDir)) {
console.error("❌ Template directory not found");
process.exit(1);
}
if (fs.existsSync(targetDir)) {
console.error("❌ Directory already exists");
process.exit(1);
}
fs.cpSync(templateDir, targetDir, { recursive: true });
const pkgPath = path.join(targetDir, "package.json");
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
pkg.name = projectName;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
const themePath = path.join(targetDir, "theme.json");
const theme = JSON.parse(fs.readFileSync(themePath, "utf-8"));
theme.name = projectName;
fs.writeFileSync(themePath, JSON.stringify(theme, null, 2));
console.log("📦 Installing dependencies...");
execSync("npm install", { cwd: targetDir, stdio: "inherit" });
console.log(`\n✅ Theme created!\n`);
}
const targetDir = path.resolve(process.cwd(), projectName);
const templateDir = path.resolve(__dirname, '../templates/base');
if (fs.existsSync(targetDir)) {
console.error('❌ Directory already exists');
main().catch((err) => {
console.error(err);
process.exit(1);
}
fs.cpSync(templateDir, targetDir, { recursive: true });
const pkgPath = path.join(targetDir, 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
pkg.name = projectName;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
const themePath = path.join(targetDir, 'theme.json');
const theme = JSON.parse(fs.readFileSync(themePath, 'utf-8'));
theme.name = projectName;
fs.writeFileSync(themePath, JSON.stringify(theme, null, 2));
console.log('📦 Installing dependencies...');
execSync('npm install', { cwd: targetDir, stdio: 'inherit' });
console.log(`\n✅ Theme created!\n`);
});
+6
View File
@@ -0,0 +1,6 @@
<script lang="ts">
import { asset } from "./lib/assets";
import type { EZPPLauncherThemeAPI } from "./types";
const props: { api: EZPPLauncherThemeAPI } = $props();
</script>
+9
View File
@@ -0,0 +1,9 @@
{
"name": "ezpp-theme",
"version": "1.0.0",
"apiVersion": "1.0",
"author": "HorizonCode",
"entry": "bundle.js",
"style": "style.css",
"preview": "preview.png"
}
+40
View File
@@ -0,0 +1,40 @@
import fs from "fs";
import archiver from "archiver";
import path from "path";
export const toSafeName = (name: string) => {
return name
.toLowerCase()
.replace(/[<>:"/\\|?*]/g, "_")
.replace(/[\x00-\x1F\x7F]/g, "_")
.replace(/[\s-]+/g, "_")
.replace(/_+/g, "_")
.replace(/[. ]+$/g, "_");
};
const themeInfo = JSON.parse(fs.readFileSync("theme.json", "utf-8"));
const distDir = path.resolve("dist");
const output = fs.createWriteStream(
path.join(distDir, `${toSafeName(themeInfo.name)}.ezpplauncher-theme`),
);
const archive = archiver("zip", { zlib: { level: 9 } });
archive.pipe(output);
archive.file("theme.json", { name: "theme.json" });
archive.file(path.join(distDir, "bundle.js"), { name: "bundle.js" });
if (fs.existsSync(path.join(distDir, "ezpp-theme.css"))) {
archive.file(path.join(distDir, "ezpp-theme.css"), { name: "style.css" });
}
if (fs.existsSync("public/assets")) {
archive.directory("public/assets", "assets");
}
archive.finalize();
output.on("close", () => {
console.log("✅ Theme packaged:", output.path);
});
+14
View File
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script type="module">
import { mountTheme } from '/src/main.ts';
mountTheme(document.getElementById('app'), {
assets: "yes"
});
</script>
</body>
</html>
+2778
View File
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
{
"name": "ezpp-theme",
"version": "1.0.0",
"scripts": {
"dev": "vite dev",
"build": "vite build && ts-node build-theme.ts"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^7.0.0",
"@types/archiver": "^7.0.0",
"archiver": "^7.0.1",
"svelte": "^5.55.0",
"ts-node": "^10.0.0",
"typescript": "^6.0.3",
"vite": "^8.0.10",
"tailwindcss": "^4.0.0",
"@tailwindcss/vite": "^4.0.0"
}
}

Before

Width:  |  Height:  |  Size: 2.8 MiB

After

Width:  |  Height:  |  Size: 2.8 MiB

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

+2
View File
@@ -0,0 +1,2 @@
@import "tailwindcss";
/* generate custom tailwindcss themes using https://tweakcn.com/editor/theme */
+8
View File
@@ -0,0 +1,8 @@
/// <reference types="vite/client" />
declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.gif';
declare module '*.webp';
declare module '*.svg';
+9
View File
@@ -0,0 +1,9 @@
import type { EZPPLauncherThemeAPI } from "../types";
export function asset(api: EZPPLauncherThemeAPI, path: string) {
const isDev = import.meta.env.DEV;
return isDev
? `/assets/${path}`
: `${api.assets}/${path}`;
}
+14
View File
@@ -0,0 +1,14 @@
// @ts-ignore: side-effect CSS import declaration not available in this context
import "./app.css";
import App from "./App.svelte";
import { mount } from "svelte";
import type { EZPPLauncherThemeAPI } from "./types";
export function mountTheme(target: HTMLElement, api: EZPPLauncherThemeAPI) {
mount(App, {
target,
props: {
api,
},
});
}
+3
View File
@@ -0,0 +1,3 @@
export type EZPPLauncherThemeAPI = {
assets: string;
}
+3
View File
@@ -0,0 +1,3 @@
{
"include": ["src"]
}
+26
View File
@@ -0,0 +1,26 @@
import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [svelte(), tailwindcss()],
build: {
cssCodeSplit: false,
lib: {
entry: "src/main.ts",
formats: ["es"],
name: "EZPPTheme",
},
rollupOptions: {
output: {
entryFileNames: "bundle.js",
assetFileNames: (assetInfo) => {
if (assetInfo.names?.some((n) => n.endsWith(".css"))) {
return "ezpp-theme.css";
}
return "assets/[name]-[hash][extname]";
},
},
},
},
});