diff --git a/.github/workflows/icu-book.yml b/.github/workflows/icu-book.yml index 0e70d0fdce77..b8e457db8bd5 100644 --- a/.github/workflows/icu-book.yml +++ b/.github/workflows/icu-book.yml @@ -49,7 +49,7 @@ jobs: - run: ./node_modules/.bin/playwright install chromium - run: ./node_modules/.bin/run-p --race test:storybook:serve test:storybook:test env: - ARTIFACTS_DIR: stories/data + ARTIFACTS_DIR: stories - run: pnpm run build:esbuild - run: node ts/scripts/compile-stories-icu-lookup.js stories @@ -59,6 +59,7 @@ jobs: with: name: desktop-test-icu path: stories + compression-level: 9 - name: Upload release artifacts if: github.event_name != 'workflow_dispatch' @@ -66,3 +67,4 @@ jobs: with: name: desktop-${{ github.ref_name }}-icu path: stories + compression-level: 9 diff --git a/.storybook/icu-lookup.html b/.storybook/icu-lookup.html index ce87ac7ead55..cfe3d0bce807 100644 --- a/.storybook/icu-lookup.html +++ b/.storybook/icu-lookup.html @@ -47,7 +47,7 @@ summary.textContent = `${key}: "${message}"`; details.appendChild(summary); - for (const storyId of stories) { + for (const [storyId, image] of stories) { const story = document.createElement('details'); details.appendChild(story); @@ -56,8 +56,7 @@ story.appendChild(title); const img = document.createElement('img'); - img.src = `data/${encodeURIComponent(storyId)}/` + - `${encodeURIComponent(key.replace(/^icu:/, ''))}.jpg`; + img.src = `images/${image}`; img.loading = 'lazy'; story.appendChild(img); } diff --git a/.storybook/test-runner.ts b/.storybook/test-runner.ts index 04e1a0701e82..f42fad292848 100644 --- a/.storybook/test-runner.ts +++ b/.storybook/test-runner.ts @@ -1,8 +1,9 @@ // Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { mkdir, writeFile } from 'node:fs/promises'; +import { mkdir, writeFile, symlink } from 'node:fs/promises'; import { join } from 'node:path'; +import { createHash } from 'node:crypto'; import { type TestRunnerConfig, waitForPageReady, @@ -40,9 +41,13 @@ const config: TestRunnerConfig = { return; } - const dir = join(ARTIFACTS_DIR, context.id); - await mkdir(dir, { recursive: true }); + const storeDir = join(ARTIFACTS_DIR, 'images'); + await mkdir(storeDir, { recursive: true }); + const componentDir = join(ARTIFACTS_DIR, 'components', context.id); + await mkdir(componentDir, { recursive: true }); + + const saves = new Array>(); for (const [key, value] of result) { const locator = page .getByText(value) @@ -76,8 +81,28 @@ const config: TestRunnerConfig = { quality: 95, }); - await writeFile(join(dir, `${key.replace(/^icu:/, '')}.jpg`), image); + const digest = createHash('sha256').update(image).digest('hex'); + const storeFile = join(storeDir, `${digest}.jpg`); + const targetFile = join(componentDir, `${key.replace(/^icu:/, '')}.jpg`); + + saves.push( + (async () => { + try { + await writeFile(storeFile, image, { + // Fail if exists + flags: 'wx', + }); + } catch (error) { + if (error.code !== 'EEXIST') { + throw error; + } + } + await symlink(storeFile, targetFile); + })() + ); } + + await Promise.all(saves); }, }; export default config; diff --git a/ts/scripts/compile-stories-icu-lookup.ts b/ts/scripts/compile-stories-icu-lookup.ts index 0846d64bfe8b..268e7925c466 100644 --- a/ts/scripts/compile-stories-icu-lookup.ts +++ b/ts/scripts/compile-stories-icu-lookup.ts @@ -1,10 +1,9 @@ // Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { readFile, writeFile } from 'node:fs/promises'; +import { readFile, writeFile, readdir, readlink } from 'node:fs/promises'; import { join, basename } from 'node:path'; import pMap from 'p-map'; -import fastGlob from 'fast-glob'; import { drop } from '../util/drop'; @@ -14,9 +13,9 @@ async function main(): Promise { throw new Error('Missing required source directory argument'); } - const dirEntries = await fastGlob('*/*.jpg', { - cwd: join(source, 'data'), - onlyFiles: true, + const dirEntries = await readdir(join(source, 'components'), { + withFileTypes: true, + recursive: true, }); const enMessages = JSON.parse( @@ -26,21 +25,31 @@ async function main(): Promise { ) ); - const icuToStory: Record> = Object.create(null); + const icuToStory: Record> = Object.create( + null + ); await pMap( dirEntries, async entry => { - const [storyId, imageFile] = entry.split('/', 2); + if (!entry.isSymbolicLink()) { + return; + } - const icuId = `icu:${basename(imageFile, '.jpg')}`; + const fullPath = join(entry.parentPath, entry.name); + const image = basename(await readlink(fullPath)); + + const storyId = basename(entry.parentPath); + const linkFile = entry.name; + + const icuId = `icu:${basename(linkFile, '.jpg')}`; let list = icuToStory[icuId]; if (list == null) { list = []; icuToStory[icuId] = list; } - list.push(storyId); + list.push([storyId, image]); }, { concurrency: 20 } );