wiki-plugin-shoppe 0.0.6 → 0.0.7
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/CLAUDE.md +15 -4
- package/client/shoppe.js +13 -2
- package/package.json +1 -1
- package/server/server.js +86 -18
package/CLAUDE.md
CHANGED
|
@@ -31,8 +31,14 @@ The first 3 emoji are fixed per wiki instance (`SHOPPE_BASE_EMOJI`, default `
|
|
|
31
31
|
my-shoppe.zip
|
|
32
32
|
manifest.json ← required: { uuid, emojicode, name }
|
|
33
33
|
books/
|
|
34
|
-
My Novel
|
|
35
|
-
|
|
34
|
+
My Novel/ ← subfolder per book
|
|
35
|
+
my-novel.epub
|
|
36
|
+
cover.jpg
|
|
37
|
+
info.json ← { "title": "…", "description": "…", "price": 0 }
|
|
38
|
+
Technical Guide/
|
|
39
|
+
guide.pdf
|
|
40
|
+
cover.jpg
|
|
41
|
+
info.json
|
|
36
42
|
music/
|
|
37
43
|
My Album/ ← album = subfolder
|
|
38
44
|
cover.jpg
|
|
@@ -40,8 +46,13 @@ my-shoppe.zip
|
|
|
40
46
|
02-track.mp3
|
|
41
47
|
Standalone Track.mp3 ← standalone track = file directly in music/
|
|
42
48
|
posts/
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
01-Hello World/ ← numeric prefix determines table of contents order
|
|
50
|
+
post.md ← the post content (required)
|
|
51
|
+
cover.jpg ← optional cover image
|
|
52
|
+
screenshot.png ← any assets referenced in the markdown
|
|
53
|
+
info.json ← optional: { "title": "…", "description": "…" }
|
|
54
|
+
02-Another Post/
|
|
55
|
+
post.md
|
|
45
56
|
albums/
|
|
46
57
|
Vacation 2025/ ← photo album = subfolder
|
|
47
58
|
photo1.jpg
|
package/client/shoppe.js
CHANGED
|
@@ -64,13 +64,24 @@
|
|
|
64
64
|
<div class="sw-step-body"><strong>Build your shoppe folder</strong> with this structure, then zip the whole thing:
|
|
65
65
|
<div class="sw-tree">my-shoppe.zip
|
|
66
66
|
manifest.json ← { "uuid": "…", "emojicode": "…", "name": "My Shoppe" }
|
|
67
|
-
books/
|
|
67
|
+
books/
|
|
68
|
+
My Novel/ ← subfolder per book
|
|
69
|
+
my-novel.epub
|
|
70
|
+
cover.jpg
|
|
71
|
+
info.json ← { "title": "…", "description": "…", "price": 0 }
|
|
68
72
|
music/
|
|
69
73
|
My Album/ ← subfolder = album (add cover.jpg inside)
|
|
70
74
|
cover.jpg
|
|
71
75
|
01-track.mp3
|
|
72
76
|
standalone.mp3 ← file directly here = single track
|
|
73
|
-
posts/
|
|
77
|
+
posts/
|
|
78
|
+
01-Hello World/ ← number prefix sets table of contents order
|
|
79
|
+
post.md ← the post content
|
|
80
|
+
cover.jpg ← optional cover image
|
|
81
|
+
screenshot.png ← any other assets referenced in the markdown
|
|
82
|
+
info.json ← optional: { "title": "…", "description": "…" }
|
|
83
|
+
02-Another Post/
|
|
84
|
+
post.md
|
|
74
85
|
albums/
|
|
75
86
|
Vacation 2025/ ← subfolder of images = photo album
|
|
76
87
|
photo1.jpg
|
package/package.json
CHANGED
package/server/server.js
CHANGED
|
@@ -266,19 +266,41 @@ async function processArchive(zipPath) {
|
|
|
266
266
|
const results = { books: [], music: [], posts: [], albums: [], products: [] };
|
|
267
267
|
|
|
268
268
|
// ---- books/ ----
|
|
269
|
+
// Each book is a subfolder containing the book file, cover.jpg, and info.json
|
|
269
270
|
const booksDir = path.join(tmpDir, 'books');
|
|
270
271
|
if (fs.existsSync(booksDir)) {
|
|
271
|
-
for (const
|
|
272
|
-
|
|
273
|
-
|
|
272
|
+
for (const entry of fs.readdirSync(booksDir)) {
|
|
273
|
+
const entryPath = path.join(booksDir, entry);
|
|
274
|
+
if (!fs.statSync(entryPath).isDirectory()) continue;
|
|
274
275
|
try {
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
276
|
+
const infoPath = path.join(entryPath, 'info.json');
|
|
277
|
+
const info = fs.existsSync(infoPath)
|
|
278
|
+
? JSON.parse(fs.readFileSync(infoPath, 'utf8'))
|
|
279
|
+
: {};
|
|
280
|
+
const title = info.title || entry;
|
|
281
|
+
const description = info.description || '';
|
|
282
|
+
const price = info.price || 0;
|
|
283
|
+
|
|
284
|
+
await sanoraCreateProduct(tenant, title, 'book', description, price, 0, 'book');
|
|
285
|
+
|
|
286
|
+
// Cover image
|
|
287
|
+
const covers = fs.readdirSync(entryPath).filter(f => IMAGE_EXTS.has(path.extname(f).toLowerCase()));
|
|
288
|
+
if (covers.length > 0) {
|
|
289
|
+
const coverBuf = fs.readFileSync(path.join(entryPath, covers[0]));
|
|
290
|
+
await sanoraUploadImage(tenant, title, coverBuf, covers[0]);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Book file
|
|
294
|
+
const bookFiles = fs.readdirSync(entryPath).filter(f => BOOK_EXTS.has(path.extname(f).toLowerCase()));
|
|
295
|
+
if (bookFiles.length > 0) {
|
|
296
|
+
const buf = fs.readFileSync(path.join(entryPath, bookFiles[0]));
|
|
297
|
+
await sanoraUploadArtifact(tenant, title, buf, bookFiles[0], 'ebook');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
results.books.push({ title, price });
|
|
279
301
|
console.log(`[shoppe] 📚 book: ${title}`);
|
|
280
302
|
} catch (err) {
|
|
281
|
-
console.warn(`[shoppe] ⚠️ book ${
|
|
303
|
+
console.warn(`[shoppe] ⚠️ book ${entry}: ${err.message}`);
|
|
282
304
|
}
|
|
283
305
|
}
|
|
284
306
|
}
|
|
@@ -328,20 +350,66 @@ async function processArchive(zipPath) {
|
|
|
328
350
|
}
|
|
329
351
|
|
|
330
352
|
// ---- posts/ ----
|
|
353
|
+
// Each post is a numbered subfolder: "01-My Title/" containing post.md,
|
|
354
|
+
// optional assets (images etc.), and optional info.json for metadata overrides.
|
|
355
|
+
// Folders are sorted by their numeric prefix to build the table of contents.
|
|
331
356
|
const postsDir = path.join(tmpDir, 'posts');
|
|
332
357
|
if (fs.existsSync(postsDir)) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
358
|
+
const postFolders = fs.readdirSync(postsDir)
|
|
359
|
+
.filter(f => fs.statSync(path.join(postsDir, f)).isDirectory())
|
|
360
|
+
.sort(); // lexicographic sort respects numeric prefixes (01-, 02-, …)
|
|
361
|
+
|
|
362
|
+
for (let order = 0; order < postFolders.length; order++) {
|
|
363
|
+
const entry = postFolders[order];
|
|
364
|
+
const entryPath = path.join(postsDir, entry);
|
|
336
365
|
try {
|
|
337
|
-
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
366
|
+
// Metadata: info.json overrides folder name and md heading
|
|
367
|
+
const infoPath = path.join(entryPath, 'info.json');
|
|
368
|
+
const info = fs.existsSync(infoPath)
|
|
369
|
+
? JSON.parse(fs.readFileSync(infoPath, 'utf8'))
|
|
370
|
+
: {};
|
|
371
|
+
|
|
372
|
+
// Find the .md file
|
|
373
|
+
const mdFiles = fs.readdirSync(entryPath).filter(f => f.endsWith('.md'));
|
|
374
|
+
if (mdFiles.length === 0) {
|
|
375
|
+
console.warn(`[shoppe] ⚠️ post ${entry}: no .md file found, skipping`);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const mdBuf = fs.readFileSync(path.join(entryPath, mdFiles[0]));
|
|
379
|
+
const mdContent = mdBuf.toString('utf8');
|
|
380
|
+
|
|
381
|
+
// Derive title: info.json > folder name stripped of numeric prefix
|
|
382
|
+
const folderTitle = entry.replace(/^\d+-/, '');
|
|
383
|
+
const firstHeading = mdContent.split('\n')[0].replace(/^#+\s*/, '');
|
|
384
|
+
const title = info.title || folderTitle;
|
|
385
|
+
const description = info.description || firstHeading || title;
|
|
386
|
+
|
|
387
|
+
await sanoraCreateProduct(tenant, title, 'post', description, 0, 0, `post,blog,order:${order}`);
|
|
388
|
+
|
|
389
|
+
// Upload the markdown as the main artifact
|
|
390
|
+
await sanoraUploadArtifact(tenant, title, mdBuf, mdFiles[0], 'text');
|
|
391
|
+
|
|
392
|
+
// Upload cover image if present
|
|
393
|
+
const covers = fs.readdirSync(entryPath).filter(f => IMAGE_EXTS.has(path.extname(f).toLowerCase()));
|
|
394
|
+
if (covers.length > 0) {
|
|
395
|
+
const coverBuf = fs.readFileSync(path.join(entryPath, covers[0]));
|
|
396
|
+
await sanoraUploadImage(tenant, title, coverBuf, covers[0]);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Upload remaining assets (images not used as cover)
|
|
400
|
+
const assets = fs.readdirSync(entryPath).filter(f =>
|
|
401
|
+
!f.endsWith('.md') && f !== 'info.json' && f !== covers[0] &&
|
|
402
|
+
IMAGE_EXTS.has(path.extname(f).toLowerCase())
|
|
403
|
+
);
|
|
404
|
+
for (const asset of assets) {
|
|
405
|
+
const buf = fs.readFileSync(path.join(entryPath, asset));
|
|
406
|
+
await sanoraUploadArtifact(tenant, title, buf, asset, 'image');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
results.posts.push({ title, order });
|
|
410
|
+
console.log(`[shoppe] 📝 post [${order + 1}]: ${title}`);
|
|
343
411
|
} catch (err) {
|
|
344
|
-
console.warn(`[shoppe] ⚠️ post ${
|
|
412
|
+
console.warn(`[shoppe] ⚠️ post ${entry}: ${err.message}`);
|
|
345
413
|
}
|
|
346
414
|
}
|
|
347
415
|
}
|