diff --git a/.changepacks/changepack_log_ci_hardening.json b/.changepacks/changepack_log_ci_hardening.json new file mode 100644 index 000000000..7d745fed5 --- /dev/null +++ b/.changepacks/changepack_log_ci_hardening.json @@ -0,0 +1,7 @@ +{ + "changes": { + "bindings/devup-ui-wasm/package.json": "Patch" + }, + "note": "CI and test infrastructure: the workflow pins every third-party action by commit SHA and every toolchain (Rust 1.99.0, Bun, Node, wasm-pack, cargo-binstall by checksum, cargo-tarpaulin) with the versions in the cache keys, runs pull requests read-only and moves npm publishing and Pages deployment into jobs that run only on pushes to main, never creates or commits e2e baselines, and sets timeouts. The wasm package build now starts from an empty pkg/ and verify:dist rejects files of the removed lite export, its README describes the real API, state and errors, and a Bun test checks the JS/WASM interface. The README benchmark tables are generated from one checked result file in both languages with the CSS size in its own column.", + "date": "2026-10-01T00:00:00.000Z" +} \ No newline at end of file diff --git a/.changepacks/changepack_log_compose_known_styles.json b/.changepacks/changepack_log_compose_known_styles.json new file mode 100644 index 000000000..6aa4c50d9 --- /dev/null +++ b/.changepacks/changepack_log_compose_known_styles.json @@ -0,0 +1,7 @@ +{ + "changes": { + "bindings/devup-ui-wasm/package.json": "Patch" + }, + "note": "css(a, b) composing classes whose styles the build knows (a const bound to css() in the file, or a css() result another module exports with a rule object every value of which is known) merges their styles: a later part's declaration replaces an earlier one's for the same property, selector, breakpoint and layer, also under conditions (css(base, cond && danger)) and for ||/?? parts, where the classes used to be joined and the stylesheet order picked the winner. vanilla-extract style([a, b]) passes each composed style as its own argument, so the later one wins, and a style composed again later (style([a, b, a])) is kept. Classes the build does not know (strings, props, CSS Modules) are kept as they are", + "date": "2026-10-01T00:00:00.000Z" +} diff --git a/.changepacks/changepack_log_compose_styled.json b/.changepacks/changepack_log_compose_styled.json new file mode 100644 index 000000000..4d05b2838 --- /dev/null +++ b/.changepacks/changepack_log_compose_styled.json @@ -0,0 +1,7 @@ +{ + "changes": { + "bindings/devup-ui-wasm/package.json": "Patch" + }, + "note": "styled(Base) extending a styled component the file binds to a const renders Base's tag directly with Base's styles composed under its own, so the extension's declarations replace Base's for the same property, selector, breakpoint and layer instead of losing to them by stylesheet order; Base's attrs apply before the extension's. attrs merge as styled-components merges them: className is joined with the caller's, style is merged and other props replace earlier ones. On an element, a className or style spread after the explicit prop now wins, as React merges props in the order they are written", + "date": "2026-10-01T00:00:00.000Z" +} diff --git a/.changepacks/changepack_log_react_18_support.json b/.changepacks/changepack_log_react_18_support.json new file mode 100644 index 000000000..ad74861ef --- /dev/null +++ b/.changepacks/changepack_log_react_18_support.json @@ -0,0 +1,8 @@ +{ + "changes": { + "packages/react/package.json": "Patch", + "packages/components/package.json": "Patch" + }, + "note": "@devup-ui/react and @devup-ui/components take React as a peer of ^18 || ^19 instead of `*` and no longer depend on React 19 themselves, so a React 18 app no longer gets a second React. Select and Stepper read their context with useContext instead of React 19's use() and provide it with Context.Provider instead of React 19's (which crashes React 18), and Select repositions its container from an effect instead of a callback ref returning cleanup, which React 18 ignores", + "date": "2026-10-01T00:00:00.000Z" +} diff --git a/.changepacks/changepack_log_styled_forward_ref.json b/.changepacks/changepack_log_styled_forward_ref.json new file mode 100644 index 000000000..7dd26ffbb --- /dev/null +++ b/.changepacks/changepack_log_styled_forward_ref.json @@ -0,0 +1,7 @@ +{ + "changes": { + "bindings/devup-ui-wasm/package.json": "Patch" + }, + "note": "Styled components the build generates are wrapped in React's forwardRef, so a ref passed to them reaches the element they render on React 18 as well as React 19; the file imports forwardRef from react when it defines one", + "date": "2026-10-01T00:00:00.000Z" +} diff --git a/.changepacks/changepack_log_styled_props.json b/.changepacks/changepack_log_styled_props.json new file mode 100644 index 000000000..8dfbb440e --- /dev/null +++ b/.changepacks/changepack_log_styled_props.json @@ -0,0 +1,7 @@ +{ + "changes": { + "bindings/devup-ui-wasm/package.json": "Patch" + }, + "note": "Styled components no longer pass every prop to the tag they render: `$` props, `theme` and the props their style functions or attrs read are kept away from a tag unless it takes them as attributes, and `shouldForwardProp` (Emotion options or styled-components withConfig) is evaluated at build time. A `shouldForwardProp` the build cannot evaluate is a build error naming the file, line and code, with the forms it accepts. An element using a styled component the file defines drops props the component neither reads nor passes on; spread props are passed as written", + "date": "2026-10-01T00:00:00.000Z" +} diff --git a/.changepacks/changepack_log_styled_targets.json b/.changepacks/changepack_log_styled_targets.json new file mode 100644 index 000000000..a51d45fe7 --- /dev/null +++ b/.changepacks/changepack_log_styled_targets.json @@ -0,0 +1,7 @@ +{ + "changes": { + "bindings/devup-ui-wasm/package.json": "Patch" + }, + "note": "Styled components render what their `as` prop names, defaulting to the tag or component they were defined with, and pass `forwardedAs` on as `as`, as styled-components and Emotion do; `as` used to reach the rendered tag as an attribute. `Component.withComponent(target)` on a styled component the file binds to a const builds a component rendering the same styles and attrs as `target` (a tag name or a component JSX can name), where it used to call a method the generated function does not have and throw", + "date": "2026-10-01T00:00:00.000Z" +} diff --git a/.changepacks/changepack_log_ui_kit_forward_ref.json b/.changepacks/changepack_log_ui_kit_forward_ref.json new file mode 100644 index 000000000..883e0e61b --- /dev/null +++ b/.changepacks/changepack_log_ui_kit_forward_ref.json @@ -0,0 +1,7 @@ +{ + "changes": { + "packages/components/package.json": "Patch" + }, + "note": "Button, Checkbox, Input, Radio and Textarea forward their ref to the button, input or textarea they render, so a ref reaches the form element on React 18 as well as React 19", + "date": "2026-10-01T00:00:00.000Z" +} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0e6edbd21..6d3289bae 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -7,51 +7,76 @@ on: pull_request: branches: - main + +# Pull requests and every job below run read-only. Only the `release` and +# `deploy-pages` jobs get write permissions, and they run on pushes to main +# alone, so no step a pull request controls can publish, tag, comment or deploy. permissions: contents: read +# Toolchain versions are pinned here once and used by every job and cache key. +# Third-party actions are pinned by commit SHA, with the release they come from +# in a comment. Bump a version and its SHA together, in a reviewed change. +env: + RUST_VERSION: "1.99.0" + BUN_VERSION: "1.4.0" + NODE_VERSION: "24.21.0" + WASM_PACK_VERSION: "v0.15.0" + CARGO_BINSTALL_VERSION: "v1.25.1" + CARGO_BINSTALL_SHA256: "8e06c41af972f9922f28c6ae467e41ffa9b0688d563d0aabd0dcbd5244ab849b" + CARGO_TARPAULIN_VERSION: "0.37.5" + CHANGEPACKS_CLI_VERSION: "0.3.6" + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false jobs: benchmark: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 60 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions-rust-lang/setup-rust-toolchain@v2 + - uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2 + with: + toolchain: ${{ env.RUST_VERSION }} + components: clippy, rustfmt - name: Cache cargo registry + target - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: cargo-benchmark-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} - - uses: oven-sh/setup-bun@v2 + key: cargo-benchmark-ubuntu-24.04-rust${{ env.RUST_VERSION }}-wasm-pack${{ env.WASM_PACK_VERSION }}-${{ hashFiles('**/Cargo.lock') }} + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: - bun-version: "1.4.0" + bun-version: ${{ env.BUN_VERSION }} name: Install bun - name: Cache bun dependencies - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: ~/.bun/install/cache - key: bun-benchmark-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} - - uses: jetli/wasm-pack-action@v0.4.0 + key: bun-benchmark-ubuntu-24.04-bun${{ env.BUN_VERSION }}-node${{ env.NODE_VERSION }}-${{ hashFiles('**/bun.lock') }} + - uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0 with: - version: "latest" + version: ${{ env.WASM_PACK_VERSION }} - name: Install Node.js - uses: actions/setup-node@v7 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: registry-url: "https://registry.npmjs.org" - node-version: 24 + node-version: ${{ env.NODE_VERSION }} - run: bun install - run: bun run build + # Every row is the median of the same number of cold samples taken in + # rotating order (benchmark-manifest.json). The run goes to an artifact and + # the Devup UI rows are compared with the checked baseline at the end of the + # job, so the profile and the Rust measurement still run when it fails. - name: Benchmark run: bun run benchmark - name: Profile Devup UI Turbopack @@ -62,48 +87,64 @@ jobs: benchmark/next-devup-ui-single-turbo/df \ benchmark/next-devup-ui-single-turbo/tsconfig.tsbuildinfo bun run --filter next-devup-ui-single-turbo-benchmark build + - name: Upload the measured run + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + if: ${{ !cancelled() }} + with: + name: benchmark-run + path: benchmark-run.json + if-no-files-found: ignore + retention-days: 30 + - name: Measure the SemanticBuilder passes of the extractor + run: cargo bench -p extractor --bench semantic_pass_benchmark -- --quick + - name: Compare the Devup UI rows with the baseline + run: bun benchmark-check.js # The landing app is the only real-scale acceptance target in this repo, and # `publish` runs it against the deployed vinext/@devup-ui/vite-plugin build. # This job runs the SAME 128 assertions and the SAME screenshot baselines # against a Next 16 + Turbopack build, so @devup-ui/next-plugin's production - # CSS path stays gated at 64 routes — the scale where extraction-wave races + # CSS path stays gated at 64 routes - the scale where extraction-wave races # actually reproduce. It runs in parallel so it stays off the publish critical # path, and its artifact is never deployed. landing-next-e2e: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 60 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions-rust-lang/setup-rust-toolchain@v2 + - uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2 + with: + toolchain: ${{ env.RUST_VERSION }} + components: clippy, rustfmt - name: Cache cargo registry + target - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: cargo-landing-next-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} - - uses: oven-sh/setup-bun@v2 + key: cargo-landing-next-ubuntu-24.04-rust${{ env.RUST_VERSION }}-wasm-pack${{ env.WASM_PACK_VERSION }}-${{ hashFiles('**/Cargo.lock') }} + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: - bun-version: "1.4.0" + bun-version: ${{ env.BUN_VERSION }} name: Install bun - name: Cache bun dependencies - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: ~/.bun/install/cache - key: bun-landing-next-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} - - uses: jetli/wasm-pack-action@v0.4.0 + key: bun-landing-next-ubuntu-24.04-bun${{ env.BUN_VERSION }}-node${{ env.NODE_VERSION }}-${{ hashFiles('**/bun.lock') }} + - uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0 with: - version: "latest" + version: ${{ env.WASM_PACK_VERSION }} - name: Install Node.js - uses: actions/setup-node@v7 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: - node-version: 24 + node-version: ${{ env.NODE_VERSION }} - run: bun install - run: bun run build - name: Build Landing (Next) @@ -114,10 +155,10 @@ jobs: mv ./packages/components/storybook-static ./apps/landing/public/storybook bun run --filter landing build:next - name: Cache Playwright Browsers - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: ~/.cache/ms-playwright - key: playwright-landing-next-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} + key: playwright-landing-next-ubuntu-24.04-node${{ env.NODE_VERSION }}-${{ hashFiles('**/bun.lock') }} - name: Install Playwright Browsers run: bunx playwright install chromium --with-deps - name: Run E2E Tests (Next) @@ -131,60 +172,124 @@ jobs: - name: Run E2E Tests (Next, singleCss) run: bun run test:e2e:next - name: Upload Next Playwright Report - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 if: ${{ !cancelled() }} with: name: playwright-report-next path: playwright-report/ retention-days: 1 + # The packages take React ^18 || ^19 as a peer. This builds a React 18.3 app + # with the Vite plugin and the UI kit, then checks refs, context and events + # in Chromium, so React-19-only APIs cannot slip back in. + react-18: + runs-on: ubuntu-24.04 + timeout-minutes: 45 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2 + with: + toolchain: ${{ env.RUST_VERSION }} + components: clippy, rustfmt + + - name: Cache cargo registry + target + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: | + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: cargo-react-18-ubuntu-24.04-rust${{ env.RUST_VERSION }}-wasm-pack${{ env.WASM_PACK_VERSION }}-${{ hashFiles('**/Cargo.lock') }} + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: ${{ env.BUN_VERSION }} + name: Install bun + + - name: Cache bun dependencies + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: ~/.bun/install/cache + key: bun-react-18-ubuntu-24.04-bun${{ env.BUN_VERSION }}-node${{ env.NODE_VERSION }}-${{ hashFiles('**/bun.lock') }} + - uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0 + with: + version: ${{ env.WASM_PACK_VERSION }} + - name: Install Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + with: + node-version: ${{ env.NODE_VERSION }} + - run: bun install + - run: bun run build + - name: Type-check and build the React 18 app + working-directory: apps/react-18 + run: | + bun ../../node_modules/@typescript/native/bin/tsc + bun run build + - name: Cache Playwright Browsers + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: ~/.cache/ms-playwright + key: playwright-react-18-ubuntu-24.04-node${{ env.NODE_VERSION }}-${{ hashFiles('**/bun.lock') }} + - name: Install Playwright Browsers + run: bunx playwright install chromium --with-deps + - name: Run React 18 E2E Tests + working-directory: apps/react-18 + run: bun run test:e2e + # @devup-ui/vite-plugin must keep the CSS every server page needs when the # build is vinext/RSC. The fixture shares one Devup UI header between server # list pages and a client page, and the tests read the styles the browser # actually applies when each route is entered directly, so a dropped # stylesheet dependency fails here whatever the CSS files are called. vinext-rsc-css-e2e: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 30 + permissions: + contents: read steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions-rust-lang/setup-rust-toolchain@v2 + - uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2 + with: + toolchain: ${{ env.RUST_VERSION }} + components: clippy, rustfmt - name: Cache cargo registry + target - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: cargo-vinext-rsc-css-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} - - uses: oven-sh/setup-bun@v2 + key: cargo-vinext-rsc-css-ubuntu-24.04-rust${{ env.RUST_VERSION }}-wasm-pack${{ env.WASM_PACK_VERSION }}-${{ hashFiles('**/Cargo.lock') }} + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: - bun-version: "1.4.0" + bun-version: ${{ env.BUN_VERSION }} name: Install bun - name: Cache bun dependencies - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: ~/.bun/install/cache - key: bun-vinext-rsc-css-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} - - uses: jetli/wasm-pack-action@v0.4.0 + key: bun-vinext-rsc-css-ubuntu-24.04-bun${{ env.BUN_VERSION }}-node${{ env.NODE_VERSION }}-${{ hashFiles('**/bun.lock') }} + - uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0 with: - version: "latest" + version: ${{ env.WASM_PACK_VERSION }} - name: Install Node.js - uses: actions/setup-node@v7 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: - node-version: 24 + node-version: ${{ env.NODE_VERSION }} - run: bun install - run: bun run build - name: Cache Playwright Browsers - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: ~/.cache/ms-playwright - key: playwright-vinext-rsc-css-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} + key: playwright-vinext-rsc-css-ubuntu-24.04-node${{ env.NODE_VERSION }}-${{ hashFiles('**/bun.lock') }} - name: Install Playwright Browsers run: bunx playwright install chromium --with-deps - name: Build fixture (per-file CSS) @@ -197,63 +302,86 @@ jobs: DEVUP_SINGLE_CSS=1 bun run --filter vinext-rsc-css-repro build - name: Run E2E Tests (singleCss) run: bun run test:e2e:rsc-css + + # Validation: runs on pull requests and on pushes to main, read-only. The job + # id stays `publish` because it is the status check the repository requires; + # the privileged release steps live in `release` and `deploy-pages` below. publish: - runs-on: ubuntu-latest - permissions: - contents: write - id-token: write - pages: write - pull-requests: write + runs-on: ubuntu-24.04 + timeout-minutes: 90 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: # changepacks diffs HEAD against the previous release commit. The # default shallow fetch grafts away every parent, so that lookup dies - # with "Could not find a merge-base" and the release never publishes. + # with "Could not find a merge-base". fetch-depth: 0 fetch-tags: true - - uses: actions-rust-lang/setup-rust-toolchain@v2 + - uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2 + with: + toolchain: ${{ env.RUST_VERSION }} + components: clippy, rustfmt - name: Install cargo-binstall - run: curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + shell: bash + run: | + set -euo pipefail + archive="$RUNNER_TEMP/cargo-binstall.tgz" + curl -fsSL --proto '=https' --tlsv1.2 -o "$archive" \ + "https://github.com/cargo-bins/cargo-binstall/releases/download/${CARGO_BINSTALL_VERSION}/cargo-binstall-x86_64-unknown-linux-musl.tgz" + echo "${CARGO_BINSTALL_SHA256} $archive" | sha256sum --check --strict - + mkdir -p "$HOME/.cargo/bin" + tar -xzf "$archive" -C "$HOME/.cargo/bin" - name: Install cargo-tarpaulin - run: cargo binstall cargo-tarpaulin --no-confirm --force + run: cargo binstall "cargo-tarpaulin@${CARGO_TARPAULIN_VERSION}" --no-confirm --force --locked - name: Cache cargo registry + target - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: cargo-publish-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} + key: cargo-publish-ubuntu-24.04-rust${{ env.RUST_VERSION }}-tarpaulin${{ env.CARGO_TARPAULIN_VERSION }}-wasm-pack${{ env.WASM_PACK_VERSION }}-${{ hashFiles('**/Cargo.lock') }} - - uses: oven-sh/setup-bun@v2 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: - bun-version: "1.4.0" + bun-version: ${{ env.BUN_VERSION }} name: Install bun - name: Cache bun dependencies - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: ~/.bun/install/cache - key: bun-publish-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} + key: bun-publish-ubuntu-24.04-bun${{ env.BUN_VERSION }}-node${{ env.NODE_VERSION }}-${{ hashFiles('**/bun.lock') }} - - uses: jetli/wasm-pack-action@v0.4.0 + - uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0 with: - version: "latest" + version: ${{ env.WASM_PACK_VERSION }} - name: Install Node.js - uses: actions/setup-node@v7 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: registry-url: "https://registry.npmjs.org" - node-version: 24 + node-version: ${{ env.NODE_VERSION }} - run: bun install - run: bun run build - name: Verify built package entry points run: bun run verify:dist + - name: Check the WASM size budget + run: bun verify-wasm-size.js + - name: Upload the WASM size + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + if: ${{ !cancelled() }} + with: + name: wasm-size + path: wasm-size.json + if-no-files-found: ignore + retention-days: 30 + - name: Verify the README benchmark tables + run: bun render-benchmark-readme.js --check - name: Verify publish tarballs shell: bash run: | @@ -294,10 +422,16 @@ jobs: rm -rf .rustfmt.toml # Formatting with the temporary coverage config and then with the # default config is not a reversible round trip. Restore the exact - # checked-in sources so changepacks can safely switch commits. + # checked-in sources so the checkout is clean again. git restore --source=HEAD --staged --worktree -- . cargo fmt --all -- --check git diff --exit-code + - name: Check the changepacks of this change + if: github.event_name == 'pull_request' + run: | + # changepacks compares the change against a local main, which a pull request checkout does not have + git fetch --no-tags origin main:refs/heads/main + bunx "@changepacks/cli@${CHANGEPACKS_CLI_VERSION}" check --format json - name: Build Landing env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -307,27 +441,23 @@ jobs: bun run --filter landing build - name: Cache Playwright Browsers id: playwright-cache - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: ~/.cache/ms-playwright - key: playwright-publish-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} + key: playwright-publish-ubuntu-24.04-node${{ env.NODE_VERSION }}-${{ hashFiles('**/bun.lock') }} - name: Install Playwright Browsers run: bunx playwright install chromium --with-deps - - name: Check for Existing E2E Snapshots - id: check-snapshots + # A run only compares against the screenshots checked into the repository: + # it never creates or commits them. A new visual test needs its baselines + # generated by a maintainer (`bun run test:e2e:update`) and committed. + - name: Require the E2E baselines run: | - SNAPSHOT_COUNT=$(find e2e -name '*.png' -path '*-snapshots/*' 2>/dev/null | wc -l) - echo "count=$SNAPSHOT_COUNT" >> $GITHUB_OUTPUT - echo "Found $SNAPSHOT_COUNT existing snapshot(s)" - - name: Generate E2E Snapshots - if: steps.check-snapshots.outputs.count == '0' - run: bunx playwright test --update-snapshots - - name: Commit E2E Snapshots - if: steps.check-snapshots.outputs.count == '0' && github.event_name == 'pull_request' - uses: stefanzweifel/git-auto-commit-action@v7 - with: - commit_message: "chore: generate e2e visual snapshots from CI" - file_pattern: "e2e/**/*-snapshots/*.png" + SNAPSHOT_COUNT=$(find e2e -name '*-linux.png' -path '*-snapshots/*' 2>/dev/null | wc -l) + echo "Found $SNAPSHOT_COUNT linux baseline snapshot(s)" + if [ "$SNAPSHOT_COUNT" -eq 0 ]; then + echo "No checked-in baselines under e2e/**/*-snapshots; generate them with 'bun run test:e2e:update' and commit them." >&2 + exit 1 + fi - name: Run E2E Tests run: bun run test:e2e - name: Build Landing (singleCss) @@ -339,43 +469,118 @@ jobs: - name: Run E2E Tests (singleCss) run: bun run test:e2e - name: Upload singleCss Playwright Report - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 if: ${{ !cancelled() }} with: name: playwright-report-singlecss path: playwright-report/ retention-days: 1 - - name: Upload artifact - uses: actions/upload-pages-artifact@v5 + - name: Upload the site for deployment + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 with: path: ./apps/landing/dist/client retention-days: 1 if: github.event_name == 'push' && github.ref == 'refs/heads/main' - - uses: actions/deploy-pages@v5 - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - name: Upload to codecov.io - uses: codecov/codecov-action@v7 + uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true files: ./coverage/lcov.info - # Publishes through npm OIDC trusted publishing. The job's - # `id-token: write` permission lets npm exchange GitHub's short-lived OIDC - # token, and public packages receive provenance automatically. - # - # PREREQUISITE: every @devup-ui/* package must be registered as a Trusted - # Publisher on npmjs.com against repo dev-five-git/devup-ui and workflow - # file `publish.yml`. npm matches that filename exactly, so renaming or - # moving this file breaks publishing until the entries are updated. - # - # The publish command itself is selected in .changepacks/config.json. - # Bun packs each workspace so dependency ranges are materialized, then - # npm publishes that tarball with OIDC (Bun cannot do OIDC itself: - # oven-sh/bun#22423). + # Pages deployment: the only job with `pages` and `id-token` write, and it + # only runs for a push to main whose validation passed. + deploy-pages: + needs: [publish] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + contents: read + pages: write + id-token: write + steps: + - uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5 + + # Publishes through npm OIDC trusted publishing. The job's `id-token: write` + # permission lets npm exchange GitHub's short-lived OIDC token, and public + # packages receive provenance automatically. + # + # PREREQUISITE: every @devup-ui/* package must be registered as a Trusted + # Publisher on npmjs.com against repo dev-five-git/devup-ui and workflow + # file `publish.yml`. npm matches that filename exactly, so renaming or + # moving this file breaks publishing until the entries are updated. + # + # The publish command itself is selected in .changepacks/config.json. + # Bun packs each workspace so dependency ranges are materialized, then + # npm publishes that tarball with OIDC (Bun cannot do OIDC itself: + # oven-sh/bun#22423). + # + # It runs only for a push to main whose validation and end-to-end jobs + # passed, and is the only job that can write contents, open pull requests or + # mint an OIDC token for npm. + release: + needs: [publish, landing-next-e2e, react-18] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-24.04 + timeout-minutes: 45 + permissions: + contents: write + id-token: write + pull-requests: write + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + # changepacks diffs HEAD against the previous release commit. The + # default shallow fetch grafts away every parent, so that lookup dies + # with "Could not find a merge-base" and the release never publishes. + fetch-depth: 0 + fetch-tags: true + + - uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2 + with: + toolchain: ${{ env.RUST_VERSION }} + components: clippy, rustfmt + + - name: Cache cargo registry + target + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: | + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: cargo-release-ubuntu-24.04-rust${{ env.RUST_VERSION }}-wasm-pack${{ env.WASM_PACK_VERSION }}-${{ hashFiles('**/Cargo.lock') }} + + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: ${{ env.BUN_VERSION }} + name: Install bun + + - name: Cache bun dependencies + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: ~/.bun/install/cache + key: bun-release-ubuntu-24.04-bun${{ env.BUN_VERSION }}-node${{ env.NODE_VERSION }}-${{ hashFiles('**/bun.lock') }} + + - uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0 + with: + version: ${{ env.WASM_PACK_VERSION }} + - name: Install Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + with: + registry-url: "https://registry.npmjs.org" + node-version: ${{ env.NODE_VERSION }} + - run: bun install + - run: bun run build + - name: Verify built package entry points + run: bun run verify:dist - name: Verify clean publish checkout run: git diff --exit-code - - uses: changepacks/action@main + # changepacks/action@main as of 2026-10-03 (its newest tag, v0.1.0, is + # older); move the SHA forward deliberately. + - uses: changepacks/action@d83fb1ec3f8844ee2726a7b8b43a8786e9a528a2 # main id: changepacks with: publish: true diff --git a/.gitignore b/.gitignore index eb80866ac..0eb1f2217 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ playwright-report .playwright-mcp .omo /.changepacks/publish.tgz +benchmark-run.json +wasm-size.json diff --git a/README.md b/README.md index 354d19968..daf761b6a 100644 --- a/README.md +++ b/README.md @@ -79,31 +79,35 @@ npm install @devup-ui/webpack-plugin ## Comparison Benchmarks -[Latest CI benchmark](https://github.com/dev-five-git/devup-ui/actions/runs/33254030962) on `ubuntu-latest` with Next.js 16.3.3. All Next.js builds use the native TypeScript 7 CLI for type checking. - -Webpack values are one cold build: - -| Library | Version | Build Time | Build Size | -| --------------------------- | ------- | ---------- | ----------------- | -| tailwindcss | 4.3.3 | 12.81s | 66,453,190 bytes | -| styleX | 0.19.0 | 27.61s | 95,413,403 bytes | -| vanilla-extract | 1.21.2 | 11.96s | 67,689,923 bytes | -| kuma-ui | 1.6.4 | 13.07s | 74,747,637 bytes | -| panda-css | 1.12.0 | 13.20s | 70,970,208 bytes | -| chakra-ui | 3.37.0 | 19.98s | 206,589,898 bytes | -| mui | 9.4.0 | 13.78s | 100,603,186 bytes | -| **devup-ui (per-file CSS)** | 1.0.40 | **10.73s** | 66,535,095 bytes | -| **devup-ui (single CSS)** | 1.0.40 | **10.81s** | 66,535,321 bytes | - -Turbopack values are medians of six cold builds in alternating order: - -| Library | Version | Median Build Time | Build Size | -| -------------------------------------- | ------- | ----------------- | -------------------- | -| tailwindcss | 4.3.3 | 5.52s | 38,425,132 bytes | -| **devup-ui (direct APIs, single CSS)** | 1.0.40 | **5.46s** | **36,507,782 bytes** | -| **devup-ui (static `.css.ts`)** | 1.0.40 | **5.41s** | 36,586,883 bytes | - -The Turbopack ranges overlap, so the direct-API median is 0.06s (1.1%) ahead but still effectively parity with Tailwind on this fixture. The six cold samples were Tailwind `5.42, 5.56, 5.70, 5.53, 5.41, 5.52s`, direct Devup UI `5.64, 5.40, 5.42, 5.46, 5.54, 5.46s`, and static `.css.ts` `5.42, 5.38, 5.42, 5.47, 5.40, 5.38s`. The fixtures have comparable app shapes, not pixel-identical styling: Tailwind styles the leading paragraph and button more heavily, while Devup UI exercises typed component/style props. Treat these as build-pipeline results rather than a per-rule microbenchmark. The static `.css.ts` row was measured with the former `lite` fast path for static modules; every `.css.ts` module now runs on the full Boa evaluator, which adds about 20 ms for the first module and under 1 ms for each further one. + +[Latest CI benchmark](https://github.com/dev-five-git/devup-ui/actions/runs/37758611477) (commit `467a379a`, Devup UI 1.0.44) on `ubuntu-24.04` with Next.js 16.3.3. Every row is the median of 3 cold builds that ran in rotating order, shown with the range of the samples. All Next.js builds use the native TypeScript 7 CLI for type checking. The English and Korean tables are generated from the same checked result file, `benchmark-results.json`. + +Build Size is every byte of the build output directory (`.next`, or `dist` for vinext); CSS Size is the emitted `.css` files alone, measured separately. + +Webpack: + +| Library | Version | Median Build Time | Range | Build Size | CSS Size | +| --------------------------- | ------- | ----------------- | --------------- | ----------------- | ------------ | +| tailwindcss | 4.3.3 | 13.02s | 12.91s - 13.20s | 67,339,250 bytes | 5,852 bytes | +| styleX | 0.19.0 | 29.01s | 28.72s - 29.50s | 96,290,533 bytes | 425 bytes | +| vanilla-extract | 1.21.2 | 12.34s | 12.22s - 12.35s | 68,565,511 bytes | 294 bytes | +| kuma-ui | 1.6.4 | 13.55s | 13.49s - 13.57s | 75,627,429 bytes | 340 bytes | +| panda-css | 1.12.0 | 13.44s | 13.29s - 13.58s | 71,849,243 bytes | 15,615 bytes | +| chakra-ui | 3.37.0 | 20.29s | 19.95s - 21.06s | 207,425,028 bytes | 0 bytes | +| mui | 9.4.0 | 14.12s | 13.78s - 14.26s | 101,476,098 bytes | 0 bytes | +| **devup-ui (per-file CSS)** | 1.0.44 | **10.95s** | 10.87s - 11.09s | 67,427,695 bytes | 562 bytes | +| **devup-ui (single CSS)** | 1.0.44 | **11.04s** | 10.88s - 11.40s | 67,436,336 bytes | 790 bytes | + +Turbopack: + +| Library | Version | Median Build Time | Range | Build Size | CSS Size | +| -------------------------------------- | ------- | ----------------- | ------------- | ---------------- | ----------- | +| tailwindcss | 4.3.3 | 5.68s | 5.51s - 5.73s | 38,440,013 bytes | 6,197 bytes | +| **devup-ui (direct APIs, single CSS)** | 1.0.44 | **5.79s** | 5.77s - 5.87s | 36,538,765 bytes | 327 bytes | +| **devup-ui (static `.css.ts`)** | 1.0.44 | **5.71s** | 5.67s - 5.99s | 36,580,207 bytes | 268 bytes | + +The Turbopack ranges do not overlap, so the direct-API median is 0.12s (2.1%) behind Tailwind on this fixture. The 3 cold samples were Tailwind `5.68s, 5.73s, 5.51s`, direct Devup UI `5.77s, 5.79s, 5.87s`, and static `.css.ts` `5.71s, 5.99s, 5.67s`. The fixtures have comparable app shapes, not pixel-identical styling: Tailwind styles the leading paragraph and button more heavily, while Devup UI exercises typed component/style props. Treat these as build-pipeline results rather than a per-rule microbenchmark. Every `.css.ts` module runs on the full Boa evaluator, which adds about 20 ms for the first module and under 1 ms for each further one. CI compares the Devup UI rows with a checked baseline (see `benchmark-gate.js` for the rule) and fails on a regression beyond the measured noise. + ## How it works diff --git a/README_ko.md b/README_ko.md index 11c0caf7c..1d4c199c8 100644 --- a/README_ko.md +++ b/README_ko.md @@ -79,31 +79,35 @@ npm install @devup-ui/webpack-plugin ## 비교 벤치마크 -[최신 CI 벤치마크](https://github.com/dev-five-git/devup-ui/actions/runs/33239265133)는 `ubuntu-latest`에서 실행했습니다. 모든 Next.js 빌드는 네이티브 TypeScript 7 CLI로 타입 검사를 수행합니다. - -Webpack 값은 cold build 1회 결과입니다. - -| 라이브러리 | 버전 | 빌드 시간 | 빌드 사이즈 | -| --------------------------- | ------ | ---------- | -------------------- | -| tailwindcss | 4.3.3 | 15.55s | 66,479,450 bytes | -| styleX | 0.19.0 | 34.27s | 95,417,163 bytes | -| vanilla-extract | 1.21.2 | 14.91s | 67,711,539 bytes | -| kuma-ui | 1.6.4 | 16.43s | 74,774,187 bytes | -| panda-css | 1.12.0 | 16.82s | 70,983,831 bytes | -| chakra-ui | 3.37.0 | 24.79s | 206,598,161 bytes | -| mui | 9.4.0 | 17.12s | 100,621,370 bytes | -| **devup-ui (per-file CSS)** | 1.0.40 | **13.43s** | 66,577,087 bytes | -| **devup-ui (single CSS)** | 1.0.40 | **13.37s** | **66,564,796 bytes** | - -Turbopack 값은 실행 순서를 번갈아 측정한 cold build 6회의 중앙값입니다. - -| 라이브러리 | 버전 | 빌드 시간 중앙값 | 빌드 사이즈 | -| -------------------------------------- | ------ | ---------------- | -------------------- | -| tailwindcss | 4.3.3 | 6.55s | 38,386,955 bytes | -| **devup-ui (direct APIs, single CSS)** | 1.0.40 | **6.54s** | **36,519,234 bytes** | -| **devup-ui (static `.css.ts`)** | 1.0.40 | **6.47s** | 36,550,197 bytes | - -Turbopack 측정 범위는 서로 겹치므로 direct API 결과는 이 fixture에서 Tailwind와 사실상 동률입니다. static `.css.ts` 행은 정적 모듈용 `lite` 빠른 경로가 있던 때에 측정한 값입니다. 지금은 모든 `.css.ts`를 full Boa evaluator로 처리하며, 첫 모듈에 약 20ms, 이후 모듈마다 1ms 미만이 더 듭니다. + +[최신 CI 벤치마크](https://github.com/dev-five-git/devup-ui/actions/runs/37758611477) (커밋 `467a379a`, Devup UI 1.0.44)는 `ubuntu-24.04`에서 Next.js 16.3.3로 실행했습니다. 모든 행은 순서를 돌려 가며 측정한 cold build 3회의 중앙값이며 샘플의 범위를 함께 보여줍니다. 모든 Next.js 빌드는 네이티브 TypeScript 7 CLI로 타입 검사를 수행합니다. 영어와 한국어 표는 같은 결과 파일 `benchmark-results.json`에서 생성합니다. + +빌드 사이즈는 빌드 출력 디렉터리(`.next`, vinext는 `dist`)의 모든 바이트이고, CSS 사이즈는 출력된 `.css` 파일만 따로 잰 값입니다. + +Webpack: + +| 라이브러리 | 버전 | 빌드 시간 중앙값 | 범위 | 빌드 사이즈 | CSS 사이즈 | +| --------------------------- | ------ | ---------- | --------------- | ----------------- | ------------ | +| tailwindcss | 4.3.3 | 13.02s | 12.91s - 13.20s | 67,339,250 bytes | 5,852 bytes | +| styleX | 0.19.0 | 29.01s | 28.72s - 29.50s | 96,290,533 bytes | 425 bytes | +| vanilla-extract | 1.21.2 | 12.34s | 12.22s - 12.35s | 68,565,511 bytes | 294 bytes | +| kuma-ui | 1.6.4 | 13.55s | 13.49s - 13.57s | 75,627,429 bytes | 340 bytes | +| panda-css | 1.12.0 | 13.44s | 13.29s - 13.58s | 71,849,243 bytes | 15,615 bytes | +| chakra-ui | 3.37.0 | 20.29s | 19.95s - 21.06s | 207,425,028 bytes | 0 bytes | +| mui | 9.4.0 | 14.12s | 13.78s - 14.26s | 101,476,098 bytes | 0 bytes | +| **devup-ui (per-file CSS)** | 1.0.44 | **10.95s** | 10.87s - 11.09s | 67,427,695 bytes | 562 bytes | +| **devup-ui (single CSS)** | 1.0.44 | **11.04s** | 10.88s - 11.40s | 67,436,336 bytes | 790 bytes | + +Turbopack: + +| 라이브러리 | 버전 | 빌드 시간 중앙값 | 범위 | 빌드 사이즈 | CSS 사이즈 | +| -------------------------------------- | ------ | --------- | ------------- | ---------------- | ----------- | +| tailwindcss | 4.3.3 | 5.68s | 5.51s - 5.73s | 38,440,013 bytes | 6,197 bytes | +| **devup-ui (direct APIs, single CSS)** | 1.0.44 | **5.79s** | 5.77s - 5.87s | 36,538,765 bytes | 327 bytes | +| **devup-ui (static `.css.ts`)** | 1.0.44 | **5.71s** | 5.67s - 5.99s | 36,580,207 bytes | 268 bytes | + +Turbopack 측정 범위는 겹치지 않으므로 direct API 중앙값은 이 fixture에서 Tailwind보다 0.12s(2.1%) 느리며. cold 샘플 3개는 Tailwind `5.68s, 5.73s, 5.51s`, direct Devup UI `5.77s, 5.79s, 5.87s`, static `.css.ts` `5.71s, 5.99s, 5.67s`입니다. fixture는 앱 구조만 비슷하고 스타일이 픽셀 단위로 같지는 않습니다. Tailwind는 첫 문단과 버튼에 더 많은 스타일을 주고, Devup UI는 타입이 있는 컴포넌트/스타일 props를 사용합니다. 규칙 단위 마이크로벤치마크가 아니라 빌드 파이프라인 결과로 보세요. 모든 `.css.ts`는 full Boa evaluator로 처리하며, 첫 모듈에 약 20ms, 이후 모듈마다 1ms 미만이 더 듭니다. CI는 Devup UI 행을 체크인된 기준값과 비교해 측정된 노이즈를 넘는 회귀에서 실패합니다(규칙은 `benchmark-gate.js`). + ## 작동 원리 diff --git a/apps/react-18/e2e/react-18.spec.ts b/apps/react-18/e2e/react-18.spec.ts new file mode 100644 index 000000000..3ff8e655d --- /dev/null +++ b/apps/react-18/e2e/react-18.spec.ts @@ -0,0 +1,24 @@ +import { expect, test } from '@playwright/test' + +test('Devup UI runs on React 18', async ({ page }) => { + const errors: string[] = [] + page.on('pageerror', (error) => errors.push(error.message)) + page.on('console', (message) => { + if (message.type() === 'error') errors.push(message.text()) + }) + await page.goto('/') + + await expect(page.getByTestId('version')).toHaveText(/^18\./) + await expect(page.getByTestId('refs')).toHaveText( + 'button,input,input,input,textarea,input', + ) + + await page.getByText('select', { exact: true }).click() + await page.getByText('second', { exact: true }).click() + await expect(page.getByTestId('selected')).toHaveText('second') + + await page.getByTestId('increase').click() + await expect(page.getByTestId('stepper')).toHaveValue('1') + + expect(errors).toEqual([]) +}) diff --git a/apps/react-18/index.html b/apps/react-18/index.html new file mode 100644 index 000000000..9dfc14d76 --- /dev/null +++ b/apps/react-18/index.html @@ -0,0 +1,12 @@ + + + + + + Devup UI on React 18 + + +
+ + + diff --git a/apps/react-18/package.json b/apps/react-18/package.json new file mode 100644 index 000000000..6044cca14 --- /dev/null +++ b/apps/react-18/package.json @@ -0,0 +1,28 @@ +{ + "name": "react-18-example", + "version": "0.1.0", + "type": "module", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview --port 3098 --strictPort", + "test:e2e": "bunx playwright test -c playwright.config.ts", + "lint": "bun ../../node_modules/@typescript/native/bin/tsc && eslint" + }, + "dependencies": { + "@devup-ui/components": "workspace:^", + "@devup-ui/react": "workspace:^", + "react": "18.3.1", + "react-dom": "18.3.1" + }, + "devDependencies": { + "@devup-ui/vite-plugin": "workspace:^", + "@playwright/test": "^1.63.0", + "@types/react": "^18.3", + "@types/react-dom": "^18.3", + "@vitejs/plugin-react": "^6.1", + "typescript": "^7.0.2", + "vite": "^8.3" + } +} diff --git a/apps/react-18/playwright.config.ts b/apps/react-18/playwright.config.ts new file mode 100644 index 000000000..36905cf4c --- /dev/null +++ b/apps/react-18/playwright.config.ts @@ -0,0 +1,18 @@ +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: './e2e', + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + reporter: 'list', + use: { + baseURL: 'http://localhost:3098', + }, + projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }], + webServer: { + command: 'bun run preview', + url: 'http://localhost:3098', + reuseExistingServer: !process.env.CI, + timeout: 120_000, + }, +}) diff --git a/apps/react-18/src/App.tsx b/apps/react-18/src/App.tsx new file mode 100644 index 000000000..49ccf0502 --- /dev/null +++ b/apps/react-18/src/App.tsx @@ -0,0 +1,64 @@ +import { + Button, + Checkbox, + Input, + Radio, + Select, + SelectContainer, + SelectOption, + SelectTrigger, + Stepper, + StepperDecreaseButton, + StepperIncreaseButton, + StepperInput, + Textarea, +} from '@devup-ui/components' +import { Box, styled } from '@devup-ui/react' +import { useEffect, useRef, useState, version } from 'react' + +const Field = styled('input', { color: 'red', p: 1 }) + +export function App() { + const buttonRef = useRef(null) + const checkboxRef = useRef(null) + const inputRef = useRef(null) + const radioRef = useRef(null) + const textareaRef = useRef(null) + const fieldRef = useRef(null) + const [refs, setRefs] = useState('') + const [selected, setSelected] = useState('') + + useEffect(() => { + setRefs( + [buttonRef, checkboxRef, inputRef, radioRef, textareaRef, fieldRef] + .map((ref) => ref.current?.tagName.toLowerCase() ?? 'null') + .join(','), + ) + }, []) + + return ( + + {version} + + checkbox + + radio +