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.
Files changed (3) hide show
  1. package/README.md +10 -2
  2. package/bin/cli.js +114 -3
  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
- create-bl-theme validate [directory]
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
- Checks that your theme has all required files and valid metadata.
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] Create a new theme
39
- ${pc.cyan("create-bl-theme")} validate [dir] Validate a theme directory
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
- const fullPath = path.resolve(process.cwd(), dir);
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",
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
- "prompts": "^2.4.2",
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
  }