From bb30836351504aa4dfad537333cd851cf10efa6e Mon Sep 17 00:00:00 2001 From: Vadim Yelisseyev Date: Wed, 29 Jul 2026 23:30:34 +0500 Subject: [PATCH 1/3] ci --- .github/workflows/ci.yml | 76 +++++++++++++++++++++++++ .github/workflows/release.yml | 104 ++++++++++++++++++++++++++++++++++ README.md | 1 + 3 files changed, 181 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0e230a8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,76 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + # Позволяет прогнать CI руками на любой ветке из вкладки Actions. + workflow_dispatch: + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + ci: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + # В package.json нет поля packageManager, поэтому версия pnpm задана явно. + # Когда поле появится (задача 13), input version можно убрать — + # pnpm/action-setup прочитает версию из package.json сам. + - uses: pnpm/action-setup@v4 + with: + version: 10.11.0 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - name: Lint + run: pnpm lint + + - name: Typecheck + run: pnpm check + + # Всё, что ниже, — до unit-тестов: проект `client` в vite.config.ts + # гоняет тесты в Chromium через vitest browser mode. + - name: Resolve Playwright version + id: playwright + run: | + version=$(node -p "require('@playwright/test/package.json').version") + echo "version=$version" >> "$GITHUB_OUTPUT" + + - name: Cache Playwright browsers + id: playwright-cache + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ steps.playwright.outputs.version }} + + # Сами бинарники браузера кэшируются, системные библиотеки — нет, + # поэтому при попадании в кэш всё равно нужен install-deps. + - name: Install Chromium + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: pnpm exec playwright install --with-deps chromium + + - name: Install Chromium system dependencies + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: pnpm exec playwright install-deps chromium + + - name: Unit tests + run: pnpm test:unit -- --run + + # Шага `pnpm test:e2e` здесь пока нет: в репозитории нет ни одного файла + # *.e2e.ts, и playwright test падает с «No tests found». Вернуть шаг вместе + # с первыми реальными e2e-тестами (задача 16, 16-test-coverage.md) — + # скрипт сам ставит chromium и поднимает preview-сервер на порту 4173. + + # vite build + svelte-package + publint. + - name: Build package + run: pnpm build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..326dc14 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,104 @@ +# Публикация в npm — ТОЛЬКО вручную, кнопкой Run workflow. +# Никаких триггеров по push, тегам или GitHub-релизам: публикация требует +# внешнего разрешения (1win), поэтому автопаблиш здесь недопустим. +# +# Перед первым настоящим запуском (dry_run = false) нужно: +# 1. добавить секрет NPM_TOKEN в Settings → Secrets and variables → Actions +# (automation-токен с правом publish); +# 2. убедиться, что разрешение на публикацию получено. +# +# Каждый релиз начинается с бампа version в package.json и записи в CHANGELOG.md: +# версии на npm неизменяемы, повторно опубликовать номер нельзя. Шаг +# «Verify version is not published yet» проверяет это до сборки — в том числе +# в dry-run, где сам `npm publish --dry-run` реестр не опрашивает. +# +# Кнопка Run workflow появляется только когда этот файл есть в ветке main. + +name: Release + +on: + workflow_dispatch: + inputs: + dry_run: + description: 'Прогон без публикации: собрать пакет и показать содержимое тарбола' + type: boolean + default: true + +jobs: + release: + runs-on: ubuntu-latest + + permissions: + # id-token нужен npm для генерации provenance. + id-token: write + contents: read + + steps: + - uses: actions/checkout@v4 + + # Версия pnpm задана явно — см. комментарий в ci.yml. + - uses: pnpm/action-setup@v4 + with: + version: 10.11.0 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + registry-url: 'https://registry.npmjs.org' + + - run: pnpm install --frozen-lockfile + + # Стоит до сборки и тестов, чтобы занятый номер версии отваливался за + # секунды, а не после нескольких минут работы. + - name: Verify version is not published yet + run: | + name=$(node -p "require('./package.json').name") + version=$(node -p "require('./package.json').version") + echo "Готовим к публикации $name@$version" + + set +e + output=$(npm view "$name@$version" version 2>&1) + status=$? + set -e + + if [ $status -eq 0 ]; then + echo "::error::$name@$version уже опубликован — подними version в package.json" + exit 1 + fi + if ! echo "$output" | grep -q "E404"; then + echo "::error::Не удалось проверить версию в реестре" + echo "$output" + exit 1 + fi + echo "Версия $version свободна" + + # Тот же набор проверок, что и в ci.yml: workflow_dispatch запускается с + # любой ветки, поэтому полагаться на зелёный CI в main нельзя. + - name: Lint + run: pnpm lint + + - name: Typecheck + run: pnpm check + + # Проект `client` в vite.config.ts гоняет тесты в Chromium. + - name: Install Chromium for vitest browser mode + run: pnpm exec playwright install --with-deps chromium + + - name: Unit tests + run: pnpm test:unit -- --run + + - name: Build package + run: pnpm build + + # provenance в dry-run не проверяется — аттестация создаётся только при + # реальной загрузке, поэтому флаг стоит лишь на боевом шаге. + - name: Publish (dry run) + if: inputs.dry_run + run: npm publish --dry-run --access public + + - name: Publish to npm + if: ${{ !inputs.dry_run }} + run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/README.md b/README.md index 58b3de6..86e377a 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ from a cold start in ~90ms, in ~45 KB min. `svelte-uplot` is the ~3 KB layer that makes it feel like a Svelte component: declarative, reactive, autosizing, SSR-safe — with **zero** abstraction over uPlot's own API. +[![CI](https://github.com/twister55/svelte-uplot/actions/workflows/ci.yml/badge.svg)](https://github.com/twister55/svelte-uplot/actions/workflows/ci.yml) [![npm](https://img.shields.io/npm/v/svelte-uplot?logo=npm&color=cb3837)](https://www.npmjs.com/package/svelte-uplot) [![gzip](https://img.shields.io/badge/runtime-3.1_kB_gzip-brightgreen)](#why-this-one) [![Svelte 5](https://img.shields.io/badge/Svelte-5%20runes-ff3e00?logo=svelte&logoColor=white)](https://svelte.dev) From c3cfd29855c1ab6f0b0ac0e8b93fc60933d9196a Mon Sep 17 00:00:00 2001 From: Vadim Yelisseyev Date: Wed, 29 Jul 2026 23:40:23 +0500 Subject: [PATCH 2/3] fix --- src/lib/autosize.svelte.test.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/lib/autosize.svelte.test.ts b/src/lib/autosize.svelte.test.ts index 2d65946..c925aef 100644 --- a/src/lib/autosize.svelte.test.ts +++ b/src/lib/autosize.svelte.test.ts @@ -13,6 +13,11 @@ function chartEl(container: HTMLElement) { return container.querySelector('[data-testid="chart"]')!; } +/** uPlot's own legend — the part of the overhead that rewraps with the width */ +function legendHeight(u: uPlot) { + return u.root.querySelector('.u-legend')!.offsetHeight; +} + function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } @@ -162,14 +167,23 @@ describe('autosize', () => { await laidOut(charts[0]); await expect.poll(() => charts[0].root.offsetHeight, { timeout: 2000 }).toBe(240); const oneRow = charts[0].height; + const oneRowLegend = legendHeight(charts[0]); chartEl(container).style.width = '200px'; await expect.poll(() => charts[0].width, { timeout: 2000 }).toBe(200); - // the legend now needs a row it did not need before, and the whole chart still - // has to fit the 240px it was given - await expect.poll(() => charts[0].root.offsetHeight, { timeout: 2000 }).toBe(240); - expect(charts[0].height).toBeLessThan(oneRow); + // The legend has rewrapped by now — the width is what it wraps to, and the + // convergence passes that follow only move the height. How many rows it takes + // at 200px is a font metric, and a platform whose glyphs are a little wider + // needs more of them, so what the plot owes the legend is measured, not assumed. + const grew = legendHeight(charts[0]) - oneRowLegend; + expect(grew).toBeGreaterThan(0); + + // The plot gives up exactly that much, instead of keeping the height it was + // sized to before the rewrap. Clamped at zero: a legend taller than the box + // leaves the plotting area nothing, and the root then outgrows the element — + // the degenerate case the next test starts from. + await expect.poll(() => charts[0].height, { timeout: 2000 }).toBe(Math.max(0, oneRow - grew)); }); it('takes the plot height back once the legend can unwrap again', async () => { From c1deb59650811e44dfc5059c81d36d35aaea65e5 Mon Sep 17 00:00:00 2001 From: Vadim Yelisseyev Date: Wed, 29 Jul 2026 23:48:06 +0500 Subject: [PATCH 3/3] fix comments --- .github/workflows/ci.yml | 24 ++++++++-------- .github/workflows/release.yml | 52 +++++++++++++++++------------------ 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e230a8..05b47ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: push: branches: [main] pull_request: - # Позволяет прогнать CI руками на любой ветке из вкладки Actions. + # Lets CI be run by hand on any branch, from the Actions tab. workflow_dispatch: concurrency: @@ -18,9 +18,9 @@ jobs: steps: - uses: actions/checkout@v4 - # В package.json нет поля packageManager, поэтому версия pnpm задана явно. - # Когда поле появится (задача 13), input version можно убрать — - # pnpm/action-setup прочитает версию из package.json сам. + # package.json has no packageManager field yet, so pnpm's version is pinned + # here. Once the field lands, drop the version input — pnpm/action-setup + # reads the version out of package.json on its own. - uses: pnpm/action-setup@v4 with: version: 10.11.0 @@ -38,8 +38,8 @@ jobs: - name: Typecheck run: pnpm check - # Всё, что ниже, — до unit-тестов: проект `client` в vite.config.ts - # гоняет тесты в Chromium через vitest browser mode. + # Everything below belongs before the unit tests: the `client` project in + # vite.config.ts runs them in Chromium, through vitest's browser mode. - name: Resolve Playwright version id: playwright run: | @@ -53,8 +53,8 @@ jobs: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ steps.playwright.outputs.version }} - # Сами бинарники браузера кэшируются, системные библиотеки — нет, - # поэтому при попадании в кэш всё равно нужен install-deps. + # The browser binaries are cached, the OS-level libraries they need are not, + # so a cache hit still has to run install-deps. - name: Install Chromium if: steps.playwright-cache.outputs.cache-hit != 'true' run: pnpm exec playwright install --with-deps chromium @@ -66,10 +66,10 @@ jobs: - name: Unit tests run: pnpm test:unit -- --run - # Шага `pnpm test:e2e` здесь пока нет: в репозитории нет ни одного файла - # *.e2e.ts, и playwright test падает с «No tests found». Вернуть шаг вместе - # с первыми реальными e2e-тестами (задача 16, 16-test-coverage.md) — - # скрипт сам ставит chromium и поднимает preview-сервер на порту 4173. + # No `pnpm test:e2e` step yet: the repository has no *.e2e.ts files, and + # playwright test exits 1 with "No tests found". Bring the step back with the + # first real e2e test (task 16, 16-test-coverage.md) — the script installs + # chromium itself and starts the preview server on port 4173. # vite build + svelte-package + publint. - name: Build package diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 326dc14..c3cbeef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,18 +1,18 @@ -# Публикация в npm — ТОЛЬКО вручную, кнопкой Run workflow. -# Никаких триггеров по push, тегам или GitHub-релизам: публикация требует -# внешнего разрешения (1win), поэтому автопаблиш здесь недопустим. +# Publishing to npm is manual ONLY, through the Run workflow button. +# No push, tag or release triggers: publishing needs external sign-off, so +# nothing here may publish on its own. # -# Перед первым настоящим запуском (dry_run = false) нужно: -# 1. добавить секрет NPM_TOKEN в Settings → Secrets and variables → Actions -# (automation-токен с правом publish); -# 2. убедиться, что разрешение на публикацию получено. +# Before the first real run (dry_run = false): +# 1. add an NPM_TOKEN secret under Settings → Secrets and variables → Actions +# (an automation token with publish rights); +# 2. make sure permission to publish has been given. # -# Каждый релиз начинается с бампа version в package.json и записи в CHANGELOG.md: -# версии на npm неизменяемы, повторно опубликовать номер нельзя. Шаг -# «Verify version is not published yet» проверяет это до сборки — в том числе -# в dry-run, где сам `npm publish --dry-run` реестр не опрашивает. +# Every release starts with a version bump in package.json and an entry in +# CHANGELOG.md: npm versions are immutable, a number cannot be published twice. +# The "Verify version is not published yet" step checks that before the build — +# including on a dry run, where `npm publish --dry-run` never asks the registry. # -# Кнопка Run workflow появляется только когда этот файл есть в ветке main. +# The Run workflow button only appears once this file is on the main branch. name: Release @@ -20,7 +20,7 @@ on: workflow_dispatch: inputs: dry_run: - description: 'Прогон без публикации: собрать пакет и показать содержимое тарбола' + description: 'Rehearse without publishing: build the package and print the tarball contents' type: boolean default: true @@ -29,14 +29,14 @@ jobs: runs-on: ubuntu-latest permissions: - # id-token нужен npm для генерации provenance. + # npm needs id-token to generate provenance. id-token: write contents: read steps: - uses: actions/checkout@v4 - # Версия pnpm задана явно — см. комментарий в ci.yml. + # pnpm's version is pinned — see the comment in ci.yml. - uses: pnpm/action-setup@v4 with: version: 10.11.0 @@ -49,13 +49,13 @@ jobs: - run: pnpm install --frozen-lockfile - # Стоит до сборки и тестов, чтобы занятый номер версии отваливался за - # секунды, а не после нескольких минут работы. + # Ahead of the build and the tests, so that a taken version number fails in + # seconds instead of after several minutes of work. - name: Verify version is not published yet run: | name=$(node -p "require('./package.json').name") version=$(node -p "require('./package.json').version") - echo "Готовим к публикации $name@$version" + echo "Preparing to publish $name@$version" set +e output=$(npm view "$name@$version" version 2>&1) @@ -63,25 +63,25 @@ jobs: set -e if [ $status -eq 0 ]; then - echo "::error::$name@$version уже опубликован — подними version в package.json" + echo "::error::$name@$version is already published — bump the version in package.json" exit 1 fi if ! echo "$output" | grep -q "E404"; then - echo "::error::Не удалось проверить версию в реестре" + echo "::error::Could not check the version against the registry" echo "$output" exit 1 fi - echo "Версия $version свободна" + echo "Version $version is free" - # Тот же набор проверок, что и в ci.yml: workflow_dispatch запускается с - # любой ветки, поэтому полагаться на зелёный CI в main нельзя. + # The same checks ci.yml runs: workflow_dispatch can be launched from any + # branch, so a green CI on main proves nothing about what is being published. - name: Lint run: pnpm lint - name: Typecheck run: pnpm check - # Проект `client` в vite.config.ts гоняет тесты в Chromium. + # The `client` project in vite.config.ts runs its tests in Chromium. - name: Install Chromium for vitest browser mode run: pnpm exec playwright install --with-deps chromium @@ -91,8 +91,8 @@ jobs: - name: Build package run: pnpm build - # provenance в dry-run не проверяется — аттестация создаётся только при - # реальной загрузке, поэтому флаг стоит лишь на боевом шаге. + # A dry run never exercises provenance — the attestation is only created on a + # real upload — so the flag belongs on the publishing step alone. - name: Publish (dry run) if: inputs.dry_run run: npm publish --dry-run --access public