bluedither 1.0.7 → 1.0.9

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.
@@ -5,18 +5,22 @@
5
5
  */
6
6
 
7
7
  import { existsSync, readFileSync, createReadStream } from 'fs';
8
- import { resolve } from 'path';
8
+ import { resolve, extname } from 'path';
9
9
  import { execSync } from 'child_process';
10
10
  import { getCredentials } from '../lib/credentials.js';
11
- import { uploadPackage, publishTheme, REGISTRY_URL } from '../lib/registry.js';
11
+ import { uploadPackage, publishTheme, uploadScreenshot, REGISTRY_URL } from '../lib/registry.js';
12
12
 
13
13
  export default async function publish(args) {
14
14
  if (args.includes('--help')) {
15
15
  console.log(`
16
- bluedither publish
16
+ bluedither publish [--screenshot <path>]
17
17
 
18
18
  Validates, builds, and publishes your theme to the BlueDither marketplace.
19
19
  If you're not logged in, a browser window will open for authentication.
20
+
21
+ Options:
22
+ --screenshot <path> Path to a screenshot image (png/jpg/webp)
23
+ Auto-detected: screenshot.png, preview.png, etc.
20
24
  `);
21
25
  return;
22
26
  }
@@ -160,15 +164,55 @@ export default async function publish(args) {
160
164
 
161
165
  const { path: packagePath } = await uploadPackage(zipFile, slug, creds.access_token);
162
166
 
163
- console.log('Step 7: Publishing theme...');
167
+ // Upload screenshot if available
168
+ let screenshotUrl = null;
169
+ const ssIdx = args.indexOf('--screenshot');
170
+ const ssPath = ssIdx !== -1 ? resolve(dir, args[ssIdx + 1]) : null;
171
+
172
+ // Auto-detect screenshot file
173
+ const screenshotFile = ssPath
174
+ || ['screenshot.png', 'screenshot.jpg', 'screenshot.webp', 'preview.png', 'preview.jpg'].map(f => resolve(dir, f)).find(f => existsSync(f));
175
+
176
+ if (screenshotFile && existsSync(screenshotFile)) {
177
+ console.log(`Step 7: Uploading screenshot (${screenshotFile.split(/[\\/]/).pop()})...`);
178
+ try {
179
+ const imgBuffer = readFileSync(screenshotFile);
180
+ const ext = extname(screenshotFile).slice(1) || 'png';
181
+ const mimeType = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', webp: 'image/webp' }[ext] || 'image/png';
182
+ const imgBlob = new Blob([imgBuffer], { type: mimeType });
183
+ const imgFile = new File([imgBlob], `screenshot.${ext}`, { type: mimeType });
184
+ const result = await uploadScreenshot(imgFile, slug, creds.access_token);
185
+ screenshotUrl = result.url;
186
+ console.log(' ✓ Screenshot uploaded');
187
+ } catch (e) {
188
+ console.log(` ⚠ Screenshot upload failed: ${e.message}`);
189
+ }
190
+ } else {
191
+ console.log('Step 7: No screenshot found — skipping.');
192
+ console.log(' Tip: Add screenshot.png to your theme directory, or use --screenshot <path>');
193
+ }
194
+
195
+ console.log('\nStep 8: Publishing theme...');
164
196
  const tokens = JSON.parse(readFileSync(resolve(dir, config.tokens), 'utf-8'));
165
197
 
198
+ // Read template HTML if available
199
+ let templateHtml = null;
200
+ for (const tmplPath of ['template/index.html', 'theme/template/index.html']) {
201
+ const full = resolve(dir, tmplPath);
202
+ if (existsSync(full)) {
203
+ templateHtml = readFileSync(full, 'utf-8');
204
+ break;
205
+ }
206
+ }
207
+
166
208
  const theme = await publishTheme({
167
209
  name: config.name,
168
210
  slug,
169
211
  description: tokens.content?.subHeadline || '',
170
212
  tokens_json: tokens,
171
213
  package_path: packagePath,
214
+ screenshot_url: screenshotUrl,
215
+ template_html: templateHtml,
172
216
  }, creds.access_token);
173
217
 
174
218
  console.log(`
@@ -39,6 +39,23 @@ export async function uploadPackage(file, slug, accessToken) {
39
39
  return res.json();
40
40
  }
41
41
 
42
+ export async function uploadScreenshot(file, slug, accessToken) {
43
+ const formData = new FormData();
44
+ formData.append('file', file);
45
+ formData.append('slug', slug);
46
+
47
+ const res = await fetch(`${REGISTRY_URL}/api/upload/screenshot`, {
48
+ method: 'POST',
49
+ headers: { Authorization: `Bearer ${accessToken}` },
50
+ body: formData,
51
+ });
52
+ if (!res.ok) {
53
+ const err = await res.json().catch(() => ({}));
54
+ throw new Error(err.error || `Screenshot upload failed: ${res.statusText}`);
55
+ }
56
+ return res.json();
57
+ }
58
+
42
59
  export async function publishTheme(metadata, accessToken) {
43
60
  const res = await fetch(`${REGISTRY_URL}/api/themes`, {
44
61
  method: 'POST',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bluedither",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "A bold, dithered-shader hero theme for Claude Code — skill + fine-tuner",
5
5
  "type": "module",
6
6
  "bin": {