@gallop.software/studio 2.3.173 → 2.4.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 +307 -30
- package/bin/studio.mjs +65 -20
- package/dist/cli/index.js +1355 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client/assets/index-2UG4789Y.js +136 -0
- package/dist/client/assets/index-CdEhFsGP.js +136 -0
- package/dist/client/assets/index-Df8S-Sk8.js +138 -0
- package/dist/client/assets/index-SbUQtkcp.js +136 -0
- package/dist/client/index.html +1 -1
- package/dist/server/index.js +2512 -2446
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1355 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/config/workspace.ts
|
|
13
|
+
import path from "path";
|
|
14
|
+
function getWorkspace() {
|
|
15
|
+
if (workspacePath === null) {
|
|
16
|
+
workspacePath = process.env.STUDIO_WORKSPACE || process.cwd();
|
|
17
|
+
}
|
|
18
|
+
return workspacePath;
|
|
19
|
+
}
|
|
20
|
+
function getPublicPath(...segments) {
|
|
21
|
+
return path.join(getWorkspace(), "public", ...segments);
|
|
22
|
+
}
|
|
23
|
+
function getDataPath(...segments) {
|
|
24
|
+
return path.join(getWorkspace(), "_data", ...segments);
|
|
25
|
+
}
|
|
26
|
+
function getWorkspacePath(...segments) {
|
|
27
|
+
return path.join(getWorkspace(), ...segments);
|
|
28
|
+
}
|
|
29
|
+
var workspacePath;
|
|
30
|
+
var init_workspace = __esm({
|
|
31
|
+
"src/config/workspace.ts"() {
|
|
32
|
+
"use strict";
|
|
33
|
+
workspacePath = null;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// src/config/index.ts
|
|
38
|
+
var init_config = __esm({
|
|
39
|
+
"src/config/index.ts"() {
|
|
40
|
+
"use strict";
|
|
41
|
+
init_workspace();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// src/handlers/utils/meta.ts
|
|
46
|
+
import { promises as fs } from "fs";
|
|
47
|
+
import path2 from "path";
|
|
48
|
+
async function loadMeta() {
|
|
49
|
+
const metaPath = getDataPath("_studio.json");
|
|
50
|
+
try {
|
|
51
|
+
const content = await fs.readFile(metaPath, "utf-8");
|
|
52
|
+
return JSON.parse(content);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
if (err instanceof SyntaxError) {
|
|
58
|
+
const backupPath = metaPath + ".corrupt." + Date.now();
|
|
59
|
+
try {
|
|
60
|
+
await fs.rename(metaPath, backupPath);
|
|
61
|
+
console.warn(`[studio] _studio.json was corrupted. Backed up to ${path2.basename(backupPath)}`);
|
|
62
|
+
} catch {
|
|
63
|
+
console.warn("[studio] _studio.json was corrupted and could not be backed up");
|
|
64
|
+
}
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function saveMeta(meta) {
|
|
71
|
+
const dataDir = getDataPath();
|
|
72
|
+
await fs.mkdir(dataDir, { recursive: true });
|
|
73
|
+
const metaPath = getDataPath("_studio.json");
|
|
74
|
+
const ordered = {};
|
|
75
|
+
if (meta._cdns) {
|
|
76
|
+
ordered._cdns = meta._cdns;
|
|
77
|
+
}
|
|
78
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
79
|
+
if (key !== "_cdns") {
|
|
80
|
+
ordered[key] = value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const tempPath = metaPath + ".tmp";
|
|
84
|
+
await fs.writeFile(tempPath, JSON.stringify(ordered, null, 2));
|
|
85
|
+
await fs.rename(tempPath, metaPath);
|
|
86
|
+
}
|
|
87
|
+
function getCdnUrls(meta) {
|
|
88
|
+
return meta._cdns || [];
|
|
89
|
+
}
|
|
90
|
+
function getOrAddCdnIndex(meta, cdnUrl) {
|
|
91
|
+
if (!meta._cdns) {
|
|
92
|
+
meta._cdns = [];
|
|
93
|
+
}
|
|
94
|
+
const normalizedUrl = cdnUrl.replace(/\/$/, "");
|
|
95
|
+
const existingIndex = meta._cdns.indexOf(normalizedUrl);
|
|
96
|
+
if (existingIndex >= 0) {
|
|
97
|
+
return existingIndex;
|
|
98
|
+
}
|
|
99
|
+
meta._cdns.push(normalizedUrl);
|
|
100
|
+
return meta._cdns.length - 1;
|
|
101
|
+
}
|
|
102
|
+
function getMetaEntry(meta, key) {
|
|
103
|
+
if (key.startsWith("_")) return void 0;
|
|
104
|
+
const value = meta[key];
|
|
105
|
+
if (Array.isArray(value)) return void 0;
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
function getFileEntries(meta) {
|
|
109
|
+
return Object.entries(meta).filter(
|
|
110
|
+
([key, value]) => !key.startsWith("_") && !Array.isArray(value)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
var init_meta = __esm({
|
|
114
|
+
"src/handlers/utils/meta.ts"() {
|
|
115
|
+
"use strict";
|
|
116
|
+
init_config();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// src/handlers/utils/files.ts
|
|
121
|
+
import path3 from "path";
|
|
122
|
+
function slugifyFilename(filename) {
|
|
123
|
+
const ext = path3.extname(filename).toLowerCase();
|
|
124
|
+
const baseName = path3.basename(filename, path3.extname(filename));
|
|
125
|
+
const slugged = baseName.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[_\s]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
126
|
+
const finalSlug = slugged || "file";
|
|
127
|
+
return finalSlug + ext;
|
|
128
|
+
}
|
|
129
|
+
function isImageFile(filename) {
|
|
130
|
+
const ext = path3.extname(filename).toLowerCase();
|
|
131
|
+
return [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico", ".bmp", ".tiff", ".tif"].includes(ext);
|
|
132
|
+
}
|
|
133
|
+
function isMediaFile(filename) {
|
|
134
|
+
const ext = path3.extname(filename).toLowerCase();
|
|
135
|
+
if ([".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico", ".bmp", ".tiff", ".tif"].includes(ext)) return true;
|
|
136
|
+
if ([".mp4", ".webm", ".mov", ".avi", ".mkv", ".m4v"].includes(ext)) return true;
|
|
137
|
+
if ([".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac"].includes(ext)) return true;
|
|
138
|
+
if ([".pdf", ".json"].includes(ext)) return true;
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
function getContentType(filePath) {
|
|
142
|
+
const ext = path3.extname(filePath).toLowerCase();
|
|
143
|
+
switch (ext) {
|
|
144
|
+
case ".jpg":
|
|
145
|
+
case ".jpeg":
|
|
146
|
+
return "image/jpeg";
|
|
147
|
+
case ".png":
|
|
148
|
+
return "image/png";
|
|
149
|
+
case ".gif":
|
|
150
|
+
return "image/gif";
|
|
151
|
+
case ".webp":
|
|
152
|
+
return "image/webp";
|
|
153
|
+
case ".svg":
|
|
154
|
+
return "image/svg+xml";
|
|
155
|
+
default:
|
|
156
|
+
return "application/octet-stream";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
var init_files = __esm({
|
|
160
|
+
"src/handlers/utils/files.ts"() {
|
|
161
|
+
"use strict";
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// src/handlers/utils/thumbnails.ts
|
|
166
|
+
import { promises as fs2 } from "fs";
|
|
167
|
+
import path4 from "path";
|
|
168
|
+
import sharp from "sharp";
|
|
169
|
+
async function processImage(buffer, imageKey) {
|
|
170
|
+
const rotatedBuffer = await sharp(buffer).rotate().toBuffer();
|
|
171
|
+
const metadata = await sharp(rotatedBuffer).metadata();
|
|
172
|
+
const originalWidth = metadata.width || 0;
|
|
173
|
+
const originalHeight = metadata.height || 0;
|
|
174
|
+
const ratio = originalHeight / originalWidth;
|
|
175
|
+
const keyWithoutSlash = imageKey.startsWith("/") ? imageKey.slice(1) : imageKey;
|
|
176
|
+
const baseName = path4.basename(
|
|
177
|
+
keyWithoutSlash,
|
|
178
|
+
path4.extname(keyWithoutSlash)
|
|
179
|
+
);
|
|
180
|
+
const ext = path4.extname(keyWithoutSlash).toLowerCase();
|
|
181
|
+
const imageDir = path4.dirname(keyWithoutSlash);
|
|
182
|
+
const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
|
|
183
|
+
await fs2.mkdir(imagesPath, { recursive: true });
|
|
184
|
+
const isPng = ext === ".png";
|
|
185
|
+
const outputExt = isPng ? ".png" : ".jpg";
|
|
186
|
+
const entry = {
|
|
187
|
+
o: { w: originalWidth, h: originalHeight }
|
|
188
|
+
};
|
|
189
|
+
const fullFileName = imageDir === "." ? `${baseName}${outputExt}` : `${imageDir}/${baseName}${outputExt}`;
|
|
190
|
+
const fullPath = getPublicPath("images", fullFileName);
|
|
191
|
+
let fullWidth = originalWidth;
|
|
192
|
+
let fullHeight = originalHeight;
|
|
193
|
+
if (originalWidth > FULL_MAX_WIDTH) {
|
|
194
|
+
fullWidth = FULL_MAX_WIDTH;
|
|
195
|
+
fullHeight = Math.round(FULL_MAX_WIDTH * ratio);
|
|
196
|
+
if (isPng) {
|
|
197
|
+
await sharp(rotatedBuffer).resize(fullWidth, fullHeight).png({ quality: 85 }).toFile(fullPath);
|
|
198
|
+
} else {
|
|
199
|
+
await sharp(rotatedBuffer).resize(fullWidth, fullHeight).jpeg({ quality: 85 }).toFile(fullPath);
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
if (isPng) {
|
|
203
|
+
await sharp(rotatedBuffer).png({ quality: 85 }).toFile(fullPath);
|
|
204
|
+
} else {
|
|
205
|
+
await sharp(rotatedBuffer).jpeg({ quality: 85 }).toFile(fullPath);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
entry.f = { w: fullWidth, h: fullHeight };
|
|
209
|
+
for (const [, sizeConfig] of Object.entries(DEFAULT_SIZES)) {
|
|
210
|
+
const { width: maxWidth, suffix, key } = sizeConfig;
|
|
211
|
+
if (originalWidth <= maxWidth) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const newHeight = Math.round(maxWidth * ratio);
|
|
215
|
+
const sizeFileName = `${baseName}${suffix}${outputExt}`;
|
|
216
|
+
const sizeFilePath = imageDir === "." ? sizeFileName : `${imageDir}/${sizeFileName}`;
|
|
217
|
+
const sizePath = getPublicPath("images", sizeFilePath);
|
|
218
|
+
if (isPng) {
|
|
219
|
+
await sharp(rotatedBuffer).resize(maxWidth, newHeight).png({ quality: 80 }).toFile(sizePath);
|
|
220
|
+
} else {
|
|
221
|
+
await sharp(rotatedBuffer).resize(maxWidth, newHeight).jpeg({ quality: 80 }).toFile(sizePath);
|
|
222
|
+
}
|
|
223
|
+
entry[key] = { w: maxWidth, h: newHeight };
|
|
224
|
+
}
|
|
225
|
+
return entry;
|
|
226
|
+
}
|
|
227
|
+
var FULL_MAX_WIDTH, DEFAULT_SIZES;
|
|
228
|
+
var init_thumbnails = __esm({
|
|
229
|
+
"src/handlers/utils/thumbnails.ts"() {
|
|
230
|
+
"use strict";
|
|
231
|
+
init_config();
|
|
232
|
+
FULL_MAX_WIDTH = 2560;
|
|
233
|
+
DEFAULT_SIZES = {
|
|
234
|
+
small: { width: 300, suffix: "-sm", key: "sm" },
|
|
235
|
+
medium: { width: 700, suffix: "-md", key: "md" },
|
|
236
|
+
large: { width: 1400, suffix: "-lg", key: "lg" }
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// src/types.ts
|
|
242
|
+
function getThumbnailPath(originalPath, size) {
|
|
243
|
+
if (size === "full") {
|
|
244
|
+
const ext2 = originalPath.match(/\.\w+$/)?.[0] || ".jpg";
|
|
245
|
+
const base2 = originalPath.replace(/\.\w+$/, "");
|
|
246
|
+
const outputExt2 = ext2.toLowerCase() === ".png" ? ".png" : ".jpg";
|
|
247
|
+
return `/images${base2}${outputExt2}`;
|
|
248
|
+
}
|
|
249
|
+
const ext = originalPath.match(/\.\w+$/)?.[0] || ".jpg";
|
|
250
|
+
const base = originalPath.replace(/\.\w+$/, "");
|
|
251
|
+
const outputExt = ext.toLowerCase() === ".png" ? ".png" : ".jpg";
|
|
252
|
+
return `/images${base}-${size}${outputExt}`;
|
|
253
|
+
}
|
|
254
|
+
function getAllThumbnailPaths(originalPath) {
|
|
255
|
+
return [
|
|
256
|
+
getThumbnailPath(originalPath, "full"),
|
|
257
|
+
getThumbnailPath(originalPath, "lg"),
|
|
258
|
+
getThumbnailPath(originalPath, "md"),
|
|
259
|
+
getThumbnailPath(originalPath, "sm")
|
|
260
|
+
];
|
|
261
|
+
}
|
|
262
|
+
function isProcessed(entry) {
|
|
263
|
+
if (!entry) return false;
|
|
264
|
+
return !!(entry.f || entry.lg || entry.md || entry.sm);
|
|
265
|
+
}
|
|
266
|
+
var init_types = __esm({
|
|
267
|
+
"src/types.ts"() {
|
|
268
|
+
"use strict";
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// src/handlers/utils/cdn.ts
|
|
273
|
+
import { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand, CopyObjectCommand } from "@aws-sdk/client-s3";
|
|
274
|
+
function getR2Client() {
|
|
275
|
+
const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
|
|
276
|
+
const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
|
|
277
|
+
const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
|
|
278
|
+
if (!accountId || !accessKeyId || !secretAccessKey) {
|
|
279
|
+
throw new Error("R2 not configured");
|
|
280
|
+
}
|
|
281
|
+
return new S3Client({
|
|
282
|
+
region: "auto",
|
|
283
|
+
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
|
|
284
|
+
credentials: { accessKeyId, secretAccessKey }
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
async function downloadFromCdn(originalPath) {
|
|
288
|
+
const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
|
|
289
|
+
if (!bucketName) throw new Error("R2 bucket not configured");
|
|
290
|
+
const r2 = getR2Client();
|
|
291
|
+
const maxRetries = 3;
|
|
292
|
+
let lastError;
|
|
293
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
294
|
+
try {
|
|
295
|
+
const response = await r2.send(
|
|
296
|
+
new GetObjectCommand({
|
|
297
|
+
Bucket: bucketName,
|
|
298
|
+
Key: originalPath.replace(/^\//, "")
|
|
299
|
+
})
|
|
300
|
+
);
|
|
301
|
+
const stream = response.Body;
|
|
302
|
+
const chunks = [];
|
|
303
|
+
for await (const chunk of stream) {
|
|
304
|
+
chunks.push(Buffer.from(chunk));
|
|
305
|
+
}
|
|
306
|
+
return Buffer.concat(chunks);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
lastError = error;
|
|
309
|
+
if (attempt < maxRetries - 1) {
|
|
310
|
+
await new Promise((resolve) => setTimeout(resolve, 500 * (attempt + 1)));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
throw lastError || new Error(`Failed to download ${originalPath} after ${maxRetries} attempts`);
|
|
315
|
+
}
|
|
316
|
+
async function downloadFromRemoteUrl(url) {
|
|
317
|
+
const maxRetries = 3;
|
|
318
|
+
let lastError;
|
|
319
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
320
|
+
try {
|
|
321
|
+
const response = await fetch(url);
|
|
322
|
+
if (!response.ok) {
|
|
323
|
+
throw new Error(`Failed to download from ${url}: ${response.status}`);
|
|
324
|
+
}
|
|
325
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
326
|
+
return Buffer.from(arrayBuffer);
|
|
327
|
+
} catch (error) {
|
|
328
|
+
lastError = error;
|
|
329
|
+
if (attempt < maxRetries - 1) {
|
|
330
|
+
await new Promise((resolve) => setTimeout(resolve, 500 * (attempt + 1)));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
throw lastError || new Error(`Failed to download from ${url} after ${maxRetries} attempts`);
|
|
335
|
+
}
|
|
336
|
+
async function deleteThumbnailsFromCdn(imageKey) {
|
|
337
|
+
const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
|
|
338
|
+
if (!bucketName) throw new Error("R2 bucket not configured");
|
|
339
|
+
const r2 = getR2Client();
|
|
340
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
341
|
+
try {
|
|
342
|
+
await r2.send(
|
|
343
|
+
new DeleteObjectCommand({
|
|
344
|
+
Bucket: bucketName,
|
|
345
|
+
Key: thumbPath.replace(/^\//, "")
|
|
346
|
+
})
|
|
347
|
+
);
|
|
348
|
+
} catch {
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async function deleteOriginalFromCdn(imageKey) {
|
|
353
|
+
const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
|
|
354
|
+
if (!bucketName) throw new Error("R2 bucket not configured");
|
|
355
|
+
const r2 = getR2Client();
|
|
356
|
+
try {
|
|
357
|
+
await r2.send(
|
|
358
|
+
new DeleteObjectCommand({
|
|
359
|
+
Bucket: bucketName,
|
|
360
|
+
Key: imageKey.replace(/^\//, "")
|
|
361
|
+
})
|
|
362
|
+
);
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
var init_cdn = __esm({
|
|
367
|
+
"src/handlers/utils/cdn.ts"() {
|
|
368
|
+
"use strict";
|
|
369
|
+
init_types();
|
|
370
|
+
init_files();
|
|
371
|
+
init_config();
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// src/handlers/utils/index.ts
|
|
376
|
+
var init_utils = __esm({
|
|
377
|
+
"src/handlers/utils/index.ts"() {
|
|
378
|
+
"use strict";
|
|
379
|
+
init_meta();
|
|
380
|
+
init_files();
|
|
381
|
+
init_thumbnails();
|
|
382
|
+
init_cdn();
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// src/cli/scan.ts
|
|
387
|
+
var scan_exports = {};
|
|
388
|
+
__export(scan_exports, {
|
|
389
|
+
runScan: () => runScan
|
|
390
|
+
});
|
|
391
|
+
import { promises as fs3 } from "fs";
|
|
392
|
+
import path5 from "path";
|
|
393
|
+
import sharp2 from "sharp";
|
|
394
|
+
async function runScan(_args) {
|
|
395
|
+
console.log("Scanning for media files...");
|
|
396
|
+
const meta = await loadMeta();
|
|
397
|
+
const existingCount = Object.keys(meta).filter((k) => !k.startsWith("_")).length;
|
|
398
|
+
const existingKeys = new Set(Object.keys(meta));
|
|
399
|
+
const added = [];
|
|
400
|
+
const renamed = [];
|
|
401
|
+
const errors = [];
|
|
402
|
+
const orphanedFiles = [];
|
|
403
|
+
const pendingUpdates = [];
|
|
404
|
+
const allFiles = [];
|
|
405
|
+
async function scanDir(dir, relativePath = "") {
|
|
406
|
+
try {
|
|
407
|
+
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
408
|
+
for (const entry of entries) {
|
|
409
|
+
if (entry.name.startsWith(".")) continue;
|
|
410
|
+
const fullPath = path5.join(dir, entry.name);
|
|
411
|
+
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
412
|
+
if (relPath === "images" || relPath.startsWith("images/")) continue;
|
|
413
|
+
if (entry.isDirectory()) {
|
|
414
|
+
await scanDir(fullPath, relPath);
|
|
415
|
+
} else if (isMediaFile(entry.name)) {
|
|
416
|
+
allFiles.push({ relativePath: relPath, fullPath });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
} catch {
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const publicDir = getPublicPath();
|
|
423
|
+
await scanDir(publicDir);
|
|
424
|
+
const total = allFiles.length;
|
|
425
|
+
for (let i = 0; i < allFiles.length; i++) {
|
|
426
|
+
let { relativePath, fullPath } = allFiles[i];
|
|
427
|
+
let imageKey = "/" + relativePath;
|
|
428
|
+
printProgress(i + 1, total, relativePath);
|
|
429
|
+
if (existingKeys.has(imageKey)) {
|
|
430
|
+
const entry = meta[imageKey];
|
|
431
|
+
if (entry?.c !== void 0 && !entry?.u) {
|
|
432
|
+
entry.u = 1;
|
|
433
|
+
pendingUpdates.push(imageKey);
|
|
434
|
+
}
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const dirName = path5.dirname(relativePath);
|
|
438
|
+
const originalFileName = path5.basename(relativePath);
|
|
439
|
+
const sluggedFileName = slugifyFilename(originalFileName);
|
|
440
|
+
if (sluggedFileName !== originalFileName) {
|
|
441
|
+
const newRelativePath = dirName === "." ? sluggedFileName : `${dirName}/${sluggedFileName}`;
|
|
442
|
+
const newFullPath = getPublicPath(newRelativePath);
|
|
443
|
+
const newKey = "/" + newRelativePath;
|
|
444
|
+
if (!meta[newKey] && !existingKeys.has(newKey)) {
|
|
445
|
+
try {
|
|
446
|
+
await fs3.mkdir(path5.dirname(newFullPath), { recursive: true });
|
|
447
|
+
await fs3.rename(fullPath, newFullPath);
|
|
448
|
+
renamed.push({ from: relativePath, to: newRelativePath });
|
|
449
|
+
relativePath = newRelativePath;
|
|
450
|
+
fullPath = newFullPath;
|
|
451
|
+
imageKey = newKey;
|
|
452
|
+
} catch (err) {
|
|
453
|
+
console.error(`
|
|
454
|
+
Failed to slugify ${relativePath}:`, err);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (meta[imageKey]) {
|
|
459
|
+
const ext = path5.extname(relativePath);
|
|
460
|
+
const baseName = relativePath.slice(0, -ext.length);
|
|
461
|
+
let counter = 1;
|
|
462
|
+
let newKey = `/${baseName}-${counter}${ext}`;
|
|
463
|
+
while (meta[newKey]) {
|
|
464
|
+
counter++;
|
|
465
|
+
newKey = `/${baseName}-${counter}${ext}`;
|
|
466
|
+
}
|
|
467
|
+
const newRelativePath = `${baseName}-${counter}${ext}`;
|
|
468
|
+
const newFullPath = getPublicPath(newRelativePath);
|
|
469
|
+
try {
|
|
470
|
+
await fs3.rename(fullPath, newFullPath);
|
|
471
|
+
renamed.push({ from: relativePath, to: newRelativePath });
|
|
472
|
+
relativePath = newRelativePath;
|
|
473
|
+
fullPath = newFullPath;
|
|
474
|
+
imageKey = newKey;
|
|
475
|
+
} catch (err) {
|
|
476
|
+
console.error(`
|
|
477
|
+
Failed to rename ${relativePath}:`, err);
|
|
478
|
+
errors.push(`Failed to rename ${relativePath}`);
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
const isImage = isImageFile(relativePath);
|
|
484
|
+
if (isImage) {
|
|
485
|
+
const ext = path5.extname(relativePath).toLowerCase();
|
|
486
|
+
if (ext === ".svg") {
|
|
487
|
+
meta[imageKey] = { o: { w: 0, h: 0 } };
|
|
488
|
+
} else {
|
|
489
|
+
try {
|
|
490
|
+
const buffer = await fs3.readFile(fullPath);
|
|
491
|
+
const rotatedBuffer = await sharp2(buffer).rotate().toBuffer();
|
|
492
|
+
const metadata = await sharp2(rotatedBuffer).metadata();
|
|
493
|
+
meta[imageKey] = {
|
|
494
|
+
o: { w: metadata.width || 0, h: metadata.height || 0 }
|
|
495
|
+
};
|
|
496
|
+
} catch {
|
|
497
|
+
meta[imageKey] = { o: { w: 0, h: 0 } };
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
} else {
|
|
501
|
+
meta[imageKey] = {};
|
|
502
|
+
}
|
|
503
|
+
existingKeys.add(imageKey);
|
|
504
|
+
added.push(imageKey);
|
|
505
|
+
if (added.length % 10 === 0) {
|
|
506
|
+
await saveMeta(meta);
|
|
507
|
+
}
|
|
508
|
+
} catch (error) {
|
|
509
|
+
console.error(`
|
|
510
|
+
Failed to process ${relativePath}:`, error);
|
|
511
|
+
errors.push(relativePath);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
process.stdout.write("\n");
|
|
515
|
+
console.log(" Checking for orphaned thumbnails...");
|
|
516
|
+
const expectedThumbnails = /* @__PURE__ */ new Set();
|
|
517
|
+
const fileEntries = getFileEntries(meta);
|
|
518
|
+
for (const [imageKey, entry] of fileEntries) {
|
|
519
|
+
if (entry.c === void 0 && isProcessed(entry)) {
|
|
520
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
521
|
+
expectedThumbnails.add(thumbPath);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
async function findOrphans(dir, relativePath = "") {
|
|
526
|
+
try {
|
|
527
|
+
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
528
|
+
for (const entry of entries) {
|
|
529
|
+
if (entry.name.startsWith(".")) continue;
|
|
530
|
+
const fullPath = path5.join(dir, entry.name);
|
|
531
|
+
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
532
|
+
if (entry.isDirectory()) {
|
|
533
|
+
await findOrphans(fullPath, relPath);
|
|
534
|
+
} else if (isImageFile(entry.name)) {
|
|
535
|
+
const publicPath = `/images/${relPath}`;
|
|
536
|
+
if (!expectedThumbnails.has(publicPath)) {
|
|
537
|
+
orphanedFiles.push(publicPath);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
} catch {
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const imagesDir = getPublicPath("images");
|
|
545
|
+
try {
|
|
546
|
+
await findOrphans(imagesDir);
|
|
547
|
+
} catch {
|
|
548
|
+
}
|
|
549
|
+
console.log(" Cleaning up empty folders...");
|
|
550
|
+
let emptyFoldersDeleted = 0;
|
|
551
|
+
async function cleanEmptyFolders(dir) {
|
|
552
|
+
try {
|
|
553
|
+
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
554
|
+
for (const entry of entries) {
|
|
555
|
+
if (entry.name.startsWith(".")) continue;
|
|
556
|
+
if (!entry.isDirectory()) continue;
|
|
557
|
+
const fullPath = path5.join(dir, entry.name);
|
|
558
|
+
if (fullPath === imagesDir) continue;
|
|
559
|
+
await cleanEmptyFolders(fullPath);
|
|
560
|
+
try {
|
|
561
|
+
const subEntries = await fs3.readdir(fullPath);
|
|
562
|
+
const meaningfulEntries = subEntries.filter((e) => !e.startsWith("."));
|
|
563
|
+
if (meaningfulEntries.length === 0) {
|
|
564
|
+
await fs3.rm(fullPath, { recursive: true });
|
|
565
|
+
emptyFoldersDeleted++;
|
|
566
|
+
}
|
|
567
|
+
} catch {
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
} catch {
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
await cleanEmptyFolders(getPublicPath());
|
|
574
|
+
async function cleanImagesEmptyFolders(dir) {
|
|
575
|
+
try {
|
|
576
|
+
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
577
|
+
let isEmpty = true;
|
|
578
|
+
for (const entry of entries) {
|
|
579
|
+
if (entry.isDirectory()) {
|
|
580
|
+
const subDirEmpty = await cleanImagesEmptyFolders(path5.join(dir, entry.name));
|
|
581
|
+
if (!subDirEmpty) isEmpty = false;
|
|
582
|
+
} else if (!entry.name.startsWith(".")) {
|
|
583
|
+
isEmpty = false;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (isEmpty && dir !== imagesDir) {
|
|
587
|
+
await fs3.rm(dir, { recursive: true });
|
|
588
|
+
emptyFoldersDeleted++;
|
|
589
|
+
}
|
|
590
|
+
return isEmpty;
|
|
591
|
+
} catch {
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
try {
|
|
596
|
+
await cleanImagesEmptyFolders(imagesDir);
|
|
597
|
+
} catch {
|
|
598
|
+
}
|
|
599
|
+
console.log(" Checking for orphaned entries...");
|
|
600
|
+
const orphanedEntries = [];
|
|
601
|
+
for (const key of Object.keys(meta)) {
|
|
602
|
+
if (key.startsWith("_")) continue;
|
|
603
|
+
const entry = meta[key];
|
|
604
|
+
if (!entry) continue;
|
|
605
|
+
if (entry.c !== void 0) {
|
|
606
|
+
if (entry.u === 1) {
|
|
607
|
+
const localPath2 = getPublicPath(key);
|
|
608
|
+
try {
|
|
609
|
+
await fs3.access(localPath2);
|
|
610
|
+
} catch {
|
|
611
|
+
delete entry.u;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
const localPath = getPublicPath(key);
|
|
617
|
+
try {
|
|
618
|
+
await fs3.access(localPath);
|
|
619
|
+
} catch {
|
|
620
|
+
orphanedEntries.push(key);
|
|
621
|
+
delete meta[key];
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
await saveMeta(meta);
|
|
625
|
+
const parts = [];
|
|
626
|
+
parts.push(`${existingCount} existing`);
|
|
627
|
+
if (added.length > 0) parts.push(`${added.length} added`);
|
|
628
|
+
if (renamed.length > 0) parts.push(`${renamed.length} renamed`);
|
|
629
|
+
if (errors.length > 0) parts.push(`${errors.length} errors`);
|
|
630
|
+
if (orphanedFiles.length > 0) parts.push(`${orphanedFiles.length} orphaned thumbnails`);
|
|
631
|
+
if (orphanedEntries.length > 0) parts.push(`${orphanedEntries.length} orphaned entries removed`);
|
|
632
|
+
if (pendingUpdates.length > 0) parts.push(`${pendingUpdates.length} pending updates`);
|
|
633
|
+
if (emptyFoldersDeleted > 0) parts.push(`${emptyFoldersDeleted} empty folders removed`);
|
|
634
|
+
printComplete(`Scan complete. ${parts.join(", ")}.`);
|
|
635
|
+
}
|
|
636
|
+
var init_scan = __esm({
|
|
637
|
+
"src/cli/scan.ts"() {
|
|
638
|
+
"use strict";
|
|
639
|
+
init_cli();
|
|
640
|
+
init_utils();
|
|
641
|
+
init_types();
|
|
642
|
+
init_config();
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// src/cli/process.ts
|
|
647
|
+
var process_exports = {};
|
|
648
|
+
__export(process_exports, {
|
|
649
|
+
runProcess: () => runProcess
|
|
650
|
+
});
|
|
651
|
+
import { promises as fs4 } from "fs";
|
|
652
|
+
import path6 from "path";
|
|
653
|
+
async function runProcess(args) {
|
|
654
|
+
const prefix = args[0] || "";
|
|
655
|
+
if (prefix) {
|
|
656
|
+
console.log(`Processing unprocessed images matching "/${prefix}"...`);
|
|
657
|
+
} else {
|
|
658
|
+
console.log("Processing all unprocessed images...");
|
|
659
|
+
}
|
|
660
|
+
const meta = await loadMeta();
|
|
661
|
+
const processed = [];
|
|
662
|
+
const errors = [];
|
|
663
|
+
let alreadyProcessed = 0;
|
|
664
|
+
const imagesToProcess = [];
|
|
665
|
+
for (const [key, entry] of getFileEntries(meta)) {
|
|
666
|
+
const fileName = path6.basename(key);
|
|
667
|
+
if (!isImageFile(fileName)) continue;
|
|
668
|
+
if (prefix && !key.startsWith(`/${prefix}`)) continue;
|
|
669
|
+
if (!isProcessed(entry)) {
|
|
670
|
+
imagesToProcess.push({ key });
|
|
671
|
+
} else {
|
|
672
|
+
alreadyProcessed++;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (imagesToProcess.length === 0) {
|
|
676
|
+
console.log(`No unprocessed images found${prefix ? ` matching "/${prefix}"` : ""}. ${alreadyProcessed} already processed.`);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
const total = imagesToProcess.length;
|
|
680
|
+
console.log(`Found ${total} unprocessed image${total !== 1 ? "s" : ""} (${alreadyProcessed} already processed)`);
|
|
681
|
+
for (let i = 0; i < imagesToProcess.length; i++) {
|
|
682
|
+
const { key } = imagesToProcess[i];
|
|
683
|
+
const fullPath = getPublicPath(key);
|
|
684
|
+
printProgress(i + 1, total, key.slice(1));
|
|
685
|
+
try {
|
|
686
|
+
let buffer;
|
|
687
|
+
try {
|
|
688
|
+
buffer = await fs4.readFile(fullPath);
|
|
689
|
+
} catch {
|
|
690
|
+
printError2(`File not found: ${key}`);
|
|
691
|
+
errors.push(key);
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
const ext = path6.extname(key).toLowerCase();
|
|
695
|
+
const isSvg = ext === ".svg";
|
|
696
|
+
if (isSvg) {
|
|
697
|
+
const imageDir = path6.dirname(key.slice(1));
|
|
698
|
+
const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
|
|
699
|
+
await fs4.mkdir(imagesPath, { recursive: true });
|
|
700
|
+
const fileName = path6.basename(key);
|
|
701
|
+
const destPath = path6.join(imagesPath, fileName);
|
|
702
|
+
await fs4.writeFile(destPath, buffer);
|
|
703
|
+
const existingEntry = meta[key];
|
|
704
|
+
meta[key] = {
|
|
705
|
+
...typeof existingEntry === "object" && !Array.isArray(existingEntry) ? existingEntry : {},
|
|
706
|
+
o: { w: 0, h: 0 },
|
|
707
|
+
f: { w: 0, h: 0 }
|
|
708
|
+
};
|
|
709
|
+
} else {
|
|
710
|
+
const updatedEntry = await processImage(buffer, key);
|
|
711
|
+
meta[key] = updatedEntry;
|
|
712
|
+
}
|
|
713
|
+
await saveMeta(meta);
|
|
714
|
+
processed.push(key);
|
|
715
|
+
} catch (error) {
|
|
716
|
+
console.error(`
|
|
717
|
+
Failed to process ${key}:`, error);
|
|
718
|
+
errors.push(key);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
await saveMeta(meta);
|
|
722
|
+
const parts = [];
|
|
723
|
+
parts.push(`${processed.length} processed`);
|
|
724
|
+
if (errors.length > 0) parts.push(`${errors.length} failed`);
|
|
725
|
+
if (errors.length > 0) {
|
|
726
|
+
printError2(`Processing complete. ${parts.join(", ")}.`);
|
|
727
|
+
} else {
|
|
728
|
+
printComplete(`Processing complete. ${parts.join(", ")}.`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
var init_process = __esm({
|
|
732
|
+
"src/cli/process.ts"() {
|
|
733
|
+
"use strict";
|
|
734
|
+
init_cli();
|
|
735
|
+
init_utils();
|
|
736
|
+
init_types();
|
|
737
|
+
init_config();
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
// src/handlers/utils/folders.ts
|
|
742
|
+
import { promises as fs5 } from "fs";
|
|
743
|
+
import path7 from "path";
|
|
744
|
+
function isHiddenOrSystemFile(filename) {
|
|
745
|
+
if (filename.startsWith(".")) return true;
|
|
746
|
+
const windowsSystemFiles = ["thumbs.db", "desktop.ini", "ehthumbs.db", "ehthumbs_vista.db"];
|
|
747
|
+
if (windowsSystemFiles.includes(filename.toLowerCase())) return true;
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
async function deleteEmptyFolders(folderPath) {
|
|
751
|
+
const publicPath = getPublicPath();
|
|
752
|
+
const normalizedFolder = path7.resolve(folderPath);
|
|
753
|
+
const normalizedPublic = path7.resolve(publicPath);
|
|
754
|
+
if (normalizedFolder === normalizedPublic) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (!normalizedFolder.startsWith(normalizedPublic)) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
try {
|
|
761
|
+
const entries = await fs5.readdir(folderPath);
|
|
762
|
+
const meaningfulEntries = entries.filter((e) => !isHiddenOrSystemFile(e));
|
|
763
|
+
if (meaningfulEntries.length === 0) {
|
|
764
|
+
for (const entry of entries) {
|
|
765
|
+
if (isHiddenOrSystemFile(entry)) {
|
|
766
|
+
try {
|
|
767
|
+
await fs5.unlink(path7.join(folderPath, entry));
|
|
768
|
+
} catch {
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
await fs5.rmdir(folderPath);
|
|
773
|
+
const parentFolder = path7.dirname(folderPath);
|
|
774
|
+
await deleteEmptyFolders(parentFolder);
|
|
775
|
+
}
|
|
776
|
+
} catch {
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
var init_folders = __esm({
|
|
780
|
+
"src/handlers/utils/folders.ts"() {
|
|
781
|
+
"use strict";
|
|
782
|
+
init_config();
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
// src/cli/push.ts
|
|
787
|
+
var push_exports = {};
|
|
788
|
+
__export(push_exports, {
|
|
789
|
+
runPush: () => runPush
|
|
790
|
+
});
|
|
791
|
+
import { promises as fs6 } from "fs";
|
|
792
|
+
import path8 from "path";
|
|
793
|
+
import { S3Client as S3Client2, PutObjectCommand as PutObjectCommand2 } from "@aws-sdk/client-s3";
|
|
794
|
+
async function runPush(args) {
|
|
795
|
+
const prefix = args[0] || "";
|
|
796
|
+
const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
|
|
797
|
+
const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
|
|
798
|
+
const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
|
|
799
|
+
const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
|
|
800
|
+
const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\/\s*$/, "");
|
|
801
|
+
if (!accountId || !accessKeyId || !secretAccessKey || !bucketName || !publicUrl) {
|
|
802
|
+
printError2("R2 not configured. Set CLOUDFLARE_R2_* environment variables in .env.local");
|
|
803
|
+
process.exit(1);
|
|
804
|
+
}
|
|
805
|
+
if (prefix) {
|
|
806
|
+
console.log(`Pushing local images matching "/${prefix}" to CDN...`);
|
|
807
|
+
} else {
|
|
808
|
+
console.log("Pushing all local images to CDN...");
|
|
809
|
+
}
|
|
810
|
+
const meta = await loadMeta();
|
|
811
|
+
const cdnUrls = getCdnUrls(meta);
|
|
812
|
+
const cdnIndex = getOrAddCdnIndex(meta, publicUrl);
|
|
813
|
+
const r2 = new S3Client2({
|
|
814
|
+
region: "auto",
|
|
815
|
+
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
|
|
816
|
+
credentials: { accessKeyId, secretAccessKey }
|
|
817
|
+
});
|
|
818
|
+
const imagesToPush = [];
|
|
819
|
+
for (const [key, entry] of getFileEntries(meta)) {
|
|
820
|
+
if (prefix && !key.startsWith(`/${prefix}`)) continue;
|
|
821
|
+
const existingCdnUrl = entry.c !== void 0 ? cdnUrls[entry.c] : void 0;
|
|
822
|
+
if (existingCdnUrl === publicUrl) continue;
|
|
823
|
+
imagesToPush.push(key);
|
|
824
|
+
}
|
|
825
|
+
if (imagesToPush.length === 0) {
|
|
826
|
+
console.log(`No local images to push${prefix ? ` matching "/${prefix}"` : ""}.`);
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
const total = imagesToPush.length;
|
|
830
|
+
console.log(`Pushing ${total} image${total !== 1 ? "s" : ""} to CDN...`);
|
|
831
|
+
const pushed = [];
|
|
832
|
+
const errors = [];
|
|
833
|
+
const sourceFolders = /* @__PURE__ */ new Set();
|
|
834
|
+
for (let i = 0; i < imagesToPush.length; i++) {
|
|
835
|
+
const imageKey = imagesToPush[i];
|
|
836
|
+
const entry = getMetaEntry(meta, imageKey);
|
|
837
|
+
printProgress(i + 1, total, path8.basename(imageKey));
|
|
838
|
+
if (!entry) {
|
|
839
|
+
errors.push(`Not in meta: ${imageKey}`);
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
const existingCdnUrl = entry.c !== void 0 ? cdnUrls[entry.c] : void 0;
|
|
843
|
+
const isRemote = entry.c !== void 0 && existingCdnUrl !== publicUrl;
|
|
844
|
+
try {
|
|
845
|
+
let originalBuffer;
|
|
846
|
+
if (isRemote && existingCdnUrl) {
|
|
847
|
+
const remoteUrl = `${existingCdnUrl}${imageKey}`;
|
|
848
|
+
originalBuffer = await downloadFromRemoteUrl(remoteUrl);
|
|
849
|
+
} else {
|
|
850
|
+
const originalLocalPath = getPublicPath(imageKey);
|
|
851
|
+
try {
|
|
852
|
+
originalBuffer = await fs6.readFile(originalLocalPath);
|
|
853
|
+
} catch {
|
|
854
|
+
errors.push(`File not found: ${imageKey}`);
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
await r2.send(
|
|
859
|
+
new PutObjectCommand2({
|
|
860
|
+
Bucket: bucketName,
|
|
861
|
+
Key: imageKey.replace(/^\//, ""),
|
|
862
|
+
Body: originalBuffer,
|
|
863
|
+
ContentType: getContentType(imageKey)
|
|
864
|
+
})
|
|
865
|
+
);
|
|
866
|
+
if (!isRemote && isProcessed(entry)) {
|
|
867
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
868
|
+
const localPath = getPublicPath(thumbPath);
|
|
869
|
+
try {
|
|
870
|
+
const fileBuffer = await fs6.readFile(localPath);
|
|
871
|
+
await r2.send(
|
|
872
|
+
new PutObjectCommand2({
|
|
873
|
+
Bucket: bucketName,
|
|
874
|
+
Key: thumbPath.replace(/^\//, ""),
|
|
875
|
+
Body: fileBuffer,
|
|
876
|
+
ContentType: getContentType(thumbPath)
|
|
877
|
+
})
|
|
878
|
+
);
|
|
879
|
+
} catch {
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
entry.c = cdnIndex;
|
|
884
|
+
if (!isRemote) {
|
|
885
|
+
const originalLocalPath = getPublicPath(imageKey);
|
|
886
|
+
sourceFolders.add(path8.dirname(originalLocalPath));
|
|
887
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
888
|
+
const localPath = getPublicPath(thumbPath);
|
|
889
|
+
sourceFolders.add(path8.dirname(localPath));
|
|
890
|
+
try {
|
|
891
|
+
await fs6.unlink(localPath);
|
|
892
|
+
} catch {
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
try {
|
|
896
|
+
await fs6.unlink(originalLocalPath);
|
|
897
|
+
} catch {
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
await saveMeta(meta);
|
|
901
|
+
pushed.push(imageKey);
|
|
902
|
+
} catch (error) {
|
|
903
|
+
console.error(`
|
|
904
|
+
Failed to push ${imageKey}:`, error);
|
|
905
|
+
errors.push(`Failed: ${imageKey}`);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
for (const folder of sourceFolders) {
|
|
909
|
+
await deleteEmptyFolders(folder);
|
|
910
|
+
}
|
|
911
|
+
const parts = [];
|
|
912
|
+
parts.push(`${pushed.length} pushed`);
|
|
913
|
+
if (errors.length > 0) parts.push(`${errors.length} failed`);
|
|
914
|
+
if (errors.length > 0) {
|
|
915
|
+
printError2(`Push complete. ${parts.join(", ")}.`);
|
|
916
|
+
} else {
|
|
917
|
+
printComplete(`Push complete. ${parts.join(", ")}.`);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
var init_push = __esm({
|
|
921
|
+
"src/cli/push.ts"() {
|
|
922
|
+
"use strict";
|
|
923
|
+
init_cli();
|
|
924
|
+
init_utils();
|
|
925
|
+
init_types();
|
|
926
|
+
init_config();
|
|
927
|
+
init_folders();
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
// src/cli/download.ts
|
|
932
|
+
var download_exports = {};
|
|
933
|
+
__export(download_exports, {
|
|
934
|
+
runDownload: () => runDownload
|
|
935
|
+
});
|
|
936
|
+
import { promises as fs7 } from "fs";
|
|
937
|
+
import path9 from "path";
|
|
938
|
+
async function runDownload(args) {
|
|
939
|
+
const prefix = args[0] || "";
|
|
940
|
+
if (prefix) {
|
|
941
|
+
console.log(`Downloading cloud images matching "/${prefix}" to local...`);
|
|
942
|
+
} else {
|
|
943
|
+
console.log("Downloading all cloud images to local...");
|
|
944
|
+
}
|
|
945
|
+
const meta = await loadMeta();
|
|
946
|
+
const imagesToDownload = [];
|
|
947
|
+
for (const [key, entry] of getFileEntries(meta)) {
|
|
948
|
+
if (prefix && !key.startsWith(`/${prefix}`)) continue;
|
|
949
|
+
if (entry.c === void 0) continue;
|
|
950
|
+
imagesToDownload.push(key);
|
|
951
|
+
}
|
|
952
|
+
if (imagesToDownload.length === 0) {
|
|
953
|
+
console.log(`No cloud images to download${prefix ? ` matching "/${prefix}"` : ""}.`);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
const total = imagesToDownload.length;
|
|
957
|
+
console.log(`Downloading ${total} image${total !== 1 ? "s" : ""} from CDN...`);
|
|
958
|
+
const downloaded = [];
|
|
959
|
+
const errors = [];
|
|
960
|
+
for (let i = 0; i < imagesToDownload.length; i++) {
|
|
961
|
+
const imageKey = imagesToDownload[i];
|
|
962
|
+
const entry = getMetaEntry(meta, imageKey);
|
|
963
|
+
printProgress(i + 1, total, path9.basename(imageKey));
|
|
964
|
+
if (!entry || entry.c === void 0) {
|
|
965
|
+
errors.push(`Not on cloud: ${imageKey}`);
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
try {
|
|
969
|
+
const imageBuffer = await downloadFromCdn(imageKey);
|
|
970
|
+
const localPath = getPublicPath(imageKey.replace(/^\//, ""));
|
|
971
|
+
await fs7.mkdir(path9.dirname(localPath), { recursive: true });
|
|
972
|
+
await fs7.writeFile(localPath, imageBuffer);
|
|
973
|
+
await deleteOriginalFromCdn(imageKey);
|
|
974
|
+
await deleteThumbnailsFromCdn(imageKey);
|
|
975
|
+
const wasProcessed = isProcessed(entry);
|
|
976
|
+
delete entry.c;
|
|
977
|
+
if (wasProcessed) {
|
|
978
|
+
const processedEntry = await processImage(imageBuffer, imageKey);
|
|
979
|
+
entry.sm = processedEntry.sm;
|
|
980
|
+
entry.md = processedEntry.md;
|
|
981
|
+
entry.lg = processedEntry.lg;
|
|
982
|
+
entry.f = processedEntry.f;
|
|
983
|
+
}
|
|
984
|
+
await saveMeta(meta);
|
|
985
|
+
downloaded.push(imageKey);
|
|
986
|
+
} catch (error) {
|
|
987
|
+
console.error(`
|
|
988
|
+
Failed to download ${imageKey}:`, error);
|
|
989
|
+
errors.push(imageKey);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
await saveMeta(meta);
|
|
993
|
+
const parts = [];
|
|
994
|
+
parts.push(`${downloaded.length} downloaded`);
|
|
995
|
+
if (errors.length > 0) parts.push(`${errors.length} failed`);
|
|
996
|
+
if (errors.length > 0) {
|
|
997
|
+
printError2(`Download complete. ${parts.join(", ")}.`);
|
|
998
|
+
} else {
|
|
999
|
+
printComplete(`Download complete. ${parts.join(", ")}.`);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
var init_download = __esm({
|
|
1003
|
+
"src/cli/download.ts"() {
|
|
1004
|
+
"use strict";
|
|
1005
|
+
init_cli();
|
|
1006
|
+
init_utils();
|
|
1007
|
+
init_types();
|
|
1008
|
+
init_config();
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
// src/handlers/utils/response.ts
|
|
1013
|
+
var init_response = __esm({
|
|
1014
|
+
"src/handlers/utils/response.ts"() {
|
|
1015
|
+
"use strict";
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
// src/handlers/utils/errors.ts
|
|
1020
|
+
var init_errors = __esm({
|
|
1021
|
+
"src/handlers/utils/errors.ts"() {
|
|
1022
|
+
"use strict";
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
// src/handlers/utils/cancellation.ts
|
|
1027
|
+
var init_cancellation = __esm({
|
|
1028
|
+
"src/handlers/utils/cancellation.ts"() {
|
|
1029
|
+
"use strict";
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
// src/handlers/fonts.ts
|
|
1034
|
+
function parseFontMetadata(filename) {
|
|
1035
|
+
const name = filename.toLowerCase();
|
|
1036
|
+
let weight = "400";
|
|
1037
|
+
let style = "normal";
|
|
1038
|
+
const isVariable = name.includes("variable");
|
|
1039
|
+
if (isVariable) {
|
|
1040
|
+
weight = "100 900";
|
|
1041
|
+
} else {
|
|
1042
|
+
const numericMatch = name.match(/[-_]w?(\d{3})(?:[-_.]|$)/);
|
|
1043
|
+
if (numericMatch) {
|
|
1044
|
+
const num = parseInt(numericMatch[1]);
|
|
1045
|
+
if (num >= 100 && num <= 900 && num % 100 === 0) {
|
|
1046
|
+
weight = String(num);
|
|
1047
|
+
}
|
|
1048
|
+
} else {
|
|
1049
|
+
if (name.includes("ultralight")) weight = weightMap.ultralight;
|
|
1050
|
+
else if (name.includes("extralight")) weight = weightMap.extralight;
|
|
1051
|
+
else if (name.includes("ultrabold")) weight = weightMap.ultrabold;
|
|
1052
|
+
else if (name.includes("extrabold")) weight = weightMap.extrabold;
|
|
1053
|
+
else if (name.includes("demibold")) weight = weightMap.demibold;
|
|
1054
|
+
else if (name.includes("semibold")) weight = weightMap.semibold;
|
|
1055
|
+
else if (name.includes("hairline")) weight = weightMap.hairline;
|
|
1056
|
+
else if (name.includes("thin")) weight = weightMap.thin;
|
|
1057
|
+
else if (name.includes("light")) weight = weightMap.light;
|
|
1058
|
+
else if (name.includes("heavy")) weight = weightMap.heavy;
|
|
1059
|
+
else if (name.includes("black")) weight = weightMap.black;
|
|
1060
|
+
else if (name.includes("bold")) weight = weightMap.bold;
|
|
1061
|
+
else if (name.includes("medium")) weight = weightMap.medium;
|
|
1062
|
+
else if (name.includes("book")) weight = weightMap.book;
|
|
1063
|
+
else if (name.includes("regular")) weight = weightMap.regular;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
if (name.includes("italic")) style = "italic";
|
|
1067
|
+
return { weight, style, isVariable };
|
|
1068
|
+
}
|
|
1069
|
+
var weightMap;
|
|
1070
|
+
var init_fonts = __esm({
|
|
1071
|
+
"src/handlers/fonts.ts"() {
|
|
1072
|
+
"use strict";
|
|
1073
|
+
init_config();
|
|
1074
|
+
init_response();
|
|
1075
|
+
init_files();
|
|
1076
|
+
init_errors();
|
|
1077
|
+
init_cancellation();
|
|
1078
|
+
weightMap = {
|
|
1079
|
+
thin: "100",
|
|
1080
|
+
hairline: "100",
|
|
1081
|
+
extralight: "200",
|
|
1082
|
+
ultralight: "200",
|
|
1083
|
+
light: "300",
|
|
1084
|
+
regular: "400",
|
|
1085
|
+
book: "400",
|
|
1086
|
+
medium: "500",
|
|
1087
|
+
semibold: "600",
|
|
1088
|
+
demibold: "600",
|
|
1089
|
+
bold: "700",
|
|
1090
|
+
extrabold: "800",
|
|
1091
|
+
ultrabold: "800",
|
|
1092
|
+
black: "900",
|
|
1093
|
+
heavy: "900"
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
// src/cli/fonts.ts
|
|
1099
|
+
var fonts_exports = {};
|
|
1100
|
+
__export(fonts_exports, {
|
|
1101
|
+
runFonts: () => runFonts
|
|
1102
|
+
});
|
|
1103
|
+
import { promises as fs8 } from "fs";
|
|
1104
|
+
import path10 from "path";
|
|
1105
|
+
async function runFonts(args) {
|
|
1106
|
+
const subcommand = args[0];
|
|
1107
|
+
if (!subcommand) {
|
|
1108
|
+
console.error("Usage:");
|
|
1109
|
+
console.error(" studio fonts woff2 <folder> Convert TTF/OTF to woff2");
|
|
1110
|
+
console.error(" studio fonts assign <folder> --name <name> Generate src/fonts/<name>.ts");
|
|
1111
|
+
process.exit(1);
|
|
1112
|
+
}
|
|
1113
|
+
switch (subcommand) {
|
|
1114
|
+
case "woff2":
|
|
1115
|
+
await runFontsWoff2(args.slice(1));
|
|
1116
|
+
break;
|
|
1117
|
+
case "assign":
|
|
1118
|
+
await runFontsAssign(args.slice(1));
|
|
1119
|
+
break;
|
|
1120
|
+
default:
|
|
1121
|
+
console.error(`Unknown fonts subcommand: ${subcommand}`);
|
|
1122
|
+
console.error("Available: woff2, assign");
|
|
1123
|
+
process.exit(1);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
async function runFontsWoff2(args) {
|
|
1127
|
+
const folder = args[0];
|
|
1128
|
+
if (!folder) {
|
|
1129
|
+
console.error("Usage: studio fonts woff2 <folder>");
|
|
1130
|
+
console.error(" <folder> is the folder name inside _fonts/");
|
|
1131
|
+
process.exit(1);
|
|
1132
|
+
}
|
|
1133
|
+
const folderPath = getWorkspacePath("_fonts", folder);
|
|
1134
|
+
try {
|
|
1135
|
+
const stat = await fs8.stat(folderPath);
|
|
1136
|
+
if (!stat.isDirectory()) {
|
|
1137
|
+
printError2(`Not a directory: _fonts/${folder}`);
|
|
1138
|
+
process.exit(1);
|
|
1139
|
+
}
|
|
1140
|
+
} catch {
|
|
1141
|
+
printError2(`Folder not found: _fonts/${folder}`);
|
|
1142
|
+
process.exit(1);
|
|
1143
|
+
}
|
|
1144
|
+
const entries = await fs8.readdir(folderPath);
|
|
1145
|
+
const sourceFiles = entries.filter((f) => {
|
|
1146
|
+
const lower = f.toLowerCase();
|
|
1147
|
+
return lower.endsWith(".ttf") || lower.endsWith(".otf");
|
|
1148
|
+
});
|
|
1149
|
+
if (sourceFiles.length === 0) {
|
|
1150
|
+
console.log(`No TTF/OTF files found in _fonts/${folder}/`);
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
console.log(`Converting ${sourceFiles.length} font file${sourceFiles.length !== 1 ? "s" : ""} to woff2...`);
|
|
1154
|
+
const ttf2woff2Module = await import("ttf2woff2");
|
|
1155
|
+
const ttf2woff2 = ttf2woff2Module.default;
|
|
1156
|
+
const converted = [];
|
|
1157
|
+
const errors = [];
|
|
1158
|
+
for (let i = 0; i < sourceFiles.length; i++) {
|
|
1159
|
+
const sourceFile = sourceFiles[i];
|
|
1160
|
+
const sourceExt = path10.extname(sourceFile);
|
|
1161
|
+
const baseName = path10.basename(sourceFile, sourceExt);
|
|
1162
|
+
const woff2Name = baseName + ".woff2";
|
|
1163
|
+
printProgress(i + 1, sourceFiles.length, sourceFile);
|
|
1164
|
+
try {
|
|
1165
|
+
const sourcePath = path10.join(folderPath, sourceFile);
|
|
1166
|
+
const input = await fs8.readFile(sourcePath);
|
|
1167
|
+
const woff2Data = ttf2woff2(input);
|
|
1168
|
+
await fs8.writeFile(path10.join(folderPath, woff2Name), woff2Data);
|
|
1169
|
+
converted.push(woff2Name);
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
console.error(`
|
|
1172
|
+
Failed to convert ${sourceFile}:`, error);
|
|
1173
|
+
errors.push(sourceFile);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (errors.length > 0) {
|
|
1177
|
+
printError2(`Converted ${converted.length} files, ${errors.length} failed.`);
|
|
1178
|
+
} else {
|
|
1179
|
+
printComplete(`Converted ${converted.length} file${converted.length !== 1 ? "s" : ""} to woff2.`);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
async function runFontsAssign(args) {
|
|
1183
|
+
const folder = args[0];
|
|
1184
|
+
let name = "";
|
|
1185
|
+
for (let i = 0; i < args.length; i++) {
|
|
1186
|
+
if (args[i] === "--name" && args[i + 1]) {
|
|
1187
|
+
name = args[i + 1];
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
if (!folder || !name) {
|
|
1192
|
+
console.error("Usage: studio fonts assign <folder> --name <name>");
|
|
1193
|
+
console.error(" <folder> is the folder name inside _fonts/");
|
|
1194
|
+
console.error(" <name> is the variable name for src/fonts/<name>.ts");
|
|
1195
|
+
process.exit(1);
|
|
1196
|
+
}
|
|
1197
|
+
if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(name)) {
|
|
1198
|
+
printError2(`Invalid assignment name: ${name}. Must start with a letter and contain only letters/numbers.`);
|
|
1199
|
+
process.exit(1);
|
|
1200
|
+
}
|
|
1201
|
+
const folderPath = getWorkspacePath("_fonts", folder);
|
|
1202
|
+
try {
|
|
1203
|
+
const stat = await fs8.stat(folderPath);
|
|
1204
|
+
if (!stat.isDirectory()) {
|
|
1205
|
+
printError2(`Not a directory: _fonts/${folder}`);
|
|
1206
|
+
process.exit(1);
|
|
1207
|
+
}
|
|
1208
|
+
} catch {
|
|
1209
|
+
printError2(`Folder not found: _fonts/${folder}`);
|
|
1210
|
+
process.exit(1);
|
|
1211
|
+
}
|
|
1212
|
+
const entries = await fs8.readdir(folderPath);
|
|
1213
|
+
const woff2Files = entries.filter((f) => f.toLowerCase().endsWith(".woff2"));
|
|
1214
|
+
if (woff2Files.length === 0) {
|
|
1215
|
+
printError2(`No woff2 files found in _fonts/${folder}/. Run 'studio fonts woff2 ${folder}' first.`);
|
|
1216
|
+
process.exit(1);
|
|
1217
|
+
}
|
|
1218
|
+
console.log(`Generating font assignment from ${woff2Files.length} woff2 file${woff2Files.length !== 1 ? "s" : ""}...`);
|
|
1219
|
+
const fontMap = woff2Files.map((file) => {
|
|
1220
|
+
const baseName = path10.basename(file, ".woff2");
|
|
1221
|
+
const { weight, style } = parseFontMetadata(baseName);
|
|
1222
|
+
return { path: `${folder}/${file}`, weight, style };
|
|
1223
|
+
});
|
|
1224
|
+
fontMap.sort((a, b) => {
|
|
1225
|
+
const wa = parseInt(a.weight) || 400;
|
|
1226
|
+
const wb = parseInt(b.weight) || 400;
|
|
1227
|
+
if (wa !== wb) return wa - wb;
|
|
1228
|
+
return a.style === "normal" ? -1 : 1;
|
|
1229
|
+
});
|
|
1230
|
+
const variableName = `${name}Font`;
|
|
1231
|
+
const srcArray = fontMap.map((font) => ` { path: '../../_fonts/${font.path}', weight: '${font.weight}', style: '${font.style}' },`).join("\n");
|
|
1232
|
+
const template = `import localFont from 'next/font/local'
|
|
1233
|
+
|
|
1234
|
+
export const ${variableName} = localFont({
|
|
1235
|
+
src: [
|
|
1236
|
+
${srcArray}
|
|
1237
|
+
],
|
|
1238
|
+
display: 'swap',
|
|
1239
|
+
})
|
|
1240
|
+
`;
|
|
1241
|
+
const srcFontsPath = getWorkspacePath("src/fonts");
|
|
1242
|
+
await fs8.mkdir(srcFontsPath, { recursive: true });
|
|
1243
|
+
const filePath = path10.join(srcFontsPath, `${name}.ts`);
|
|
1244
|
+
let overwritten = false;
|
|
1245
|
+
try {
|
|
1246
|
+
await fs8.stat(filePath);
|
|
1247
|
+
overwritten = true;
|
|
1248
|
+
} catch {
|
|
1249
|
+
}
|
|
1250
|
+
await fs8.writeFile(filePath, template, "utf8");
|
|
1251
|
+
if (overwritten) {
|
|
1252
|
+
printComplete(`Overwrote src/fonts/${name}.ts with ${woff2Files.length} font source${woff2Files.length !== 1 ? "s" : ""}.`);
|
|
1253
|
+
} else {
|
|
1254
|
+
printComplete(`Created src/fonts/${name}.ts with ${woff2Files.length} font source${woff2Files.length !== 1 ? "s" : ""}.`);
|
|
1255
|
+
}
|
|
1256
|
+
for (const font of fontMap) {
|
|
1257
|
+
const weightName = getWeightNameLocal(font.weight);
|
|
1258
|
+
console.log(` ${font.path} -> ${weightName} ${font.style}`);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
function getWeightNameLocal(weight) {
|
|
1262
|
+
if (weight === "100 900") return "Variable";
|
|
1263
|
+
const names = {
|
|
1264
|
+
"100": "Thin",
|
|
1265
|
+
"200": "ExtraLight",
|
|
1266
|
+
"300": "Light",
|
|
1267
|
+
"400": "Regular",
|
|
1268
|
+
"500": "Medium",
|
|
1269
|
+
"600": "SemiBold",
|
|
1270
|
+
"700": "Bold",
|
|
1271
|
+
"800": "ExtraBold",
|
|
1272
|
+
"900": "Black"
|
|
1273
|
+
};
|
|
1274
|
+
return names[weight] || weight;
|
|
1275
|
+
}
|
|
1276
|
+
var init_fonts2 = __esm({
|
|
1277
|
+
"src/cli/fonts.ts"() {
|
|
1278
|
+
"use strict";
|
|
1279
|
+
init_cli();
|
|
1280
|
+
init_fonts();
|
|
1281
|
+
init_config();
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
// src/cli/index.ts
|
|
1286
|
+
import { config as loadEnv } from "dotenv";
|
|
1287
|
+
import { join } from "path";
|
|
1288
|
+
import { existsSync } from "fs";
|
|
1289
|
+
function printProgress(current, total, message) {
|
|
1290
|
+
const pct = Math.round(current / total * 100);
|
|
1291
|
+
process.stdout.write(`\r [${current}/${total}] ${pct}% ${message}`);
|
|
1292
|
+
}
|
|
1293
|
+
function printComplete(message) {
|
|
1294
|
+
process.stdout.write("\n");
|
|
1295
|
+
console.log(`\u2713 ${message}`);
|
|
1296
|
+
}
|
|
1297
|
+
function printError2(message) {
|
|
1298
|
+
process.stderr.write("\n");
|
|
1299
|
+
console.error(`\u2717 ${message}`);
|
|
1300
|
+
}
|
|
1301
|
+
async function run(command, workspace, args) {
|
|
1302
|
+
process.env.STUDIO_WORKSPACE = workspace;
|
|
1303
|
+
const envPath = join(workspace, ".env.local");
|
|
1304
|
+
if (existsSync(envPath)) {
|
|
1305
|
+
loadEnv({ path: envPath });
|
|
1306
|
+
}
|
|
1307
|
+
try {
|
|
1308
|
+
switch (command) {
|
|
1309
|
+
case "scan": {
|
|
1310
|
+
const { runScan: runScan2 } = await Promise.resolve().then(() => (init_scan(), scan_exports));
|
|
1311
|
+
await runScan2(args);
|
|
1312
|
+
break;
|
|
1313
|
+
}
|
|
1314
|
+
case "process": {
|
|
1315
|
+
const { runProcess: runProcess2 } = await Promise.resolve().then(() => (init_process(), process_exports));
|
|
1316
|
+
await runProcess2(args);
|
|
1317
|
+
break;
|
|
1318
|
+
}
|
|
1319
|
+
case "push": {
|
|
1320
|
+
const { runPush: runPush2 } = await Promise.resolve().then(() => (init_push(), push_exports));
|
|
1321
|
+
await runPush2(args);
|
|
1322
|
+
break;
|
|
1323
|
+
}
|
|
1324
|
+
case "download": {
|
|
1325
|
+
const { runDownload: runDownload2 } = await Promise.resolve().then(() => (init_download(), download_exports));
|
|
1326
|
+
await runDownload2(args);
|
|
1327
|
+
break;
|
|
1328
|
+
}
|
|
1329
|
+
case "fonts": {
|
|
1330
|
+
const { runFonts: runFonts2 } = await Promise.resolve().then(() => (init_fonts2(), fonts_exports));
|
|
1331
|
+
await runFonts2(args);
|
|
1332
|
+
break;
|
|
1333
|
+
}
|
|
1334
|
+
default:
|
|
1335
|
+
console.error(`Unknown command: ${command}`);
|
|
1336
|
+
console.error("Available commands: scan, process, push, download, fonts");
|
|
1337
|
+
process.exit(1);
|
|
1338
|
+
}
|
|
1339
|
+
} catch (error) {
|
|
1340
|
+
console.error("Command failed:", error instanceof Error ? error.message : error);
|
|
1341
|
+
process.exit(1);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
var init_cli = __esm({
|
|
1345
|
+
"src/cli/index.ts"() {
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
init_cli();
|
|
1349
|
+
export {
|
|
1350
|
+
printComplete,
|
|
1351
|
+
printError2 as printError,
|
|
1352
|
+
printProgress,
|
|
1353
|
+
run
|
|
1354
|
+
};
|
|
1355
|
+
//# sourceMappingURL=index.js.map
|