create-bl-theme 1.0.3 → 1.0.4
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 +10 -2
- package/bin/cli.js +114 -3
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -34,10 +34,18 @@ The CLI will prompt you for:
|
|
|
34
34
|
### Validate a theme
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
|
|
37
|
+
# Validate a local directory
|
|
38
|
+
create-bl-theme validate ./my-theme
|
|
39
|
+
|
|
40
|
+
# Validate directly from a GitHub repository
|
|
41
|
+
create-bl-theme validate https://github.com/username/theme-repo
|
|
38
42
|
```
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
The validator checks:
|
|
45
|
+
- Required files (metadata.json, style.css, images/)
|
|
46
|
+
- Valid JSON structure and required fields
|
|
47
|
+
- Image integrity (detects corrupted files)
|
|
48
|
+
- Image dimensions (recommends 1280x720, but other sizes work fine)
|
|
41
49
|
|
|
42
50
|
## Generated Structure
|
|
43
51
|
|
package/bin/cli.js
CHANGED
|
@@ -4,13 +4,23 @@ import prompts from "prompts";
|
|
|
4
4
|
import pc from "picocolors";
|
|
5
5
|
import fs from "fs";
|
|
6
6
|
import path from "path";
|
|
7
|
+
import os from "os";
|
|
7
8
|
import { fileURLToPath } from "url";
|
|
9
|
+
import { imageSize } from "image-size";
|
|
10
|
+
import { execSync } from "child_process";
|
|
8
11
|
|
|
9
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
13
|
const __dirname = path.dirname(__filename);
|
|
11
14
|
|
|
12
15
|
const TEMPLATES_DIR = path.join(__dirname, "..", "templates");
|
|
13
16
|
|
|
17
|
+
// Recommended image dimensions
|
|
18
|
+
const RECOMMENDED_WIDTH = 1280;
|
|
19
|
+
const RECOMMENDED_HEIGHT = 720;
|
|
20
|
+
|
|
21
|
+
// GitHub URL patterns
|
|
22
|
+
const GITHUB_URL_PATTERN = /^(?:https?:\/\/)?(?:www\.)?github\.com\/([^\/]+)\/([^\/]+)(?:\/)?(?:\.git)?$/;
|
|
23
|
+
|
|
14
24
|
async function main() {
|
|
15
25
|
const args = process.argv.slice(2);
|
|
16
26
|
const command = args[0];
|
|
@@ -35,12 +45,13 @@ async function main() {
|
|
|
35
45
|
|
|
36
46
|
function showHelp() {
|
|
37
47
|
console.log(`${pc.bold("Usage:")}
|
|
38
|
-
${pc.cyan("create-bl-theme")} [name]
|
|
39
|
-
${pc.cyan("create-bl-theme")} validate [dir]
|
|
48
|
+
${pc.cyan("create-bl-theme")} [name] Create a new theme
|
|
49
|
+
${pc.cyan("create-bl-theme")} validate [dir|url] Validate a theme (local or GitHub)
|
|
40
50
|
|
|
41
51
|
${pc.bold("Examples:")}
|
|
42
52
|
${pc.dim("$")} create-bl-theme my-awesome-theme
|
|
43
53
|
${pc.dim("$")} create-bl-theme validate ./my-theme
|
|
54
|
+
${pc.dim("$")} create-bl-theme validate https://github.com/user/theme-repo
|
|
44
55
|
`);
|
|
45
56
|
}
|
|
46
57
|
|
|
@@ -277,10 +288,45 @@ MIT
|
|
|
277
288
|
}
|
|
278
289
|
|
|
279
290
|
async function validate(dir) {
|
|
280
|
-
|
|
291
|
+
let fullPath;
|
|
292
|
+
let tempDir = null;
|
|
281
293
|
let errors = [];
|
|
282
294
|
let warnings = [];
|
|
283
295
|
|
|
296
|
+
// Check if input is a GitHub URL
|
|
297
|
+
const githubMatch = dir.match(GITHUB_URL_PATTERN);
|
|
298
|
+
|
|
299
|
+
if (githubMatch) {
|
|
300
|
+
const [, owner, repo] = githubMatch;
|
|
301
|
+
const repoUrl = `https://github.com/${owner}/${repo}.git`;
|
|
302
|
+
|
|
303
|
+
console.log(pc.dim(`Cloning ${pc.cyan(`${owner}/${repo}`)} from GitHub...\n`));
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
// Create temp directory
|
|
307
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "bl-theme-"));
|
|
308
|
+
fullPath = tempDir;
|
|
309
|
+
|
|
310
|
+
// Clone the repository (shallow clone for speed)
|
|
311
|
+
execSync(`git clone --depth 1 ${repoUrl} "${tempDir}"`, {
|
|
312
|
+
stdio: "pipe",
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
console.log(pc.green(` Cloned successfully!\n`));
|
|
316
|
+
} catch (e) {
|
|
317
|
+
console.log(pc.red(`Error: Could not clone repository "${owner}/${repo}"`));
|
|
318
|
+
console.log(pc.dim(` Make sure the repository exists and is publicly accessible.\n`));
|
|
319
|
+
|
|
320
|
+
if (e.message) {
|
|
321
|
+
console.log(pc.dim(` ${e.message}`));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
fullPath = path.resolve(process.cwd(), dir);
|
|
328
|
+
}
|
|
329
|
+
|
|
284
330
|
console.log(pc.dim(`Validating theme at ${fullPath}...\n`));
|
|
285
331
|
|
|
286
332
|
// Check directory exists
|
|
@@ -344,6 +390,19 @@ async function validate(dir) {
|
|
|
344
390
|
errors.push("metadata.json: images must be an array");
|
|
345
391
|
}
|
|
346
392
|
|
|
393
|
+
// Check if images referenced in metadata.json exist in images/
|
|
394
|
+
if (metadata.images && Array.isArray(metadata.images)) {
|
|
395
|
+
const imagesDir = path.join(fullPath, "images");
|
|
396
|
+
for (const image of metadata.images) {
|
|
397
|
+
const imagePath = path.join(imagesDir, image);
|
|
398
|
+
if (!fs.existsSync(imagePath)) {
|
|
399
|
+
errors.push(
|
|
400
|
+
`metadata.json: image "${image}" not found in images/ directory`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
347
406
|
if (!metadata.tags) {
|
|
348
407
|
warnings.push("metadata.json: consider adding tags for discoverability");
|
|
349
408
|
}
|
|
@@ -373,6 +432,45 @@ async function validate(dir) {
|
|
|
373
432
|
.filter((f) => /\.(png|jpg|jpeg|gif|webp)$/i.test(f));
|
|
374
433
|
if (images.length === 0) {
|
|
375
434
|
errors.push("images/ directory must contain at least one image");
|
|
435
|
+
} else {
|
|
436
|
+
// Validate each image
|
|
437
|
+
for (const image of images) {
|
|
438
|
+
const imagePath = path.join(imagesDir, image);
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
const imageBuffer = fs.readFileSync(imagePath);
|
|
442
|
+
const dimensions = imageSize(imageBuffer);
|
|
443
|
+
|
|
444
|
+
if (!dimensions || !dimensions.width || !dimensions.height) {
|
|
445
|
+
errors.push(
|
|
446
|
+
`${pc.bold(image)}: Unable to read image dimensions - the file may be corrupted or in an unsupported format`
|
|
447
|
+
);
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const { width, height } = dimensions;
|
|
452
|
+
const aspectRatio = (width / height).toFixed(2);
|
|
453
|
+
const recommendedAspectRatio = (RECOMMENDED_WIDTH / RECOMMENDED_HEIGHT).toFixed(2);
|
|
454
|
+
|
|
455
|
+
// Only warn if aspect ratio differs from recommended 16:9
|
|
456
|
+
if (aspectRatio !== recommendedAspectRatio) {
|
|
457
|
+
warnings.push(`${pc.bold(image)}: ${width}x${height} (aspect ratio ${aspectRatio})`);
|
|
458
|
+
}
|
|
459
|
+
} catch (e) {
|
|
460
|
+
// Handle corrupted or unreadable images
|
|
461
|
+
if (e.message.includes("unsupported") || e.message.includes("Invalid")) {
|
|
462
|
+
errors.push(
|
|
463
|
+
`${pc.bold(image)}: This image appears to be corrupted or in an unsupported format\n ${pc.dim("Please ensure the file is a valid image (PNG, JPG, GIF, or WebP)")}`
|
|
464
|
+
);
|
|
465
|
+
} else if (e.code === "ENOENT") {
|
|
466
|
+
errors.push(`${pc.bold(image)}: File not found`);
|
|
467
|
+
} else {
|
|
468
|
+
errors.push(
|
|
469
|
+
`${pc.bold(image)}: Could not validate image - ${e.message}\n ${pc.dim("The file may be corrupted or inaccessible")}`
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
376
474
|
}
|
|
377
475
|
}
|
|
378
476
|
|
|
@@ -401,12 +499,25 @@ async function validate(dir) {
|
|
|
401
499
|
}
|
|
402
500
|
if (warnings.length > 0) {
|
|
403
501
|
console.log(pc.yellow(pc.bold("\n Warnings:")));
|
|
502
|
+
console.log(pc.yellow(" Non-standard aspect ratios (recommended: 16:9)"));
|
|
404
503
|
warnings.forEach((w) => console.log(pc.yellow(` - ${w}`)));
|
|
504
|
+
console.log();
|
|
505
|
+
console.log(pc.dim(pc.yellow(" This is just a suggestion - your images will still work fine!")));
|
|
506
|
+
console.log(pc.dim(pc.yellow(" Different aspect ratios can be intentional for your theme's design.")));
|
|
405
507
|
}
|
|
406
508
|
}
|
|
407
509
|
|
|
408
510
|
console.log();
|
|
409
511
|
|
|
512
|
+
// Cleanup temp directory if we cloned from GitHub
|
|
513
|
+
if (tempDir) {
|
|
514
|
+
try {
|
|
515
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
516
|
+
} catch (e) {
|
|
517
|
+
// Ignore cleanup errors
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
410
521
|
if (errors.length > 0) {
|
|
411
522
|
process.exit(1);
|
|
412
523
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-bl-theme",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "CLI tool to scaffold Better Lyrics themes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"author": "boidushya",
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"
|
|
22
|
-
"picocolors": "^1.1.1"
|
|
21
|
+
"image-size": "^2.0.2",
|
|
22
|
+
"picocolors": "^1.1.1",
|
|
23
|
+
"prompts": "^2.4.2"
|
|
23
24
|
}
|
|
24
25
|
}
|