simple-photo-gallery 2.0.8 → 2.0.10-rc.1
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/README.md +15 -0
- package/dist/index.js +741 -330
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -1,73 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import process3, { stdout } from 'process';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { LogLevels, createConsola } from 'consola';
|
|
5
5
|
import { execSync, spawn } from 'child_process';
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import { z } from 'zod';
|
|
6
|
+
import fs8, { promises } from 'fs';
|
|
7
|
+
import path7 from 'path';
|
|
9
8
|
import { Buffer } from 'buffer';
|
|
10
9
|
import sharp2 from 'sharp';
|
|
11
|
-
import ExifReader from 'exifreader';
|
|
12
10
|
import { encode } from 'blurhash';
|
|
11
|
+
import ExifReader from 'exifreader';
|
|
12
|
+
import { z } from 'zod';
|
|
13
13
|
import ffprobe from 'node-ffprobe';
|
|
14
|
+
import os from 'os';
|
|
15
|
+
import Conf from 'conf';
|
|
14
16
|
import axios from 'axios';
|
|
15
17
|
import { compareSemVer, parseSemVer } from 'semver-parser';
|
|
16
18
|
|
|
17
|
-
var ThumbnailSchema = z.object({
|
|
18
|
-
path: z.string(),
|
|
19
|
-
pathRetina: z.string(),
|
|
20
|
-
width: z.number(),
|
|
21
|
-
height: z.number(),
|
|
22
|
-
blurHash: z.string().optional()
|
|
23
|
-
});
|
|
24
|
-
var MediaFileSchema = z.object({
|
|
25
|
-
type: z.enum(["image", "video"]),
|
|
26
|
-
path: z.string(),
|
|
27
|
-
alt: z.string().optional(),
|
|
28
|
-
width: z.number(),
|
|
29
|
-
height: z.number(),
|
|
30
|
-
thumbnail: ThumbnailSchema.optional(),
|
|
31
|
-
lastMediaTimestamp: z.string().optional()
|
|
32
|
-
});
|
|
33
|
-
var GallerySectionSchema = z.object({
|
|
34
|
-
title: z.string().optional(),
|
|
35
|
-
description: z.string().optional(),
|
|
36
|
-
images: z.array(MediaFileSchema)
|
|
37
|
-
});
|
|
38
|
-
var SubGallerySchema = z.object({
|
|
39
|
-
title: z.string(),
|
|
40
|
-
headerImage: z.string(),
|
|
41
|
-
path: z.string()
|
|
42
|
-
});
|
|
43
|
-
var GalleryMetadataSchema = z.object({
|
|
44
|
-
image: z.string().optional(),
|
|
45
|
-
imageWidth: z.number().optional(),
|
|
46
|
-
imageHeight: z.number().optional(),
|
|
47
|
-
ogUrl: z.string().optional(),
|
|
48
|
-
ogType: z.string().optional(),
|
|
49
|
-
ogSiteName: z.string().optional(),
|
|
50
|
-
twitterSite: z.string().optional(),
|
|
51
|
-
twitterCreator: z.string().optional(),
|
|
52
|
-
author: z.string().optional(),
|
|
53
|
-
keywords: z.string().optional(),
|
|
54
|
-
canonicalUrl: z.string().optional(),
|
|
55
|
-
language: z.string().optional(),
|
|
56
|
-
robots: z.string().optional()
|
|
57
|
-
});
|
|
58
|
-
var GalleryDataSchema = z.object({
|
|
59
|
-
title: z.string(),
|
|
60
|
-
description: z.string(),
|
|
61
|
-
url: z.string().optional(),
|
|
62
|
-
headerImage: z.string(),
|
|
63
|
-
thumbnailSize: z.number().optional(),
|
|
64
|
-
metadata: GalleryMetadataSchema,
|
|
65
|
-
galleryOutputPath: z.string().optional(),
|
|
66
|
-
mediaBaseUrl: z.string().optional(),
|
|
67
|
-
sections: z.array(GallerySectionSchema),
|
|
68
|
-
subGalleries: z.object({ title: z.string(), galleries: z.array(SubGallerySchema) })
|
|
69
|
-
});
|
|
70
|
-
|
|
71
19
|
// src/config/index.ts
|
|
72
20
|
var DEFAULT_THUMBNAIL_SIZE = 300;
|
|
73
21
|
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".tif", ".svg", ".avif"]);
|
|
@@ -135,13 +83,22 @@ async function createImageThumbnails(image, metadata, outputPath, outputPathReti
|
|
|
135
83
|
return { width, height };
|
|
136
84
|
}
|
|
137
85
|
|
|
86
|
+
// src/utils/blurhash.ts
|
|
87
|
+
async function generateBlurHash(imagePath, componentX = 4, componentY = 3) {
|
|
88
|
+
const image = await loadImage(imagePath);
|
|
89
|
+
const { data, info } = await image.resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
90
|
+
const pixels = new Uint8ClampedArray(data.buffer);
|
|
91
|
+
return encode(pixels, info.width, info.height, componentX, componentY);
|
|
92
|
+
}
|
|
93
|
+
|
|
138
94
|
// src/modules/build/utils/index.ts
|
|
139
|
-
|
|
95
|
+
path7.dirname(new URL(import.meta.url).pathname);
|
|
140
96
|
async function createGallerySocialMediaCardImage(headerPhotoPath, title, ouputPath, ui) {
|
|
141
97
|
ui.start(`Creating social media card image`);
|
|
142
|
-
|
|
98
|
+
const headerBasename = path7.basename(headerPhotoPath, path7.extname(headerPhotoPath));
|
|
99
|
+
if (fs8.existsSync(ouputPath)) {
|
|
143
100
|
ui.success(`Social media card image already exists`);
|
|
144
|
-
return;
|
|
101
|
+
return headerBasename;
|
|
145
102
|
}
|
|
146
103
|
const image = await loadImage(headerPhotoPath);
|
|
147
104
|
const resizedImageBuffer = await image.resize(1200, 631, { fit: "cover" }).jpeg({ quality: 90 }).toBuffer();
|
|
@@ -161,76 +118,115 @@ async function createGallerySocialMediaCardImage(headerPhotoPath, title, ouputPa
|
|
|
161
118
|
const finalImageBuffer = await sharp2(resizedImageBuffer).composite([{ input: Buffer.from(svgText), top: 0, left: 0 }]).jpeg({ quality: 90 }).toBuffer();
|
|
162
119
|
await sharp2(finalImageBuffer).toFile(outputPath);
|
|
163
120
|
ui.success(`Created social media card image successfully`);
|
|
121
|
+
return headerBasename;
|
|
164
122
|
}
|
|
165
123
|
async function createOptimizedHeaderImage(headerPhotoPath, outputFolder, ui) {
|
|
166
124
|
ui.start(`Creating optimized header images`);
|
|
167
125
|
const image = await loadImage(headerPhotoPath);
|
|
126
|
+
const headerBasename = path7.basename(headerPhotoPath, path7.extname(headerPhotoPath));
|
|
127
|
+
const generatedFiles = [];
|
|
128
|
+
ui.debug("Generating blurhash for header image");
|
|
129
|
+
const blurHash = await generateBlurHash(headerPhotoPath);
|
|
168
130
|
const landscapeYFactor = 3 / 4;
|
|
169
131
|
for (const width of HEADER_IMAGE_LANDSCAPE_WIDTHS) {
|
|
170
132
|
ui.debug(`Creating landscape header image ${width}`);
|
|
171
|
-
|
|
133
|
+
const avifFilename = `${headerBasename}_landscape_${width}.avif`;
|
|
134
|
+
const jpgFilename = `${headerBasename}_landscape_${width}.jpg`;
|
|
135
|
+
if (fs8.existsSync(path7.join(outputFolder, avifFilename))) {
|
|
172
136
|
ui.debug(`Landscape header image ${width} AVIF already exists`);
|
|
173
137
|
} else {
|
|
174
138
|
await cropAndResizeImage(
|
|
175
139
|
image.clone(),
|
|
176
|
-
|
|
140
|
+
path7.join(outputFolder, avifFilename),
|
|
177
141
|
width,
|
|
178
142
|
width * landscapeYFactor,
|
|
179
143
|
"avif"
|
|
180
144
|
);
|
|
181
145
|
}
|
|
182
|
-
|
|
146
|
+
generatedFiles.push(avifFilename);
|
|
147
|
+
if (fs8.existsSync(path7.join(outputFolder, jpgFilename))) {
|
|
183
148
|
ui.debug(`Landscape header image ${width} JPG already exists`);
|
|
184
149
|
} else {
|
|
185
|
-
await cropAndResizeImage(
|
|
186
|
-
image.clone(),
|
|
187
|
-
path4.join(outputFolder, `header_landscape_${width}.jpg`),
|
|
188
|
-
width,
|
|
189
|
-
width * landscapeYFactor,
|
|
190
|
-
"jpg"
|
|
191
|
-
);
|
|
150
|
+
await cropAndResizeImage(image.clone(), path7.join(outputFolder, jpgFilename), width, width * landscapeYFactor, "jpg");
|
|
192
151
|
}
|
|
152
|
+
generatedFiles.push(jpgFilename);
|
|
193
153
|
}
|
|
194
154
|
const portraitYFactor = 4 / 3;
|
|
195
155
|
for (const width of HEADER_IMAGE_PORTRAIT_WIDTHS) {
|
|
196
156
|
ui.debug(`Creating portrait header image ${width}`);
|
|
197
|
-
|
|
157
|
+
const avifFilename = `${headerBasename}_portrait_${width}.avif`;
|
|
158
|
+
const jpgFilename = `${headerBasename}_portrait_${width}.jpg`;
|
|
159
|
+
if (fs8.existsSync(path7.join(outputFolder, avifFilename))) {
|
|
198
160
|
ui.debug(`Portrait header image ${width} AVIF already exists`);
|
|
199
161
|
} else {
|
|
200
|
-
await cropAndResizeImage(
|
|
201
|
-
image.clone(),
|
|
202
|
-
path4.join(outputFolder, `header_portrait_${width}.avif`),
|
|
203
|
-
width,
|
|
204
|
-
width * portraitYFactor,
|
|
205
|
-
"avif"
|
|
206
|
-
);
|
|
162
|
+
await cropAndResizeImage(image.clone(), path7.join(outputFolder, avifFilename), width, width * portraitYFactor, "avif");
|
|
207
163
|
}
|
|
208
|
-
|
|
164
|
+
generatedFiles.push(avifFilename);
|
|
165
|
+
if (fs8.existsSync(path7.join(outputFolder, jpgFilename))) {
|
|
209
166
|
ui.debug(`Portrait header image ${width} JPG already exists`);
|
|
210
167
|
} else {
|
|
211
|
-
await cropAndResizeImage(
|
|
212
|
-
image.clone(),
|
|
213
|
-
path4.join(outputFolder, `header_portrait_${width}.jpg`),
|
|
214
|
-
width,
|
|
215
|
-
width * portraitYFactor,
|
|
216
|
-
"jpg"
|
|
217
|
-
);
|
|
168
|
+
await cropAndResizeImage(image.clone(), path7.join(outputFolder, jpgFilename), width, width * portraitYFactor, "jpg");
|
|
218
169
|
}
|
|
170
|
+
generatedFiles.push(jpgFilename);
|
|
219
171
|
}
|
|
220
172
|
ui.success(`Created optimized header image successfully`);
|
|
173
|
+
return { headerBasename, generatedFiles, blurHash };
|
|
174
|
+
}
|
|
175
|
+
function hasOldHeaderImages(outputFolder, currentHeaderBasename) {
|
|
176
|
+
if (!fs8.existsSync(outputFolder)) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
const files = fs8.readdirSync(outputFolder);
|
|
180
|
+
for (const file of files) {
|
|
181
|
+
const landscapeMatch = file.match(/^(.+)_landscape_\d+\.(avif|jpg)$/);
|
|
182
|
+
const portraitMatch = file.match(/^(.+)_portrait_\d+\.(avif|jpg)$/);
|
|
183
|
+
if (landscapeMatch && landscapeMatch[1] !== currentHeaderBasename || portraitMatch && portraitMatch[1] !== currentHeaderBasename) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
function cleanupOldHeaderImages(outputFolder, currentHeaderBasename, ui) {
|
|
190
|
+
ui.start(`Cleaning up old header images`);
|
|
191
|
+
if (!fs8.existsSync(outputFolder)) {
|
|
192
|
+
ui.debug(`Output folder ${outputFolder} does not exist, skipping cleanup`);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const files = fs8.readdirSync(outputFolder);
|
|
196
|
+
let deletedCount = 0;
|
|
197
|
+
for (const file of files) {
|
|
198
|
+
const landscapeMatch = file.match(/^(.+)_landscape_\d+\.(avif|jpg)$/);
|
|
199
|
+
const portraitMatch = file.match(/^(.+)_portrait_\d+\.(avif|jpg)$/);
|
|
200
|
+
if (landscapeMatch && landscapeMatch[1] !== currentHeaderBasename) {
|
|
201
|
+
const filePath = path7.join(outputFolder, file);
|
|
202
|
+
ui.debug(`Deleting old landscape header image: ${file}`);
|
|
203
|
+
fs8.unlinkSync(filePath);
|
|
204
|
+
deletedCount++;
|
|
205
|
+
} else if (portraitMatch && portraitMatch[1] !== currentHeaderBasename) {
|
|
206
|
+
const filePath = path7.join(outputFolder, file);
|
|
207
|
+
ui.debug(`Deleting old portrait header image: ${file}`);
|
|
208
|
+
fs8.unlinkSync(filePath);
|
|
209
|
+
deletedCount++;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (deletedCount > 0) {
|
|
213
|
+
ui.success(`Deleted ${deletedCount} old header image(s)`);
|
|
214
|
+
} else {
|
|
215
|
+
ui.debug(`No old header images to clean up`);
|
|
216
|
+
}
|
|
221
217
|
}
|
|
222
218
|
function findGalleries(basePath, recursive) {
|
|
223
219
|
const galleryDirs = [];
|
|
224
|
-
const galleryJsonPath =
|
|
225
|
-
if (
|
|
220
|
+
const galleryJsonPath = path7.join(basePath, "gallery", "gallery.json");
|
|
221
|
+
if (fs8.existsSync(galleryJsonPath)) {
|
|
226
222
|
galleryDirs.push(basePath);
|
|
227
223
|
}
|
|
228
224
|
if (recursive) {
|
|
229
225
|
try {
|
|
230
|
-
const entries =
|
|
226
|
+
const entries = fs8.readdirSync(basePath, { withFileTypes: true });
|
|
231
227
|
for (const entry of entries) {
|
|
232
228
|
if (entry.isDirectory() && entry.name !== "gallery") {
|
|
233
|
-
const subPath =
|
|
229
|
+
const subPath = path7.join(basePath, entry.name);
|
|
234
230
|
const subResults = findGalleries(subPath, recursive);
|
|
235
231
|
galleryDirs.push(...subResults);
|
|
236
232
|
}
|
|
@@ -252,16 +248,341 @@ function handleFileProcessingError(error, filename, ui) {
|
|
|
252
248
|
}
|
|
253
249
|
ui.debug(error);
|
|
254
250
|
}
|
|
251
|
+
function parseTelemetryOption(value) {
|
|
252
|
+
if (value !== "0" && value !== "1") {
|
|
253
|
+
throw new Error("Telemetry option must be either 0 or 1.");
|
|
254
|
+
}
|
|
255
|
+
return value;
|
|
256
|
+
}
|
|
257
|
+
var ThumbnailSchema = z.object({
|
|
258
|
+
path: z.string(),
|
|
259
|
+
pathRetina: z.string(),
|
|
260
|
+
width: z.number(),
|
|
261
|
+
height: z.number(),
|
|
262
|
+
blurHash: z.string().optional()
|
|
263
|
+
});
|
|
264
|
+
var MediaFileSchema = z.object({
|
|
265
|
+
type: z.enum(["image", "video"]),
|
|
266
|
+
filename: z.string(),
|
|
267
|
+
alt: z.string().optional(),
|
|
268
|
+
width: z.number(),
|
|
269
|
+
height: z.number(),
|
|
270
|
+
thumbnail: ThumbnailSchema.optional(),
|
|
271
|
+
lastMediaTimestamp: z.string().optional()
|
|
272
|
+
});
|
|
273
|
+
var MediaFileDeprecatedSchema = z.object({
|
|
274
|
+
type: z.enum(["image", "video"]),
|
|
275
|
+
path: z.string(),
|
|
276
|
+
alt: z.string().optional(),
|
|
277
|
+
width: z.number(),
|
|
278
|
+
height: z.number(),
|
|
279
|
+
thumbnail: ThumbnailSchema.optional(),
|
|
280
|
+
lastMediaTimestamp: z.string().optional()
|
|
281
|
+
});
|
|
282
|
+
var GallerySectionSchema = z.object({
|
|
283
|
+
title: z.string().optional(),
|
|
284
|
+
description: z.string().optional(),
|
|
285
|
+
images: z.array(MediaFileSchema)
|
|
286
|
+
});
|
|
287
|
+
var GallerySectionDeprecatedSchema = z.object({
|
|
288
|
+
title: z.string().optional(),
|
|
289
|
+
description: z.string().optional(),
|
|
290
|
+
images: z.array(MediaFileDeprecatedSchema)
|
|
291
|
+
});
|
|
292
|
+
var SubGallerySchema = z.object({
|
|
293
|
+
title: z.string(),
|
|
294
|
+
headerImage: z.string(),
|
|
295
|
+
path: z.string()
|
|
296
|
+
});
|
|
297
|
+
var GalleryMetadataSchema = z.object({
|
|
298
|
+
image: z.string().optional(),
|
|
299
|
+
imageWidth: z.number().optional(),
|
|
300
|
+
imageHeight: z.number().optional(),
|
|
301
|
+
ogUrl: z.string().optional(),
|
|
302
|
+
ogType: z.string().optional(),
|
|
303
|
+
ogSiteName: z.string().optional(),
|
|
304
|
+
twitterSite: z.string().optional(),
|
|
305
|
+
twitterCreator: z.string().optional(),
|
|
306
|
+
author: z.string().optional(),
|
|
307
|
+
keywords: z.string().optional(),
|
|
308
|
+
canonicalUrl: z.string().optional(),
|
|
309
|
+
language: z.string().optional(),
|
|
310
|
+
robots: z.string().optional()
|
|
311
|
+
});
|
|
312
|
+
var GalleryDataSchema = z.object({
|
|
313
|
+
title: z.string(),
|
|
314
|
+
description: z.string(),
|
|
315
|
+
mediaBasePath: z.string().optional(),
|
|
316
|
+
url: z.string().optional(),
|
|
317
|
+
headerImage: z.string(),
|
|
318
|
+
headerImageBlurHash: z.string().optional(),
|
|
319
|
+
thumbnailSize: z.number().optional(),
|
|
320
|
+
metadata: GalleryMetadataSchema,
|
|
321
|
+
mediaBaseUrl: z.string().optional(),
|
|
322
|
+
analyticsScript: z.string().optional(),
|
|
323
|
+
sections: z.array(GallerySectionSchema),
|
|
324
|
+
subGalleries: z.object({ title: z.string(), galleries: z.array(SubGallerySchema) })
|
|
325
|
+
});
|
|
326
|
+
var GalleryDataDeprecatedSchema = z.object({
|
|
327
|
+
title: z.string(),
|
|
328
|
+
description: z.string(),
|
|
329
|
+
url: z.string().optional(),
|
|
330
|
+
headerImage: z.string(),
|
|
331
|
+
thumbnailSize: z.number().optional(),
|
|
332
|
+
metadata: GalleryMetadataSchema,
|
|
333
|
+
mediaBaseUrl: z.string().optional(),
|
|
334
|
+
analyticsScript: z.string().optional(),
|
|
335
|
+
sections: z.array(GallerySectionDeprecatedSchema),
|
|
336
|
+
subGalleries: z.object({ title: z.string(), galleries: z.array(SubGallerySchema) })
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// src/utils/gallery.ts
|
|
340
|
+
function parseGalleryJson(galleryJsonPath, ui) {
|
|
341
|
+
let galleryContent;
|
|
342
|
+
try {
|
|
343
|
+
galleryContent = fs8.readFileSync(galleryJsonPath, "utf8");
|
|
344
|
+
} catch (error) {
|
|
345
|
+
ui.error("Error parsing gallery.json: file not found");
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
let galleryJSON;
|
|
349
|
+
try {
|
|
350
|
+
galleryJSON = JSON.parse(galleryContent);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
ui.error("Error parsing gallery.json: invalid JSON");
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
return GalleryDataSchema.parse(galleryJSON);
|
|
357
|
+
} catch (error) {
|
|
358
|
+
try {
|
|
359
|
+
const deprecatedGalleryData = GalleryDataDeprecatedSchema.parse(galleryJSON);
|
|
360
|
+
return migrateGalleryJson(deprecatedGalleryData, galleryJsonPath, ui);
|
|
361
|
+
} catch {
|
|
362
|
+
ui.error("Error parsing gallery.json: invalid gallery data");
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function migrateGalleryJson(deprecatedGalleryData, galleryJsonPath, ui) {
|
|
368
|
+
ui.start("Old gallery.json format detected. Migrating gallery.json to the new data format.");
|
|
369
|
+
let mediaBasePath;
|
|
370
|
+
const imagePath = deprecatedGalleryData.sections[0].images[0].path;
|
|
371
|
+
if (imagePath && imagePath !== path7.join("..", path7.basename(imagePath))) {
|
|
372
|
+
mediaBasePath = path7.resolve(path7.join(path7.dirname(galleryJsonPath)), path7.dirname(imagePath));
|
|
373
|
+
}
|
|
374
|
+
const sections = deprecatedGalleryData.sections.map((section) => ({
|
|
375
|
+
...section,
|
|
376
|
+
images: section.images.map((image) => ({
|
|
377
|
+
...image,
|
|
378
|
+
path: void 0,
|
|
379
|
+
filename: path7.basename(image.path)
|
|
380
|
+
}))
|
|
381
|
+
}));
|
|
382
|
+
const galleryData = {
|
|
383
|
+
...deprecatedGalleryData,
|
|
384
|
+
headerImage: path7.basename(deprecatedGalleryData.headerImage),
|
|
385
|
+
sections,
|
|
386
|
+
mediaBasePath
|
|
387
|
+
};
|
|
388
|
+
ui.debug("Backing up old gallery.json file");
|
|
389
|
+
fs8.copyFileSync(galleryJsonPath, `${galleryJsonPath}.old`);
|
|
390
|
+
ui.debug("Writing gallery data to gallery.json file");
|
|
391
|
+
fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
392
|
+
ui.success("Gallery data migrated to the new data format successfully.");
|
|
393
|
+
return galleryData;
|
|
394
|
+
}
|
|
395
|
+
function getMediaFileType(fileName) {
|
|
396
|
+
const ext = path7.extname(fileName).toLowerCase();
|
|
397
|
+
if (IMAGE_EXTENSIONS.has(ext)) return "image";
|
|
398
|
+
if (VIDEO_EXTENSIONS.has(ext)) return "video";
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
function capitalizeTitle(folderName) {
|
|
402
|
+
return folderName.replace("-", " ").replace("_", " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/modules/init/index.ts
|
|
406
|
+
async function scanDirectory(dirPath, ui) {
|
|
407
|
+
const mediaFiles = [];
|
|
408
|
+
const subGalleryDirectories = [];
|
|
409
|
+
try {
|
|
410
|
+
const entries = await promises.readdir(dirPath, { withFileTypes: true });
|
|
411
|
+
for (const entry of entries) {
|
|
412
|
+
if (entry.isFile()) {
|
|
413
|
+
const mediaType = getMediaFileType(entry.name);
|
|
414
|
+
if (mediaType) {
|
|
415
|
+
const mediaFile = {
|
|
416
|
+
type: mediaType,
|
|
417
|
+
filename: entry.name,
|
|
418
|
+
width: 0,
|
|
419
|
+
height: 0
|
|
420
|
+
};
|
|
421
|
+
mediaFiles.push(mediaFile);
|
|
422
|
+
}
|
|
423
|
+
} else if (entry.isDirectory() && entry.name !== "gallery") {
|
|
424
|
+
subGalleryDirectories.push(path7.join(dirPath, entry.name));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
} catch (error) {
|
|
428
|
+
if (error instanceof Error && error.message.includes("ENOENT")) {
|
|
429
|
+
ui.error(`Directory does not exist: ${dirPath}`);
|
|
430
|
+
} else if (error instanceof Error && error.message.includes("ENOTDIR")) {
|
|
431
|
+
ui.error(`Path is not a directory: ${dirPath}`);
|
|
432
|
+
} else {
|
|
433
|
+
ui.error(`Error scanning directory ${dirPath}:`, error);
|
|
434
|
+
}
|
|
435
|
+
throw error;
|
|
436
|
+
}
|
|
437
|
+
return { mediaFiles, subGalleryDirectories };
|
|
438
|
+
}
|
|
439
|
+
async function getGallerySettingsFromUser(galleryName, defaultImage, ui) {
|
|
440
|
+
ui.info(`Enter gallery settings for the gallery in folder "${galleryName}"`);
|
|
441
|
+
const title = await ui.prompt("Enter gallery title", { type: "text", default: "My Gallery", placeholder: "My Gallery" });
|
|
442
|
+
const description = await ui.prompt("Enter gallery description", {
|
|
443
|
+
type: "text",
|
|
444
|
+
default: "My gallery with fantastic photos.",
|
|
445
|
+
placeholder: "My gallery with fantastic photos."
|
|
446
|
+
});
|
|
447
|
+
const url = await ui.prompt("Enter the URL where the gallery will be hosted (important for social media image)", {
|
|
448
|
+
type: "text",
|
|
449
|
+
default: "",
|
|
450
|
+
placeholder: ""
|
|
451
|
+
});
|
|
452
|
+
const headerImage = await ui.prompt("Enter the name of the header image", {
|
|
453
|
+
type: "text",
|
|
454
|
+
default: defaultImage,
|
|
455
|
+
placeholder: defaultImage
|
|
456
|
+
});
|
|
457
|
+
return { title, description, url, headerImage };
|
|
458
|
+
}
|
|
459
|
+
async function createGalleryJson(mediaFiles, galleryJsonPath, scanPath, subGalleries = [], useDefaultSettings, ui) {
|
|
460
|
+
const galleryDir = path7.dirname(galleryJsonPath);
|
|
461
|
+
const isSameLocation = path7.relative(scanPath, path7.join(galleryDir, "..")) === "";
|
|
462
|
+
const mediaBasePath = isSameLocation ? void 0 : scanPath;
|
|
463
|
+
const relativeSubGalleries = subGalleries.map((subGallery) => ({
|
|
464
|
+
...subGallery,
|
|
465
|
+
headerImage: subGallery.headerImage ? path7.relative(galleryDir, subGallery.headerImage) : ""
|
|
466
|
+
}));
|
|
467
|
+
let galleryData = {
|
|
468
|
+
title: "My Gallery",
|
|
469
|
+
description: "My gallery with fantastic photos.",
|
|
470
|
+
headerImage: mediaFiles[0]?.filename || "",
|
|
471
|
+
mediaBasePath,
|
|
472
|
+
metadata: {},
|
|
473
|
+
sections: [
|
|
474
|
+
{
|
|
475
|
+
images: mediaFiles
|
|
476
|
+
}
|
|
477
|
+
],
|
|
478
|
+
subGalleries: {
|
|
479
|
+
title: "Sub Galleries",
|
|
480
|
+
galleries: relativeSubGalleries
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
if (!useDefaultSettings) {
|
|
484
|
+
galleryData = {
|
|
485
|
+
...galleryData,
|
|
486
|
+
...await getGallerySettingsFromUser(
|
|
487
|
+
path7.basename(path7.join(galleryDir, "..")),
|
|
488
|
+
path7.basename(mediaFiles[0]?.filename || ""),
|
|
489
|
+
ui
|
|
490
|
+
)
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
await promises.writeFile(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
494
|
+
}
|
|
495
|
+
async function galleryExists(outputPath) {
|
|
496
|
+
const galleryPath = path7.join(outputPath, "gallery");
|
|
497
|
+
const galleryJsonPath = path7.join(galleryPath, "gallery.json");
|
|
498
|
+
try {
|
|
499
|
+
await promises.access(galleryJsonPath);
|
|
500
|
+
return true;
|
|
501
|
+
} catch {
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async function processDirectory(scanPath, outputPath, recursive, useDefaultSettings, force, ui) {
|
|
506
|
+
ui.start(`Scanning ${scanPath}`);
|
|
507
|
+
let totalFiles = 0;
|
|
508
|
+
let totalGalleries = 1;
|
|
509
|
+
const subGalleries = [];
|
|
510
|
+
const { mediaFiles, subGalleryDirectories } = await scanDirectory(scanPath, ui);
|
|
511
|
+
totalFiles += mediaFiles.length;
|
|
512
|
+
if (recursive) {
|
|
513
|
+
for (const subGalleryDir of subGalleryDirectories) {
|
|
514
|
+
const result2 = await processDirectory(
|
|
515
|
+
subGalleryDir,
|
|
516
|
+
path7.join(outputPath, path7.basename(subGalleryDir)),
|
|
517
|
+
recursive,
|
|
518
|
+
useDefaultSettings,
|
|
519
|
+
force,
|
|
520
|
+
ui
|
|
521
|
+
);
|
|
522
|
+
totalFiles += result2.totalFiles;
|
|
523
|
+
totalGalleries += result2.totalGalleries;
|
|
524
|
+
if (result2.subGallery) {
|
|
525
|
+
subGalleries.push(result2.subGallery);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (mediaFiles.length > 0 || subGalleries.length > 0) {
|
|
530
|
+
const galleryPath = path7.join(outputPath, "gallery");
|
|
531
|
+
const galleryJsonPath = path7.join(galleryPath, "gallery.json");
|
|
532
|
+
const exists = await galleryExists(outputPath);
|
|
533
|
+
if (exists && !force) {
|
|
534
|
+
const shouldOverride = await ui.prompt(`Gallery already exists at ${galleryJsonPath}. Do you want to override it?`, {
|
|
535
|
+
type: "confirm",
|
|
536
|
+
default: false
|
|
537
|
+
});
|
|
538
|
+
if (!shouldOverride) {
|
|
539
|
+
ui.info("Skipping gallery creation");
|
|
540
|
+
return { totalFiles: 0, totalGalleries: 0 };
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
await promises.mkdir(galleryPath, { recursive: true });
|
|
545
|
+
await createGalleryJson(mediaFiles, galleryJsonPath, scanPath, subGalleries, useDefaultSettings, ui);
|
|
546
|
+
ui.success(
|
|
547
|
+
`Create gallery with ${mediaFiles.length} files and ${subGalleries.length} subgalleries at: ${galleryJsonPath}`
|
|
548
|
+
);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
ui.error(`Error creating gallery.json at ${galleryJsonPath}`);
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
const result = { totalFiles, totalGalleries };
|
|
555
|
+
if (mediaFiles.length > 0 || subGalleries.length > 0) {
|
|
556
|
+
const dirName = path7.basename(scanPath);
|
|
557
|
+
result.subGallery = {
|
|
558
|
+
title: capitalizeTitle(dirName),
|
|
559
|
+
headerImage: mediaFiles[0]?.filename || "",
|
|
560
|
+
path: path7.join("..", dirName)
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
return result;
|
|
564
|
+
}
|
|
565
|
+
async function init(options, ui) {
|
|
566
|
+
try {
|
|
567
|
+
const scanPath = path7.resolve(options.photos);
|
|
568
|
+
const outputPath = options.gallery ? path7.resolve(options.gallery) : scanPath;
|
|
569
|
+
const result = await processDirectory(scanPath, outputPath, options.recursive, options.default, options.force, ui);
|
|
570
|
+
ui.box(
|
|
571
|
+
`Created ${result.totalGalleries} ${result.totalGalleries === 1 ? "gallery" : "galleries"} with ${result.totalFiles} media ${result.totalFiles === 1 ? "file" : "files"}`
|
|
572
|
+
);
|
|
573
|
+
return {
|
|
574
|
+
processedMediaCount: result.totalFiles,
|
|
575
|
+
processedGalleryCount: result.totalGalleries
|
|
576
|
+
};
|
|
577
|
+
} catch (error) {
|
|
578
|
+
ui.error("Error initializing gallery");
|
|
579
|
+
throw error;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
255
582
|
async function getFileMtime(filePath) {
|
|
256
583
|
const stats = await promises.stat(filePath);
|
|
257
584
|
return stats.mtime;
|
|
258
585
|
}
|
|
259
|
-
async function generateBlurHash(imagePath, componentX = 4, componentY = 3) {
|
|
260
|
-
const image = await loadImage(imagePath);
|
|
261
|
-
const { data, info } = await image.resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
262
|
-
const pixels = new Uint8ClampedArray(data.buffer);
|
|
263
|
-
return encode(pixels, info.width, info.height, componentX, componentY);
|
|
264
|
-
}
|
|
265
586
|
async function getVideoDimensions(filePath) {
|
|
266
587
|
const data = await ffprobe(filePath);
|
|
267
588
|
const videoStream = data.streams.find((stream) => stream.codec_type === "video");
|
|
@@ -322,7 +643,7 @@ async function createVideoThumbnails(inputPath, videoDimensions, outputPath, out
|
|
|
322
643
|
// src/modules/thumbnails/index.ts
|
|
323
644
|
async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, lastMediaTimestamp) {
|
|
324
645
|
const fileMtime = await getFileMtime(imagePath);
|
|
325
|
-
if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp &&
|
|
646
|
+
if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs8.existsSync(thumbnailPath)) {
|
|
326
647
|
return void 0;
|
|
327
648
|
}
|
|
328
649
|
const { image, metadata } = await loadImageWithMetadata(imagePath);
|
|
@@ -344,7 +665,7 @@ async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
344
665
|
const blurHash = await generateBlurHash(thumbnailPath);
|
|
345
666
|
return {
|
|
346
667
|
type: "image",
|
|
347
|
-
|
|
668
|
+
filename: path7.basename(imagePath),
|
|
348
669
|
alt: description,
|
|
349
670
|
width: imageDimensions.width,
|
|
350
671
|
height: imageDimensions.height,
|
|
@@ -360,7 +681,7 @@ async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
360
681
|
}
|
|
361
682
|
async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumbnailSize, verbose, lastMediaTimestamp) {
|
|
362
683
|
const fileMtime = await getFileMtime(videoPath);
|
|
363
|
-
if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp &&
|
|
684
|
+
if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs8.existsSync(thumbnailPath)) {
|
|
364
685
|
return void 0;
|
|
365
686
|
}
|
|
366
687
|
const videoDimensions = await getVideoDimensions(videoPath);
|
|
@@ -375,7 +696,7 @@ async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
375
696
|
const blurHash = await generateBlurHash(thumbnailPath);
|
|
376
697
|
return {
|
|
377
698
|
type: "video",
|
|
378
|
-
|
|
699
|
+
filename: path7.basename(videoPath),
|
|
379
700
|
alt: void 0,
|
|
380
701
|
width: videoDimensions.width,
|
|
381
702
|
height: videoDimensions.height,
|
|
@@ -389,24 +710,24 @@ async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
389
710
|
lastMediaTimestamp: fileMtime.toISOString()
|
|
390
711
|
};
|
|
391
712
|
}
|
|
392
|
-
async function processMediaFile(mediaFile, galleryDir, thumbnailsPath, thumbnailSize, ui) {
|
|
713
|
+
async function processMediaFile(mediaFile, mediaBasePath, galleryDir, thumbnailsPath, thumbnailSize, ui) {
|
|
393
714
|
try {
|
|
394
|
-
const
|
|
395
|
-
const
|
|
396
|
-
const
|
|
397
|
-
const
|
|
715
|
+
const filePath = path7.resolve(path7.join(mediaBasePath, mediaFile.filename));
|
|
716
|
+
const fileName = mediaFile.filename;
|
|
717
|
+
const fileNameWithoutExt = path7.parse(fileName).name;
|
|
718
|
+
const galleryJsonDir = path7.join(galleryDir, "gallery");
|
|
398
719
|
const thumbnailFileName = `${fileNameWithoutExt}.avif`;
|
|
399
|
-
const thumbnailPath =
|
|
720
|
+
const thumbnailPath = path7.join(thumbnailsPath, thumbnailFileName);
|
|
400
721
|
const thumbnailPathRetina = thumbnailPath.replace(".avif", "@2x.avif");
|
|
401
|
-
const relativeThumbnailPath =
|
|
402
|
-
const relativeThumbnailRetinaPath =
|
|
722
|
+
const relativeThumbnailPath = path7.relative(galleryJsonDir, thumbnailPath);
|
|
723
|
+
const relativeThumbnailRetinaPath = path7.relative(galleryJsonDir, thumbnailPathRetina);
|
|
403
724
|
const lastMediaTimestamp = mediaFile.lastMediaTimestamp ? new Date(mediaFile.lastMediaTimestamp) : void 0;
|
|
404
725
|
const verbose = ui.level === LogLevels.debug;
|
|
405
726
|
ui.debug(` Processing ${mediaFile.type}: ${fileName}`);
|
|
406
727
|
const updatedMediaFile = await (mediaFile.type === "image" ? processImage(filePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, lastMediaTimestamp) : processVideo(filePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, verbose, lastMediaTimestamp));
|
|
407
728
|
if (!updatedMediaFile) {
|
|
408
729
|
ui.debug(` Skipping ${fileName} because it has already been processed`);
|
|
409
|
-
if (mediaFile.thumbnail && !mediaFile.thumbnail.blurHash &&
|
|
730
|
+
if (mediaFile.thumbnail && !mediaFile.thumbnail.blurHash && fs8.existsSync(thumbnailPath)) {
|
|
410
731
|
try {
|
|
411
732
|
const blurHash = await generateBlurHash(thumbnailPath);
|
|
412
733
|
return {
|
|
@@ -422,34 +743,41 @@ async function processMediaFile(mediaFile, galleryDir, thumbnailsPath, thumbnail
|
|
|
422
743
|
}
|
|
423
744
|
return mediaFile;
|
|
424
745
|
}
|
|
425
|
-
updatedMediaFile.
|
|
746
|
+
updatedMediaFile.filename = mediaFile.filename;
|
|
426
747
|
if (updatedMediaFile.thumbnail) {
|
|
427
748
|
updatedMediaFile.thumbnail.path = relativeThumbnailPath;
|
|
428
749
|
updatedMediaFile.thumbnail.pathRetina = relativeThumbnailRetinaPath;
|
|
429
750
|
}
|
|
430
751
|
return updatedMediaFile;
|
|
431
752
|
} catch (error) {
|
|
432
|
-
handleFileProcessingError(error,
|
|
433
|
-
return mediaFile;
|
|
753
|
+
handleFileProcessingError(error, mediaFile.filename, ui);
|
|
754
|
+
return { ...mediaFile, thumbnail: void 0 };
|
|
434
755
|
}
|
|
435
756
|
}
|
|
436
757
|
async function processGalleryThumbnails(galleryDir, ui) {
|
|
437
|
-
const galleryJsonPath =
|
|
438
|
-
const thumbnailsPath =
|
|
758
|
+
const galleryJsonPath = path7.join(galleryDir, "gallery", "gallery.json");
|
|
759
|
+
const thumbnailsPath = path7.join(galleryDir, "gallery", "images");
|
|
439
760
|
ui.start(`Creating thumbnails: ${galleryDir}`);
|
|
440
761
|
try {
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
const galleryData = GalleryDataSchema.parse(JSON.parse(galleryContent));
|
|
762
|
+
fs8.mkdirSync(thumbnailsPath, { recursive: true });
|
|
763
|
+
const galleryData = parseGalleryJson(galleryJsonPath, ui);
|
|
444
764
|
const thumbnailSize = galleryData.thumbnailSize || DEFAULT_THUMBNAIL_SIZE;
|
|
765
|
+
const mediaBasePath = galleryData.mediaBasePath ?? path7.join(galleryDir);
|
|
445
766
|
let processedCount = 0;
|
|
446
767
|
for (const section of galleryData.sections) {
|
|
447
768
|
for (const [index, mediaFile] of section.images.entries()) {
|
|
448
|
-
section.images[index] = await processMediaFile(
|
|
769
|
+
section.images[index] = await processMediaFile(
|
|
770
|
+
mediaFile,
|
|
771
|
+
mediaBasePath,
|
|
772
|
+
galleryDir,
|
|
773
|
+
thumbnailsPath,
|
|
774
|
+
thumbnailSize,
|
|
775
|
+
ui
|
|
776
|
+
);
|
|
449
777
|
}
|
|
450
778
|
processedCount += section.images.length;
|
|
451
779
|
}
|
|
452
|
-
|
|
780
|
+
fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
453
781
|
ui.success(`Created thumbnails for ${processedCount} media files`);
|
|
454
782
|
return processedCount;
|
|
455
783
|
} catch (error) {
|
|
@@ -462,7 +790,7 @@ async function thumbnails(options, ui) {
|
|
|
462
790
|
const galleryDirs = findGalleries(options.gallery, options.recursive);
|
|
463
791
|
if (galleryDirs.length === 0) {
|
|
464
792
|
ui.error("No galleries found.");
|
|
465
|
-
return;
|
|
793
|
+
return { processedGalleryCount: 0, processedMediaCount: 0 };
|
|
466
794
|
}
|
|
467
795
|
let totalGalleries = 0;
|
|
468
796
|
let totalProcessed = 0;
|
|
@@ -476,6 +804,7 @@ async function thumbnails(options, ui) {
|
|
|
476
804
|
ui.box(
|
|
477
805
|
`Created thumbnails for ${totalGalleries} ${totalGalleries === 1 ? "gallery" : "galleries"} with ${totalProcessed} media ${totalProcessed === 1 ? "file" : "files"}`
|
|
478
806
|
);
|
|
807
|
+
return { processedGalleryCount: totalGalleries, processedMediaCount: totalProcessed };
|
|
479
808
|
} catch (error) {
|
|
480
809
|
ui.error("Error creating thumbnails");
|
|
481
810
|
throw error;
|
|
@@ -483,68 +812,113 @@ async function thumbnails(options, ui) {
|
|
|
483
812
|
}
|
|
484
813
|
|
|
485
814
|
// src/modules/build/index.ts
|
|
486
|
-
function checkFileIsOneFolderUp(filePath) {
|
|
487
|
-
const normalizedPath = path4.normalize(filePath);
|
|
488
|
-
const pathParts = normalizedPath.split(path4.sep);
|
|
489
|
-
return pathParts.length === 2 && pathParts[0] === "..";
|
|
490
|
-
}
|
|
491
815
|
function copyPhotos(galleryData, galleryDir, ui) {
|
|
492
816
|
for (const section of galleryData.sections) {
|
|
493
817
|
for (const image of section.images) {
|
|
494
|
-
if (
|
|
495
|
-
const sourcePath =
|
|
496
|
-
const
|
|
497
|
-
const destPath = path4.join(galleryDir, fileName);
|
|
818
|
+
if (galleryData.mediaBasePath) {
|
|
819
|
+
const sourcePath = path7.join(galleryData.mediaBasePath, image.filename);
|
|
820
|
+
const destPath = path7.join(galleryDir, image.filename);
|
|
498
821
|
ui.debug(`Copying photo to ${destPath}`);
|
|
499
|
-
|
|
822
|
+
fs8.copyFileSync(sourcePath, destPath);
|
|
500
823
|
}
|
|
501
824
|
}
|
|
502
825
|
}
|
|
503
826
|
}
|
|
827
|
+
async function scanAndAppendNewFiles(galleryDir, galleryJsonPath, galleryData, ui) {
|
|
828
|
+
const scanPath = galleryData.mediaBasePath || galleryDir;
|
|
829
|
+
ui.debug(`Scanning ${scanPath} for new media files`);
|
|
830
|
+
let scanResult;
|
|
831
|
+
try {
|
|
832
|
+
scanResult = await scanDirectory(scanPath, ui);
|
|
833
|
+
} catch {
|
|
834
|
+
ui.debug(`Could not scan directory ${scanPath}`);
|
|
835
|
+
return galleryData;
|
|
836
|
+
}
|
|
837
|
+
const existingFilenames = new Set(
|
|
838
|
+
galleryData.sections.flatMap((section) => section.images.map((image) => image.filename))
|
|
839
|
+
);
|
|
840
|
+
const newMediaFiles = scanResult.mediaFiles.filter((file) => !existingFilenames.has(file.filename));
|
|
841
|
+
if (newMediaFiles.length > 0) {
|
|
842
|
+
ui.info(`Found ${newMediaFiles.length} new media ${newMediaFiles.length === 1 ? "file" : "files"}`);
|
|
843
|
+
if (galleryData.sections.length === 0) {
|
|
844
|
+
galleryData.sections.push({ images: [] });
|
|
845
|
+
}
|
|
846
|
+
const lastSectionIndex = galleryData.sections.length - 1;
|
|
847
|
+
const lastSection = galleryData.sections[lastSectionIndex];
|
|
848
|
+
lastSection.images.push(...newMediaFiles);
|
|
849
|
+
ui.debug("Updating gallery.json with new files");
|
|
850
|
+
fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
851
|
+
ui.success(`Added ${newMediaFiles.length} new ${newMediaFiles.length === 1 ? "file" : "files"} to gallery.json`);
|
|
852
|
+
} else {
|
|
853
|
+
ui.debug("No new media files found");
|
|
854
|
+
}
|
|
855
|
+
return galleryData;
|
|
856
|
+
}
|
|
504
857
|
async function buildGallery(galleryDir, templateDir, ui, baseUrl) {
|
|
505
858
|
ui.start(`Building gallery ${galleryDir}`);
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
const
|
|
510
|
-
const
|
|
511
|
-
const
|
|
859
|
+
const galleryJsonPath = path7.join(galleryDir, "gallery", "gallery.json");
|
|
860
|
+
let galleryData = parseGalleryJson(galleryJsonPath, ui);
|
|
861
|
+
galleryData = await scanAndAppendNewFiles(galleryDir, galleryJsonPath, galleryData, ui);
|
|
862
|
+
const socialMediaCardImagePath = path7.join(galleryDir, "gallery", "images", "social-media-card.jpg");
|
|
863
|
+
const mediaBasePath = galleryData.mediaBasePath;
|
|
864
|
+
const mediaBaseUrl = baseUrl || galleryData.mediaBaseUrl;
|
|
865
|
+
const headerImagePath = mediaBasePath ? path7.join(mediaBasePath, galleryData.headerImage) : path7.resolve(galleryDir, galleryData.headerImage);
|
|
866
|
+
const imagesFolder = path7.join(galleryDir, "gallery", "images");
|
|
867
|
+
const currentHeaderBasename = path7.basename(headerImagePath, path7.extname(headerImagePath));
|
|
868
|
+
if (!fs8.existsSync(imagesFolder)) {
|
|
869
|
+
fs8.mkdirSync(imagesFolder, { recursive: true });
|
|
870
|
+
}
|
|
871
|
+
const headerImageChanged = hasOldHeaderImages(imagesFolder, currentHeaderBasename);
|
|
872
|
+
if (headerImageChanged) {
|
|
873
|
+
ui.info("Header image changed, cleaning up old assets");
|
|
874
|
+
cleanupOldHeaderImages(imagesFolder, currentHeaderBasename, ui);
|
|
875
|
+
if (fs8.existsSync(socialMediaCardImagePath)) {
|
|
876
|
+
fs8.unlinkSync(socialMediaCardImagePath);
|
|
877
|
+
ui.debug("Deleted old social media card");
|
|
878
|
+
}
|
|
879
|
+
}
|
|
512
880
|
await createGallerySocialMediaCardImage(headerImagePath, galleryData.title, socialMediaCardImagePath, ui);
|
|
513
|
-
galleryData.metadata.image = galleryData.metadata.image || `${galleryData.url || ""}/${
|
|
514
|
-
|
|
515
|
-
await createOptimizedHeaderImage(headerImagePath,
|
|
516
|
-
if (
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
);
|
|
520
|
-
|
|
881
|
+
galleryData.metadata.image = galleryData.metadata.image || `${galleryData.url || ""}/${path7.relative(galleryDir, socialMediaCardImagePath)}`;
|
|
882
|
+
fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
883
|
+
const { blurHash } = await createOptimizedHeaderImage(headerImagePath, imagesFolder, ui);
|
|
884
|
+
if (galleryData.headerImageBlurHash !== blurHash) {
|
|
885
|
+
ui.debug("Updating gallery.json with header image blurhash");
|
|
886
|
+
galleryData.headerImageBlurHash = blurHash;
|
|
887
|
+
fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
888
|
+
}
|
|
889
|
+
if (!mediaBaseUrl && mediaBasePath) {
|
|
890
|
+
const shouldCopyPhotos = await ui.prompt("All photos need to be copied. Are you sure you want to continue?", {
|
|
891
|
+
type: "confirm"
|
|
892
|
+
});
|
|
893
|
+
if (shouldCopyPhotos) {
|
|
521
894
|
ui.debug("Copying photos");
|
|
522
895
|
copyPhotos(galleryData, galleryDir, ui);
|
|
523
896
|
}
|
|
524
897
|
}
|
|
525
|
-
if (
|
|
898
|
+
if (mediaBaseUrl && galleryData.mediaBaseUrl !== mediaBaseUrl) {
|
|
526
899
|
ui.debug("Updating gallery.json with baseUrl");
|
|
527
|
-
galleryData.mediaBaseUrl =
|
|
528
|
-
|
|
900
|
+
galleryData.mediaBaseUrl = mediaBaseUrl;
|
|
901
|
+
fs8.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
529
902
|
}
|
|
530
|
-
|
|
903
|
+
await processGalleryThumbnails(galleryDir, ui);
|
|
904
|
+
ui.debug("Building gallery from template");
|
|
531
905
|
try {
|
|
532
|
-
|
|
533
|
-
|
|
906
|
+
process3.env.GALLERY_JSON_PATH = galleryJsonPath;
|
|
907
|
+
process3.env.GALLERY_OUTPUT_DIR = path7.join(galleryDir, "gallery");
|
|
534
908
|
execSync("npx astro build", { cwd: templateDir, stdio: ui.level === LogLevels.debug ? "inherit" : "ignore" });
|
|
535
909
|
} catch (error) {
|
|
536
910
|
ui.error(`Build failed for ${galleryDir}`);
|
|
537
911
|
throw error;
|
|
538
912
|
}
|
|
539
|
-
const outputDir =
|
|
540
|
-
const buildDir =
|
|
913
|
+
const outputDir = path7.join(galleryDir, "gallery");
|
|
914
|
+
const buildDir = path7.join(outputDir, "_build");
|
|
541
915
|
ui.debug(`Copying build output to ${outputDir}`);
|
|
542
|
-
|
|
916
|
+
fs8.cpSync(buildDir, outputDir, { recursive: true });
|
|
543
917
|
ui.debug("Moving index.html to gallery directory");
|
|
544
|
-
|
|
545
|
-
|
|
918
|
+
fs8.copyFileSync(path7.join(outputDir, "index.html"), path7.join(galleryDir, "index.html"));
|
|
919
|
+
fs8.rmSync(path7.join(outputDir, "index.html"));
|
|
546
920
|
ui.debug("Cleaning up build directory");
|
|
547
|
-
|
|
921
|
+
fs8.rmSync(buildDir, { recursive: true, force: true });
|
|
548
922
|
ui.success(`Gallery built successfully`);
|
|
549
923
|
}
|
|
550
924
|
async function build(options, ui) {
|
|
@@ -552,17 +926,18 @@ async function build(options, ui) {
|
|
|
552
926
|
const galleryDirs = findGalleries(options.gallery, options.recursive);
|
|
553
927
|
if (galleryDirs.length === 0) {
|
|
554
928
|
ui.error("No galleries found.");
|
|
555
|
-
return;
|
|
929
|
+
return { processedGalleryCount: 0 };
|
|
556
930
|
}
|
|
557
931
|
const themePath = await import.meta.resolve("@simple-photo-gallery/theme-modern/package.json");
|
|
558
|
-
const themeDir =
|
|
932
|
+
const themeDir = path7.dirname(new URL(themePath).pathname);
|
|
559
933
|
let totalGalleries = 0;
|
|
560
934
|
for (const dir of galleryDirs) {
|
|
561
|
-
const baseUrl = options.baseUrl ? `${options.baseUrl}${
|
|
562
|
-
await buildGallery(
|
|
935
|
+
const baseUrl = options.baseUrl ? `${options.baseUrl}${path7.relative(options.gallery, dir)}` : void 0;
|
|
936
|
+
await buildGallery(path7.resolve(dir), themeDir, ui, baseUrl);
|
|
563
937
|
++totalGalleries;
|
|
564
938
|
}
|
|
565
939
|
ui.box(`Built ${totalGalleries} ${totalGalleries === 1 ? "gallery" : "galleries"} successfully`);
|
|
940
|
+
return { processedGalleryCount: totalGalleries };
|
|
566
941
|
} catch (error) {
|
|
567
942
|
if (error instanceof Error && error.message.includes("Cannot find package")) {
|
|
568
943
|
ui.error("Theme package not found: @simple-photo-gallery/theme-modern/package.json");
|
|
@@ -574,20 +949,20 @@ async function build(options, ui) {
|
|
|
574
949
|
}
|
|
575
950
|
async function cleanGallery(galleryDir, ui) {
|
|
576
951
|
let filesRemoved = 0;
|
|
577
|
-
const indexHtmlPath =
|
|
578
|
-
if (
|
|
952
|
+
const indexHtmlPath = path7.join(galleryDir, "index.html");
|
|
953
|
+
if (fs8.existsSync(indexHtmlPath)) {
|
|
579
954
|
try {
|
|
580
|
-
|
|
955
|
+
fs8.rmSync(indexHtmlPath);
|
|
581
956
|
ui.debug(`Removed: ${indexHtmlPath}`);
|
|
582
957
|
filesRemoved++;
|
|
583
958
|
} catch (error) {
|
|
584
959
|
ui.warn(`Failed to remove index.html: ${error}`);
|
|
585
960
|
}
|
|
586
961
|
}
|
|
587
|
-
const galleryPath =
|
|
588
|
-
if (
|
|
962
|
+
const galleryPath = path7.join(galleryDir, "gallery");
|
|
963
|
+
if (fs8.existsSync(galleryPath)) {
|
|
589
964
|
try {
|
|
590
|
-
|
|
965
|
+
fs8.rmSync(galleryPath, { recursive: true, force: true });
|
|
591
966
|
ui.debug(`Removed directory: ${galleryPath}`);
|
|
592
967
|
filesRemoved++;
|
|
593
968
|
} catch (error) {
|
|
@@ -599,192 +974,198 @@ async function cleanGallery(galleryDir, ui) {
|
|
|
599
974
|
} else {
|
|
600
975
|
ui.info(`No gallery files found at: ${galleryDir}`);
|
|
601
976
|
}
|
|
977
|
+
return { processedGalleryCount: filesRemoved };
|
|
602
978
|
}
|
|
603
979
|
async function clean(options, ui) {
|
|
604
980
|
try {
|
|
605
|
-
const basePath =
|
|
606
|
-
if (!
|
|
981
|
+
const basePath = path7.resolve(options.gallery);
|
|
982
|
+
if (!fs8.existsSync(basePath)) {
|
|
607
983
|
ui.error(`Directory does not exist: ${basePath}`);
|
|
608
|
-
return;
|
|
984
|
+
return { processedGalleryCount: 0 };
|
|
609
985
|
}
|
|
610
986
|
const galleryDirs = findGalleries(basePath, options.recursive);
|
|
611
987
|
if (galleryDirs.length === 0) {
|
|
612
988
|
ui.info("No galleries found to clean.");
|
|
613
|
-
return;
|
|
989
|
+
return { processedGalleryCount: 0 };
|
|
614
990
|
}
|
|
615
991
|
for (const dir of galleryDirs) {
|
|
616
992
|
await cleanGallery(dir, ui);
|
|
617
993
|
}
|
|
618
994
|
ui.box(`Successfully cleaned ${galleryDirs.length} ${galleryDirs.length === 1 ? "gallery" : "galleries"}`);
|
|
995
|
+
return { processedGalleryCount: galleryDirs.length };
|
|
619
996
|
} catch (error) {
|
|
620
997
|
ui.error("Error cleaning galleries");
|
|
621
998
|
throw error;
|
|
622
999
|
}
|
|
623
1000
|
}
|
|
624
|
-
function getMediaFileType(fileName) {
|
|
625
|
-
const ext = path4.extname(fileName).toLowerCase();
|
|
626
|
-
if (IMAGE_EXTENSIONS.has(ext)) return "image";
|
|
627
|
-
if (VIDEO_EXTENSIONS.has(ext)) return "video";
|
|
628
|
-
return null;
|
|
629
|
-
}
|
|
630
|
-
function capitalizeTitle(folderName) {
|
|
631
|
-
return folderName.replace("-", " ").replace("_", " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
632
|
-
}
|
|
633
1001
|
|
|
634
|
-
// src/modules/
|
|
635
|
-
async function
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const fullPath = path4.join(dirPath, entry.name);
|
|
643
|
-
const mediaType = getMediaFileType(entry.name);
|
|
644
|
-
if (mediaType) {
|
|
645
|
-
const mediaFile = {
|
|
646
|
-
type: mediaType,
|
|
647
|
-
path: fullPath,
|
|
648
|
-
width: 0,
|
|
649
|
-
height: 0
|
|
650
|
-
};
|
|
651
|
-
mediaFiles.push(mediaFile);
|
|
652
|
-
}
|
|
653
|
-
} else if (entry.isDirectory() && entry.name !== "gallery") {
|
|
654
|
-
subGalleryDirectories.push(path4.join(dirPath, entry.name));
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
} catch (error) {
|
|
658
|
-
if (error instanceof Error && error.message.includes("ENOENT")) {
|
|
659
|
-
ui.error(`Directory does not exist: ${dirPath}`);
|
|
660
|
-
} else if (error instanceof Error && error.message.includes("ENOTDIR")) {
|
|
661
|
-
ui.error(`Path is not a directory: ${dirPath}`);
|
|
1002
|
+
// src/modules/telemetry/index.ts
|
|
1003
|
+
async function telemetry(options, ui, telemetryService2) {
|
|
1004
|
+
const updates = {};
|
|
1005
|
+
if (options.state === void 0) {
|
|
1006
|
+
const current = telemetryService2.getStoredPreference();
|
|
1007
|
+
if (current === void 0) {
|
|
1008
|
+
ui.info("Telemetry preference not set yet. It will be requested on next command run.");
|
|
1009
|
+
updates.telemetryStatus = "unset";
|
|
662
1010
|
} else {
|
|
663
|
-
ui.
|
|
1011
|
+
ui.info(`Telemetry is currently ${current ? "enabled" : "disabled"}.`);
|
|
1012
|
+
updates.telemetryEnabled = current;
|
|
664
1013
|
}
|
|
665
|
-
|
|
1014
|
+
} else {
|
|
1015
|
+
telemetryService2.setStoredPreference(options.state === "1");
|
|
1016
|
+
ui.success(`Anonymous telemetry ${options.state === "1" ? "enabled" : "disabled"}.`);
|
|
1017
|
+
updates.telemetryEnabled = options.state === "1";
|
|
666
1018
|
}
|
|
667
|
-
return
|
|
668
|
-
}
|
|
669
|
-
async function getGallerySettingsFromUser(galleryName, defaultImage, ui) {
|
|
670
|
-
ui.info(`Enter gallery settings for the gallery in folder "${galleryName}"`);
|
|
671
|
-
const title = await ui.prompt("Enter gallery title", { type: "text", default: "My Gallery", placeholder: "My Gallery" });
|
|
672
|
-
const description = await ui.prompt("Enter gallery description", {
|
|
673
|
-
type: "text",
|
|
674
|
-
default: "My gallery with fantastic photos.",
|
|
675
|
-
placeholder: "My gallery with fantastic photos."
|
|
676
|
-
});
|
|
677
|
-
const url = await ui.prompt("Enter the URL where the gallery will be hosted (important for social media image)", {
|
|
678
|
-
type: "text",
|
|
679
|
-
default: "",
|
|
680
|
-
placeholder: ""
|
|
681
|
-
});
|
|
682
|
-
const headerImageName = await ui.prompt("Enter the name of the header image", {
|
|
683
|
-
type: "text",
|
|
684
|
-
default: defaultImage,
|
|
685
|
-
placeholder: defaultImage
|
|
686
|
-
});
|
|
687
|
-
const headerImage = path4.join("..", headerImageName);
|
|
688
|
-
return { title, description, url, headerImage };
|
|
1019
|
+
return updates;
|
|
689
1020
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
headerImage: relativeMediaFiles[0]?.path || "",
|
|
704
|
-
metadata: {},
|
|
705
|
-
sections: [
|
|
706
|
-
{
|
|
707
|
-
images: relativeMediaFiles
|
|
708
|
-
}
|
|
709
|
-
],
|
|
710
|
-
subGalleries: {
|
|
711
|
-
title: "Sub Galleries",
|
|
712
|
-
galleries: relativeSubGalleries
|
|
1021
|
+
var ApiTelemetryClient = class {
|
|
1022
|
+
constructor() {
|
|
1023
|
+
this.endpoint = "https://tools.simple.photo/api/telemetry";
|
|
1024
|
+
}
|
|
1025
|
+
async record(event) {
|
|
1026
|
+
try {
|
|
1027
|
+
axios.post(this.endpoint, event, {
|
|
1028
|
+
headers: {
|
|
1029
|
+
"content-type": "application/json",
|
|
1030
|
+
"user-agent": `simple-photo-gallery/${event.packageVersion} (${process3.platform}; ${process3.arch})`
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
} catch {
|
|
713
1034
|
}
|
|
714
|
-
};
|
|
715
|
-
if (!useDefaultSettings) {
|
|
716
|
-
galleryData = {
|
|
717
|
-
...galleryData,
|
|
718
|
-
...await getGallerySettingsFromUser(
|
|
719
|
-
path4.basename(path4.join(galleryDir, "..")),
|
|
720
|
-
path4.basename(mediaFiles[0]?.path || ""),
|
|
721
|
-
ui
|
|
722
|
-
)
|
|
723
|
-
};
|
|
724
1035
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
async
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
1036
|
+
};
|
|
1037
|
+
var ConsoleTelemetryClient = class {
|
|
1038
|
+
async record(event) {
|
|
1039
|
+
const serialized = JSON.stringify(event, null, 2);
|
|
1040
|
+
stdout.write(`TELEMETRY EVENT: ${serialized}
|
|
1041
|
+
`);
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
|
|
1045
|
+
// src/modules/telemetry/service/index.ts
|
|
1046
|
+
var CONFIG_KEY = "telemetryEnabled";
|
|
1047
|
+
var TelemetryService = class {
|
|
1048
|
+
constructor(packageName, packageVersion, ui) {
|
|
1049
|
+
this.packageName = packageName;
|
|
1050
|
+
this.packageVersion = packageVersion;
|
|
1051
|
+
this.ui = ui;
|
|
1052
|
+
this.config = new Conf({ projectName: packageName });
|
|
1053
|
+
}
|
|
1054
|
+
/** Returns the stored telemetry preference, if any. */
|
|
1055
|
+
getStoredPreference() {
|
|
1056
|
+
return this.config.get(CONFIG_KEY);
|
|
1057
|
+
}
|
|
1058
|
+
/** Updates the persisted telemetry preference. */
|
|
1059
|
+
setStoredPreference(enabled) {
|
|
1060
|
+
this.config.set(CONFIG_KEY, enabled);
|
|
1061
|
+
}
|
|
1062
|
+
/** Determines whether telemetry should be collected for this run. */
|
|
1063
|
+
async shouldCollectTelemetry(override) {
|
|
1064
|
+
if (override) {
|
|
1065
|
+
return override === "1";
|
|
1066
|
+
}
|
|
1067
|
+
if (process3.env.CI || process3.env.DO_NOT_TRACK) {
|
|
1068
|
+
return false;
|
|
1069
|
+
}
|
|
1070
|
+
if (process3.env.SPG_TELEMETRY) {
|
|
1071
|
+
return process3.env.SPG_TELEMETRY === "1";
|
|
1072
|
+
}
|
|
1073
|
+
const stored = this.getStoredPreference();
|
|
1074
|
+
if (stored === void 0) {
|
|
1075
|
+
const consent = await this.promptForConsent();
|
|
1076
|
+
this.setStoredPreference(consent);
|
|
1077
|
+
return consent;
|
|
1078
|
+
} else {
|
|
1079
|
+
return stored;
|
|
748
1080
|
}
|
|
749
1081
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
const
|
|
1082
|
+
/** Builds and dispatches a telemetry event when telemetry is enabled. */
|
|
1083
|
+
async emit(params) {
|
|
1084
|
+
const event = this.buildEvent(params);
|
|
753
1085
|
try {
|
|
754
|
-
await
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
);
|
|
1086
|
+
const client = await this.getClient();
|
|
1087
|
+
if (client) {
|
|
1088
|
+
await client.record(event);
|
|
1089
|
+
}
|
|
759
1090
|
} catch (error) {
|
|
760
|
-
ui.
|
|
761
|
-
throw error;
|
|
1091
|
+
this.ui.debug("Error recording telemetry event", error);
|
|
762
1092
|
}
|
|
763
1093
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1094
|
+
/** Builds a telemetry event. */
|
|
1095
|
+
buildEvent({
|
|
1096
|
+
command,
|
|
1097
|
+
argumentsProvided,
|
|
1098
|
+
globalOptionsProvided,
|
|
1099
|
+
metrics,
|
|
1100
|
+
success,
|
|
1101
|
+
error,
|
|
1102
|
+
startedAt
|
|
1103
|
+
}) {
|
|
1104
|
+
const now = Date.now();
|
|
1105
|
+
const event = {
|
|
1106
|
+
command,
|
|
1107
|
+
argumentsProvided,
|
|
1108
|
+
globalOptionsProvided,
|
|
1109
|
+
timestamp: new Date(now).toISOString(),
|
|
1110
|
+
durationMs: now - startedAt,
|
|
1111
|
+
packageName: this.packageName,
|
|
1112
|
+
packageVersion: this.packageVersion,
|
|
1113
|
+
nodeVersion: process3.version,
|
|
1114
|
+
osPlatform: os.platform(),
|
|
1115
|
+
osRelease: os.release(),
|
|
1116
|
+
osArch: os.arch(),
|
|
1117
|
+
result: success ? "success" : "error"
|
|
771
1118
|
};
|
|
1119
|
+
if (metrics && Object.keys(metrics).length > 0) {
|
|
1120
|
+
event.metrics = metrics;
|
|
1121
|
+
}
|
|
1122
|
+
if (!success && error) {
|
|
1123
|
+
event.errorName = error.name;
|
|
1124
|
+
event.errorMessage = error.message;
|
|
1125
|
+
}
|
|
1126
|
+
return event;
|
|
772
1127
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
const scanPath = path4.resolve(options.photos);
|
|
778
|
-
const outputPath = options.gallery ? path4.resolve(options.gallery) : scanPath;
|
|
779
|
-
const result = await processDirectory(scanPath, outputPath, options.recursive, options.default, ui);
|
|
780
|
-
ui.box(
|
|
781
|
-
`Created ${result.totalGalleries} ${result.totalGalleries === 1 ? "gallery" : "galleries"} with ${result.totalFiles} media ${result.totalFiles === 1 ? "file" : "files"}`
|
|
1128
|
+
/** Prompts the user for consent to collect telemetry. */
|
|
1129
|
+
async promptForConsent() {
|
|
1130
|
+
this.ui.info(
|
|
1131
|
+
"Simple Photo Gallery collects anonymous usage telemetry to improve the CLI. No personal data or information about your photos is collected. For more information, see https://simple.photo/telemetry."
|
|
782
1132
|
);
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1133
|
+
const answer = await this.ui.prompt("Would you like to enable telemetry?", {
|
|
1134
|
+
type: "confirm",
|
|
1135
|
+
initial: true
|
|
1136
|
+
});
|
|
1137
|
+
if (!answer) {
|
|
1138
|
+
this.ui.info('Anonymous telemetry disabled. You can re-enable it with "spg telemetry 1".');
|
|
1139
|
+
return false;
|
|
1140
|
+
}
|
|
1141
|
+
this.ui.success("Thank you! Telemetry will help us improve Simple Photo Gallery.");
|
|
1142
|
+
return true;
|
|
786
1143
|
}
|
|
787
|
-
|
|
1144
|
+
/** Returns the telemetry client. */
|
|
1145
|
+
getClient() {
|
|
1146
|
+
if (!this.client) {
|
|
1147
|
+
switch (process3.env.SPG_TELEMETRY_PROVIDER) {
|
|
1148
|
+
case "none": {
|
|
1149
|
+
this.client = void 0;
|
|
1150
|
+
break;
|
|
1151
|
+
}
|
|
1152
|
+
case "console": {
|
|
1153
|
+
this.client = new ConsoleTelemetryClient();
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
case "api": {
|
|
1157
|
+
this.client = new ApiTelemetryClient();
|
|
1158
|
+
break;
|
|
1159
|
+
}
|
|
1160
|
+
default: {
|
|
1161
|
+
this.client = new ApiTelemetryClient();
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return this.client;
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
788
1169
|
var NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
789
1170
|
var CHECK_TIMEOUT_MS = 3e3;
|
|
790
1171
|
async function fetchLatestStableVersion(packageName) {
|
|
@@ -855,11 +1236,12 @@ async function waitForUpdateCheck(checkPromise) {
|
|
|
855
1236
|
// package.json
|
|
856
1237
|
var package_default = {
|
|
857
1238
|
name: "simple-photo-gallery",
|
|
858
|
-
version: "2.0.
|
|
1239
|
+
version: "2.0.10-rc.1"};
|
|
859
1240
|
|
|
860
1241
|
// src/index.ts
|
|
861
1242
|
var program = new Command();
|
|
862
|
-
|
|
1243
|
+
var telemetryService = new TelemetryService(package_default.name, package_default.version, createConsolaUI(program.opts()));
|
|
1244
|
+
program.name("gallery").description("Simple Photo Gallery CLI").version(package_default.version).option("-v, --verbose", "Verbose output (debug level)", false).option("-q, --quiet", "Minimal output (only warnings/errors)", false).option("--telemetry <state>", "Enable (1) or disable (0) telemetry for this command", parseTelemetryOption).showHelpAfterError(true);
|
|
863
1245
|
function createConsolaUI(globalOpts) {
|
|
864
1246
|
let level = LogLevels.info;
|
|
865
1247
|
if (globalOpts.quiet) {
|
|
@@ -871,33 +1253,62 @@ function createConsolaUI(globalOpts) {
|
|
|
871
1253
|
level
|
|
872
1254
|
}).withTag("simple-photo-gallery");
|
|
873
1255
|
}
|
|
874
|
-
function
|
|
875
|
-
return
|
|
1256
|
+
function collectCommandArguments(command) {
|
|
1257
|
+
return command.options.map((option) => ({ name: option.attributeName(), source: command.getOptionValueSource(option.attributeName()) })).filter((option) => option.source && option.source !== "default").map((option) => option.name);
|
|
1258
|
+
}
|
|
1259
|
+
function collectGlobalArguments() {
|
|
1260
|
+
return program.options.map((option) => ({ name: option.attributeName(), source: program.getOptionValueSource(option.attributeName()) })).filter((option) => option.source && option.source !== "default").map((option) => option.name);
|
|
1261
|
+
}
|
|
1262
|
+
function withCommandContext(handler) {
|
|
1263
|
+
return async (rawOpts, command) => {
|
|
1264
|
+
const { telemetry: telemetryOverride, ...commandOptions } = rawOpts;
|
|
876
1265
|
const ui = createConsolaUI(program.opts());
|
|
1266
|
+
const shouldCollectTelemetry = await telemetryService.shouldCollectTelemetry(telemetryOverride);
|
|
1267
|
+
const startedAt = Date.now();
|
|
877
1268
|
const updateCheckPromise = checkForUpdates(package_default.name, package_default.version);
|
|
1269
|
+
let success = false;
|
|
1270
|
+
let metrics;
|
|
1271
|
+
let errorInfo;
|
|
878
1272
|
try {
|
|
879
|
-
await handler(
|
|
1273
|
+
const result = await handler(commandOptions, ui, command);
|
|
1274
|
+
if (result && Object.keys(result).length > 0) {
|
|
1275
|
+
metrics = result;
|
|
1276
|
+
}
|
|
1277
|
+
success = true;
|
|
880
1278
|
} catch (error) {
|
|
881
1279
|
ui.debug(error);
|
|
882
|
-
|
|
1280
|
+
errorInfo = error instanceof Error ? { name: error.name, message: error.message } : { name: "UnknownError", message: String(error) };
|
|
1281
|
+
process3.exitCode = 1;
|
|
883
1282
|
}
|
|
884
1283
|
const updateInfo = await waitForUpdateCheck(updateCheckPromise);
|
|
885
1284
|
if (updateInfo) {
|
|
886
1285
|
displayUpdateNotification(updateInfo, ui);
|
|
887
1286
|
}
|
|
1287
|
+
if (shouldCollectTelemetry) {
|
|
1288
|
+
await telemetryService.emit({
|
|
1289
|
+
command: command.name(),
|
|
1290
|
+
argumentsProvided: collectCommandArguments(command),
|
|
1291
|
+
globalOptionsProvided: collectGlobalArguments(),
|
|
1292
|
+
metrics,
|
|
1293
|
+
success,
|
|
1294
|
+
error: errorInfo,
|
|
1295
|
+
startedAt
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
888
1298
|
};
|
|
889
1299
|
}
|
|
890
1300
|
program.command("init").description("Initialize a gallery by scaning a folder for images and videos").option(
|
|
891
1301
|
"-p, --photos <path>",
|
|
892
1302
|
"Path to the folder where the photos are stored. Default: current working directory",
|
|
893
|
-
|
|
1303
|
+
process3.cwd()
|
|
894
1304
|
).option(
|
|
895
1305
|
"-g, --gallery <path>",
|
|
896
1306
|
"Path to the directory where the gallery will be initialized. Default: same directory as the photos folder"
|
|
897
|
-
).option("-r, --recursive", "Recursively create galleries from all photos subdirectories", false).option("-d, --default", "Use default gallery settings instead of asking the user", false).action(
|
|
898
|
-
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",
|
|
899
|
-
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",
|
|
900
|
-
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",
|
|
1307
|
+
).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)));
|
|
1308
|
+
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)));
|
|
1309
|
+
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").action(withCommandContext((options, ui) => build(options, ui)));
|
|
1310
|
+
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)));
|
|
1311
|
+
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)));
|
|
901
1312
|
program.parse();
|
|
902
1313
|
//# sourceMappingURL=index.js.map
|
|
903
1314
|
//# sourceMappingURL=index.js.map
|