simple-photo-gallery 2.0.17 → 2.1.0
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/dist/index.cjs +418 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +415 -51
- package/dist/index.js.map +1 -1
- package/dist/lib/index.cjs +80 -8
- package/dist/lib/index.cjs.map +1 -1
- package/dist/lib/index.d.cts +8 -4
- package/dist/lib/index.d.ts +8 -4
- package/dist/lib/index.js +80 -8
- package/dist/lib/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import process4, { stdout } from 'process';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { LogLevels, createConsola } from 'consola';
|
|
5
5
|
import { execSync, spawn } from 'child_process';
|
|
@@ -9,15 +9,16 @@ import { Buffer } from 'buffer';
|
|
|
9
9
|
import sharp2 from 'sharp';
|
|
10
10
|
import { encode } from 'blurhash';
|
|
11
11
|
import { GalleryDataSchema, GalleryDataDeprecatedSchema } from '@simple-photo-gallery/common';
|
|
12
|
+
import { extractThumbnailConfigFromGallery, loadThemeConfig, mergeThumbnailConfig } from '@simple-photo-gallery/common/theme';
|
|
12
13
|
import ExifReader from 'exifreader';
|
|
13
14
|
import ffprobe from 'node-ffprobe';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
14
16
|
import os from 'os';
|
|
15
17
|
import Conf from 'conf';
|
|
16
18
|
import axios from 'axios';
|
|
17
19
|
import { compareSemVer, parseSemVer } from 'semver-parser';
|
|
18
20
|
|
|
19
21
|
// src/config/index.ts
|
|
20
|
-
var DEFAULT_THUMBNAIL_SIZE = 300;
|
|
21
22
|
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".tif", ".svg", ".avif"]);
|
|
22
23
|
var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".avi", ".mov", ".wmv", ".flv", ".webm", ".mkv", ".m4v", ".3gp"]);
|
|
23
24
|
var HEADER_IMAGE_LANDSCAPE_WIDTHS = [3840, 2560, 1920, 1280, 960, 640];
|
|
@@ -46,7 +47,7 @@ async function cropAndResizeImage(image, outputPath, width, height, format = "av
|
|
|
46
47
|
withoutEnlargement: true
|
|
47
48
|
}).toFormat(format).toFile(outputPath);
|
|
48
49
|
}
|
|
49
|
-
async function createImageThumbnails(image, metadata, outputPath, outputPathRetina, size) {
|
|
50
|
+
async function createImageThumbnails(image, metadata, outputPath, outputPathRetina, size, sizeDimension = "auto") {
|
|
50
51
|
const originalWidth = metadata.width || 0;
|
|
51
52
|
const originalHeight = metadata.height || 0;
|
|
52
53
|
if (originalWidth === 0 || originalHeight === 0) {
|
|
@@ -55,12 +56,20 @@ async function createImageThumbnails(image, metadata, outputPath, outputPathReti
|
|
|
55
56
|
const aspectRatio = originalWidth / originalHeight;
|
|
56
57
|
let width;
|
|
57
58
|
let height;
|
|
58
|
-
if (
|
|
59
|
+
if (sizeDimension === "width") {
|
|
59
60
|
width = size;
|
|
60
61
|
height = Math.round(size / aspectRatio);
|
|
61
|
-
} else {
|
|
62
|
+
} else if (sizeDimension === "height") {
|
|
62
63
|
width = Math.round(size * aspectRatio);
|
|
63
64
|
height = size;
|
|
65
|
+
} else {
|
|
66
|
+
if (originalWidth > originalHeight) {
|
|
67
|
+
width = size;
|
|
68
|
+
height = Math.round(size / aspectRatio);
|
|
69
|
+
} else {
|
|
70
|
+
width = Math.round(size * aspectRatio);
|
|
71
|
+
height = size;
|
|
72
|
+
}
|
|
64
73
|
}
|
|
65
74
|
await resizeImage(image, outputPath, width, height);
|
|
66
75
|
await resizeImage(image, outputPathRetina, width * 2, height * 2);
|
|
@@ -76,6 +85,30 @@ async function generateBlurHash(imagePath, componentX = 4, componentY = 3) {
|
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
// src/modules/build/utils/index.ts
|
|
88
|
+
function wrapText(text, maxCharsPerLine) {
|
|
89
|
+
const words = text.split(" ");
|
|
90
|
+
const lines = [];
|
|
91
|
+
let currentLine = "";
|
|
92
|
+
for (const word of words) {
|
|
93
|
+
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
94
|
+
if (word.length > maxCharsPerLine) {
|
|
95
|
+
if (currentLine) {
|
|
96
|
+
lines.push(currentLine);
|
|
97
|
+
currentLine = "";
|
|
98
|
+
}
|
|
99
|
+
lines.push(word);
|
|
100
|
+
} else if (testLine.length > maxCharsPerLine && currentLine) {
|
|
101
|
+
lines.push(currentLine);
|
|
102
|
+
currentLine = word;
|
|
103
|
+
} else {
|
|
104
|
+
currentLine = testLine;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (currentLine) {
|
|
108
|
+
lines.push(currentLine);
|
|
109
|
+
}
|
|
110
|
+
return lines;
|
|
111
|
+
}
|
|
79
112
|
async function createGallerySocialMediaCardImage(headerPhotoPath, title, ouputPath, ui) {
|
|
80
113
|
ui?.start(`Creating social media card image`);
|
|
81
114
|
const headerBasename = path7.basename(headerPhotoPath, path7.extname(headerPhotoPath));
|
|
@@ -87,14 +120,38 @@ async function createGallerySocialMediaCardImage(headerPhotoPath, title, ouputPa
|
|
|
87
120
|
const resizedImageBuffer = await image.resize(1200, 631, { fit: "cover" }).jpeg({ quality: 90 }).toBuffer();
|
|
88
121
|
const outputPath = ouputPath;
|
|
89
122
|
await sharp2(resizedImageBuffer).toFile(outputPath);
|
|
123
|
+
const CANVAS_WIDTH = 1200;
|
|
124
|
+
const CANVAS_HEIGHT = 631;
|
|
125
|
+
const FONT_SIZE = 72;
|
|
126
|
+
const MARGIN = 50;
|
|
127
|
+
const CHAR_WIDTH_RATIO = 0.6;
|
|
128
|
+
const usableWidth = CANVAS_WIDTH - 2 * MARGIN;
|
|
129
|
+
const maxCharsPerLine = Math.floor(usableWidth / (FONT_SIZE * CHAR_WIDTH_RATIO));
|
|
130
|
+
const lines = wrapText(title, maxCharsPerLine);
|
|
131
|
+
const lineHeight = FONT_SIZE * 1.2;
|
|
132
|
+
const totalTextHeight = FONT_SIZE + (lines.length - 1) * lineHeight;
|
|
133
|
+
const startY = CANVAS_HEIGHT - MARGIN - totalTextHeight + FONT_SIZE;
|
|
134
|
+
const leftX = MARGIN;
|
|
135
|
+
const tspanElements = lines.map((line, index) => {
|
|
136
|
+
const yPosition = startY + index * lineHeight;
|
|
137
|
+
const escapedLine = line.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
138
|
+
return `<tspan x="${leftX}" y="${yPosition}">${escapedLine}</tspan>`;
|
|
139
|
+
}).join("\n ");
|
|
90
140
|
const svgText = `
|
|
91
|
-
<svg width="
|
|
141
|
+
<svg width="${CANVAS_WIDTH}" height="${CANVAS_HEIGHT}" xmlns="http://www.w3.org/2000/svg">
|
|
92
142
|
<defs>
|
|
143
|
+
<linearGradient id="darkGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
144
|
+
<stop offset="0%" style="stop-color:rgb(0,0,0);stop-opacity:0" />
|
|
145
|
+
<stop offset="100%" style="stop-color:rgb(0,0,0);stop-opacity:0.65" />
|
|
146
|
+
</linearGradient>
|
|
93
147
|
<style>
|
|
94
|
-
.title { font-family: 'Arial, sans-serif'; font-size:
|
|
148
|
+
.title { font-family: 'Arial, sans-serif'; font-size: ${FONT_SIZE}px; font-weight: bold; fill: white; text-anchor: start; }
|
|
95
149
|
</style>
|
|
96
150
|
</defs>
|
|
97
|
-
<
|
|
151
|
+
<rect x="0" y="0" width="${CANVAS_WIDTH}" height="${CANVAS_HEIGHT}" fill="url(#darkGradient)" />
|
|
152
|
+
<text x="${leftX}" class="title">
|
|
153
|
+
${tspanElements}
|
|
154
|
+
</text>
|
|
98
155
|
</svg>
|
|
99
156
|
`;
|
|
100
157
|
const finalImageBuffer = await sharp2(resizedImageBuffer).composite([{ input: Buffer.from(svgText), top: 0, left: 0 }]).jpeg({ quality: 90 }).toBuffer();
|
|
@@ -278,11 +335,15 @@ function migrateGalleryJson(deprecatedGalleryData, galleryJsonPath, ui) {
|
|
|
278
335
|
filename: path7.basename(image.path)
|
|
279
336
|
}))
|
|
280
337
|
}));
|
|
338
|
+
const thumbnails2 = deprecatedGalleryData.thumbnailSize === void 0 ? void 0 : { size: deprecatedGalleryData.thumbnailSize };
|
|
281
339
|
const galleryData = {
|
|
282
340
|
...deprecatedGalleryData,
|
|
341
|
+
thumbnailSize: void 0,
|
|
342
|
+
// Remove old field
|
|
283
343
|
headerImage: path7.basename(deprecatedGalleryData.headerImage),
|
|
284
344
|
sections,
|
|
285
|
-
mediaBasePath
|
|
345
|
+
mediaBasePath,
|
|
346
|
+
thumbnails: thumbnails2
|
|
286
347
|
};
|
|
287
348
|
ui.debug("Backing up old gallery.json file");
|
|
288
349
|
fs8.copyFileSync(galleryJsonPath, `${galleryJsonPath}.old`);
|
|
@@ -353,9 +414,18 @@ async function getGallerySettingsFromUser(galleryName, defaultImage, ui) {
|
|
|
353
414
|
default: defaultImage,
|
|
354
415
|
placeholder: defaultImage
|
|
355
416
|
});
|
|
356
|
-
return {
|
|
417
|
+
return {
|
|
418
|
+
title,
|
|
419
|
+
description,
|
|
420
|
+
url,
|
|
421
|
+
headerImage,
|
|
422
|
+
thumbnails: {
|
|
423
|
+
size: "",
|
|
424
|
+
edge: void 0
|
|
425
|
+
}
|
|
426
|
+
};
|
|
357
427
|
}
|
|
358
|
-
async function createGalleryJson(mediaFiles, galleryJsonPath, scanPath, subGalleries = [], useDefaultSettings, ctaBanner, ui) {
|
|
428
|
+
async function createGalleryJson(mediaFiles, galleryJsonPath, scanPath, subGalleries = [], useDefaultSettings, ctaBanner, configOptions, ui) {
|
|
359
429
|
const galleryDir = path7.dirname(galleryJsonPath);
|
|
360
430
|
const isSameLocation = path7.relative(scanPath, path7.join(galleryDir, "..")) === "";
|
|
361
431
|
const mediaBasePath = isSameLocation ? void 0 : scanPath;
|
|
@@ -363,12 +433,23 @@ async function createGalleryJson(mediaFiles, galleryJsonPath, scanPath, subGalle
|
|
|
363
433
|
...subGallery,
|
|
364
434
|
headerImage: subGallery.headerImage ? path7.relative(galleryDir, subGallery.headerImage) : ""
|
|
365
435
|
}));
|
|
436
|
+
const thumbnailsConfig = {};
|
|
437
|
+
if (configOptions.thumbnailSize !== void 0) {
|
|
438
|
+
thumbnailsConfig.size = configOptions.thumbnailSize;
|
|
439
|
+
}
|
|
440
|
+
if (configOptions.thumbnailEdge !== void 0) {
|
|
441
|
+
thumbnailsConfig.edge = configOptions.thumbnailEdge;
|
|
442
|
+
}
|
|
366
443
|
let galleryData = {
|
|
367
444
|
title: "My Gallery",
|
|
368
445
|
description: "My gallery with fantastic photos.",
|
|
369
446
|
headerImage: mediaFiles[0]?.filename || "",
|
|
370
447
|
mediaBasePath,
|
|
371
448
|
metadata: {},
|
|
449
|
+
// Include theme if provided via CLI
|
|
450
|
+
...configOptions.theme && { theme: configOptions.theme },
|
|
451
|
+
// Include thumbnails if any values were set via CLI
|
|
452
|
+
...Object.keys(thumbnailsConfig).length > 0 && { thumbnails: thumbnailsConfig },
|
|
372
453
|
sections: [
|
|
373
454
|
{
|
|
374
455
|
images: mediaFiles
|
|
@@ -381,13 +462,15 @@ async function createGalleryJson(mediaFiles, galleryJsonPath, scanPath, subGalle
|
|
|
381
462
|
...ctaBanner !== void 0 && { ctaBanner }
|
|
382
463
|
};
|
|
383
464
|
if (!useDefaultSettings) {
|
|
465
|
+
const userSettings = await getGallerySettingsFromUser(
|
|
466
|
+
path7.basename(path7.join(galleryDir, "..")),
|
|
467
|
+
path7.basename(mediaFiles[0]?.filename || ""),
|
|
468
|
+
ui
|
|
469
|
+
);
|
|
470
|
+
const { thumbnails: thumbnails2, ...otherSettings } = userSettings;
|
|
384
471
|
galleryData = {
|
|
385
472
|
...galleryData,
|
|
386
|
-
...
|
|
387
|
-
path7.basename(path7.join(galleryDir, "..")),
|
|
388
|
-
path7.basename(mediaFiles[0]?.filename || ""),
|
|
389
|
-
ui
|
|
390
|
-
)
|
|
473
|
+
...otherSettings
|
|
391
474
|
};
|
|
392
475
|
}
|
|
393
476
|
await promises.writeFile(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
@@ -402,7 +485,7 @@ async function galleryExists(outputPath) {
|
|
|
402
485
|
return false;
|
|
403
486
|
}
|
|
404
487
|
}
|
|
405
|
-
async function processDirectory(scanPath, outputPath, recursive, useDefaultSettings, force, ctaBanner, ui) {
|
|
488
|
+
async function processDirectory(scanPath, outputPath, recursive, useDefaultSettings, force, ctaBanner, configOptions, ui) {
|
|
406
489
|
ui.start(`Scanning ${scanPath}`);
|
|
407
490
|
let totalFiles = 0;
|
|
408
491
|
let totalGalleries = 1;
|
|
@@ -418,6 +501,7 @@ async function processDirectory(scanPath, outputPath, recursive, useDefaultSetti
|
|
|
418
501
|
useDefaultSettings,
|
|
419
502
|
force,
|
|
420
503
|
ctaBanner,
|
|
504
|
+
configOptions,
|
|
421
505
|
ui
|
|
422
506
|
);
|
|
423
507
|
totalFiles += result2.totalFiles;
|
|
@@ -443,7 +527,16 @@ async function processDirectory(scanPath, outputPath, recursive, useDefaultSetti
|
|
|
443
527
|
}
|
|
444
528
|
try {
|
|
445
529
|
await promises.mkdir(galleryPath, { recursive: true });
|
|
446
|
-
await createGalleryJson(
|
|
530
|
+
await createGalleryJson(
|
|
531
|
+
mediaFiles,
|
|
532
|
+
galleryJsonPath,
|
|
533
|
+
scanPath,
|
|
534
|
+
subGalleries,
|
|
535
|
+
useDefaultSettings,
|
|
536
|
+
ctaBanner,
|
|
537
|
+
configOptions,
|
|
538
|
+
ui
|
|
539
|
+
);
|
|
447
540
|
ui.success(
|
|
448
541
|
`Create gallery with ${mediaFiles.length} files and ${subGalleries.length} subgalleries at: ${galleryJsonPath}`
|
|
449
542
|
);
|
|
@@ -467,6 +560,11 @@ async function init(options, ui) {
|
|
|
467
560
|
try {
|
|
468
561
|
const scanPath = path7.resolve(options.photos);
|
|
469
562
|
const outputPath = options.gallery ? path7.resolve(options.gallery) : scanPath;
|
|
563
|
+
const configOptions = {
|
|
564
|
+
theme: options.theme,
|
|
565
|
+
thumbnailSize: options.thumbnailSize,
|
|
566
|
+
thumbnailEdge: options.thumbnailEdge
|
|
567
|
+
};
|
|
470
568
|
const result = await processDirectory(
|
|
471
569
|
scanPath,
|
|
472
570
|
outputPath,
|
|
@@ -474,6 +572,7 @@ async function init(options, ui) {
|
|
|
474
572
|
options.default,
|
|
475
573
|
options.force,
|
|
476
574
|
options.ctaBanner,
|
|
575
|
+
configOptions,
|
|
477
576
|
ui
|
|
478
577
|
);
|
|
479
578
|
ui.box(
|
|
@@ -523,9 +622,25 @@ async function getVideoDimensions(filePath) {
|
|
|
523
622
|
}
|
|
524
623
|
return dimensions;
|
|
525
624
|
}
|
|
526
|
-
async function createVideoThumbnails(inputPath, videoDimensions, outputPath, outputPathRetina,
|
|
625
|
+
async function createVideoThumbnails(inputPath, videoDimensions, outputPath, outputPathRetina, size, sizeDimension = "auto", verbose = false) {
|
|
527
626
|
const aspectRatio = videoDimensions.width / videoDimensions.height;
|
|
528
|
-
|
|
627
|
+
let width;
|
|
628
|
+
let height;
|
|
629
|
+
if (sizeDimension === "width") {
|
|
630
|
+
width = size;
|
|
631
|
+
height = Math.round(size / aspectRatio);
|
|
632
|
+
} else if (sizeDimension === "height") {
|
|
633
|
+
width = Math.round(size * aspectRatio);
|
|
634
|
+
height = size;
|
|
635
|
+
} else {
|
|
636
|
+
if (videoDimensions.width > videoDimensions.height) {
|
|
637
|
+
width = size;
|
|
638
|
+
height = Math.round(size / aspectRatio);
|
|
639
|
+
} else {
|
|
640
|
+
width = Math.round(size * aspectRatio);
|
|
641
|
+
height = size;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
529
644
|
const tempFramePath = `${outputPath}.temp.png`;
|
|
530
645
|
return new Promise((resolve, reject) => {
|
|
531
646
|
const ffmpeg = spawn("ffmpeg", [
|
|
@@ -566,7 +681,7 @@ async function createVideoThumbnails(inputPath, videoDimensions, outputPath, out
|
|
|
566
681
|
}
|
|
567
682
|
|
|
568
683
|
// src/modules/thumbnails/index.ts
|
|
569
|
-
async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, lastMediaTimestamp) {
|
|
684
|
+
async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, thumbnailSizeDimension = "auto", lastMediaTimestamp) {
|
|
570
685
|
const fileMtime = await getFileMtime(imagePath);
|
|
571
686
|
if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs8.existsSync(thumbnailPath)) {
|
|
572
687
|
return void 0;
|
|
@@ -585,7 +700,8 @@ async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
585
700
|
metadata,
|
|
586
701
|
thumbnailPath,
|
|
587
702
|
thumbnailPathRetina,
|
|
588
|
-
thumbnailSize
|
|
703
|
+
thumbnailSize,
|
|
704
|
+
thumbnailSizeDimension
|
|
589
705
|
);
|
|
590
706
|
const blurHash = await generateBlurHash(thumbnailPath);
|
|
591
707
|
return {
|
|
@@ -604,7 +720,7 @@ async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
604
720
|
lastMediaTimestamp: fileMtime.toISOString()
|
|
605
721
|
};
|
|
606
722
|
}
|
|
607
|
-
async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumbnailSize, verbose, lastMediaTimestamp) {
|
|
723
|
+
async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumbnailSize, thumbnailSizeDimension = "auto", verbose, lastMediaTimestamp) {
|
|
608
724
|
const fileMtime = await getFileMtime(videoPath);
|
|
609
725
|
if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs8.existsSync(thumbnailPath)) {
|
|
610
726
|
return void 0;
|
|
@@ -616,6 +732,7 @@ async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
616
732
|
thumbnailPath,
|
|
617
733
|
thumbnailPathRetina,
|
|
618
734
|
thumbnailSize,
|
|
735
|
+
thumbnailSizeDimension,
|
|
619
736
|
verbose
|
|
620
737
|
);
|
|
621
738
|
const blurHash = await generateBlurHash(thumbnailPath);
|
|
@@ -635,7 +752,7 @@ async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
635
752
|
lastMediaTimestamp: fileMtime.toISOString()
|
|
636
753
|
};
|
|
637
754
|
}
|
|
638
|
-
async function processMediaFile(mediaFile, mediaBasePath, thumbnailsPath,
|
|
755
|
+
async function processMediaFile(mediaFile, mediaBasePath, thumbnailsPath, thumbnailConfig, ui) {
|
|
639
756
|
try {
|
|
640
757
|
const filePath = path7.resolve(path7.join(mediaBasePath, mediaFile.filename));
|
|
641
758
|
const fileName = mediaFile.filename;
|
|
@@ -646,7 +763,22 @@ async function processMediaFile(mediaFile, mediaBasePath, thumbnailsPath, thumbn
|
|
|
646
763
|
const lastMediaTimestamp = mediaFile.lastMediaTimestamp ? new Date(mediaFile.lastMediaTimestamp) : void 0;
|
|
647
764
|
const verbose = ui.level === LogLevels.debug;
|
|
648
765
|
ui.debug(` Processing ${mediaFile.type}: ${fileName}`);
|
|
649
|
-
const updatedMediaFile = await (mediaFile.type === "image" ? processImage(
|
|
766
|
+
const updatedMediaFile = await (mediaFile.type === "image" ? processImage(
|
|
767
|
+
filePath,
|
|
768
|
+
thumbnailPath,
|
|
769
|
+
thumbnailPathRetina,
|
|
770
|
+
thumbnailConfig.size,
|
|
771
|
+
thumbnailConfig.edge,
|
|
772
|
+
lastMediaTimestamp
|
|
773
|
+
) : processVideo(
|
|
774
|
+
filePath,
|
|
775
|
+
thumbnailPath,
|
|
776
|
+
thumbnailPathRetina,
|
|
777
|
+
thumbnailConfig.size,
|
|
778
|
+
thumbnailConfig.edge,
|
|
779
|
+
verbose,
|
|
780
|
+
lastMediaTimestamp
|
|
781
|
+
));
|
|
650
782
|
if (!updatedMediaFile) {
|
|
651
783
|
ui.debug(` Skipping ${fileName} because it has already been processed`);
|
|
652
784
|
if (mediaFile.thumbnail && !mediaFile.thumbnail.blurHash && fs8.existsSync(thumbnailPath)) {
|
|
@@ -682,19 +814,30 @@ async function processMediaFile(mediaFile, mediaBasePath, thumbnailsPath, thumbn
|
|
|
682
814
|
return { ...mediaFile, thumbnail: void 0 };
|
|
683
815
|
}
|
|
684
816
|
}
|
|
685
|
-
async function processGalleryThumbnails(galleryDir, ui) {
|
|
817
|
+
async function processGalleryThumbnails(galleryDir, ui, cliThumbnailConfig) {
|
|
686
818
|
const galleryJsonPath = path7.join(galleryDir, "gallery", "gallery.json");
|
|
687
819
|
const thumbnailsPath = path7.join(galleryDir, "gallery", "images");
|
|
688
820
|
ui.start(`Creating thumbnails: ${galleryDir}`);
|
|
689
821
|
try {
|
|
690
822
|
fs8.mkdirSync(thumbnailsPath, { recursive: true });
|
|
691
823
|
const galleryData = parseGalleryJson(galleryJsonPath, ui);
|
|
692
|
-
const
|
|
824
|
+
const galleryThumbnailConfig = extractThumbnailConfigFromGallery(galleryData);
|
|
825
|
+
let themeConfig;
|
|
826
|
+
if (galleryData.theme) {
|
|
827
|
+
try {
|
|
828
|
+
const themeDir = await resolveThemeDir(galleryData.theme, ui);
|
|
829
|
+
themeConfig = loadThemeConfig(themeDir);
|
|
830
|
+
} catch {
|
|
831
|
+
ui.debug(`Could not load theme config from ${galleryData.theme}, using defaults`);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
const thumbnailConfig = mergeThumbnailConfig(cliThumbnailConfig, galleryThumbnailConfig, themeConfig);
|
|
835
|
+
ui.debug(`Thumbnail config: size=${thumbnailConfig.size}, edge=${thumbnailConfig.edge}`);
|
|
693
836
|
const mediaBasePath = galleryData.mediaBasePath ?? path7.join(galleryDir);
|
|
694
837
|
let processedCount = 0;
|
|
695
838
|
for (const section of galleryData.sections) {
|
|
696
839
|
for (const [index, mediaFile] of section.images.entries()) {
|
|
697
|
-
section.images[index] = await processMediaFile(mediaFile, mediaBasePath, thumbnailsPath,
|
|
840
|
+
section.images[index] = await processMediaFile(mediaFile, mediaBasePath, thumbnailsPath, thumbnailConfig, ui);
|
|
698
841
|
}
|
|
699
842
|
processedCount += section.images.length;
|
|
700
843
|
}
|
|
@@ -713,10 +856,11 @@ async function thumbnails(options, ui) {
|
|
|
713
856
|
ui.error("No galleries found.");
|
|
714
857
|
return { processedGalleryCount: 0, processedMediaCount: 0 };
|
|
715
858
|
}
|
|
859
|
+
const cliThumbnailConfig = options.thumbnailSize !== void 0 || options.thumbnailEdge !== void 0 ? { size: options.thumbnailSize, edge: options.thumbnailEdge } : void 0;
|
|
716
860
|
let totalGalleries = 0;
|
|
717
861
|
let totalProcessed = 0;
|
|
718
862
|
for (const galleryDir of galleryDirs) {
|
|
719
|
-
const processed = await processGalleryThumbnails(galleryDir, ui);
|
|
863
|
+
const processed = await processGalleryThumbnails(galleryDir, ui, cliThumbnailConfig);
|
|
720
864
|
if (processed > 0) {
|
|
721
865
|
++totalGalleries;
|
|
722
866
|
totalProcessed += processed;
|
|
@@ -775,7 +919,7 @@ async function scanAndAppendNewFiles(galleryDir, galleryJsonPath, galleryData, u
|
|
|
775
919
|
}
|
|
776
920
|
return galleryData;
|
|
777
921
|
}
|
|
778
|
-
async function buildGallery(galleryDir, templateDir, scan, shouldCreateThumbnails, ui, baseUrl, thumbsBaseUrl) {
|
|
922
|
+
async function buildGallery(galleryDir, templateDir, scan, shouldCreateThumbnails, ui, baseUrl, thumbsBaseUrl, cliThumbnailConfig, cliTheme) {
|
|
779
923
|
ui.start(`Building gallery ${galleryDir}`);
|
|
780
924
|
const galleryJsonPath = path7.join(galleryDir, "gallery", "gallery.json");
|
|
781
925
|
let galleryData = parseGalleryJson(galleryJsonPath, ui);
|
|
@@ -828,18 +972,35 @@ async function buildGallery(galleryDir, templateDir, scan, shouldCreateThumbnail
|
|
|
828
972
|
galleryData.thumbsBaseUrl = thumbsBaseUrl;
|
|
829
973
|
fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
830
974
|
}
|
|
975
|
+
if (cliTheme && galleryData.theme !== cliTheme) {
|
|
976
|
+
ui.debug("Updating gallery.json with theme");
|
|
977
|
+
galleryData.theme = cliTheme;
|
|
978
|
+
fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
979
|
+
}
|
|
980
|
+
if (cliThumbnailConfig) {
|
|
981
|
+
const needsUpdate = cliThumbnailConfig.size !== void 0 && galleryData.thumbnails?.size !== cliThumbnailConfig.size || cliThumbnailConfig.edge !== void 0 && galleryData.thumbnails?.edge !== cliThumbnailConfig.edge;
|
|
982
|
+
if (needsUpdate) {
|
|
983
|
+
ui.debug("Updating gallery.json with thumbnail settings");
|
|
984
|
+
galleryData.thumbnails = {
|
|
985
|
+
...galleryData.thumbnails,
|
|
986
|
+
...cliThumbnailConfig.size !== void 0 && { size: cliThumbnailConfig.size },
|
|
987
|
+
...cliThumbnailConfig.edge !== void 0 && { edge: cliThumbnailConfig.edge }
|
|
988
|
+
};
|
|
989
|
+
fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
990
|
+
}
|
|
991
|
+
}
|
|
831
992
|
if (!galleryData.metadata.image) {
|
|
832
993
|
ui.debug("Updating gallery.json with social media card URL");
|
|
833
994
|
galleryData.metadata.image = thumbsBaseUrl ? `${thumbsBaseUrl}/${path7.basename(socialMediaCardImagePath)}` : `${galleryData.url || ""}/${path7.relative(galleryDir, socialMediaCardImagePath)}`;
|
|
834
995
|
fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
835
996
|
}
|
|
836
997
|
if (shouldCreateThumbnails) {
|
|
837
|
-
await processGalleryThumbnails(galleryDir, ui);
|
|
998
|
+
await processGalleryThumbnails(galleryDir, ui, cliThumbnailConfig);
|
|
838
999
|
}
|
|
839
1000
|
ui.debug("Building gallery from template");
|
|
840
1001
|
try {
|
|
841
|
-
|
|
842
|
-
|
|
1002
|
+
process4.env.GALLERY_JSON_PATH = galleryJsonPath;
|
|
1003
|
+
process4.env.GALLERY_OUTPUT_DIR = path7.join(galleryDir, "gallery");
|
|
843
1004
|
execSync("npx astro build", { cwd: templateDir, stdio: ui.level === LogLevels.debug ? "inherit" : "ignore" });
|
|
844
1005
|
} catch (error) {
|
|
845
1006
|
ui.error(`Build failed for ${galleryDir}`);
|
|
@@ -856,6 +1017,25 @@ async function buildGallery(galleryDir, templateDir, scan, shouldCreateThumbnail
|
|
|
856
1017
|
fs8.rmSync(buildDir, { recursive: true, force: true });
|
|
857
1018
|
ui.success(`Gallery built successfully`);
|
|
858
1019
|
}
|
|
1020
|
+
function isLocalThemePath(theme) {
|
|
1021
|
+
return theme.startsWith("./") || theme.startsWith("../") || theme.startsWith("/");
|
|
1022
|
+
}
|
|
1023
|
+
async function resolveThemeDir(theme, ui) {
|
|
1024
|
+
if (isLocalThemePath(theme)) {
|
|
1025
|
+
const themeDir = path7.resolve(theme);
|
|
1026
|
+
const packageJsonPath = path7.join(themeDir, "package.json");
|
|
1027
|
+
if (!fs8.existsSync(packageJsonPath)) {
|
|
1028
|
+
throw new Error(`Theme directory not found or invalid: ${themeDir}. package.json not found.`);
|
|
1029
|
+
}
|
|
1030
|
+
ui.debug(`Using local theme: ${themeDir}`);
|
|
1031
|
+
return themeDir;
|
|
1032
|
+
} else {
|
|
1033
|
+
const themePath = await import.meta.resolve(`${theme}/package.json`);
|
|
1034
|
+
const themeDir = path7.dirname(new URL(themePath).pathname);
|
|
1035
|
+
ui.debug(`Using npm theme package: ${theme} (${themeDir})`);
|
|
1036
|
+
return themeDir;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
859
1039
|
async function build(options, ui) {
|
|
860
1040
|
try {
|
|
861
1041
|
const galleryDirs = findGalleries(options.gallery, options.recursive);
|
|
@@ -863,20 +1043,41 @@ async function build(options, ui) {
|
|
|
863
1043
|
ui.error("No galleries found.");
|
|
864
1044
|
return { processedGalleryCount: 0 };
|
|
865
1045
|
}
|
|
866
|
-
const
|
|
867
|
-
const themeDir = path7.dirname(new URL(themePath).pathname);
|
|
1046
|
+
const cliThumbnailConfig = options.thumbnailSize !== void 0 || options.thumbnailEdge !== void 0 ? { size: options.thumbnailSize, edge: options.thumbnailEdge } : void 0;
|
|
868
1047
|
let totalGalleries = 0;
|
|
869
1048
|
for (const dir of galleryDirs) {
|
|
1049
|
+
const galleryJsonPath = path7.join(dir, "gallery", "gallery.json");
|
|
1050
|
+
const galleryData = parseGalleryJson(galleryJsonPath, ui);
|
|
1051
|
+
const themeIdentifier = options.theme || galleryData.theme || "@simple-photo-gallery/theme-modern";
|
|
1052
|
+
const themeDir = await resolveThemeDir(themeIdentifier, ui);
|
|
870
1053
|
const baseUrl = options.baseUrl ? `${options.baseUrl}${path7.relative(options.gallery, dir)}` : void 0;
|
|
871
1054
|
const thumbsBaseUrl = options.thumbsBaseUrl ? `${options.thumbsBaseUrl}${path7.relative(options.gallery, dir)}` : void 0;
|
|
872
|
-
await buildGallery(
|
|
1055
|
+
await buildGallery(
|
|
1056
|
+
path7.resolve(dir),
|
|
1057
|
+
themeDir,
|
|
1058
|
+
options.scan,
|
|
1059
|
+
options.thumbnails,
|
|
1060
|
+
ui,
|
|
1061
|
+
baseUrl,
|
|
1062
|
+
thumbsBaseUrl,
|
|
1063
|
+
cliThumbnailConfig,
|
|
1064
|
+
options.theme
|
|
1065
|
+
);
|
|
873
1066
|
++totalGalleries;
|
|
874
1067
|
}
|
|
875
1068
|
ui.box(`Built ${totalGalleries} ${totalGalleries === 1 ? "gallery" : "galleries"} successfully`);
|
|
876
1069
|
return { processedGalleryCount: totalGalleries };
|
|
877
1070
|
} catch (error) {
|
|
878
|
-
if (error instanceof Error
|
|
879
|
-
|
|
1071
|
+
if (error instanceof Error) {
|
|
1072
|
+
if (error.message.includes("Cannot find package")) {
|
|
1073
|
+
ui.error(
|
|
1074
|
+
`Theme package not found: ${options.theme || "@simple-photo-gallery/theme-modern"}. Make sure it's installed.`
|
|
1075
|
+
);
|
|
1076
|
+
} else if (error.message.includes("Theme directory not found") || error.message.includes("package.json not found")) {
|
|
1077
|
+
ui.error(error.message);
|
|
1078
|
+
} else {
|
|
1079
|
+
ui.error("Error building gallery");
|
|
1080
|
+
}
|
|
880
1081
|
} else {
|
|
881
1082
|
ui.error("Error building gallery");
|
|
882
1083
|
}
|
|
@@ -934,6 +1135,162 @@ async function clean(options, ui) {
|
|
|
934
1135
|
throw error;
|
|
935
1136
|
}
|
|
936
1137
|
}
|
|
1138
|
+
function findMonorepoRoot(startDir) {
|
|
1139
|
+
let dir = path7.resolve(startDir);
|
|
1140
|
+
while (true) {
|
|
1141
|
+
const pkgPath = path7.join(dir, "package.json");
|
|
1142
|
+
if (fs8.existsSync(pkgPath)) {
|
|
1143
|
+
try {
|
|
1144
|
+
const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf8"));
|
|
1145
|
+
if (pkg && typeof pkg === "object" && "workspaces" in pkg) {
|
|
1146
|
+
return dir;
|
|
1147
|
+
}
|
|
1148
|
+
} catch {
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
const parent = path7.dirname(dir);
|
|
1152
|
+
if (parent === dir) {
|
|
1153
|
+
return void 0;
|
|
1154
|
+
}
|
|
1155
|
+
dir = parent;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
function validateThemeName(name) {
|
|
1159
|
+
if (!name || name.trim().length === 0) {
|
|
1160
|
+
throw new Error("Theme name cannot be empty");
|
|
1161
|
+
}
|
|
1162
|
+
if (!/^[a-z0-9-]+$/i.test(name)) {
|
|
1163
|
+
throw new Error("Theme name can only contain letters, numbers, and hyphens");
|
|
1164
|
+
}
|
|
1165
|
+
return true;
|
|
1166
|
+
}
|
|
1167
|
+
async function ensureDirectory(dirPath, ui) {
|
|
1168
|
+
try {
|
|
1169
|
+
await fs8.promises.mkdir(dirPath, { recursive: true });
|
|
1170
|
+
ui.debug(`Created directory: ${dirPath}`);
|
|
1171
|
+
} catch (error) {
|
|
1172
|
+
if (error instanceof Error && "code" in error && error.code !== "EEXIST") {
|
|
1173
|
+
throw new Error(`Failed to create directory ${dirPath}: ${error.message}`);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
var EXCLUDE_PATTERNS = ["node_modules", ".astro", "dist", "_build", ".git", "*.log", ".DS_Store"];
|
|
1178
|
+
function shouldExclude(name) {
|
|
1179
|
+
if (name === "README.md" || name === "README_BASE.md") {
|
|
1180
|
+
return true;
|
|
1181
|
+
}
|
|
1182
|
+
return EXCLUDE_PATTERNS.some((pattern) => {
|
|
1183
|
+
if (pattern.includes("*")) {
|
|
1184
|
+
const regexPattern = pattern.split("*").join(".*");
|
|
1185
|
+
const regex = new RegExp(regexPattern);
|
|
1186
|
+
return regex.test(name);
|
|
1187
|
+
}
|
|
1188
|
+
return name === pattern;
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
async function copyDirectory(src, dest, ui) {
|
|
1192
|
+
await fs8.promises.mkdir(dest, { recursive: true });
|
|
1193
|
+
const entries = await fs8.promises.readdir(src, { withFileTypes: true });
|
|
1194
|
+
for (const entry of entries) {
|
|
1195
|
+
if (shouldExclude(entry.name)) {
|
|
1196
|
+
ui.debug(`Skipping excluded file/directory: ${entry.name}`);
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
const srcPath = path7.join(src, entry.name);
|
|
1200
|
+
const destPath = path7.join(dest, entry.name);
|
|
1201
|
+
if (entry.isDirectory()) {
|
|
1202
|
+
await copyDirectory(srcPath, destPath, ui);
|
|
1203
|
+
} else {
|
|
1204
|
+
await fs8.promises.copyFile(srcPath, destPath);
|
|
1205
|
+
ui.debug(`Copied file: ${destPath}`);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
function findBaseThemePath() {
|
|
1210
|
+
const moduleDir = path7.dirname(fileURLToPath(import.meta.url));
|
|
1211
|
+
const bundledTemplatePath = path7.resolve(moduleDir, "../../../src/modules/create-theme/templates/base");
|
|
1212
|
+
if (fs8.existsSync(bundledTemplatePath)) {
|
|
1213
|
+
return bundledTemplatePath;
|
|
1214
|
+
}
|
|
1215
|
+
const monorepoRoot = findMonorepoRoot(process4.cwd());
|
|
1216
|
+
const workspaceRoot = monorepoRoot ?? process4.cwd();
|
|
1217
|
+
const workspaceBaseThemePath = path7.join(workspaceRoot, "themes", "base");
|
|
1218
|
+
if (fs8.existsSync(workspaceBaseThemePath)) {
|
|
1219
|
+
return workspaceBaseThemePath;
|
|
1220
|
+
}
|
|
1221
|
+
throw new Error(
|
|
1222
|
+
`Base theme template not found. Tried:
|
|
1223
|
+
- ${bundledTemplatePath}
|
|
1224
|
+
- ${workspaceBaseThemePath}
|
|
1225
|
+
|
|
1226
|
+
Please ensure the templates are included in the package or themes/base exists in the workspace.`
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
async function updatePackageJson(themeDir, themeName, ui) {
|
|
1230
|
+
const packageJsonPath = path7.join(themeDir, "package.json");
|
|
1231
|
+
const packageJsonContent = await fs8.promises.readFile(packageJsonPath, "utf8");
|
|
1232
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
1233
|
+
packageJson.name = themeName;
|
|
1234
|
+
await fs8.promises.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n", "utf8");
|
|
1235
|
+
ui.debug(`Updated package.json with theme name: ${themeName}`);
|
|
1236
|
+
}
|
|
1237
|
+
async function createReadmeFromBase(baseThemePath, themeDir, themeName, ui) {
|
|
1238
|
+
const readmeBasePath = path7.join(baseThemePath, "README_BASE.md");
|
|
1239
|
+
const readmePath = path7.join(themeDir, "README.md");
|
|
1240
|
+
if (!fs8.existsSync(readmeBasePath)) {
|
|
1241
|
+
throw new Error(`README_BASE.md not found in template: ${readmeBasePath}`);
|
|
1242
|
+
}
|
|
1243
|
+
let readme = await fs8.promises.readFile(readmeBasePath, "utf8");
|
|
1244
|
+
const displayName = themeName.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1245
|
+
readme = readme.replaceAll("{THEME_NAME}", displayName);
|
|
1246
|
+
readme = readme.replaceAll("{THEME_NAME_LOWER}", displayName.toLowerCase());
|
|
1247
|
+
await fs8.promises.writeFile(readmePath, readme, "utf8");
|
|
1248
|
+
ui.debug(`Created README.md from README_BASE.md for theme: ${themeName}`);
|
|
1249
|
+
}
|
|
1250
|
+
async function createTheme(options, ui) {
|
|
1251
|
+
try {
|
|
1252
|
+
validateThemeName(options.name);
|
|
1253
|
+
let themeDir;
|
|
1254
|
+
if (options.path) {
|
|
1255
|
+
themeDir = path7.resolve(options.path);
|
|
1256
|
+
} else {
|
|
1257
|
+
const monorepoRoot = findMonorepoRoot(process4.cwd());
|
|
1258
|
+
const baseDir = monorepoRoot ?? process4.cwd();
|
|
1259
|
+
const themesBaseDir = path7.resolve(baseDir, "themes");
|
|
1260
|
+
themeDir = path7.join(themesBaseDir, options.name);
|
|
1261
|
+
if (!fs8.existsSync(themesBaseDir)) {
|
|
1262
|
+
await ensureDirectory(themesBaseDir, ui);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
if (fs8.existsSync(themeDir)) {
|
|
1266
|
+
throw new Error(`Theme directory already exists: ${themeDir}. Cannot overwrite existing theme.`);
|
|
1267
|
+
}
|
|
1268
|
+
ui.start(`Creating theme: ${options.name}`);
|
|
1269
|
+
const baseThemePath = findBaseThemePath();
|
|
1270
|
+
ui.debug(`Using base theme from: ${baseThemePath}`);
|
|
1271
|
+
ui.debug("Copying base theme files...");
|
|
1272
|
+
await copyDirectory(baseThemePath, themeDir, ui);
|
|
1273
|
+
ui.debug("Updating theme-specific files...");
|
|
1274
|
+
await updatePackageJson(themeDir, options.name, ui);
|
|
1275
|
+
await createReadmeFromBase(baseThemePath, themeDir, options.name, ui);
|
|
1276
|
+
ui.success(`Theme created successfully at: ${themeDir}`);
|
|
1277
|
+
ui.info(`
|
|
1278
|
+
Next steps:`);
|
|
1279
|
+
ui.info(`1. cd ${themeDir}`);
|
|
1280
|
+
ui.info(`2. yarn install`);
|
|
1281
|
+
ui.info(`3. Customize your theme in src/pages/index.astro`);
|
|
1282
|
+
ui.info(`4. Initialize a gallery (run from directory with your images): spg init -p <images-folder>`);
|
|
1283
|
+
ui.info(`5. Build a gallery with your theme: spg build --theme ${themeDir} -g <gallery-folder>`);
|
|
1284
|
+
return { processedGalleryCount: 0 };
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
if (error instanceof Error) {
|
|
1287
|
+
ui.error(error.message);
|
|
1288
|
+
} else {
|
|
1289
|
+
ui.error("Failed to create theme");
|
|
1290
|
+
}
|
|
1291
|
+
throw error;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
937
1294
|
|
|
938
1295
|
// src/modules/telemetry/index.ts
|
|
939
1296
|
async function telemetry(options, ui, telemetryService2) {
|
|
@@ -963,7 +1320,7 @@ var ApiTelemetryClient = class {
|
|
|
963
1320
|
axios.post(this.endpoint, event, {
|
|
964
1321
|
headers: {
|
|
965
1322
|
"content-type": "application/json",
|
|
966
|
-
"user-agent": `simple-photo-gallery/${event.packageVersion} (${
|
|
1323
|
+
"user-agent": `simple-photo-gallery/${event.packageVersion} (${process4.platform}; ${process4.arch})`
|
|
967
1324
|
}
|
|
968
1325
|
});
|
|
969
1326
|
} catch {
|
|
@@ -1000,11 +1357,11 @@ var TelemetryService = class {
|
|
|
1000
1357
|
if (override) {
|
|
1001
1358
|
return override === "1";
|
|
1002
1359
|
}
|
|
1003
|
-
if (
|
|
1360
|
+
if (process4.env.CI || process4.env.DO_NOT_TRACK) {
|
|
1004
1361
|
return false;
|
|
1005
1362
|
}
|
|
1006
|
-
if (
|
|
1007
|
-
return
|
|
1363
|
+
if (process4.env.SPG_TELEMETRY) {
|
|
1364
|
+
return process4.env.SPG_TELEMETRY === "1";
|
|
1008
1365
|
}
|
|
1009
1366
|
const stored = this.getStoredPreference();
|
|
1010
1367
|
if (stored === void 0) {
|
|
@@ -1046,7 +1403,7 @@ var TelemetryService = class {
|
|
|
1046
1403
|
durationMs: now - startedAt,
|
|
1047
1404
|
packageName: this.packageName,
|
|
1048
1405
|
packageVersion: this.packageVersion,
|
|
1049
|
-
nodeVersion:
|
|
1406
|
+
nodeVersion: process4.version,
|
|
1050
1407
|
osPlatform: os.platform(),
|
|
1051
1408
|
osRelease: os.release(),
|
|
1052
1409
|
osArch: os.arch(),
|
|
@@ -1080,7 +1437,7 @@ var TelemetryService = class {
|
|
|
1080
1437
|
/** Returns the telemetry client. */
|
|
1081
1438
|
getClient() {
|
|
1082
1439
|
if (!this.client) {
|
|
1083
|
-
switch (
|
|
1440
|
+
switch (process4.env.SPG_TELEMETRY_PROVIDER) {
|
|
1084
1441
|
case "none": {
|
|
1085
1442
|
this.client = void 0;
|
|
1086
1443
|
break;
|
|
@@ -1172,7 +1529,7 @@ async function waitForUpdateCheck(checkPromise) {
|
|
|
1172
1529
|
// package.json
|
|
1173
1530
|
var package_default = {
|
|
1174
1531
|
name: "simple-photo-gallery",
|
|
1175
|
-
version: "2.0
|
|
1532
|
+
version: "2.1.0"};
|
|
1176
1533
|
|
|
1177
1534
|
// src/index.ts
|
|
1178
1535
|
var program = new Command();
|
|
@@ -1214,7 +1571,7 @@ function withCommandContext(handler) {
|
|
|
1214
1571
|
} catch (error) {
|
|
1215
1572
|
ui.debug(error);
|
|
1216
1573
|
errorInfo = error instanceof Error ? { name: error.name, message: error.message } : { name: "UnknownError", message: String(error) };
|
|
1217
|
-
|
|
1574
|
+
process4.exitCode = 1;
|
|
1218
1575
|
}
|
|
1219
1576
|
const updateInfo = await waitForUpdateCheck(updateCheckPromise);
|
|
1220
1577
|
if (updateInfo) {
|
|
@@ -1236,14 +1593,21 @@ function withCommandContext(handler) {
|
|
|
1236
1593
|
program.command("init").description("Initialize a gallery by scaning a folder for images and videos").option(
|
|
1237
1594
|
"-p, --photos <path>",
|
|
1238
1595
|
"Path to the folder where the photos are stored. Default: current working directory",
|
|
1239
|
-
|
|
1596
|
+
process4.cwd()
|
|
1240
1597
|
).option(
|
|
1241
1598
|
"-g, --gallery <path>",
|
|
1242
1599
|
"Path to the directory where the gallery will be initialized. Default: same directory as the photos folder"
|
|
1243
|
-
).option("-r, --recursive", "Recursively create galleries from all photos subdirectories", false).option("-d, --default", "Use default gallery settings instead of asking the user", false).option("-f, --force", "Force override existing galleries without asking", false).option("--cta-banner", "Add a Simple Photo Gallery call-to-action banner to the end of the gallery", false).action(withCommandContext((options, ui) => init(options, ui)));
|
|
1244
|
-
program.command("thumbnails").description("Create thumbnails for all media files in the gallery").option("-g, --gallery <path>", "Path to the directory of the gallery. Default: current working directory",
|
|
1245
|
-
program.command("build").description("Build the HTML gallery in the specified directory").option("-g, --gallery <path>", "Path to the directory of the gallery. Default: current working directory",
|
|
1246
|
-
|
|
1600
|
+
).option("-r, --recursive", "Recursively create galleries from all photos subdirectories", false).option("-d, --default", "Use default gallery settings instead of asking the user", false).option("-f, --force", "Force override existing galleries without asking", false).option("--cta-banner", "Add a Simple Photo Gallery call-to-action banner to the end of the gallery", false).option("--theme <package|path>", "Theme package name or local path to store in gallery.json").option("--thumbnail-size <pixels>", "Thumbnail size in pixels to store in gallery.json", Number.parseInt).option("--thumbnail-edge <mode>", "How thumbnail size is applied: auto, width, or height").action(withCommandContext((options, ui) => init(options, ui)));
|
|
1601
|
+
program.command("thumbnails").description("Create thumbnails for all media files in the gallery").option("-g, --gallery <path>", "Path to the directory of the gallery. Default: current working directory", process4.cwd()).option("-r, --recursive", "Scan subdirectories recursively", false).option("--thumbnail-size <pixels>", "Override thumbnail size in pixels", Number.parseInt).option("--thumbnail-edge <mode>", "Override how thumbnail size is applied: auto, width, or height").action(withCommandContext((options, ui) => thumbnails(options, ui)));
|
|
1602
|
+
program.command("build").description("Build the HTML gallery in the specified directory").option("-g, --gallery <path>", "Path to the directory of the gallery. Default: current working directory", process4.cwd()).option("-r, --recursive", "Scan subdirectories recursively", false).option("-b, --base-url <url>", "Base URL where the photos are hosted").option("-t, --thumbs-base-url <url>", "Base URL where the thumbnails are hosted").option("--no-thumbnails", "Skip creating thumbnails when building the gallery", true).option("--no-scan", "Do not scan for new photos when building the gallery", true).option(
|
|
1603
|
+
"--theme <package|path>",
|
|
1604
|
+
"Theme package name (e.g., @simple-photo-gallery/theme-modern) or local path (e.g., ./themes/my-theme)"
|
|
1605
|
+
).option("--thumbnail-size <pixels>", "Override thumbnail size in pixels", Number.parseInt).option("--thumbnail-edge <mode>", "Override how thumbnail size is applied: auto, width, or height").action(withCommandContext((options, ui) => build(options, ui)));
|
|
1606
|
+
program.command("clean").description("Remove all gallery files and folders (index.html, gallery/)").option("-g, --gallery <path>", "Path to the directory of the gallery. Default: current working directory", process4.cwd()).option("-r, --recursive", "Clean subdirectories recursively", false).action(withCommandContext((options, ui) => clean(options, ui)));
|
|
1607
|
+
program.command("create-theme").description("Create a new theme template").argument("<name>", "Name of the theme to create").option("-p, --path <path>", "Path where the theme should be created. Default: ./themes/<name>").action(async (name, options, command) => {
|
|
1608
|
+
const handler = withCommandContext((opts, ui) => createTheme({ name, path: opts.path }, ui));
|
|
1609
|
+
await handler(options, command);
|
|
1610
|
+
});
|
|
1247
1611
|
program.command("telemetry").description("Manage anonymous telemetry preferences. Use 1 to enable, 0 to disable, or no argument to check status").option("-s, --state <state>", "Enable (1) or disable (0) telemetry", parseTelemetryOption).action(withCommandContext((options, ui) => telemetry(options, ui, telemetryService)));
|
|
1248
1612
|
program.parse();
|
|
1249
1613
|
//# sourceMappingURL=index.js.map
|