@epic-web/workshop-utils 6.84.3 → 6.84.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cache.server.d.ts
CHANGED
|
@@ -407,6 +407,14 @@ export declare function getGlobalCaches(): Promise<{
|
|
|
407
407
|
}>;
|
|
408
408
|
}>;
|
|
409
409
|
}[]>;
|
|
410
|
+
export declare function readWorkshopCacheMetadata(workshopId: string): Promise<{
|
|
411
|
+
schemaVersion: 1;
|
|
412
|
+
workshopId: string;
|
|
413
|
+
displayName: string;
|
|
414
|
+
createdAt: number;
|
|
415
|
+
repoName?: string | undefined;
|
|
416
|
+
subtitle?: string | undefined;
|
|
417
|
+
} | null>;
|
|
410
418
|
export declare function getWorkshopFileCaches(): Promise<Record<string, any>>;
|
|
411
419
|
export declare function readEntryByPath(cacheFilePath: string): Promise<any>;
|
|
412
420
|
export declare function deleteCache(): Promise<null | undefined>;
|
package/dist/cache.server.js
CHANGED
|
@@ -11,11 +11,13 @@ import { resolveCacheDir } from "./data-storage.server.js";
|
|
|
11
11
|
import { logger } from "./logger.js";
|
|
12
12
|
import { cachifiedTimingReporter } from "./timing.server.js";
|
|
13
13
|
import { checkConnection } from "./utils.server.js";
|
|
14
|
+
import { ensureWorkshopCacheMetadataFile, getWorkshopCacheMetadataFilePath, readWorkshopCacheMetadataFile, } from "./workshop-cache-metadata.server.js";
|
|
14
15
|
const MAX_CACHE_FILE_SIZE = 3 * 1024 * 1024; // 3MB in bytes
|
|
15
16
|
const cacheDir = resolveCacheDir();
|
|
16
17
|
const log = logger('epic:cache');
|
|
17
18
|
// Throttle repeated Sentry reports for corrupted cache files to reduce noise
|
|
18
19
|
const corruptedReportThrottle = remember('epic:cache:corruption-throttle', () => new LRUCache({ max: 2000, ttl: 60_000 }));
|
|
20
|
+
const ensuredWorkshopCacheMetadata = remember('epic:cache:ensured-workshop-cache-metadata', () => new Set());
|
|
19
21
|
// Format cache time helper function (copied from @epic-web/cachified for consistency)
|
|
20
22
|
function formatCacheTime(metadata, formatDuration) {
|
|
21
23
|
const ttl = metadata?.ttl;
|
|
@@ -180,6 +182,51 @@ export const notificationsCache = makeSingletonCache('NotificationsCache');
|
|
|
180
182
|
export const directoryEmptyCache = makeSingletonCache('DirectoryEmptyCache');
|
|
181
183
|
export const discordCache = makeSingletonFsCache('DiscordCache');
|
|
182
184
|
export const epicApiCache = makeSingletonFsCache('EpicApiCache');
|
|
185
|
+
async function getCurrentWorkshopDisplayInfo() {
|
|
186
|
+
const env = getEnv();
|
|
187
|
+
const contextCwd = env.EPICSHOP_CONTEXT_CWD;
|
|
188
|
+
const repoName = contextCwd
|
|
189
|
+
? path.basename(contextCwd) || undefined
|
|
190
|
+
: undefined;
|
|
191
|
+
let displayName = repoName || env.EPICSHOP_WORKSHOP_INSTANCE_ID || 'Unknown';
|
|
192
|
+
let subtitle;
|
|
193
|
+
try {
|
|
194
|
+
const { getWorkshopConfig } = await import("./config.server.js");
|
|
195
|
+
const config = getWorkshopConfig();
|
|
196
|
+
displayName = config.title || displayName;
|
|
197
|
+
subtitle = config.subtitle;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// Best-effort only: cache metadata should never break caching.
|
|
201
|
+
}
|
|
202
|
+
return { displayName, repoName, subtitle };
|
|
203
|
+
}
|
|
204
|
+
async function ensureWorkshopCacheMetadata(workshopId) {
|
|
205
|
+
if (!workshopId)
|
|
206
|
+
return;
|
|
207
|
+
if (ensuredWorkshopCacheMetadata.has(workshopId)) {
|
|
208
|
+
try {
|
|
209
|
+
const filePath = getWorkshopCacheMetadataFilePath({ cacheDir, workshopId });
|
|
210
|
+
if (await fsExtra.pathExists(filePath))
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// Treat errors as "missing" and attempt to re-create.
|
|
215
|
+
}
|
|
216
|
+
ensuredWorkshopCacheMetadata.delete(workshopId);
|
|
217
|
+
}
|
|
218
|
+
const { displayName, repoName, subtitle } = await getCurrentWorkshopDisplayInfo();
|
|
219
|
+
const metadata = await ensureWorkshopCacheMetadataFile({
|
|
220
|
+
cacheDir,
|
|
221
|
+
workshopId,
|
|
222
|
+
displayName,
|
|
223
|
+
repoName,
|
|
224
|
+
subtitle,
|
|
225
|
+
});
|
|
226
|
+
if (metadata) {
|
|
227
|
+
ensuredWorkshopCacheMetadata.add(workshopId);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
183
230
|
export function makeGlobalFsCache(name) {
|
|
184
231
|
return remember(`global-${name}`, () => {
|
|
185
232
|
const cacheInstanceDir = path.join(cacheDir, 'global', name);
|
|
@@ -419,6 +466,9 @@ export async function getGlobalCaches() {
|
|
|
419
466
|
workshopId: 'global',
|
|
420
467
|
}));
|
|
421
468
|
}
|
|
469
|
+
export async function readWorkshopCacheMetadata(workshopId) {
|
|
470
|
+
return readWorkshopCacheMetadataFile({ cacheDir, workshopId });
|
|
471
|
+
}
|
|
422
472
|
export async function getWorkshopFileCaches() {
|
|
423
473
|
const workshopCacheDir = path.join(cacheDir, getEnv().EPICSHOP_WORKSHOP_INSTANCE_ID);
|
|
424
474
|
const caches = readJsonFilesInDirectory(workshopCacheDir);
|
|
@@ -436,6 +486,7 @@ export async function deleteCache() {
|
|
|
436
486
|
if (await fsExtra.exists(cacheDir)) {
|
|
437
487
|
await fsExtra.remove(cacheDir);
|
|
438
488
|
}
|
|
489
|
+
ensuredWorkshopCacheMetadata.clear();
|
|
439
490
|
}
|
|
440
491
|
catch (error) {
|
|
441
492
|
console.error(`Error deleting the cache in ${cacheDir}`, error);
|
|
@@ -469,6 +520,7 @@ export async function deleteWorkshopCache(workshopId, cacheName) {
|
|
|
469
520
|
if (await fsExtra.exists(workshopCachePath)) {
|
|
470
521
|
await fsExtra.remove(workshopCachePath);
|
|
471
522
|
}
|
|
523
|
+
ensuredWorkshopCacheMetadata.delete(workshopId);
|
|
472
524
|
}
|
|
473
525
|
}
|
|
474
526
|
catch (error) {
|
|
@@ -526,7 +578,8 @@ export function makeSingletonCache(name) {
|
|
|
526
578
|
}
|
|
527
579
|
export function makeSingletonFsCache(name) {
|
|
528
580
|
return remember(name, () => {
|
|
529
|
-
const
|
|
581
|
+
const workshopId = getEnv().EPICSHOP_WORKSHOP_INSTANCE_ID;
|
|
582
|
+
const cacheInstanceDir = path.join(cacheDir, workshopId, name);
|
|
530
583
|
const fsCache = {
|
|
531
584
|
name: `Filesystem cache (${name})`,
|
|
532
585
|
async get(key) {
|
|
@@ -558,6 +611,12 @@ export function makeSingletonFsCache(name) {
|
|
|
558
611
|
return null;
|
|
559
612
|
},
|
|
560
613
|
async set(key, entry) {
|
|
614
|
+
try {
|
|
615
|
+
await ensureWorkshopCacheMetadata(workshopId);
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
// Ignore: this is optional metadata only.
|
|
619
|
+
}
|
|
561
620
|
const filePath = path.join(cacheInstanceDir, md5(key));
|
|
562
621
|
const tempPath = `${filePath}.tmp`;
|
|
563
622
|
await fsExtra.ensureDir(path.dirname(filePath));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
export declare const WORKSHOP_CACHE_METADATA_FILE_NAME: ".workshop-cache-metadata.json";
|
|
3
|
+
declare const WorkshopCacheMetadataSchema: z.ZodObject<{
|
|
4
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
5
|
+
workshopId: z.ZodString;
|
|
6
|
+
displayName: z.ZodString;
|
|
7
|
+
repoName: z.ZodOptional<z.ZodString>;
|
|
8
|
+
subtitle: z.ZodOptional<z.ZodString>;
|
|
9
|
+
createdAt: z.ZodNumber;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
export type WorkshopCacheMetadata = z.infer<typeof WorkshopCacheMetadataSchema>;
|
|
12
|
+
export declare function getWorkshopCacheMetadataFilePath({ cacheDir, workshopId, }: {
|
|
13
|
+
cacheDir: string;
|
|
14
|
+
workshopId: string;
|
|
15
|
+
}): string;
|
|
16
|
+
export declare function readWorkshopCacheMetadataFile({ cacheDir, workshopId, }: {
|
|
17
|
+
cacheDir: string;
|
|
18
|
+
workshopId: string;
|
|
19
|
+
}): Promise<WorkshopCacheMetadata | null>;
|
|
20
|
+
export declare function ensureWorkshopCacheMetadataFile({ cacheDir, workshopId, displayName, repoName, subtitle, }: {
|
|
21
|
+
cacheDir: string;
|
|
22
|
+
workshopId: string;
|
|
23
|
+
displayName: string;
|
|
24
|
+
repoName?: string;
|
|
25
|
+
subtitle?: string;
|
|
26
|
+
}): Promise<WorkshopCacheMetadata | null>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fsExtra from 'fs-extra';
|
|
3
|
+
import z from 'zod';
|
|
4
|
+
export const WORKSHOP_CACHE_METADATA_FILE_NAME = '.workshop-cache-metadata.json';
|
|
5
|
+
const WorkshopCacheMetadataSchema = z.object({
|
|
6
|
+
schemaVersion: z.literal(1),
|
|
7
|
+
workshopId: z.string().min(1),
|
|
8
|
+
displayName: z.string().min(1),
|
|
9
|
+
repoName: z.string().optional(),
|
|
10
|
+
subtitle: z.string().optional(),
|
|
11
|
+
createdAt: z.number(),
|
|
12
|
+
});
|
|
13
|
+
export function getWorkshopCacheMetadataFilePath({ cacheDir, workshopId, }) {
|
|
14
|
+
return path.join(cacheDir, workshopId, WORKSHOP_CACHE_METADATA_FILE_NAME);
|
|
15
|
+
}
|
|
16
|
+
async function writeWorkshopCacheMetadataAtomic({ filePath, metadata, }) {
|
|
17
|
+
const tmpPath = `${filePath}.tmp`;
|
|
18
|
+
await fsExtra.ensureDir(path.dirname(filePath));
|
|
19
|
+
await fsExtra.writeJSON(tmpPath, metadata, { spaces: 2 });
|
|
20
|
+
await fsExtra.move(tmpPath, filePath, { overwrite: true });
|
|
21
|
+
}
|
|
22
|
+
export async function readWorkshopCacheMetadataFile({ cacheDir, workshopId, }) {
|
|
23
|
+
const filePath = getWorkshopCacheMetadataFilePath({ cacheDir, workshopId });
|
|
24
|
+
try {
|
|
25
|
+
const raw = await fsExtra.readJSON(filePath);
|
|
26
|
+
const parsed = WorkshopCacheMetadataSchema.safeParse(raw);
|
|
27
|
+
return parsed.success ? parsed.data : null;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error instanceof Error &&
|
|
31
|
+
'code' in error &&
|
|
32
|
+
error.code === 'ENOENT') {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function ensureWorkshopCacheMetadataFile({ cacheDir, workshopId, displayName, repoName, subtitle, }) {
|
|
39
|
+
const filePath = getWorkshopCacheMetadataFilePath({ cacheDir, workshopId });
|
|
40
|
+
// Fast path: already exists and parses.
|
|
41
|
+
const existing = await readWorkshopCacheMetadataFile({ cacheDir, workshopId });
|
|
42
|
+
if (existing)
|
|
43
|
+
return existing;
|
|
44
|
+
const metadata = {
|
|
45
|
+
schemaVersion: 1,
|
|
46
|
+
workshopId,
|
|
47
|
+
displayName,
|
|
48
|
+
repoName,
|
|
49
|
+
subtitle,
|
|
50
|
+
createdAt: Date.now(),
|
|
51
|
+
};
|
|
52
|
+
try {
|
|
53
|
+
await writeWorkshopCacheMetadataAtomic({ filePath, metadata });
|
|
54
|
+
return metadata;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|