create-bl-theme 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # create-bl-theme
2
+
3
+ CLI tool to scaffold [Better Lyrics](https://github.com/better-lyrics/better-lyrics) themes.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g create-bl-theme
9
+ ```
10
+
11
+ Or use directly with npx:
12
+
13
+ ```bash
14
+ npx create-bl-theme my-theme
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Create a new theme
20
+
21
+ ```bash
22
+ create-bl-theme [theme-name]
23
+ ```
24
+
25
+ The CLI will prompt you for:
26
+
27
+ - Theme ID (lowercase, hyphens allowed)
28
+ - Theme title
29
+ - Description
30
+ - Your GitHub username
31
+ - Tags (dark, light, minimal, colorful, etc.)
32
+ - Whether to include shaders
33
+
34
+ ### Validate a theme
35
+
36
+ ```bash
37
+ create-bl-theme validate [directory]
38
+ ```
39
+
40
+ Checks that your theme has all required files and valid metadata.
41
+
42
+ ## Generated Structure
43
+
44
+ ```
45
+ my-theme/
46
+ ├── metadata.json # Theme metadata (required)
47
+ ├── style.css # Your CSS styles (required)
48
+ ├── shader.json # Shader config (if enabled)
49
+ ├── README.md # Theme documentation
50
+ └── images/ # Screenshots (required)
51
+ └── preview.png
52
+ ```
53
+
54
+ ## Theme Development
55
+
56
+ 1. **Edit `style.css`** - Add your custom styles. Use browser DevTools to inspect Better Lyrics elements and find the right selectors.
57
+
58
+ 2. **Add screenshots** - Place at least one preview image in `images/`. Recommended size: 1280x720 (16:9 aspect ratio).
59
+
60
+ 3. **Update `metadata.json`** - Ensure all fields are correct before submission.
61
+
62
+ 4. **Test locally** - Install your theme via "Install from URL" in Better Lyrics using your local path or GitHub repo URL.
63
+
64
+ ## Submitting to Theme Store
65
+
66
+ 1. Push your theme to a GitHub repository
67
+ 2. Fork [better-lyrics-themes](https://github.com/better-lyrics/better-lyrics-themes)
68
+ 3. Add your theme to `index.json`:
69
+ ```json
70
+ {
71
+ "themes": [
72
+ { "repo": "your-username/your-theme-repo" }
73
+ ]
74
+ }
75
+ ```
76
+ 4. Open a pull request
77
+
78
+ ## License
79
+
80
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,343 @@
1
+ #!/usr/bin/env node
2
+
3
+ import prompts from "prompts";
4
+ import pc from "picocolors";
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import { fileURLToPath } from "url";
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ const TEMPLATES_DIR = path.join(__dirname, "..", "templates");
13
+
14
+ async function main() {
15
+ const args = process.argv.slice(2);
16
+ const command = args[0];
17
+
18
+ console.log();
19
+ console.log(pc.bold(pc.cyan(" Better Lyrics Theme Creator")));
20
+ console.log(pc.dim(" Create themes for Better Lyrics extension"));
21
+ console.log();
22
+
23
+ if (command === "validate") {
24
+ await validate(args[1] || ".");
25
+ return;
26
+ }
27
+
28
+ if (command === "help" || command === "--help" || command === "-h") {
29
+ showHelp();
30
+ return;
31
+ }
32
+
33
+ await create(command);
34
+ }
35
+
36
+ function showHelp() {
37
+ console.log(`${pc.bold("Usage:")}
38
+ ${pc.cyan("create-bl-theme")} [name] Create a new theme
39
+ ${pc.cyan("create-bl-theme")} validate [dir] Validate a theme directory
40
+
41
+ ${pc.bold("Examples:")}
42
+ ${pc.dim("$")} create-bl-theme my-awesome-theme
43
+ ${pc.dim("$")} create-bl-theme validate ./my-theme
44
+ `);
45
+ }
46
+
47
+ async function create(targetDir) {
48
+ const questions = [
49
+ {
50
+ type: targetDir ? null : "text",
51
+ name: "directory",
52
+ message: "Theme directory name:",
53
+ initial: "my-bl-theme",
54
+ validate: (value) =>
55
+ value.length > 0 ? true : "Directory name is required",
56
+ },
57
+ {
58
+ type: "text",
59
+ name: "id",
60
+ message: "Theme ID (lowercase, hyphens allowed):",
61
+ initial: (prev) => (targetDir || prev).toLowerCase().replace(/\s+/g, "-"),
62
+ validate: (value) =>
63
+ /^[a-z0-9-]+$/.test(value)
64
+ ? true
65
+ : "Only lowercase letters, numbers, and hyphens allowed",
66
+ },
67
+ {
68
+ type: "text",
69
+ name: "title",
70
+ message: "Theme title:",
71
+ initial: (prev, values) =>
72
+ (targetDir || values.directory)
73
+ .split("-")
74
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
75
+ .join(" "),
76
+ validate: (value) => (value.length > 0 ? true : "Title is required"),
77
+ },
78
+ {
79
+ type: "text",
80
+ name: "description",
81
+ message: "Description:",
82
+ initial: "A custom theme for Better Lyrics",
83
+ },
84
+ {
85
+ type: "text",
86
+ name: "creator",
87
+ message: "Your GitHub username:",
88
+ validate: (value) =>
89
+ value.length > 0 ? true : "GitHub username is required",
90
+ },
91
+ {
92
+ type: "multiselect",
93
+ name: "tags",
94
+ message: "Select tags (space to toggle, enter to confirm):",
95
+ choices: [
96
+ { title: "dark", value: "dark" },
97
+ { title: "light", value: "light" },
98
+ { title: "minimal", value: "minimal" },
99
+ { title: "colorful", value: "colorful" },
100
+ { title: "animated", value: "animated" },
101
+ { title: "glassmorphism", value: "glassmorphism" },
102
+ { title: "retro", value: "retro" },
103
+ { title: "neon", value: "neon" },
104
+ ],
105
+ hint: "- Space to select. Return to submit",
106
+ },
107
+ {
108
+ type: "confirm",
109
+ name: "hasShaders",
110
+ message: "Will this theme include shaders?",
111
+ initial: false,
112
+ },
113
+ ];
114
+
115
+ const response = await prompts(questions, {
116
+ onCancel: () => {
117
+ console.log(pc.red("\nCancelled."));
118
+ process.exit(1);
119
+ },
120
+ });
121
+
122
+ const dir = targetDir || response.directory;
123
+ const fullPath = path.resolve(process.cwd(), dir);
124
+
125
+ if (fs.existsSync(fullPath)) {
126
+ console.log(pc.red(`\nError: Directory "${dir}" already exists.`));
127
+ process.exit(1);
128
+ }
129
+
130
+ console.log();
131
+ console.log(pc.dim(`Creating theme in ${fullPath}...`));
132
+
133
+ // Create directories
134
+ fs.mkdirSync(fullPath, { recursive: true });
135
+ fs.mkdirSync(path.join(fullPath, "images"), { recursive: true });
136
+
137
+ // Create metadata.json
138
+ const metadata = {
139
+ id: response.id,
140
+ title: response.title,
141
+ description: response.description,
142
+ creators: [response.creator],
143
+ minVersion: "2.0.5.6",
144
+ hasShaders: response.hasShaders,
145
+ version: "1.0.0",
146
+ tags: response.tags,
147
+ images: ["preview.png"],
148
+ };
149
+
150
+ fs.writeFileSync(
151
+ path.join(fullPath, "metadata.json"),
152
+ JSON.stringify(metadata, null, 2)
153
+ );
154
+
155
+ // Create style.css from template
156
+ const cssTemplate = fs.readFileSync(
157
+ path.join(TEMPLATES_DIR, "style.css"),
158
+ "utf-8"
159
+ );
160
+ fs.writeFileSync(path.join(fullPath, "style.css"), cssTemplate);
161
+
162
+ // Create shader.json if needed
163
+ if (response.hasShaders) {
164
+ const shaderTemplate = fs.readFileSync(
165
+ path.join(TEMPLATES_DIR, "shader.json"),
166
+ "utf-8"
167
+ );
168
+ fs.writeFileSync(path.join(fullPath, "shader.json"), shaderTemplate);
169
+ }
170
+
171
+ // Create README
172
+ const readme = `# ${response.title}
173
+
174
+ ${response.description}
175
+
176
+ ## Installation
177
+
178
+ 1. Open Better Lyrics extension options
179
+ 2. Go to **Themes** tab
180
+ 3. Click **Install from URL**
181
+ 4. Enter: \`https://github.com/${response.creator}/${dir}\`
182
+
183
+ ## Preview
184
+
185
+ ![Preview](images/preview.png)
186
+
187
+ ## License
188
+
189
+ MIT
190
+ `;
191
+ fs.writeFileSync(path.join(fullPath, "README.md"), readme);
192
+
193
+ // Create placeholder image note
194
+ fs.writeFileSync(
195
+ path.join(fullPath, "images", ".gitkeep"),
196
+ "Add your preview screenshots here.\nRecommended: 1280x720 (16:9 aspect ratio)\nRename your main preview to preview.png\n"
197
+ );
198
+
199
+ console.log();
200
+ console.log(pc.green(" Theme scaffolded successfully!"));
201
+ console.log();
202
+ console.log(pc.bold(" Next steps:"));
203
+ console.log(` ${pc.dim("1.")} cd ${dir}`);
204
+ console.log(` ${pc.dim("2.")} Edit ${pc.cyan("style.css")} with your theme styles`);
205
+ console.log(
206
+ ` ${pc.dim("3.")} Add a preview screenshot to ${pc.cyan("images/preview.png")}`
207
+ );
208
+ if (response.hasShaders) {
209
+ console.log(` ${pc.dim("4.")} Configure ${pc.cyan("shader.json")} for shader effects`);
210
+ }
211
+ console.log(
212
+ ` ${pc.dim(response.hasShaders ? "5." : "4.")} Push to GitHub and submit to the theme store!`
213
+ );
214
+ console.log();
215
+ console.log(
216
+ pc.dim(" Validate your theme with: ") + pc.cyan(`create-bl-theme validate ${dir}`)
217
+ );
218
+ console.log();
219
+ }
220
+
221
+ async function validate(dir) {
222
+ const fullPath = path.resolve(process.cwd(), dir);
223
+ let errors = [];
224
+ let warnings = [];
225
+
226
+ console.log(pc.dim(`Validating theme at ${fullPath}...\n`));
227
+
228
+ // Check directory exists
229
+ if (!fs.existsSync(fullPath)) {
230
+ console.log(pc.red(`Error: Directory "${dir}" does not exist.`));
231
+ process.exit(1);
232
+ }
233
+
234
+ // Check metadata.json
235
+ const metadataPath = path.join(fullPath, "metadata.json");
236
+ if (!fs.existsSync(metadataPath)) {
237
+ errors.push("metadata.json is missing");
238
+ } else {
239
+ try {
240
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
241
+ const required = [
242
+ "id",
243
+ "title",
244
+ "description",
245
+ "creators",
246
+ "minVersion",
247
+ "hasShaders",
248
+ "version",
249
+ "images",
250
+ ];
251
+
252
+ for (const field of required) {
253
+ if (metadata[field] === undefined) {
254
+ errors.push(`metadata.json: missing required field "${field}"`);
255
+ }
256
+ }
257
+
258
+ if (metadata.id && !/^[a-z0-9-]+$/.test(metadata.id)) {
259
+ errors.push(
260
+ "metadata.json: id must be lowercase letters, numbers, and hyphens only"
261
+ );
262
+ }
263
+
264
+ if (metadata.creators && !Array.isArray(metadata.creators)) {
265
+ errors.push("metadata.json: creators must be an array");
266
+ }
267
+
268
+ if (metadata.images && !Array.isArray(metadata.images)) {
269
+ errors.push("metadata.json: images must be an array");
270
+ }
271
+
272
+ if (!metadata.tags) {
273
+ warnings.push("metadata.json: consider adding tags for discoverability");
274
+ }
275
+ } catch (e) {
276
+ errors.push(`metadata.json: invalid JSON - ${e.message}`);
277
+ }
278
+ }
279
+
280
+ // Check style.css
281
+ const stylePath = path.join(fullPath, "style.css");
282
+ if (!fs.existsSync(stylePath)) {
283
+ errors.push("style.css is missing");
284
+ } else {
285
+ const css = fs.readFileSync(stylePath, "utf-8");
286
+ if (css.trim().length === 0) {
287
+ warnings.push("style.css is empty");
288
+ }
289
+ }
290
+
291
+ // Check images directory
292
+ const imagesDir = path.join(fullPath, "images");
293
+ if (!fs.existsSync(imagesDir)) {
294
+ errors.push("images/ directory is missing");
295
+ } else {
296
+ const images = fs
297
+ .readdirSync(imagesDir)
298
+ .filter((f) => /\.(png|jpg|jpeg|gif|webp)$/i.test(f));
299
+ if (images.length === 0) {
300
+ errors.push("images/ directory must contain at least one image");
301
+ }
302
+ }
303
+
304
+ // Check shader.json if hasShaders is true
305
+ if (fs.existsSync(metadataPath)) {
306
+ try {
307
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
308
+ if (metadata.hasShaders) {
309
+ const shaderPath = path.join(fullPath, "shader.json");
310
+ if (!fs.existsSync(shaderPath)) {
311
+ errors.push("shader.json is missing but hasShaders is true");
312
+ }
313
+ }
314
+ } catch (e) {
315
+ // Already reported JSON error above
316
+ }
317
+ }
318
+
319
+ // Print results
320
+ if (errors.length === 0 && warnings.length === 0) {
321
+ console.log(pc.green(" All checks passed!"));
322
+ } else {
323
+ if (errors.length > 0) {
324
+ console.log(pc.red(pc.bold(" Errors:")));
325
+ errors.forEach((e) => console.log(pc.red(` - ${e}`)));
326
+ }
327
+ if (warnings.length > 0) {
328
+ console.log(pc.yellow(pc.bold("\n Warnings:")));
329
+ warnings.forEach((w) => console.log(pc.yellow(` - ${w}`)));
330
+ }
331
+ }
332
+
333
+ console.log();
334
+
335
+ if (errors.length > 0) {
336
+ process.exit(1);
337
+ }
338
+ }
339
+
340
+ main().catch((err) => {
341
+ console.error(pc.red(err.message));
342
+ process.exit(1);
343
+ });
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "create-bl-theme",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool to scaffold Better Lyrics themes",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-bl-theme": "bin/cli.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node bin/cli.js"
11
+ },
12
+ "keywords": [
13
+ "better-lyrics",
14
+ "theme",
15
+ "cli",
16
+ "scaffold"
17
+ ],
18
+ "author": "boidushya",
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "prompts": "^2.4.2",
22
+ "picocolors": "^1.1.1"
23
+ }
24
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "_comment": "Export your shader config from Better Lyrics and replace this file. Go to Settings > Export Settings",
3
+ "enabled": false
4
+ }
@@ -0,0 +1,51 @@
1
+ /*
2
+ * Better Lyrics Theme
3
+ *
4
+ * This CSS file customizes the appearance of lyrics in Better Lyrics.
5
+ * Edit these styles to create your unique theme.
6
+ *
7
+ * Tip: Use the browser DevTools to inspect Better Lyrics elements
8
+ * and find the selectors you want to style.
9
+ */
10
+
11
+ /* Main lyrics container */
12
+ .blyrics-container {
13
+ /* Add your container styles here */
14
+ }
15
+
16
+ /* Individual lyric lines */
17
+ .blyrics-line {
18
+ /* Style for each line of lyrics */
19
+ }
20
+
21
+ /* Currently active/highlighted lyric */
22
+ .blyrics-line.active {
23
+ /* Make the current line stand out */
24
+ }
25
+
26
+ /* Lyrics text styling */
27
+ .blyrics-text {
28
+ /* Font, color, and text styles */
29
+ }
30
+
31
+ /*
32
+ * Example: Dark theme starter
33
+ * Uncomment and modify as needed
34
+ */
35
+
36
+ /*
37
+ .blyrics-container {
38
+ background: rgba(0, 0, 0, 0.8);
39
+ backdrop-filter: blur(10px);
40
+ }
41
+
42
+ .blyrics-line {
43
+ color: rgba(255, 255, 255, 0.6);
44
+ transition: all 0.3s ease;
45
+ }
46
+
47
+ .blyrics-line.active {
48
+ color: #ffffff;
49
+ transform: scale(1.05);
50
+ }
51
+ */