feat: pre-generate Directus image previews
Bundle extension (upload hook + API endpoint + admin module) that warms
Directus image transform cache in-process with an explicit acceptFormat,
so the generated file matches the browser's cache key byte-for-byte.
- resumable, window-aware backfill with progress + cancel
- system + custom presets, effective-format reporting, shadow warnings
- storage breakdown (previews vs originals), human-readable API fields
- diagnostics: resolve-plan prediction, per-file generate, ops ring buffer
- isolated dev/staging compose stack + deploy/rollback scripts
Verified on Directus 11.17.4: 40/40 tests, hash-match gate passed,
idempotent backfill, custom fixed-format presets, error surfacing.
2026-10-25 11:19:54 +03:00
|
|
|
import assert from 'node:assert/strict';
|
|
|
|
|
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
|
|
|
import { tmpdir } from 'node:os';
|
|
|
|
|
import { join } from 'node:path';
|
|
|
|
|
import { test } from 'node:test';
|
2026-10-25 13:52:28 +03:00
|
|
|
import { isPreviewPath, isStatePath, listFileNames, storageUsage } from './storage';
|
feat: pre-generate Directus image previews
Bundle extension (upload hook + API endpoint + admin module) that warms
Directus image transform cache in-process with an explicit acceptFormat,
so the generated file matches the browser's cache key byte-for-byte.
- resumable, window-aware backfill with progress + cancel
- system + custom presets, effective-format reporting, shadow warnings
- storage breakdown (previews vs originals), human-readable API fields
- diagnostics: resolve-plan prediction, per-file generate, ops ring buffer
- isolated dev/staging compose stack + deploy/rollback scripts
Verified on Directus 11.17.4: 40/40 tests, hash-match gate passed,
idempotent backfill, custom fixed-format presets, error surfacing.
2026-10-25 11:19:54 +03:00
|
|
|
|
|
|
|
|
test('isPreviewPath matches transform cache files only', () => {
|
|
|
|
|
assert.equal(isPreviewPath('id__0123456789abcdef0123456789abcdef01234567.avif'), true);
|
|
|
|
|
assert.equal(isPreviewPath('id__0123456789abcdef0123456789abcdef01234567.webp'), true);
|
|
|
|
|
assert.equal(isPreviewPath('id.jpg'), false);
|
|
|
|
|
assert.equal(isPreviewPath('state.jsonl'), false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('isStatePath detects the state directory', () => {
|
|
|
|
|
assert.equal(isStatePath('.image-previews/settings.json'), true);
|
|
|
|
|
assert.equal(isStatePath('.image-previews'), true);
|
|
|
|
|
assert.equal(isStatePath('abc.jpg'), false);
|
|
|
|
|
});
|
|
|
|
|
|
2026-10-25 13:52:28 +03:00
|
|
|
test('listFileNames lists files and excludes the state dir', async () => {
|
|
|
|
|
const dir = await mkdtemp(join(tmpdir(), 'previews-names-'));
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await writeFile(join(dir, 'a__0123456789abcdef0123456789abcdef01234567.avif'), 'x');
|
|
|
|
|
await writeFile(join(dir, 'a.jpg'), 'y');
|
|
|
|
|
await mkdir(join(dir, '.image-previews'));
|
|
|
|
|
await writeFile(join(dir, '.image-previews', 'settings.json'), 'z');
|
|
|
|
|
|
|
|
|
|
const names = await listFileNames({ STORAGE_LOCAL_ROOT: dir, STORAGE_LOCATIONS: 'local' });
|
|
|
|
|
|
|
|
|
|
assert.equal(names.size, 2);
|
|
|
|
|
assert.ok(names.has('a__0123456789abcdef0123456789abcdef01234567.avif'));
|
|
|
|
|
assert.ok(names.has('a.jpg'));
|
|
|
|
|
assert.equal(names.has('settings.json'), false);
|
|
|
|
|
} finally {
|
|
|
|
|
await rm(dir, { recursive: true, force: true });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
feat: pre-generate Directus image previews
Bundle extension (upload hook + API endpoint + admin module) that warms
Directus image transform cache in-process with an explicit acceptFormat,
so the generated file matches the browser's cache key byte-for-byte.
- resumable, window-aware backfill with progress + cancel
- system + custom presets, effective-format reporting, shadow warnings
- storage breakdown (previews vs originals), human-readable API fields
- diagnostics: resolve-plan prediction, per-file generate, ops ring buffer
- isolated dev/staging compose stack + deploy/rollback scripts
Verified on Directus 11.17.4: 40/40 tests, hash-match gate passed,
idempotent backfill, custom fixed-format presets, error surfacing.
2026-10-25 11:19:54 +03:00
|
|
|
test('storageUsage splits previews and originals, excluding state', async () => {
|
|
|
|
|
const dir = await mkdtemp(join(tmpdir(), 'previews-usage-'));
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await writeFile(join(dir, 'a__0123456789abcdef0123456789abcdef01234567.avif'), 'x'.repeat(10));
|
|
|
|
|
await writeFile(join(dir, 'a.jpg'), 'y'.repeat(99));
|
|
|
|
|
await mkdir(join(dir, '.image-previews'));
|
|
|
|
|
await writeFile(join(dir, '.image-previews', 'settings.json'), 'z'.repeat(500));
|
|
|
|
|
|
|
|
|
|
const usage = await storageUsage({ STORAGE_LOCAL_ROOT: dir, STORAGE_LOCATIONS: 'local' });
|
|
|
|
|
|
|
|
|
|
assert.equal(usage.source, 'local');
|
|
|
|
|
assert.equal(usage.previews.count, 1);
|
|
|
|
|
assert.equal(usage.previews.bytes, 10);
|
|
|
|
|
assert.equal(usage.originals.count, 1);
|
|
|
|
|
assert.equal(usage.originals.bytes, 99);
|
|
|
|
|
assert.equal(usage.total.count, 2);
|
|
|
|
|
assert.equal(usage.total.bytes, 109);
|
|
|
|
|
} finally {
|
|
|
|
|
await rm(dir, { recursive: true, force: true });
|
|
|
|
|
}
|
|
|
|
|
});
|