simple-photo-gallery 2.0.18 → 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 +367 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +364 -48
- package/dist/index.js.map +1 -1
- package/dist/lib/index.cjs +29 -5
- 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 +29 -5
- 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);
|
|
@@ -326,11 +335,15 @@ function migrateGalleryJson(deprecatedGalleryData, galleryJsonPath, ui) {
|
|
|
326
335
|
filename: path7.basename(image.path)
|
|
327
336
|
}))
|
|
328
337
|
}));
|
|
338
|
+
const thumbnails2 = deprecatedGalleryData.thumbnailSize === void 0 ? void 0 : { size: deprecatedGalleryData.thumbnailSize };
|
|
329
339
|
const galleryData = {
|
|
330
340
|
...deprecatedGalleryData,
|
|
341
|
+
thumbnailSize: void 0,
|
|
342
|
+
// Remove old field
|
|
331
343
|
headerImage: path7.basename(deprecatedGalleryData.headerImage),
|
|
332
344
|
sections,
|
|
333
|
-
mediaBasePath
|
|
345
|
+
mediaBasePath,
|
|
346
|
+
thumbnails: thumbnails2
|
|
334
347
|
};
|
|
335
348
|
ui.debug("Backing up old gallery.json file");
|
|
336
349
|
fs8.copyFileSync(galleryJsonPath, `${galleryJsonPath}.old`);
|
|
@@ -401,9 +414,18 @@ async function getGallerySettingsFromUser(galleryName, defaultImage, ui) {
|
|
|
401
414
|
default: defaultImage,
|
|
402
415
|
placeholder: defaultImage
|
|
403
416
|
});
|
|
404
|
-
return {
|
|
417
|
+
return {
|
|
418
|
+
title,
|
|
419
|
+
description,
|
|
420
|
+
url,
|
|
421
|
+
headerImage,
|
|
422
|
+
thumbnails: {
|
|
423
|
+
size: "",
|
|
424
|
+
edge: void 0
|
|
425
|
+
}
|
|
426
|
+
};
|
|
405
427
|
}
|
|
406
|
-
async function createGalleryJson(mediaFiles, galleryJsonPath, scanPath, subGalleries = [], useDefaultSettings, ctaBanner, ui) {
|
|
428
|
+
async function createGalleryJson(mediaFiles, galleryJsonPath, scanPath, subGalleries = [], useDefaultSettings, ctaBanner, configOptions, ui) {
|
|
407
429
|
const galleryDir = path7.dirname(galleryJsonPath);
|
|
408
430
|
const isSameLocation = path7.relative(scanPath, path7.join(galleryDir, "..")) === "";
|
|
409
431
|
const mediaBasePath = isSameLocation ? void 0 : scanPath;
|
|
@@ -411,12 +433,23 @@ async function createGalleryJson(mediaFiles, galleryJsonPath, scanPath, subGalle
|
|
|
411
433
|
...subGallery,
|
|
412
434
|
headerImage: subGallery.headerImage ? path7.relative(galleryDir, subGallery.headerImage) : ""
|
|
413
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
|
+
}
|
|
414
443
|
let galleryData = {
|
|
415
444
|
title: "My Gallery",
|
|
416
445
|
description: "My gallery with fantastic photos.",
|
|
417
446
|
headerImage: mediaFiles[0]?.filename || "",
|
|
418
447
|
mediaBasePath,
|
|
419
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 },
|
|
420
453
|
sections: [
|
|
421
454
|
{
|
|
422
455
|
images: mediaFiles
|
|
@@ -429,13 +462,15 @@ async function createGalleryJson(mediaFiles, galleryJsonPath, scanPath, subGalle
|
|
|
429
462
|
...ctaBanner !== void 0 && { ctaBanner }
|
|
430
463
|
};
|
|
431
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;
|
|
432
471
|
galleryData = {
|
|
433
472
|
...galleryData,
|
|
434
|
-
...
|
|
435
|
-
path7.basename(path7.join(galleryDir, "..")),
|
|
436
|
-
path7.basename(mediaFiles[0]?.filename || ""),
|
|
437
|
-
ui
|
|
438
|
-
)
|
|
473
|
+
...otherSettings
|
|
439
474
|
};
|
|
440
475
|
}
|
|
441
476
|
await promises.writeFile(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
@@ -450,7 +485,7 @@ async function galleryExists(outputPath) {
|
|
|
450
485
|
return false;
|
|
451
486
|
}
|
|
452
487
|
}
|
|
453
|
-
async function processDirectory(scanPath, outputPath, recursive, useDefaultSettings, force, ctaBanner, ui) {
|
|
488
|
+
async function processDirectory(scanPath, outputPath, recursive, useDefaultSettings, force, ctaBanner, configOptions, ui) {
|
|
454
489
|
ui.start(`Scanning ${scanPath}`);
|
|
455
490
|
let totalFiles = 0;
|
|
456
491
|
let totalGalleries = 1;
|
|
@@ -466,6 +501,7 @@ async function processDirectory(scanPath, outputPath, recursive, useDefaultSetti
|
|
|
466
501
|
useDefaultSettings,
|
|
467
502
|
force,
|
|
468
503
|
ctaBanner,
|
|
504
|
+
configOptions,
|
|
469
505
|
ui
|
|
470
506
|
);
|
|
471
507
|
totalFiles += result2.totalFiles;
|
|
@@ -491,7 +527,16 @@ async function processDirectory(scanPath, outputPath, recursive, useDefaultSetti
|
|
|
491
527
|
}
|
|
492
528
|
try {
|
|
493
529
|
await promises.mkdir(galleryPath, { recursive: true });
|
|
494
|
-
await createGalleryJson(
|
|
530
|
+
await createGalleryJson(
|
|
531
|
+
mediaFiles,
|
|
532
|
+
galleryJsonPath,
|
|
533
|
+
scanPath,
|
|
534
|
+
subGalleries,
|
|
535
|
+
useDefaultSettings,
|
|
536
|
+
ctaBanner,
|
|
537
|
+
configOptions,
|
|
538
|
+
ui
|
|
539
|
+
);
|
|
495
540
|
ui.success(
|
|
496
541
|
`Create gallery with ${mediaFiles.length} files and ${subGalleries.length} subgalleries at: ${galleryJsonPath}`
|
|
497
542
|
);
|
|
@@ -515,6 +560,11 @@ async function init(options, ui) {
|
|
|
515
560
|
try {
|
|
516
561
|
const scanPath = path7.resolve(options.photos);
|
|
517
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
|
+
};
|
|
518
568
|
const result = await processDirectory(
|
|
519
569
|
scanPath,
|
|
520
570
|
outputPath,
|
|
@@ -522,6 +572,7 @@ async function init(options, ui) {
|
|
|
522
572
|
options.default,
|
|
523
573
|
options.force,
|
|
524
574
|
options.ctaBanner,
|
|
575
|
+
configOptions,
|
|
525
576
|
ui
|
|
526
577
|
);
|
|
527
578
|
ui.box(
|
|
@@ -571,9 +622,25 @@ async function getVideoDimensions(filePath) {
|
|
|
571
622
|
}
|
|
572
623
|
return dimensions;
|
|
573
624
|
}
|
|
574
|
-
async function createVideoThumbnails(inputPath, videoDimensions, outputPath, outputPathRetina,
|
|
625
|
+
async function createVideoThumbnails(inputPath, videoDimensions, outputPath, outputPathRetina, size, sizeDimension = "auto", verbose = false) {
|
|
575
626
|
const aspectRatio = videoDimensions.width / videoDimensions.height;
|
|
576
|
-
|
|
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
|
+
}
|
|
577
644
|
const tempFramePath = `${outputPath}.temp.png`;
|
|
578
645
|
return new Promise((resolve, reject) => {
|
|
579
646
|
const ffmpeg = spawn("ffmpeg", [
|
|
@@ -614,7 +681,7 @@ async function createVideoThumbnails(inputPath, videoDimensions, outputPath, out
|
|
|
614
681
|
}
|
|
615
682
|
|
|
616
683
|
// src/modules/thumbnails/index.ts
|
|
617
|
-
async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, lastMediaTimestamp) {
|
|
684
|
+
async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, thumbnailSizeDimension = "auto", lastMediaTimestamp) {
|
|
618
685
|
const fileMtime = await getFileMtime(imagePath);
|
|
619
686
|
if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs8.existsSync(thumbnailPath)) {
|
|
620
687
|
return void 0;
|
|
@@ -633,7 +700,8 @@ async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
633
700
|
metadata,
|
|
634
701
|
thumbnailPath,
|
|
635
702
|
thumbnailPathRetina,
|
|
636
|
-
thumbnailSize
|
|
703
|
+
thumbnailSize,
|
|
704
|
+
thumbnailSizeDimension
|
|
637
705
|
);
|
|
638
706
|
const blurHash = await generateBlurHash(thumbnailPath);
|
|
639
707
|
return {
|
|
@@ -652,7 +720,7 @@ async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
652
720
|
lastMediaTimestamp: fileMtime.toISOString()
|
|
653
721
|
};
|
|
654
722
|
}
|
|
655
|
-
async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumbnailSize, verbose, lastMediaTimestamp) {
|
|
723
|
+
async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumbnailSize, thumbnailSizeDimension = "auto", verbose, lastMediaTimestamp) {
|
|
656
724
|
const fileMtime = await getFileMtime(videoPath);
|
|
657
725
|
if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs8.existsSync(thumbnailPath)) {
|
|
658
726
|
return void 0;
|
|
@@ -664,6 +732,7 @@ async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
664
732
|
thumbnailPath,
|
|
665
733
|
thumbnailPathRetina,
|
|
666
734
|
thumbnailSize,
|
|
735
|
+
thumbnailSizeDimension,
|
|
667
736
|
verbose
|
|
668
737
|
);
|
|
669
738
|
const blurHash = await generateBlurHash(thumbnailPath);
|
|
@@ -683,7 +752,7 @@ async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
683
752
|
lastMediaTimestamp: fileMtime.toISOString()
|
|
684
753
|
};
|
|
685
754
|
}
|
|
686
|
-
async function processMediaFile(mediaFile, mediaBasePath, thumbnailsPath,
|
|
755
|
+
async function processMediaFile(mediaFile, mediaBasePath, thumbnailsPath, thumbnailConfig, ui) {
|
|
687
756
|
try {
|
|
688
757
|
const filePath = path7.resolve(path7.join(mediaBasePath, mediaFile.filename));
|
|
689
758
|
const fileName = mediaFile.filename;
|
|
@@ -694,7 +763,22 @@ async function processMediaFile(mediaFile, mediaBasePath, thumbnailsPath, thumbn
|
|
|
694
763
|
const lastMediaTimestamp = mediaFile.lastMediaTimestamp ? new Date(mediaFile.lastMediaTimestamp) : void 0;
|
|
695
764
|
const verbose = ui.level === LogLevels.debug;
|
|
696
765
|
ui.debug(` Processing ${mediaFile.type}: ${fileName}`);
|
|
697
|
-
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
|
+
));
|
|
698
782
|
if (!updatedMediaFile) {
|
|
699
783
|
ui.debug(` Skipping ${fileName} because it has already been processed`);
|
|
700
784
|
if (mediaFile.thumbnail && !mediaFile.thumbnail.blurHash && fs8.existsSync(thumbnailPath)) {
|
|
@@ -730,19 +814,30 @@ async function processMediaFile(mediaFile, mediaBasePath, thumbnailsPath, thumbn
|
|
|
730
814
|
return { ...mediaFile, thumbnail: void 0 };
|
|
731
815
|
}
|
|
732
816
|
}
|
|
733
|
-
async function processGalleryThumbnails(galleryDir, ui) {
|
|
817
|
+
async function processGalleryThumbnails(galleryDir, ui, cliThumbnailConfig) {
|
|
734
818
|
const galleryJsonPath = path7.join(galleryDir, "gallery", "gallery.json");
|
|
735
819
|
const thumbnailsPath = path7.join(galleryDir, "gallery", "images");
|
|
736
820
|
ui.start(`Creating thumbnails: ${galleryDir}`);
|
|
737
821
|
try {
|
|
738
822
|
fs8.mkdirSync(thumbnailsPath, { recursive: true });
|
|
739
823
|
const galleryData = parseGalleryJson(galleryJsonPath, ui);
|
|
740
|
-
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}`);
|
|
741
836
|
const mediaBasePath = galleryData.mediaBasePath ?? path7.join(galleryDir);
|
|
742
837
|
let processedCount = 0;
|
|
743
838
|
for (const section of galleryData.sections) {
|
|
744
839
|
for (const [index, mediaFile] of section.images.entries()) {
|
|
745
|
-
section.images[index] = await processMediaFile(mediaFile, mediaBasePath, thumbnailsPath,
|
|
840
|
+
section.images[index] = await processMediaFile(mediaFile, mediaBasePath, thumbnailsPath, thumbnailConfig, ui);
|
|
746
841
|
}
|
|
747
842
|
processedCount += section.images.length;
|
|
748
843
|
}
|
|
@@ -761,10 +856,11 @@ async function thumbnails(options, ui) {
|
|
|
761
856
|
ui.error("No galleries found.");
|
|
762
857
|
return { processedGalleryCount: 0, processedMediaCount: 0 };
|
|
763
858
|
}
|
|
859
|
+
const cliThumbnailConfig = options.thumbnailSize !== void 0 || options.thumbnailEdge !== void 0 ? { size: options.thumbnailSize, edge: options.thumbnailEdge } : void 0;
|
|
764
860
|
let totalGalleries = 0;
|
|
765
861
|
let totalProcessed = 0;
|
|
766
862
|
for (const galleryDir of galleryDirs) {
|
|
767
|
-
const processed = await processGalleryThumbnails(galleryDir, ui);
|
|
863
|
+
const processed = await processGalleryThumbnails(galleryDir, ui, cliThumbnailConfig);
|
|
768
864
|
if (processed > 0) {
|
|
769
865
|
++totalGalleries;
|
|
770
866
|
totalProcessed += processed;
|
|
@@ -823,7 +919,7 @@ async function scanAndAppendNewFiles(galleryDir, galleryJsonPath, galleryData, u
|
|
|
823
919
|
}
|
|
824
920
|
return galleryData;
|
|
825
921
|
}
|
|
826
|
-
async function buildGallery(galleryDir, templateDir, scan, shouldCreateThumbnails, ui, baseUrl, thumbsBaseUrl) {
|
|
922
|
+
async function buildGallery(galleryDir, templateDir, scan, shouldCreateThumbnails, ui, baseUrl, thumbsBaseUrl, cliThumbnailConfig, cliTheme) {
|
|
827
923
|
ui.start(`Building gallery ${galleryDir}`);
|
|
828
924
|
const galleryJsonPath = path7.join(galleryDir, "gallery", "gallery.json");
|
|
829
925
|
let galleryData = parseGalleryJson(galleryJsonPath, ui);
|
|
@@ -876,18 +972,35 @@ async function buildGallery(galleryDir, templateDir, scan, shouldCreateThumbnail
|
|
|
876
972
|
galleryData.thumbsBaseUrl = thumbsBaseUrl;
|
|
877
973
|
fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
878
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
|
+
}
|
|
879
992
|
if (!galleryData.metadata.image) {
|
|
880
993
|
ui.debug("Updating gallery.json with social media card URL");
|
|
881
994
|
galleryData.metadata.image = thumbsBaseUrl ? `${thumbsBaseUrl}/${path7.basename(socialMediaCardImagePath)}` : `${galleryData.url || ""}/${path7.relative(galleryDir, socialMediaCardImagePath)}`;
|
|
882
995
|
fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
883
996
|
}
|
|
884
997
|
if (shouldCreateThumbnails) {
|
|
885
|
-
await processGalleryThumbnails(galleryDir, ui);
|
|
998
|
+
await processGalleryThumbnails(galleryDir, ui, cliThumbnailConfig);
|
|
886
999
|
}
|
|
887
1000
|
ui.debug("Building gallery from template");
|
|
888
1001
|
try {
|
|
889
|
-
|
|
890
|
-
|
|
1002
|
+
process4.env.GALLERY_JSON_PATH = galleryJsonPath;
|
|
1003
|
+
process4.env.GALLERY_OUTPUT_DIR = path7.join(galleryDir, "gallery");
|
|
891
1004
|
execSync("npx astro build", { cwd: templateDir, stdio: ui.level === LogLevels.debug ? "inherit" : "ignore" });
|
|
892
1005
|
} catch (error) {
|
|
893
1006
|
ui.error(`Build failed for ${galleryDir}`);
|
|
@@ -904,6 +1017,25 @@ async function buildGallery(galleryDir, templateDir, scan, shouldCreateThumbnail
|
|
|
904
1017
|
fs8.rmSync(buildDir, { recursive: true, force: true });
|
|
905
1018
|
ui.success(`Gallery built successfully`);
|
|
906
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
|
+
}
|
|
907
1039
|
async function build(options, ui) {
|
|
908
1040
|
try {
|
|
909
1041
|
const galleryDirs = findGalleries(options.gallery, options.recursive);
|
|
@@ -911,20 +1043,41 @@ async function build(options, ui) {
|
|
|
911
1043
|
ui.error("No galleries found.");
|
|
912
1044
|
return { processedGalleryCount: 0 };
|
|
913
1045
|
}
|
|
914
|
-
const
|
|
915
|
-
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;
|
|
916
1047
|
let totalGalleries = 0;
|
|
917
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);
|
|
918
1053
|
const baseUrl = options.baseUrl ? `${options.baseUrl}${path7.relative(options.gallery, dir)}` : void 0;
|
|
919
1054
|
const thumbsBaseUrl = options.thumbsBaseUrl ? `${options.thumbsBaseUrl}${path7.relative(options.gallery, dir)}` : void 0;
|
|
920
|
-
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
|
+
);
|
|
921
1066
|
++totalGalleries;
|
|
922
1067
|
}
|
|
923
1068
|
ui.box(`Built ${totalGalleries} ${totalGalleries === 1 ? "gallery" : "galleries"} successfully`);
|
|
924
1069
|
return { processedGalleryCount: totalGalleries };
|
|
925
1070
|
} catch (error) {
|
|
926
|
-
if (error instanceof Error
|
|
927
|
-
|
|
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
|
+
}
|
|
928
1081
|
} else {
|
|
929
1082
|
ui.error("Error building gallery");
|
|
930
1083
|
}
|
|
@@ -982,6 +1135,162 @@ async function clean(options, ui) {
|
|
|
982
1135
|
throw error;
|
|
983
1136
|
}
|
|
984
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
|
+
}
|
|
985
1294
|
|
|
986
1295
|
// src/modules/telemetry/index.ts
|
|
987
1296
|
async function telemetry(options, ui, telemetryService2) {
|
|
@@ -1011,7 +1320,7 @@ var ApiTelemetryClient = class {
|
|
|
1011
1320
|
axios.post(this.endpoint, event, {
|
|
1012
1321
|
headers: {
|
|
1013
1322
|
"content-type": "application/json",
|
|
1014
|
-
"user-agent": `simple-photo-gallery/${event.packageVersion} (${
|
|
1323
|
+
"user-agent": `simple-photo-gallery/${event.packageVersion} (${process4.platform}; ${process4.arch})`
|
|
1015
1324
|
}
|
|
1016
1325
|
});
|
|
1017
1326
|
} catch {
|
|
@@ -1048,11 +1357,11 @@ var TelemetryService = class {
|
|
|
1048
1357
|
if (override) {
|
|
1049
1358
|
return override === "1";
|
|
1050
1359
|
}
|
|
1051
|
-
if (
|
|
1360
|
+
if (process4.env.CI || process4.env.DO_NOT_TRACK) {
|
|
1052
1361
|
return false;
|
|
1053
1362
|
}
|
|
1054
|
-
if (
|
|
1055
|
-
return
|
|
1363
|
+
if (process4.env.SPG_TELEMETRY) {
|
|
1364
|
+
return process4.env.SPG_TELEMETRY === "1";
|
|
1056
1365
|
}
|
|
1057
1366
|
const stored = this.getStoredPreference();
|
|
1058
1367
|
if (stored === void 0) {
|
|
@@ -1094,7 +1403,7 @@ var TelemetryService = class {
|
|
|
1094
1403
|
durationMs: now - startedAt,
|
|
1095
1404
|
packageName: this.packageName,
|
|
1096
1405
|
packageVersion: this.packageVersion,
|
|
1097
|
-
nodeVersion:
|
|
1406
|
+
nodeVersion: process4.version,
|
|
1098
1407
|
osPlatform: os.platform(),
|
|
1099
1408
|
osRelease: os.release(),
|
|
1100
1409
|
osArch: os.arch(),
|
|
@@ -1128,7 +1437,7 @@ var TelemetryService = class {
|
|
|
1128
1437
|
/** Returns the telemetry client. */
|
|
1129
1438
|
getClient() {
|
|
1130
1439
|
if (!this.client) {
|
|
1131
|
-
switch (
|
|
1440
|
+
switch (process4.env.SPG_TELEMETRY_PROVIDER) {
|
|
1132
1441
|
case "none": {
|
|
1133
1442
|
this.client = void 0;
|
|
1134
1443
|
break;
|
|
@@ -1220,7 +1529,7 @@ async function waitForUpdateCheck(checkPromise) {
|
|
|
1220
1529
|
// package.json
|
|
1221
1530
|
var package_default = {
|
|
1222
1531
|
name: "simple-photo-gallery",
|
|
1223
|
-
version: "2.0
|
|
1532
|
+
version: "2.1.0"};
|
|
1224
1533
|
|
|
1225
1534
|
// src/index.ts
|
|
1226
1535
|
var program = new Command();
|
|
@@ -1262,7 +1571,7 @@ function withCommandContext(handler) {
|
|
|
1262
1571
|
} catch (error) {
|
|
1263
1572
|
ui.debug(error);
|
|
1264
1573
|
errorInfo = error instanceof Error ? { name: error.name, message: error.message } : { name: "UnknownError", message: String(error) };
|
|
1265
|
-
|
|
1574
|
+
process4.exitCode = 1;
|
|
1266
1575
|
}
|
|
1267
1576
|
const updateInfo = await waitForUpdateCheck(updateCheckPromise);
|
|
1268
1577
|
if (updateInfo) {
|
|
@@ -1284,14 +1593,21 @@ function withCommandContext(handler) {
|
|
|
1284
1593
|
program.command("init").description("Initialize a gallery by scaning a folder for images and videos").option(
|
|
1285
1594
|
"-p, --photos <path>",
|
|
1286
1595
|
"Path to the folder where the photos are stored. Default: current working directory",
|
|
1287
|
-
|
|
1596
|
+
process4.cwd()
|
|
1288
1597
|
).option(
|
|
1289
1598
|
"-g, --gallery <path>",
|
|
1290
1599
|
"Path to the directory where the gallery will be initialized. Default: same directory as the photos folder"
|
|
1291
|
-
).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)));
|
|
1292
|
-
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",
|
|
1293
|
-
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",
|
|
1294
|
-
|
|
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
|
+
});
|
|
1295
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)));
|
|
1296
1612
|
program.parse();
|
|
1297
1613
|
//# sourceMappingURL=index.js.map
|