create-bl-theme 1.0.9 → 1.0.11
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 +1 -1
- package/bin/cli.js +46 -7
- package/package.json +5 -1
- package/.claude/settings.local.json +0 -12
- package/CLAUDE.md +0 -73
- package/PLAN.md +0 -399
package/README.md
CHANGED
|
@@ -164,7 +164,7 @@ Better Lyrics supports **GitHub Flavored Markdown (GFM)** in DESCRIPTION.md, so
|
|
|
164
164
|
|
|
165
165
|
### Auto-Publishing
|
|
166
166
|
|
|
167
|
-
After your theme is registered, install the [Better Lyrics
|
|
167
|
+
After your theme is registered, install the [Better Lyrics Harmonizer](https://github.com/apps/better-lyrics-harmonizer/installations/new) GitHub App on your repo. This enables automatic updates:
|
|
168
168
|
|
|
169
169
|
1. Bump your version: `create-bl-theme bump patch`
|
|
170
170
|
2. Commit and push
|
package/bin/cli.js
CHANGED
|
@@ -170,7 +170,28 @@ ${pc.bold("Documentation:")}
|
|
|
170
170
|
`);
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
function isDirectoryEmpty(dirPath) {
|
|
174
|
+
const entries = fs.readdirSync(dirPath);
|
|
175
|
+
return entries.every((entry) => entry.startsWith("."));
|
|
176
|
+
}
|
|
177
|
+
|
|
173
178
|
async function create(targetDir) {
|
|
179
|
+
if (targetDir) {
|
|
180
|
+
const earlyPath = path.resolve(process.cwd(), targetDir);
|
|
181
|
+
if (fs.existsSync(earlyPath) && !isDirectoryEmpty(earlyPath)) {
|
|
182
|
+
const { proceed } = await prompts({
|
|
183
|
+
type: "confirm",
|
|
184
|
+
name: "proceed",
|
|
185
|
+
message: `Directory "${targetDir}" is not empty. Continue anyway? (files may be overwritten)`,
|
|
186
|
+
initial: false,
|
|
187
|
+
});
|
|
188
|
+
if (!proceed) {
|
|
189
|
+
console.log(pc.red("\nCancelled."));
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
174
195
|
const questions = [
|
|
175
196
|
{
|
|
176
197
|
type: targetDir ? null : "text",
|
|
@@ -254,9 +275,17 @@ async function create(targetDir) {
|
|
|
254
275
|
const dir = targetDir || response.directory;
|
|
255
276
|
const fullPath = path.resolve(process.cwd(), dir);
|
|
256
277
|
|
|
257
|
-
if (fs.existsSync(fullPath)) {
|
|
258
|
-
|
|
259
|
-
|
|
278
|
+
if (!targetDir && fs.existsSync(fullPath) && !isDirectoryEmpty(fullPath)) {
|
|
279
|
+
const { proceed } = await prompts({
|
|
280
|
+
type: "confirm",
|
|
281
|
+
name: "proceed",
|
|
282
|
+
message: `Directory "${dir}" is not empty. Continue anyway? (files may be overwritten)`,
|
|
283
|
+
initial: false,
|
|
284
|
+
});
|
|
285
|
+
if (!proceed) {
|
|
286
|
+
console.log(pc.red("\nCancelled."));
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
260
289
|
}
|
|
261
290
|
|
|
262
291
|
console.log();
|
|
@@ -365,6 +394,11 @@ MIT
|
|
|
365
394
|
`;
|
|
366
395
|
fs.writeFileSync(path.join(fullPath, "README.md"), readme);
|
|
367
396
|
|
|
397
|
+
fs.writeFileSync(
|
|
398
|
+
path.join(fullPath, ".gitattributes"),
|
|
399
|
+
"*.rics linguist-language=SCSS\n"
|
|
400
|
+
);
|
|
401
|
+
|
|
368
402
|
// Create placeholder image note
|
|
369
403
|
fs.writeFileSync(
|
|
370
404
|
path.join(fullPath, "images", ".gitkeep"),
|
|
@@ -375,12 +409,17 @@ MIT
|
|
|
375
409
|
console.log(pc.green(" Theme scaffolded successfully!"));
|
|
376
410
|
console.log();
|
|
377
411
|
console.log(pc.bold(" Next steps:"));
|
|
378
|
-
|
|
379
|
-
|
|
412
|
+
let stepNum = 1;
|
|
413
|
+
if (dir !== ".") {
|
|
414
|
+
console.log(` ${pc.dim(`${stepNum}.`)} cd ${dir}`);
|
|
415
|
+
stepNum++;
|
|
416
|
+
}
|
|
417
|
+
console.log(` ${pc.dim(`${stepNum}.`)} Edit ${pc.cyan("style.rics")} with your theme styles`);
|
|
418
|
+
stepNum++;
|
|
380
419
|
console.log(
|
|
381
|
-
` ${pc.dim(
|
|
420
|
+
` ${pc.dim(`${stepNum}.`)} Add a preview screenshot to ${pc.cyan("images/preview.png")}`
|
|
382
421
|
);
|
|
383
|
-
|
|
422
|
+
stepNum++;
|
|
384
423
|
if (response.useDescriptionFile) {
|
|
385
424
|
console.log(` ${pc.dim(`${stepNum}.`)} Edit ${pc.cyan("DESCRIPTION.md")} with your theme description`);
|
|
386
425
|
stepNum++;
|
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-bl-theme",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "CLI tool to scaffold Better Lyrics themes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-bl-theme": "bin/cli.js"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
9
13
|
"scripts": {
|
|
10
14
|
"start": "node bin/cli.js"
|
|
11
15
|
},
|
package/CLAUDE.md
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
## Project Overview
|
|
6
|
-
|
|
7
|
-
**create-bl-theme** is a CLI tool for scaffolding and managing themes for the Better Lyrics browser extension. It provides commands to create new themes, validate their structure, manage versioning, and handle publishing to a theme registry.
|
|
8
|
-
|
|
9
|
-
## Commands
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
# Run the CLI locally during development
|
|
13
|
-
node bin/cli.js [command]
|
|
14
|
-
|
|
15
|
-
# Create a new theme (interactive prompts)
|
|
16
|
-
node bin/cli.js [theme-name]
|
|
17
|
-
|
|
18
|
-
# Validate a theme (supports local paths or GitHub URLs)
|
|
19
|
-
node bin/cli.js validate [dir|url]
|
|
20
|
-
|
|
21
|
-
# Check publishing status against the theme registry
|
|
22
|
-
node bin/cli.js publish [dir]
|
|
23
|
-
|
|
24
|
-
# Bump version (patch/minor/major)
|
|
25
|
-
node bin/cli.js bump [patch|minor|major] [dir]
|
|
26
|
-
|
|
27
|
-
# Show version/help
|
|
28
|
-
node bin/cli.js --version
|
|
29
|
-
node bin/cli.js --help
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Architecture
|
|
33
|
-
|
|
34
|
-
The entire CLI is implemented in a single file: `bin/cli.js` (ES6 module).
|
|
35
|
-
|
|
36
|
-
**Main routing pattern:**
|
|
37
|
-
- `main()` parses `process.argv` and routes to command handlers
|
|
38
|
-
- Commands: `create()`, `validate()`, `publish()`, `bump()`
|
|
39
|
-
|
|
40
|
-
**Key external dependencies:**
|
|
41
|
-
- `rics` - RICS CSS preprocessor for syntax validation via `compileWithDetails()`
|
|
42
|
-
- `prompts` - Interactive CLI prompts
|
|
43
|
-
- `image-size` - Image dimension validation
|
|
44
|
-
- `picocolors` - Terminal colors
|
|
45
|
-
|
|
46
|
-
**External API calls:**
|
|
47
|
-
- Theme registry: `https://raw.githubusercontent.com/better-lyrics/themes/master/index.json`
|
|
48
|
-
- Lock file: `https://raw.githubusercontent.com/better-lyrics/themes/master/index.lock.json`
|
|
49
|
-
|
|
50
|
-
## Theme Structure Generated
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
theme-name/
|
|
54
|
-
├── metadata.json # Required: id, title, creators, minVersion, hasShaders, version, images
|
|
55
|
-
├── style.rics # Required: styles using RICS preprocessor (plain CSS is valid)
|
|
56
|
-
├── images/ # Required: at least one screenshot
|
|
57
|
-
├── DESCRIPTION.md # Optional: rich markdown description
|
|
58
|
-
├── shader.json # Required if hasShaders: true
|
|
59
|
-
└── README.md
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Validation Rules
|
|
63
|
-
|
|
64
|
-
- `metadata.json` requires fields: id, title, creators, minVersion, hasShaders, version, images
|
|
65
|
-
- Version must be strict semver (e.g., `1.0.0`)
|
|
66
|
-
- Image IDs: lowercase letters, numbers, hyphens only
|
|
67
|
-
- Images: PNG, JPG, GIF, WebP; warns if not 16:9 aspect ratio
|
|
68
|
-
- RICS syntax validated with detailed error reporting
|
|
69
|
-
- Shader.json required when `hasShaders: true`
|
|
70
|
-
|
|
71
|
-
## RICS Notes
|
|
72
|
-
|
|
73
|
-
RICS is a lightweight CSS preprocessor with full CSS parity. Plain CSS is valid RICS. Supports variables, nesting, mixins. Documentation: https://github.com/better-lyrics/rics
|
package/PLAN.md
DELETED
|
@@ -1,399 +0,0 @@
|
|
|
1
|
-
# create-bl-theme - Implementation Plan
|
|
2
|
-
|
|
3
|
-
This document outlines changes needed to support the new theme registry system.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
create-bl-theme is a CLI tool that:
|
|
8
|
-
|
|
9
|
-
- Scaffolds new Better Lyrics themes
|
|
10
|
-
- Validates theme structure and files
|
|
11
|
-
- Provides RICS syntax validation
|
|
12
|
-
|
|
13
|
-
This plan adds:
|
|
14
|
-
|
|
15
|
-
- `publish` command to help creators set up auto-publishing
|
|
16
|
-
- Updated validation for vendored structure
|
|
17
|
-
- Semver validation improvements
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Current Structure
|
|
22
|
-
|
|
23
|
-
```text
|
|
24
|
-
create-bl-theme/
|
|
25
|
-
├── bin/
|
|
26
|
-
│ └── cli.js # Main CLI implementation
|
|
27
|
-
├── templates/
|
|
28
|
-
│ ├── style.rics # RICS template
|
|
29
|
-
│ └── shader.json # Shader template
|
|
30
|
-
├── package.json
|
|
31
|
-
└── README.md
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### Current Commands
|
|
35
|
-
|
|
36
|
-
| Command | Purpose |
|
|
37
|
-
|----------------------------|-----------------------------|
|
|
38
|
-
| `create-bl-theme [name]` | Scaffold new theme |
|
|
39
|
-
| `create-bl-theme validate` | Validate theme structure |
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## New Command: `publish`
|
|
44
|
-
|
|
45
|
-
Help creators set up auto-publishing via GitHub App.
|
|
46
|
-
|
|
47
|
-
### Usage
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
# In theme repo directory
|
|
51
|
-
create-bl-theme publish
|
|
52
|
-
|
|
53
|
-
# Or specify path
|
|
54
|
-
create-bl-theme publish ./my-theme
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### What It Does
|
|
58
|
-
|
|
59
|
-
1. **Check prerequisites**
|
|
60
|
-
- Validate theme structure first
|
|
61
|
-
- Check if repo is a git repository
|
|
62
|
-
- Check if repo has a remote
|
|
63
|
-
|
|
64
|
-
2. **Guide through setup**
|
|
65
|
-
- Check if GitHub App is installed
|
|
66
|
-
- Provide installation link if not
|
|
67
|
-
- Verify theme is registered in better-lyrics/themes
|
|
68
|
-
|
|
69
|
-
3. **Verify publishing works**
|
|
70
|
-
- Simulate what would happen on push
|
|
71
|
-
- Show current version and what would be published
|
|
72
|
-
|
|
73
|
-
### Implementation
|
|
74
|
-
|
|
75
|
-
```javascript
|
|
76
|
-
// bin/cli.js (add to existing)
|
|
77
|
-
|
|
78
|
-
async function publishCommand(args) {
|
|
79
|
-
const themePath = args[0] || '.';
|
|
80
|
-
|
|
81
|
-
console.log(chalk.blue('Checking theme for publishing...'));
|
|
82
|
-
|
|
83
|
-
// 1. Validate theme first
|
|
84
|
-
const validationResult = await validateTheme(themePath);
|
|
85
|
-
if (!validationResult.success) {
|
|
86
|
-
console.log(chalk.red('Theme validation failed. Fix errors before publishing.'));
|
|
87
|
-
process.exit(1);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// 2. Check git repo
|
|
91
|
-
const isGitRepo = await checkIsGitRepo(themePath);
|
|
92
|
-
if (!isGitRepo) {
|
|
93
|
-
console.log(chalk.red('Not a git repository. Initialize git first:'));
|
|
94
|
-
console.log(chalk.gray(' git init'));
|
|
95
|
-
console.log(chalk.gray(' git remote add origin https://github.com/username/theme-name.git'));
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// 3. Get remote info
|
|
100
|
-
const remote = await getGitRemote(themePath);
|
|
101
|
-
if (!remote) {
|
|
102
|
-
console.log(chalk.red('No git remote found. Add a remote:'));
|
|
103
|
-
console.log(chalk.gray(' git remote add origin https://github.com/username/theme-name.git'));
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// 4. Parse repo from remote
|
|
108
|
-
const repoMatch = remote.match(/github\.com[:/](.+?)(?:\.git)?$/);
|
|
109
|
-
if (!repoMatch) {
|
|
110
|
-
console.log(chalk.red('Could not parse GitHub repo from remote:', remote));
|
|
111
|
-
process.exit(1);
|
|
112
|
-
}
|
|
113
|
-
const repo = repoMatch[1];
|
|
114
|
-
|
|
115
|
-
// 5. Read metadata
|
|
116
|
-
const metadata = JSON.parse(fs.readFileSync(path.join(themePath, 'metadata.json'), 'utf8'));
|
|
117
|
-
|
|
118
|
-
console.log();
|
|
119
|
-
console.log(chalk.bold('Theme Info:'));
|
|
120
|
-
console.log(` ID: ${metadata.id}`);
|
|
121
|
-
console.log(` Title: ${metadata.title}`);
|
|
122
|
-
console.log(` Version: ${metadata.version}`);
|
|
123
|
-
console.log(` Repo: ${repo}`);
|
|
124
|
-
console.log();
|
|
125
|
-
|
|
126
|
-
// 6. Check if registered
|
|
127
|
-
const isRegistered = await checkThemeRegistered(repo);
|
|
128
|
-
|
|
129
|
-
if (!isRegistered) {
|
|
130
|
-
console.log(chalk.yellow('Theme is not registered in the theme store.'));
|
|
131
|
-
console.log();
|
|
132
|
-
console.log(chalk.bold('To register your theme:'));
|
|
133
|
-
console.log(' 1. Fork https://github.com/better-lyrics/themes');
|
|
134
|
-
console.log(` 2. Add { "repo": "${repo}" } to index.json`);
|
|
135
|
-
console.log(' 3. Open a pull request');
|
|
136
|
-
console.log();
|
|
137
|
-
console.log('After your PR is merged, install the GitHub App for auto-updates:');
|
|
138
|
-
console.log(chalk.cyan(' https://github.com/apps/better-lyrics-themes'));
|
|
139
|
-
process.exit(0);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// 7. Check if GitHub App is installed
|
|
143
|
-
console.log(chalk.green('Theme is registered in the theme store.'));
|
|
144
|
-
console.log();
|
|
145
|
-
console.log(chalk.bold('Auto-publishing Setup:'));
|
|
146
|
-
console.log();
|
|
147
|
-
console.log('To enable automatic updates when you push:');
|
|
148
|
-
console.log(' 1. Install the GitHub App on your repo:');
|
|
149
|
-
console.log(chalk.cyan(' https://github.com/apps/better-lyrics-themes'));
|
|
150
|
-
console.log();
|
|
151
|
-
console.log(' 2. Push changes to your repo:');
|
|
152
|
-
console.log(chalk.gray(' git add .'));
|
|
153
|
-
console.log(chalk.gray(' git commit -m "feat: update theme"'));
|
|
154
|
-
console.log(chalk.gray(' git push'));
|
|
155
|
-
console.log();
|
|
156
|
-
console.log('The registry will automatically:');
|
|
157
|
-
console.log(' - Validate your theme');
|
|
158
|
-
console.log(' - Update the lockfile');
|
|
159
|
-
console.log(' - Vendor your theme files');
|
|
160
|
-
console.log(' - Show a commit status on your push');
|
|
161
|
-
console.log();
|
|
162
|
-
|
|
163
|
-
// 8. Check current lockfile status
|
|
164
|
-
const lockStatus = await checkLockStatus(repo);
|
|
165
|
-
if (lockStatus) {
|
|
166
|
-
console.log(chalk.bold('Current Registry Status:'));
|
|
167
|
-
console.log(` Locked Version: ${lockStatus.version}`);
|
|
168
|
-
console.log(` Locked At: ${lockStatus.locked}`);
|
|
169
|
-
console.log(` Commit: ${lockStatus.commit.slice(0, 7)}`);
|
|
170
|
-
|
|
171
|
-
if (metadata.version === lockStatus.version) {
|
|
172
|
-
console.log();
|
|
173
|
-
console.log(chalk.yellow('Your local version matches the registry.'));
|
|
174
|
-
console.log('Bump the version in metadata.json to publish an update.');
|
|
175
|
-
} else {
|
|
176
|
-
const semver = require('semver');
|
|
177
|
-
if (semver.gt(metadata.version, lockStatus.version)) {
|
|
178
|
-
console.log();
|
|
179
|
-
console.log(chalk.green(`Ready to publish: ${lockStatus.version} -> ${metadata.version}`));
|
|
180
|
-
console.log('Push to your repo to trigger an update.');
|
|
181
|
-
} else {
|
|
182
|
-
console.log();
|
|
183
|
-
console.log(chalk.red(`Version ${metadata.version} is not greater than ${lockStatus.version}`));
|
|
184
|
-
console.log('Versions must increase. Update metadata.json with a higher version.');
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async function checkThemeRegistered(repo) {
|
|
191
|
-
try {
|
|
192
|
-
const response = await fetch(
|
|
193
|
-
'https://raw.githubusercontent.com/better-lyrics/themes/main/index.json'
|
|
194
|
-
);
|
|
195
|
-
const index = await response.json();
|
|
196
|
-
return index.themes?.some(t => t.repo === repo) ?? false;
|
|
197
|
-
} catch {
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
async function checkLockStatus(repo) {
|
|
203
|
-
try {
|
|
204
|
-
const response = await fetch(
|
|
205
|
-
'https://raw.githubusercontent.com/better-lyrics/themes/main/index.lock.json'
|
|
206
|
-
);
|
|
207
|
-
const lock = await response.json();
|
|
208
|
-
return lock.themes?.find(t => t.repo === repo) ?? null;
|
|
209
|
-
} catch {
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
async function checkIsGitRepo(themePath) {
|
|
215
|
-
try {
|
|
216
|
-
execSync('git rev-parse --git-dir', { cwd: themePath, stdio: 'ignore' });
|
|
217
|
-
return true;
|
|
218
|
-
} catch {
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
async function getGitRemote(themePath) {
|
|
224
|
-
try {
|
|
225
|
-
return execSync('git remote get-url origin', { cwd: themePath, encoding: 'utf8' }).trim();
|
|
226
|
-
} catch {
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
---
|
|
233
|
-
|
|
234
|
-
## Updated Validation
|
|
235
|
-
|
|
236
|
-
### Add Semver Validation
|
|
237
|
-
|
|
238
|
-
```javascript
|
|
239
|
-
// In validateTheme function
|
|
240
|
-
|
|
241
|
-
// Validate version format
|
|
242
|
-
const version = metadata.version;
|
|
243
|
-
const semverRegex = /^\d+\.\d+\.\d+(-[\w.]+)?(\+[\w.]+)?$/;
|
|
244
|
-
|
|
245
|
-
if (!semverRegex.test(version)) {
|
|
246
|
-
errors.push({
|
|
247
|
-
type: 'error',
|
|
248
|
-
message: `Invalid version format: "${version}". Must be semver (e.g., 1.0.0, 1.0.0-beta.1)`,
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
### Validate Required Files for Registry
|
|
254
|
-
|
|
255
|
-
```javascript
|
|
256
|
-
// Ensure files needed for vendoring exist
|
|
257
|
-
|
|
258
|
-
const requiredFiles = ['metadata.json'];
|
|
259
|
-
const styleFiles = ['style.rics', 'style.css'];
|
|
260
|
-
|
|
261
|
-
// Check style file exists
|
|
262
|
-
const hasStyleFile = styleFiles.some(f => fs.existsSync(path.join(themePath, f)));
|
|
263
|
-
if (!hasStyleFile) {
|
|
264
|
-
errors.push({
|
|
265
|
-
type: 'error',
|
|
266
|
-
message: 'Missing style file. Create style.rics or style.css',
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Check images directory
|
|
271
|
-
const imagesDir = path.join(themePath, 'images');
|
|
272
|
-
if (!fs.existsSync(imagesDir)) {
|
|
273
|
-
errors.push({
|
|
274
|
-
type: 'error',
|
|
275
|
-
message: 'Missing images/ directory. Add at least one screenshot.',
|
|
276
|
-
});
|
|
277
|
-
} else {
|
|
278
|
-
const images = fs.readdirSync(imagesDir).filter(f =>
|
|
279
|
-
/\.(png|jpg|jpeg|gif|webp)$/i.test(f)
|
|
280
|
-
);
|
|
281
|
-
if (images.length === 0) {
|
|
282
|
-
errors.push({
|
|
283
|
-
type: 'error',
|
|
284
|
-
message: 'No images found in images/ directory. Add at least one screenshot.',
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Validate images array matches actual files
|
|
290
|
-
const declaredImages = metadata.images || [];
|
|
291
|
-
for (const img of declaredImages) {
|
|
292
|
-
if (!fs.existsSync(path.join(imagesDir, img))) {
|
|
293
|
-
errors.push({
|
|
294
|
-
type: 'error',
|
|
295
|
-
message: `Image "${img}" declared in metadata.json but not found in images/`,
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Check shader.json if hasShaders is true
|
|
301
|
-
if (metadata.hasShaders && !fs.existsSync(path.join(themePath, 'shader.json'))) {
|
|
302
|
-
errors.push({
|
|
303
|
-
type: 'error',
|
|
304
|
-
message: 'hasShaders is true but shader.json not found',
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Check description exists (metadata or DESCRIPTION.md)
|
|
309
|
-
const hasDescription = metadata.description || fs.existsSync(path.join(themePath, 'DESCRIPTION.md'));
|
|
310
|
-
if (!hasDescription) {
|
|
311
|
-
errors.push({
|
|
312
|
-
type: 'error',
|
|
313
|
-
message: 'Missing description. Add "description" to metadata.json or create DESCRIPTION.md',
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
### Validate Cover Image
|
|
319
|
-
|
|
320
|
-
```javascript
|
|
321
|
-
// Check cover.png or first image in images array
|
|
322
|
-
const hasCoverPng = fs.existsSync(path.join(themePath, 'cover.png'));
|
|
323
|
-
const hasImagesArray = metadata.images && metadata.images.length > 0;
|
|
324
|
-
|
|
325
|
-
if (!hasCoverPng && !hasImagesArray) {
|
|
326
|
-
warnings.push({
|
|
327
|
-
type: 'warning',
|
|
328
|
-
message: 'No cover image. Add cover.png or images to the images/ folder.',
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
---
|
|
334
|
-
|
|
335
|
-
## Updated CLI Help
|
|
336
|
-
|
|
337
|
-
```javascript
|
|
338
|
-
function showHelp() {
|
|
339
|
-
console.log(`
|
|
340
|
-
${chalk.bold('create-bl-theme')} - CLI for Better Lyrics themes
|
|
341
|
-
|
|
342
|
-
${chalk.bold('Usage:')}
|
|
343
|
-
create-bl-theme [theme-name] Create a new theme
|
|
344
|
-
create-bl-theme validate [path] Validate a theme
|
|
345
|
-
create-bl-theme publish [path] Check publishing status
|
|
346
|
-
|
|
347
|
-
${chalk.bold('Examples:')}
|
|
348
|
-
create-bl-theme my-theme
|
|
349
|
-
create-bl-theme validate ./my-theme
|
|
350
|
-
create-bl-theme publish
|
|
351
|
-
|
|
352
|
-
${chalk.bold('Theme Structure:')}
|
|
353
|
-
my-theme/
|
|
354
|
-
├── metadata.json # Required - Theme metadata
|
|
355
|
-
├── style.rics # Required - Styles (or style.css)
|
|
356
|
-
├── shader.json # Optional - If hasShaders: true
|
|
357
|
-
├── DESCRIPTION.md # Optional - Rich description
|
|
358
|
-
├── cover.png # Optional - Cover image
|
|
359
|
-
└── images/ # Required - Screenshots
|
|
360
|
-
└── preview.png
|
|
361
|
-
|
|
362
|
-
${chalk.bold('Documentation:')}
|
|
363
|
-
https://github.com/better-lyrics/themes
|
|
364
|
-
`);
|
|
365
|
-
}
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
---
|
|
369
|
-
|
|
370
|
-
## Package.json Updates
|
|
371
|
-
|
|
372
|
-
```json
|
|
373
|
-
{
|
|
374
|
-
"dependencies": {
|
|
375
|
-
"semver": "^7.5.0"
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
---
|
|
381
|
-
|
|
382
|
-
## Action Items
|
|
383
|
-
|
|
384
|
-
- [ ] Add `publish` command
|
|
385
|
-
- [ ] Add semver validation to validate command
|
|
386
|
-
- [ ] Validate all required files for vendoring
|
|
387
|
-
- [ ] Add cover.png validation
|
|
388
|
-
- [ ] Update help text
|
|
389
|
-
- [ ] Add semver dependency
|
|
390
|
-
- [ ] Update README with publish command docs
|
|
391
|
-
- [ ] Test with existing themes
|
|
392
|
-
|
|
393
|
-
---
|
|
394
|
-
|
|
395
|
-
## Related Documents
|
|
396
|
-
|
|
397
|
-
- [better-lyrics-themes PLAN.md](../better-lyrics-themes/PLAN.md)
|
|
398
|
-
- [store-api PLAN.md](../store-api/PLAN.md)
|
|
399
|
-
- [better-lyrics PLAN.md](../better-lyrics/PLAN.md)
|