@epic-web/workshop-utils 6.84.3 → 6.84.5
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,54 @@ 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({
|
|
210
|
+
cacheDir,
|
|
211
|
+
workshopId,
|
|
212
|
+
});
|
|
213
|
+
if (await fsExtra.pathExists(filePath))
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Treat errors as "missing" and attempt to re-create.
|
|
218
|
+
}
|
|
219
|
+
ensuredWorkshopCacheMetadata.delete(workshopId);
|
|
220
|
+
}
|
|
221
|
+
const { displayName, repoName, subtitle } = await getCurrentWorkshopDisplayInfo();
|
|
222
|
+
const metadata = await ensureWorkshopCacheMetadataFile({
|
|
223
|
+
cacheDir,
|
|
224
|
+
workshopId,
|
|
225
|
+
displayName,
|
|
226
|
+
repoName,
|
|
227
|
+
subtitle,
|
|
228
|
+
});
|
|
229
|
+
if (metadata) {
|
|
230
|
+
ensuredWorkshopCacheMetadata.add(workshopId);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
183
233
|
export function makeGlobalFsCache(name) {
|
|
184
234
|
return remember(`global-${name}`, () => {
|
|
185
235
|
const cacheInstanceDir = path.join(cacheDir, 'global', name);
|
|
@@ -419,6 +469,9 @@ export async function getGlobalCaches() {
|
|
|
419
469
|
workshopId: 'global',
|
|
420
470
|
}));
|
|
421
471
|
}
|
|
472
|
+
export async function readWorkshopCacheMetadata(workshopId) {
|
|
473
|
+
return readWorkshopCacheMetadataFile({ cacheDir, workshopId });
|
|
474
|
+
}
|
|
422
475
|
export async function getWorkshopFileCaches() {
|
|
423
476
|
const workshopCacheDir = path.join(cacheDir, getEnv().EPICSHOP_WORKSHOP_INSTANCE_ID);
|
|
424
477
|
const caches = readJsonFilesInDirectory(workshopCacheDir);
|
|
@@ -436,6 +489,7 @@ export async function deleteCache() {
|
|
|
436
489
|
if (await fsExtra.exists(cacheDir)) {
|
|
437
490
|
await fsExtra.remove(cacheDir);
|
|
438
491
|
}
|
|
492
|
+
ensuredWorkshopCacheMetadata.clear();
|
|
439
493
|
}
|
|
440
494
|
catch (error) {
|
|
441
495
|
console.error(`Error deleting the cache in ${cacheDir}`, error);
|
|
@@ -469,6 +523,7 @@ export async function deleteWorkshopCache(workshopId, cacheName) {
|
|
|
469
523
|
if (await fsExtra.exists(workshopCachePath)) {
|
|
470
524
|
await fsExtra.remove(workshopCachePath);
|
|
471
525
|
}
|
|
526
|
+
ensuredWorkshopCacheMetadata.delete(workshopId);
|
|
472
527
|
}
|
|
473
528
|
}
|
|
474
529
|
catch (error) {
|
|
@@ -526,7 +581,8 @@ export function makeSingletonCache(name) {
|
|
|
526
581
|
}
|
|
527
582
|
export function makeSingletonFsCache(name) {
|
|
528
583
|
return remember(name, () => {
|
|
529
|
-
const
|
|
584
|
+
const workshopId = getEnv().EPICSHOP_WORKSHOP_INSTANCE_ID;
|
|
585
|
+
const cacheInstanceDir = path.join(cacheDir, workshopId, name);
|
|
530
586
|
const fsCache = {
|
|
531
587
|
name: `Filesystem cache (${name})`,
|
|
532
588
|
async get(key) {
|
|
@@ -558,6 +614,12 @@ export function makeSingletonFsCache(name) {
|
|
|
558
614
|
return null;
|
|
559
615
|
},
|
|
560
616
|
async set(key, entry) {
|
|
617
|
+
try {
|
|
618
|
+
await ensureWorkshopCacheMetadata(workshopId);
|
|
619
|
+
}
|
|
620
|
+
catch {
|
|
621
|
+
// Ignore: this is optional metadata only.
|
|
622
|
+
}
|
|
561
623
|
const filePath = path.join(cacheInstanceDir, md5(key));
|
|
562
624
|
const tempPath = `${filePath}.tmp`;
|
|
563
625
|
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
|
+
}
|