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 +80 -0
- package/bin/cli.js +343 -0
- package/package.json +24 -0
- package/templates/shader.json +4 -0
- package/templates/style.css +51 -0
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
|
+

|
|
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,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
|
+
*/
|