simple-photo-gallery 2.0.10 → 2.0.11-rc.10

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.js CHANGED
@@ -8,8 +8,8 @@ import path7 from 'path';
8
8
  import { Buffer } from 'buffer';
9
9
  import sharp2 from 'sharp';
10
10
  import { encode } from 'blurhash';
11
+ import { GalleryDataSchema, GalleryDataDeprecatedSchema } from '@simple-photo-gallery/common';
11
12
  import ExifReader from 'exifreader';
12
- import { z } from 'zod';
13
13
  import ffprobe from 'node-ffprobe';
14
14
  import os from 'os';
15
15
  import Conf from 'conf';
@@ -46,22 +46,6 @@ async function cropAndResizeImage(image, outputPath, width, height, format = "av
46
46
  withoutEnlargement: true
47
47
  }).toFormat(format).toFile(outputPath);
48
48
  }
49
- async function getImageDescription(imagePath) {
50
- try {
51
- const tags = await ExifReader.load(imagePath);
52
- if (tags.description?.description) return tags.description.description;
53
- if (tags.ImageDescription?.description) return tags.ImageDescription.description;
54
- if (tags.UserComment && typeof tags.UserComment === "object" && tags.UserComment !== null && "description" in tags.UserComment) {
55
- return tags.UserComment.description;
56
- }
57
- if (tags.ExtDescrAccessibility?.description) return tags.ExtDescrAccessibility.description;
58
- if (tags["Caption/Abstract"]?.description) return tags["Caption/Abstract"].description;
59
- if (tags.XPTitle?.description) return tags.XPTitle.description;
60
- if (tags.XPComment?.description) return tags.XPComment.description;
61
- } catch {
62
- return void 0;
63
- }
64
- }
65
49
  async function createImageThumbnails(image, metadata, outputPath, outputPathRetina, size) {
66
50
  const originalWidth = metadata.width || 0;
67
51
  const originalHeight = metadata.height || 0;
@@ -94,10 +78,10 @@ async function generateBlurHash(imagePath, componentX = 4, componentY = 3) {
94
78
  // src/modules/build/utils/index.ts
95
79
  path7.dirname(new URL(import.meta.url).pathname);
96
80
  async function createGallerySocialMediaCardImage(headerPhotoPath, title, ouputPath, ui) {
97
- ui.start(`Creating social media card image`);
81
+ ui?.start(`Creating social media card image`);
98
82
  const headerBasename = path7.basename(headerPhotoPath, path7.extname(headerPhotoPath));
99
83
  if (fs8.existsSync(ouputPath)) {
100
- ui.success(`Social media card image already exists`);
84
+ ui?.success(`Social media card image already exists`);
101
85
  return headerBasename;
102
86
  }
103
87
  const image = await loadImage(headerPhotoPath);
@@ -116,23 +100,23 @@ async function createGallerySocialMediaCardImage(headerPhotoPath, title, ouputPa
116
100
  `;
117
101
  const finalImageBuffer = await sharp2(resizedImageBuffer).composite([{ input: Buffer.from(svgText), top: 0, left: 0 }]).jpeg({ quality: 90 }).toBuffer();
118
102
  await sharp2(finalImageBuffer).toFile(outputPath);
119
- ui.success(`Created social media card image successfully`);
103
+ ui?.success(`Created social media card image successfully`);
120
104
  return headerBasename;
121
105
  }
122
106
  async function createOptimizedHeaderImage(headerPhotoPath, outputFolder, ui) {
123
- ui.start(`Creating optimized header images`);
107
+ ui?.start(`Creating optimized header images`);
124
108
  const image = await loadImage(headerPhotoPath);
125
109
  const headerBasename = path7.basename(headerPhotoPath, path7.extname(headerPhotoPath));
126
110
  const generatedFiles = [];
127
- ui.debug("Generating blurhash for header image");
111
+ ui?.debug("Generating blurhash for header image");
128
112
  const blurHash = await generateBlurHash(headerPhotoPath);
129
113
  const landscapeYFactor = 3 / 4;
130
114
  for (const width of HEADER_IMAGE_LANDSCAPE_WIDTHS) {
131
- ui.debug(`Creating landscape header image ${width}`);
115
+ ui?.debug(`Creating landscape header image ${width}`);
132
116
  const avifFilename = `${headerBasename}_landscape_${width}.avif`;
133
117
  const jpgFilename = `${headerBasename}_landscape_${width}.jpg`;
134
118
  if (fs8.existsSync(path7.join(outputFolder, avifFilename))) {
135
- ui.debug(`Landscape header image ${width} AVIF already exists`);
119
+ ui?.debug(`Landscape header image ${width} AVIF already exists`);
136
120
  } else {
137
121
  await cropAndResizeImage(
138
122
  image.clone(),
@@ -144,7 +128,7 @@ async function createOptimizedHeaderImage(headerPhotoPath, outputFolder, ui) {
144
128
  }
145
129
  generatedFiles.push(avifFilename);
146
130
  if (fs8.existsSync(path7.join(outputFolder, jpgFilename))) {
147
- ui.debug(`Landscape header image ${width} JPG already exists`);
131
+ ui?.debug(`Landscape header image ${width} JPG already exists`);
148
132
  } else {
149
133
  await cropAndResizeImage(image.clone(), path7.join(outputFolder, jpgFilename), width, width * landscapeYFactor, "jpg");
150
134
  }
@@ -152,23 +136,23 @@ async function createOptimizedHeaderImage(headerPhotoPath, outputFolder, ui) {
152
136
  }
153
137
  const portraitYFactor = 4 / 3;
154
138
  for (const width of HEADER_IMAGE_PORTRAIT_WIDTHS) {
155
- ui.debug(`Creating portrait header image ${width}`);
139
+ ui?.debug(`Creating portrait header image ${width}`);
156
140
  const avifFilename = `${headerBasename}_portrait_${width}.avif`;
157
141
  const jpgFilename = `${headerBasename}_portrait_${width}.jpg`;
158
142
  if (fs8.existsSync(path7.join(outputFolder, avifFilename))) {
159
- ui.debug(`Portrait header image ${width} AVIF already exists`);
143
+ ui?.debug(`Portrait header image ${width} AVIF already exists`);
160
144
  } else {
161
145
  await cropAndResizeImage(image.clone(), path7.join(outputFolder, avifFilename), width, width * portraitYFactor, "avif");
162
146
  }
163
147
  generatedFiles.push(avifFilename);
164
148
  if (fs8.existsSync(path7.join(outputFolder, jpgFilename))) {
165
- ui.debug(`Portrait header image ${width} JPG already exists`);
149
+ ui?.debug(`Portrait header image ${width} JPG already exists`);
166
150
  } else {
167
151
  await cropAndResizeImage(image.clone(), path7.join(outputFolder, jpgFilename), width, width * portraitYFactor, "jpg");
168
152
  }
169
153
  generatedFiles.push(jpgFilename);
170
154
  }
171
- ui.success(`Created optimized header image successfully`);
155
+ ui?.success(`Created optimized header image successfully`);
172
156
  return { headerBasename, generatedFiles, blurHash };
173
157
  }
174
158
  function hasOldHeaderImages(outputFolder, currentHeaderBasename) {
@@ -186,9 +170,9 @@ function hasOldHeaderImages(outputFolder, currentHeaderBasename) {
186
170
  return false;
187
171
  }
188
172
  function cleanupOldHeaderImages(outputFolder, currentHeaderBasename, ui) {
189
- ui.start(`Cleaning up old header images`);
173
+ ui?.start(`Cleaning up old header images`);
190
174
  if (!fs8.existsSync(outputFolder)) {
191
- ui.debug(`Output folder ${outputFolder} does not exist, skipping cleanup`);
175
+ ui?.debug(`Output folder ${outputFolder} does not exist, skipping cleanup`);
192
176
  return;
193
177
  }
194
178
  const files = fs8.readdirSync(outputFolder);
@@ -198,20 +182,20 @@ function cleanupOldHeaderImages(outputFolder, currentHeaderBasename, ui) {
198
182
  const portraitMatch = file.match(/^(.+)_portrait_\d+\.(avif|jpg)$/);
199
183
  if (landscapeMatch && landscapeMatch[1] !== currentHeaderBasename) {
200
184
  const filePath = path7.join(outputFolder, file);
201
- ui.debug(`Deleting old landscape header image: ${file}`);
185
+ ui?.debug(`Deleting old landscape header image: ${file}`);
202
186
  fs8.unlinkSync(filePath);
203
187
  deletedCount++;
204
188
  } else if (portraitMatch && portraitMatch[1] !== currentHeaderBasename) {
205
189
  const filePath = path7.join(outputFolder, file);
206
- ui.debug(`Deleting old portrait header image: ${file}`);
190
+ ui?.debug(`Deleting old portrait header image: ${file}`);
207
191
  fs8.unlinkSync(filePath);
208
192
  deletedCount++;
209
193
  }
210
194
  }
211
195
  if (deletedCount > 0) {
212
- ui.success(`Deleted ${deletedCount} old header image(s)`);
196
+ ui?.success(`Deleted ${deletedCount} old header image(s)`);
213
197
  } else {
214
- ui.debug(`No old header images to clean up`);
198
+ ui?.debug(`No old header images to clean up`);
215
199
  }
216
200
  }
217
201
  function findGalleries(basePath, recursive) {
@@ -253,89 +237,6 @@ function parseTelemetryOption(value) {
253
237
  }
254
238
  return value;
255
239
  }
256
- var ThumbnailSchema = z.object({
257
- path: z.string(),
258
- pathRetina: z.string(),
259
- width: z.number(),
260
- height: z.number(),
261
- blurHash: z.string().optional()
262
- });
263
- var MediaFileSchema = z.object({
264
- type: z.enum(["image", "video"]),
265
- filename: z.string(),
266
- alt: z.string().optional(),
267
- width: z.number(),
268
- height: z.number(),
269
- thumbnail: ThumbnailSchema.optional(),
270
- lastMediaTimestamp: z.string().optional()
271
- });
272
- var MediaFileDeprecatedSchema = z.object({
273
- type: z.enum(["image", "video"]),
274
- path: z.string(),
275
- alt: z.string().optional(),
276
- width: z.number(),
277
- height: z.number(),
278
- thumbnail: ThumbnailSchema.optional(),
279
- lastMediaTimestamp: z.string().optional()
280
- });
281
- var GallerySectionSchema = z.object({
282
- title: z.string().optional(),
283
- description: z.string().optional(),
284
- images: z.array(MediaFileSchema)
285
- });
286
- var GallerySectionDeprecatedSchema = z.object({
287
- title: z.string().optional(),
288
- description: z.string().optional(),
289
- images: z.array(MediaFileDeprecatedSchema)
290
- });
291
- var SubGallerySchema = z.object({
292
- title: z.string(),
293
- headerImage: z.string(),
294
- path: z.string()
295
- });
296
- var GalleryMetadataSchema = z.object({
297
- image: z.string().optional(),
298
- imageWidth: z.number().optional(),
299
- imageHeight: z.number().optional(),
300
- ogUrl: z.string().optional(),
301
- ogType: z.string().optional(),
302
- ogSiteName: z.string().optional(),
303
- twitterSite: z.string().optional(),
304
- twitterCreator: z.string().optional(),
305
- author: z.string().optional(),
306
- keywords: z.string().optional(),
307
- canonicalUrl: z.string().optional(),
308
- language: z.string().optional(),
309
- robots: z.string().optional()
310
- });
311
- var GalleryDataSchema = z.object({
312
- title: z.string(),
313
- description: z.string(),
314
- mediaBasePath: z.string().optional(),
315
- url: z.string().optional(),
316
- headerImage: z.string(),
317
- headerImageBlurHash: z.string().optional(),
318
- thumbnailSize: z.number().optional(),
319
- metadata: GalleryMetadataSchema,
320
- mediaBaseUrl: z.string().optional(),
321
- analyticsScript: z.string().optional(),
322
- sections: z.array(GallerySectionSchema),
323
- subGalleries: z.object({ title: z.string(), galleries: z.array(SubGallerySchema) })
324
- });
325
- var GalleryDataDeprecatedSchema = z.object({
326
- title: z.string(),
327
- description: z.string(),
328
- url: z.string().optional(),
329
- headerImage: z.string(),
330
- thumbnailSize: z.number().optional(),
331
- metadata: GalleryMetadataSchema,
332
- mediaBaseUrl: z.string().optional(),
333
- analyticsScript: z.string().optional(),
334
- sections: z.array(GallerySectionDeprecatedSchema),
335
- subGalleries: z.object({ title: z.string(), galleries: z.array(SubGallerySchema) })
336
- });
337
-
338
- // src/utils/gallery.ts
339
240
  function parseGalleryJson(galleryJsonPath, ui) {
340
241
  let galleryContent;
341
242
  try {
@@ -582,6 +483,22 @@ async function getFileMtime(filePath) {
582
483
  const stats = await promises.stat(filePath);
583
484
  return stats.mtime;
584
485
  }
486
+ async function getImageDescription(image) {
487
+ try {
488
+ const tags = await ExifReader.load(image);
489
+ if (tags.description?.description) return tags.description.description;
490
+ if (tags.ImageDescription?.description) return tags.ImageDescription.description;
491
+ if (tags.UserComment && typeof tags.UserComment === "object" && tags.UserComment !== null && "description" in tags.UserComment) {
492
+ return tags.UserComment.description;
493
+ }
494
+ if (tags.ExtDescrAccessibility?.description) return tags.ExtDescrAccessibility.description;
495
+ if (tags["Caption/Abstract"]?.description) return tags["Caption/Abstract"].description;
496
+ if (tags.XPTitle?.description) return tags.XPTitle.description;
497
+ if (tags.XPComment?.description) return tags.XPComment.description;
498
+ } catch {
499
+ return void 0;
500
+ }
501
+ }
585
502
  async function getVideoDimensions(filePath) {
586
503
  const data = await ffprobe(filePath);
587
504
  const videoStream = data.streams.find((stream) => stream.codec_type === "video");
@@ -709,17 +626,14 @@ async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumb
709
626
  lastMediaTimestamp: fileMtime.toISOString()
710
627
  };
711
628
  }
712
- async function processMediaFile(mediaFile, mediaBasePath, galleryDir, thumbnailsPath, thumbnailSize, ui) {
629
+ async function processMediaFile(mediaFile, mediaBasePath, thumbnailsPath, thumbnailSize, ui) {
713
630
  try {
714
631
  const filePath = path7.resolve(path7.join(mediaBasePath, mediaFile.filename));
715
632
  const fileName = mediaFile.filename;
716
633
  const fileNameWithoutExt = path7.parse(fileName).name;
717
- const galleryJsonDir = path7.join(galleryDir, "gallery");
718
634
  const thumbnailFileName = `${fileNameWithoutExt}.avif`;
719
635
  const thumbnailPath = path7.join(thumbnailsPath, thumbnailFileName);
720
636
  const thumbnailPathRetina = thumbnailPath.replace(".avif", "@2x.avif");
721
- const relativeThumbnailPath = path7.relative(galleryJsonDir, thumbnailPath);
722
- const relativeThumbnailRetinaPath = path7.relative(galleryJsonDir, thumbnailPathRetina);
723
637
  const lastMediaTimestamp = mediaFile.lastMediaTimestamp ? new Date(mediaFile.lastMediaTimestamp) : void 0;
724
638
  const verbose = ui.level === LogLevels.debug;
725
639
  ui.debug(` Processing ${mediaFile.type}: ${fileName}`);
@@ -744,8 +658,8 @@ async function processMediaFile(mediaFile, mediaBasePath, galleryDir, thumbnails
744
658
  }
745
659
  updatedMediaFile.filename = mediaFile.filename;
746
660
  if (updatedMediaFile.thumbnail) {
747
- updatedMediaFile.thumbnail.path = relativeThumbnailPath;
748
- updatedMediaFile.thumbnail.pathRetina = relativeThumbnailRetinaPath;
661
+ updatedMediaFile.thumbnail.path = path7.basename(thumbnailPath);
662
+ updatedMediaFile.thumbnail.pathRetina = path7.basename(thumbnailPathRetina);
749
663
  }
750
664
  return updatedMediaFile;
751
665
  } catch (error) {
@@ -765,14 +679,7 @@ async function processGalleryThumbnails(galleryDir, ui) {
765
679
  let processedCount = 0;
766
680
  for (const section of galleryData.sections) {
767
681
  for (const [index, mediaFile] of section.images.entries()) {
768
- section.images[index] = await processMediaFile(
769
- mediaFile,
770
- mediaBasePath,
771
- galleryDir,
772
- thumbnailsPath,
773
- thumbnailSize,
774
- ui
775
- );
682
+ section.images[index] = await processMediaFile(mediaFile, mediaBasePath, thumbnailsPath, thumbnailSize, ui);
776
683
  }
777
684
  processedCount += section.images.length;
778
685
  }
@@ -853,7 +760,7 @@ async function scanAndAppendNewFiles(galleryDir, galleryJsonPath, galleryData, u
853
760
  }
854
761
  return galleryData;
855
762
  }
856
- async function buildGallery(galleryDir, templateDir, scan, ui, baseUrl) {
763
+ async function buildGallery(galleryDir, templateDir, scan, shouldCreateThumbnails, ui, baseUrl, thumbsBaseUrl) {
857
764
  ui.start(`Building gallery ${galleryDir}`);
858
765
  const galleryJsonPath = path7.join(galleryDir, "gallery", "gallery.json");
859
766
  let galleryData = parseGalleryJson(galleryJsonPath, ui);
@@ -866,26 +773,26 @@ async function buildGallery(galleryDir, templateDir, scan, ui, baseUrl) {
866
773
  const headerImagePath = mediaBasePath ? path7.join(mediaBasePath, galleryData.headerImage) : path7.resolve(galleryDir, galleryData.headerImage);
867
774
  const imagesFolder = path7.join(galleryDir, "gallery", "images");
868
775
  const currentHeaderBasename = path7.basename(headerImagePath, path7.extname(headerImagePath));
869
- if (!fs8.existsSync(imagesFolder)) {
870
- fs8.mkdirSync(imagesFolder, { recursive: true });
871
- }
872
- const headerImageChanged = hasOldHeaderImages(imagesFolder, currentHeaderBasename);
873
- if (headerImageChanged) {
874
- ui.info("Header image changed, cleaning up old assets");
875
- cleanupOldHeaderImages(imagesFolder, currentHeaderBasename, ui);
876
- if (fs8.existsSync(socialMediaCardImagePath)) {
877
- fs8.unlinkSync(socialMediaCardImagePath);
878
- ui.debug("Deleted old social media card");
776
+ if (shouldCreateThumbnails) {
777
+ if (!fs8.existsSync(imagesFolder)) {
778
+ fs8.mkdirSync(imagesFolder, { recursive: true });
779
+ }
780
+ const headerImageChanged = hasOldHeaderImages(imagesFolder, currentHeaderBasename);
781
+ if (headerImageChanged) {
782
+ ui.info("Header image changed, cleaning up old assets");
783
+ cleanupOldHeaderImages(imagesFolder, currentHeaderBasename, ui);
784
+ if (fs8.existsSync(socialMediaCardImagePath)) {
785
+ fs8.unlinkSync(socialMediaCardImagePath);
786
+ ui.debug("Deleted old social media card");
787
+ }
788
+ }
789
+ await createGallerySocialMediaCardImage(headerImagePath, galleryData.title, socialMediaCardImagePath, ui);
790
+ const { blurHash } = await createOptimizedHeaderImage(headerImagePath, imagesFolder, ui);
791
+ if (galleryData.headerImageBlurHash !== blurHash) {
792
+ ui.debug("Updating gallery.json with header image blurhash");
793
+ galleryData.headerImageBlurHash = blurHash;
794
+ fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
879
795
  }
880
- }
881
- await createGallerySocialMediaCardImage(headerImagePath, galleryData.title, socialMediaCardImagePath, ui);
882
- galleryData.metadata.image = galleryData.metadata.image || `${galleryData.url || ""}/${path7.relative(galleryDir, socialMediaCardImagePath)}`;
883
- fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
884
- const { blurHash } = await createOptimizedHeaderImage(headerImagePath, imagesFolder, ui);
885
- if (galleryData.headerImageBlurHash !== blurHash) {
886
- ui.debug("Updating gallery.json with header image blurhash");
887
- galleryData.headerImageBlurHash = blurHash;
888
- fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
889
796
  }
890
797
  if (!mediaBaseUrl && mediaBasePath) {
891
798
  const shouldCopyPhotos = await ui.prompt("All photos need to be copied. Are you sure you want to continue?", {
@@ -901,7 +808,19 @@ async function buildGallery(galleryDir, templateDir, scan, ui, baseUrl) {
901
808
  galleryData.mediaBaseUrl = mediaBaseUrl;
902
809
  fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
903
810
  }
904
- await processGalleryThumbnails(galleryDir, ui);
811
+ if (thumbsBaseUrl && galleryData.thumbsBaseUrl !== thumbsBaseUrl) {
812
+ ui.debug("Updating gallery.json with thumbsBaseUrl");
813
+ galleryData.thumbsBaseUrl = thumbsBaseUrl;
814
+ fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
815
+ }
816
+ if (!galleryData.metadata.image) {
817
+ ui.debug("Updating gallery.json with social media card URL");
818
+ galleryData.metadata.image = thumbsBaseUrl ? `${thumbsBaseUrl}/${path7.basename(socialMediaCardImagePath)}` : `${galleryData.url || ""}/${path7.relative(galleryDir, socialMediaCardImagePath)}`;
819
+ fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
820
+ }
821
+ if (shouldCreateThumbnails) {
822
+ await processGalleryThumbnails(galleryDir, ui);
823
+ }
905
824
  ui.debug("Building gallery from template");
906
825
  try {
907
826
  process3.env.GALLERY_JSON_PATH = galleryJsonPath;
@@ -923,7 +842,6 @@ async function buildGallery(galleryDir, templateDir, scan, ui, baseUrl) {
923
842
  ui.success(`Gallery built successfully`);
924
843
  }
925
844
  async function build(options, ui) {
926
- console.log("DBG: options", options);
927
845
  try {
928
846
  const galleryDirs = findGalleries(options.gallery, options.recursive);
929
847
  if (galleryDirs.length === 0) {
@@ -935,7 +853,8 @@ async function build(options, ui) {
935
853
  let totalGalleries = 0;
936
854
  for (const dir of galleryDirs) {
937
855
  const baseUrl = options.baseUrl ? `${options.baseUrl}${path7.relative(options.gallery, dir)}` : void 0;
938
- await buildGallery(path7.resolve(dir), themeDir, options.scan, ui, baseUrl);
856
+ const thumbsBaseUrl = options.thumbsBaseUrl ? `${options.thumbsBaseUrl}${path7.relative(options.gallery, dir)}` : void 0;
857
+ await buildGallery(path7.resolve(dir), themeDir, options.scan, options.thumbnails, ui, baseUrl, thumbsBaseUrl);
939
858
  ++totalGalleries;
940
859
  }
941
860
  ui.box(`Built ${totalGalleries} ${totalGalleries === 1 ? "gallery" : "galleries"} successfully`);
@@ -955,26 +874,26 @@ async function cleanGallery(galleryDir, ui) {
955
874
  if (fs8.existsSync(indexHtmlPath)) {
956
875
  try {
957
876
  fs8.rmSync(indexHtmlPath);
958
- ui.debug(`Removed: ${indexHtmlPath}`);
877
+ ui?.debug(`Removed: ${indexHtmlPath}`);
959
878
  filesRemoved++;
960
879
  } catch (error) {
961
- ui.warn(`Failed to remove index.html: ${error}`);
880
+ ui?.warn(`Failed to remove index.html: ${error}`);
962
881
  }
963
882
  }
964
883
  const galleryPath = path7.join(galleryDir, "gallery");
965
884
  if (fs8.existsSync(galleryPath)) {
966
885
  try {
967
886
  fs8.rmSync(galleryPath, { recursive: true, force: true });
968
- ui.debug(`Removed directory: ${galleryPath}`);
887
+ ui?.debug(`Removed directory: ${galleryPath}`);
969
888
  filesRemoved++;
970
889
  } catch (error) {
971
- ui.warn(`Failed to remove gallery directory: ${error}`);
890
+ ui?.warn(`Failed to remove gallery directory: ${error}`);
972
891
  }
973
892
  }
974
893
  if (filesRemoved > 0) {
975
- ui.success(`Cleaned gallery at: ${galleryDir}`);
894
+ ui?.success(`Cleaned gallery at: ${galleryDir}`);
976
895
  } else {
977
- ui.info(`No gallery files found at: ${galleryDir}`);
896
+ ui?.info(`No gallery files found at: ${galleryDir}`);
978
897
  }
979
898
  return { processedGalleryCount: filesRemoved };
980
899
  }
@@ -1238,7 +1157,7 @@ async function waitForUpdateCheck(checkPromise) {
1238
1157
  // package.json
1239
1158
  var package_default = {
1240
1159
  name: "simple-photo-gallery",
1241
- version: "2.0.10"};
1160
+ version: "2.0.11-rc.10"};
1242
1161
 
1243
1162
  // src/index.ts
1244
1163
  var program = new Command();
@@ -1308,7 +1227,7 @@ program.command("init").description("Initialize a gallery by scaning a folder fo
1308
1227
  "Path to the directory where the gallery will be initialized. Default: same directory as the photos folder"
1309
1228
  ).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).action(withCommandContext((options, ui) => init(options, ui)));
1310
1229
  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", process3.cwd()).option("-r, --recursive", "Scan subdirectories recursively", false).action(withCommandContext((options, ui) => thumbnails(options, ui)));
1311
- 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", process3.cwd()).option("-r, --recursive", "Scan subdirectories recursively", false).option("-b, --base-url <url>", "Base URL where the photos are hosted").option("--no-scan", "Do not scan for new photos when buildign the gallery", true).action(withCommandContext((options, ui) => build(options, ui)));
1230
+ 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", process3.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).action(withCommandContext((options, ui) => build(options, ui)));
1312
1231
  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", process3.cwd()).option("-r, --recursive", "Clean subdirectories recursively", false).action(withCommandContext((options, ui) => clean(options, ui)));
1313
1232
  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)));
1314
1233
  program.parse();