mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-18 19:30:27 +08:00
Cache npm dependencies in Buildkite
- Add twemoji as npm dependency instead of build download - Package build assets with a Vite plugin instead of scripts - Keep the eslint and prettier caches in one directory
This commit is contained in:
@@ -1,57 +0,0 @@
|
|||||||
# Buildkite CI
|
|
||||||
|
|
||||||
The Garden Buildkite integration stores a bare `buildkite-agent pipeline
|
|
||||||
upload`, so the agent finds `pipeline.yml` by its default name. The integration
|
|
||||||
makes one pipeline per repo, so all suites land in the same build.
|
|
||||||
|
|
||||||
## Cache volumes and artifacts
|
|
||||||
|
|
||||||
A **cache volume** carries state between builds. It is an input: content that a
|
|
||||||
step needs, but that the repo does not contain. Volume names are scoped to the
|
|
||||||
pipeline.
|
|
||||||
|
|
||||||
An **artifact** is a file that one build produces. It is an output: evidence for
|
|
||||||
a person to read in the Buildkite UI. The agent uploads artifacts whatever the
|
|
||||||
exit code of the step, so a failed step still reports its results.
|
|
||||||
|
|
||||||
The visual suite uses both. The volume `visual-snapshots` holds the baseline
|
|
||||||
images, which are gitignored and exist nowhere else. `artifact_paths` uploads
|
|
||||||
the actual, expected, and diff images that a failed comparison writes. The two
|
|
||||||
cannot be swapped: a volume is not visible in the UI, and an artifact does not
|
|
||||||
persist as an input to the next build.
|
|
||||||
|
|
||||||
## One writer per volume
|
|
||||||
|
|
||||||
A job does not read its volume in place. It gets a private copy at start. That
|
|
||||||
copy becomes the version that the next job sees, but only when the job exits 0.
|
|
||||||
Every successful job is therefore a writer, whether or not it changed a file.
|
|
||||||
|
|
||||||
Two consequences follow.
|
|
||||||
|
|
||||||
Keep the volume names distinct across the uploaded files. A step that commits a
|
|
||||||
version discards what a concurrent step wrote to the same volume. This is why
|
|
||||||
`rust.yml` gives test, doc, and lint a separate volume each.
|
|
||||||
|
|
||||||
The visual suite keeps exactly one writer. `master` updates the baselines, and
|
|
||||||
`concurrency: 1` serializes the group. Other branches always exit non-zero, so
|
|
||||||
Buildkite discards their copy and no branch can republish a stale baseline set.
|
|
||||||
The branch step then cannot use the exit code to report the test result, so two
|
|
||||||
codes carry both: 100 means no differences, which `soft_fail` keeps green, and
|
|
||||||
any other non-zero code fails the build. An annotation reports the outcome in
|
|
||||||
both cases.
|
|
||||||
|
|
||||||
## Docker images
|
|
||||||
|
|
||||||
Two images cover all Node steps:
|
|
||||||
|
|
||||||
- `node:24-bookworm` tracks the major version in `.nvmrc` and the `engines`
|
|
||||||
field in `package.json`.
|
|
||||||
- `mcr.microsoft.com/playwright:v1.61.1-noble` ships the browsers at
|
|
||||||
`/ms-playwright`, so no step installs them. The tag must match
|
|
||||||
`@playwright/test` in `package.json`. Playwright refuses browsers built for
|
|
||||||
another version.
|
|
||||||
|
|
||||||
Node steps use no cache volume. Cache paths are relative to the step working
|
|
||||||
directory, and the agent home is not visible inside a container, so npm
|
|
||||||
downloads its dependencies on every run. The Rust steps run on the agent
|
|
||||||
instead, so their `~`-rooted paths work.
|
|
||||||
@@ -1,14 +1,27 @@
|
|||||||
# No explicit build step: tests/support/globalSetup.ts runs `npm run build` in
|
# No explicit build step: tests/support/globalSetup.ts runs `npm run build` in
|
||||||
# parallel with fixture creation, and asserts the pinned rad and radicle-httpd
|
# parallel with fixture creation, and asserts the pinned rad and radicle-httpd
|
||||||
# versions are on PATH.
|
# versions are on PATH.
|
||||||
|
#
|
||||||
|
# This step shares the `deps` volume with the steps in node-checks.yml. Keep the
|
||||||
|
# cache paths the same as the ones there, and see that file for how the volume
|
||||||
|
# works and why this step waits for check.
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CI: "true"
|
CI: "true"
|
||||||
|
NPM_CONFIG_CACHE: ".npm-cache"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- label: "🎭 e2e"
|
- label: "🎭 e2e"
|
||||||
key: e2e
|
key: e2e
|
||||||
|
# The check step in node-checks.yml seeds the shared volume.
|
||||||
|
depends_on: check
|
||||||
timeout_in_minutes: 30
|
timeout_in_minutes: 30
|
||||||
|
cache:
|
||||||
|
name: deps
|
||||||
|
paths:
|
||||||
|
- ".npm-cache"
|
||||||
|
- "tests/tmp/bin"
|
||||||
|
- ".lintcache"
|
||||||
artifact_paths:
|
artifact_paths:
|
||||||
- "tests/artifacts/**/*"
|
- "tests/artifacts/**/*"
|
||||||
plugins:
|
plugins:
|
||||||
@@ -19,8 +32,12 @@ steps:
|
|||||||
image: "mcr.microsoft.com/playwright:v1.61.1-noble"
|
image: "mcr.microsoft.com/playwright:v1.61.1-noble"
|
||||||
# Chromium exhausts Docker's default 64MB /dev/shm.
|
# Chromium exhausts Docker's default 64MB /dev/shm.
|
||||||
ipc: host
|
ipc: host
|
||||||
|
# Without this the cache volume links point at nothing.
|
||||||
|
volumes:
|
||||||
|
- "/cache/bkcache:/cache/bkcache"
|
||||||
environment:
|
environment:
|
||||||
- CI
|
- CI
|
||||||
|
- NPM_CONFIG_CACHE
|
||||||
commands:
|
commands:
|
||||||
- npm ci
|
- npm ci
|
||||||
# The image ships no decompressors: install-binaries unpacks .tar.xz
|
# The image ships no decompressors: install-binaries unpacks .tar.xz
|
||||||
|
|||||||
@@ -1,17 +1,38 @@
|
|||||||
# No cache volume: cache paths are relative to the step working directory, and
|
# One `deps` cache volume serves every step here and the e2e step in e2e.yml.
|
||||||
# the agent's home is not visible inside a container, so npm downloads its
|
# The volume is mounted at /cache/bkcache on the agent, and the configured paths
|
||||||
# dependencies on every run.
|
# become links into it. A container step must therefore mount that directory, or
|
||||||
|
# every link dangles and the job persists nothing.
|
||||||
|
#
|
||||||
|
# A job does not write the volume in place. It gets a private copy at start, and
|
||||||
|
# that copy becomes the version the next job sees if the job exits 0. Concurrent
|
||||||
|
# jobs are never merged: the last one to exit 0 publishes its whole copy, and
|
||||||
|
# whatever a job that finished earlier committed is gone.
|
||||||
|
#
|
||||||
|
# check therefore runs first, and is the only step that writes content no other
|
||||||
|
# step produces: the lint caches, and the pinned binaries. The four steps that
|
||||||
|
# follow all copy its version and add nothing unique to it — `npm ci` against the
|
||||||
|
# same lockfile fetches the same tarballs, and install-binaries does nothing once
|
||||||
|
# the pinned version is there. Whichever of them commits last keeps everything.
|
||||||
|
#
|
||||||
|
# Stale content is safe. The npm cache is content-addressed, and install-binaries
|
||||||
|
# keys its download on the pinned version.
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CI: "true"
|
CI: "true"
|
||||||
|
# Applies to every npm and npx call in the step, not just to `npm ci`.
|
||||||
|
NPM_CONFIG_CACHE: ".npm-cache"
|
||||||
|
|
||||||
__defs:
|
__defs:
|
||||||
# Tracks the major version in .nvmrc and the engines field in package.json.
|
# Tracks the major version in .nvmrc and the engines field in package.json.
|
||||||
node_image: &node_image
|
node_image: &node_image
|
||||||
docker#v5.14.0:
|
docker#v5.14.0:
|
||||||
image: "node:24-bookworm"
|
image: "node:24-bookworm"
|
||||||
|
# Without this the cache volume links point at nothing.
|
||||||
|
volumes:
|
||||||
|
- "/cache/bkcache:/cache/bkcache"
|
||||||
environment:
|
environment:
|
||||||
- CI
|
- CI
|
||||||
|
- NPM_CONFIG_CACHE
|
||||||
|
|
||||||
# Ships the browsers at /ms-playwright, which is why no step installs them.
|
# Ships the browsers at /ms-playwright, which is why no step installs them.
|
||||||
# The tag must match @playwright/test in package.json — Playwright refuses to
|
# The tag must match @playwright/test in package.json — Playwright refuses to
|
||||||
@@ -21,22 +42,44 @@ __defs:
|
|||||||
image: "mcr.microsoft.com/playwright:v1.61.1-noble"
|
image: "mcr.microsoft.com/playwright:v1.61.1-noble"
|
||||||
# Chromium exhausts Docker's default 64MB /dev/shm.
|
# Chromium exhausts Docker's default 64MB /dev/shm.
|
||||||
ipc: host
|
ipc: host
|
||||||
|
volumes:
|
||||||
|
- "/cache/bkcache:/cache/bkcache"
|
||||||
environment:
|
environment:
|
||||||
- CI
|
- CI
|
||||||
|
- NPM_CONFIG_CACHE
|
||||||
|
|
||||||
|
# Keep this list the same in every step that attaches the volume, here and in
|
||||||
|
# e2e.yml.
|
||||||
|
deps_cache: &deps_cache
|
||||||
|
name: deps
|
||||||
|
paths:
|
||||||
|
- ".npm-cache"
|
||||||
|
- "tests/tmp/bin"
|
||||||
|
# The eslint and prettier caches, which scripts/check keeps here rather
|
||||||
|
# than under node_modules so that `npm ci` cannot delete them. A cache
|
||||||
|
# path is always a directory, so the two files share one.
|
||||||
|
- ".lintcache"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- label: "🔎 check"
|
- label: "🔎 check"
|
||||||
key: check
|
key: check
|
||||||
timeout_in_minutes: 10
|
timeout_in_minutes: 10
|
||||||
|
cache: *deps_cache
|
||||||
plugins:
|
plugins:
|
||||||
- *node_image
|
- *node_image
|
||||||
commands:
|
commands:
|
||||||
- npm ci
|
- npm ci
|
||||||
|
# check does not use the binaries. It installs them while it is the only
|
||||||
|
# writer of the volume, so that no later step has to win a race to keep
|
||||||
|
# them. The node image ships curl and xz, so nothing is installed first.
|
||||||
|
- ./scripts/install-binaries
|
||||||
- npm run check
|
- npm run check
|
||||||
|
|
||||||
- label: "🧪 unit-tests"
|
- label: "🧪 unit-tests"
|
||||||
key: unit-tests
|
key: unit-tests
|
||||||
|
depends_on: check
|
||||||
timeout_in_minutes: 10
|
timeout_in_minutes: 10
|
||||||
|
cache: *deps_cache
|
||||||
plugins:
|
plugins:
|
||||||
- *node_image
|
- *node_image
|
||||||
commands:
|
commands:
|
||||||
@@ -47,7 +90,9 @@ steps:
|
|||||||
# binaries on PATH even though it runs under vitest rather than Playwright.
|
# binaries on PATH even though it runs under vitest rather than Playwright.
|
||||||
- label: "🔌 http-client-tests"
|
- label: "🔌 http-client-tests"
|
||||||
key: http-client-tests
|
key: http-client-tests
|
||||||
|
depends_on: check
|
||||||
timeout_in_minutes: 15
|
timeout_in_minutes: 15
|
||||||
|
cache: *deps_cache
|
||||||
artifact_paths:
|
artifact_paths:
|
||||||
- "tests/artifacts/**/*"
|
- "tests/artifacts/**/*"
|
||||||
plugins:
|
plugins:
|
||||||
@@ -62,7 +107,9 @@ steps:
|
|||||||
# needs a browser but no Radicle binaries or fixtures.
|
# needs a browser but no Radicle binaries or fixtures.
|
||||||
- label: "🏗 build-smoke"
|
- label: "🏗 build-smoke"
|
||||||
key: build-smoke
|
key: build-smoke
|
||||||
|
depends_on: check
|
||||||
timeout_in_minutes: 30
|
timeout_in_minutes: 30
|
||||||
|
cache: *deps_cache
|
||||||
artifact_paths:
|
artifact_paths:
|
||||||
- "tests/artifacts/**/*"
|
- "tests/artifacts/**/*"
|
||||||
plugins:
|
plugins:
|
||||||
|
|||||||
@@ -6,6 +6,13 @@
|
|||||||
# suite lands in this single build. Keep cache volume names distinct across the
|
# suite lands in this single build. Keep cache volume names distinct across the
|
||||||
# uploaded files: volumes are scoped to the pipeline, and a step that commits a
|
# uploaded files: volumes are scoped to the pipeline, and a step that commits a
|
||||||
# volume version discards what a concurrent step wrote to the same volume.
|
# volume version discards what a concurrent step wrote to the same volume.
|
||||||
|
#
|
||||||
|
# Upload node-checks.yml before e2e.yml: the e2e step depends on the `check` key
|
||||||
|
# defined there, and Buildkite rejects a step whose dependency is not yet part of
|
||||||
|
# the build.
|
||||||
|
#
|
||||||
|
# Superseded builds are not cancelled. Buildkite has no YAML equivalent of the
|
||||||
|
# GitHub Actions cancel-in-progress.
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- label: ":pipeline: upload"
|
- label: ":pipeline: upload"
|
||||||
|
|||||||
+15
-2
@@ -22,11 +22,21 @@ __defs:
|
|||||||
if ! command -v rustup >/dev/null 2>&1; then
|
if ! command -v rustup >/dev/null 2>&1; then
|
||||||
curl -fsSL https://sh.rustup.rs | sh -s -- -y --no-modify-path --profile minimal
|
curl -fsSL https://sh.rustup.rs | sh -s -- -y --no-modify-path --profile minimal
|
||||||
fi
|
fi
|
||||||
rustup component add rustfmt clippy
|
|
||||||
rustc --version
|
rustc --version
|
||||||
|
|
||||||
|
# Only the parts of ~/.cargo that are expensive to fetch again. registry/src
|
||||||
|
# and git/checkouts are left out on purpose: cargo re-creates both from the
|
||||||
|
# cache and the git db, so caching them would double the size of the volume
|
||||||
|
# for no gain.
|
||||||
|
#
|
||||||
|
# Nothing prunes the target directory, so it keeps the artifacts of every
|
||||||
|
# dependency version the branch has ever built. Deleting the volume is the
|
||||||
|
# only cleanup, and it costs one full rebuild.
|
||||||
cargo_cache: &cargo_cache
|
cargo_cache: &cargo_cache
|
||||||
- "~/.cargo"
|
- "~/.cargo/bin"
|
||||||
|
- "~/.cargo/registry/index"
|
||||||
|
- "~/.cargo/registry/cache"
|
||||||
|
- "~/.cargo/git/db"
|
||||||
- "~/.rustup"
|
- "~/.rustup"
|
||||||
- "~/.cache/cargo-target"
|
- "~/.cache/cargo-target"
|
||||||
|
|
||||||
@@ -62,5 +72,8 @@ steps:
|
|||||||
paths: *cargo_cache
|
paths: *cargo_cache
|
||||||
commands:
|
commands:
|
||||||
- *install_rust
|
- *install_rust
|
||||||
|
# The minimal profile ships neither, and rust-toolchain names no
|
||||||
|
# components. Only this step needs them.
|
||||||
|
- rustup component add rustfmt clippy
|
||||||
- RUSTFLAGS="-D warnings" cargo clippy --workspace --all-features --tests
|
- RUSTFLAGS="-D warnings" cargo clippy --workspace --all-features --tests
|
||||||
- cargo fmt --all -- --check
|
- cargo fmt --all -- --check
|
||||||
|
|||||||
+19
-5
@@ -4,6 +4,10 @@
|
|||||||
# volume. Master updates them with --update-snapshots; every other branch
|
# volume. Master updates them with --update-snapshots; every other branch
|
||||||
# compares against them.
|
# compares against them.
|
||||||
#
|
#
|
||||||
|
# A step can attach only one volume, so these two steps cannot also read the
|
||||||
|
# `deps` volume from node-checks.yml. This volume carries the npm cache and the
|
||||||
|
# Radicle binaries as well as the baselines.
|
||||||
|
#
|
||||||
# A job does not read the volume in place: it gets a private copy at start, and
|
# A job does not read the volume in place: it gets a private copy at start, and
|
||||||
# that copy becomes the version the next job sees if the job exits 0. So every
|
# that copy becomes the version the next job sees if the job exits 0. So every
|
||||||
# successful job is a writer, whether or not it changed a file. The two steps
|
# successful job is a writer, whether or not it changed a file. The two steps
|
||||||
@@ -16,9 +20,15 @@
|
|||||||
# --update-snapshots the mode is `missing`, under which Playwright still
|
# --update-snapshots the mode is `missing`, under which Playwright still
|
||||||
# writes a baseline that does not exist yet. A branch therefore needs no
|
# writes a baseline that does not exist yet. A branch therefore needs no
|
||||||
# concurrency limit, and any number of branch builds can run at once.
|
# concurrency limit, and any number of branch builds can run at once.
|
||||||
|
#
|
||||||
|
# The cost of that trick is that a branch build never adds to this volume, and
|
||||||
|
# so never warms the npm cache it shares with the baselines. A branch that
|
||||||
|
# changes package-lock.json therefore downloads the full dependency set on every
|
||||||
|
# build until it lands on master.
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CI: "true"
|
CI: "true"
|
||||||
|
NPM_CONFIG_CACHE: ".npm-cache"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- label: "📸 visual update snapshots"
|
- label: "📸 visual update snapshots"
|
||||||
@@ -29,10 +39,12 @@ steps:
|
|||||||
# jobs were created, so the newest master build commits last.
|
# jobs were created, so the newest master build commits last.
|
||||||
concurrency: 1
|
concurrency: 1
|
||||||
concurrency_group: "radicle-explorer/visual-snapshots"
|
concurrency_group: "radicle-explorer/visual-snapshots"
|
||||||
cache:
|
cache: &visual_cache
|
||||||
name: visual-snapshots
|
name: visual-snapshots
|
||||||
paths:
|
paths:
|
||||||
- "tests/visual/snapshots"
|
- "tests/visual/snapshots"
|
||||||
|
- ".npm-cache"
|
||||||
|
- "tests/tmp/bin"
|
||||||
artifact_paths:
|
artifact_paths:
|
||||||
- "tests/artifacts/**/*.png"
|
- "tests/artifacts/**/*.png"
|
||||||
plugins:
|
plugins:
|
||||||
@@ -47,8 +59,13 @@ steps:
|
|||||||
ipc: host
|
ipc: host
|
||||||
# buildkite-agent annotate runs inside the container.
|
# buildkite-agent annotate runs inside the container.
|
||||||
mount-buildkite-agent: true
|
mount-buildkite-agent: true
|
||||||
|
# Without this the cache volume links point at nothing, which is why
|
||||||
|
# the baselines never persisted before.
|
||||||
|
volumes:
|
||||||
|
- "/cache/bkcache:/cache/bkcache"
|
||||||
environment:
|
environment:
|
||||||
- CI
|
- CI
|
||||||
|
- NPM_CONFIG_CACHE
|
||||||
commands:
|
commands:
|
||||||
- npm ci
|
- npm ci
|
||||||
# The image ships no decompressors: install-binaries unpacks .tar.xz
|
# The image ships no decompressors: install-binaries unpacks .tar.xz
|
||||||
@@ -78,10 +95,7 @@ steps:
|
|||||||
# produces, so only the success path below is soft.
|
# produces, so only the success path below is soft.
|
||||||
soft_fail:
|
soft_fail:
|
||||||
- exit_status: 100
|
- exit_status: 100
|
||||||
cache:
|
cache: *visual_cache
|
||||||
name: visual-snapshots
|
|
||||||
paths:
|
|
||||||
- "tests/visual/snapshots"
|
|
||||||
artifact_paths:
|
artifact_paths:
|
||||||
- "tests/artifacts/**/*.png"
|
- "tests/artifacts/**/*.png"
|
||||||
plugins:
|
plugins:
|
||||||
|
|||||||
+4
-2
@@ -1,5 +1,7 @@
|
|||||||
/build/
|
/build/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
.npm-cache/
|
||||||
|
.lintcache/
|
||||||
NOTES
|
NOTES
|
||||||
config/local*
|
config/local*
|
||||||
crates/radicle-httpd/build/artifacts
|
crates/radicle-httpd/build/artifacts
|
||||||
@@ -12,8 +14,8 @@ KaTeX_**.ttf
|
|||||||
KaTeX_**.woff
|
KaTeX_**.woff
|
||||||
KaTeX_**.woff2
|
KaTeX_**.woff2
|
||||||
|
|
||||||
# Twemoji Assets
|
# Twemoji Assets, copied in by vite-plugin-assets
|
||||||
public/twemoji/*.svg
|
public/twemoji/
|
||||||
|
|
||||||
# Integration Tests
|
# Integration Tests
|
||||||
tests/tmp/**/*
|
tests/tmp/**/*
|
||||||
|
|||||||
+2
-2
@@ -27,8 +27,8 @@ WORKDIR /work
|
|||||||
# Playwright version in package-lock.json so the baked browser matches the
|
# Playwright version in package-lock.json so the baked browser matches the
|
||||||
# @playwright/test that npm ci installs into the volume at runtime. Copying
|
# @playwright/test that npm ci installs into the volume at runtime. Copying
|
||||||
# only the lockfiles keeps this layer cached across source changes.
|
# only the lockfiles keeps this layer cached across source changes.
|
||||||
# --ignore-scripts: the repo's postinstall (copy-katex/twemoji assets) needs
|
# --ignore-scripts: this layer only warms the browser cache, so dependency
|
||||||
# scripts/ which isn't in this layer; those run at runtime instead.
|
# install scripts are not necessary.
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci --ignore-scripts \
|
RUN npm ci --ignore-scripts \
|
||||||
&& npx playwright install --with-deps chromium \
|
&& npx playwright install --with-deps chromium \
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ export default [
|
|||||||
{
|
{
|
||||||
ignores: [
|
ignores: [
|
||||||
"node_modules/**/*",
|
"node_modules/**/*",
|
||||||
|
".npm-cache/**/*",
|
||||||
"build/**/*",
|
"build/**/*",
|
||||||
"public/**/*",
|
"public/**/*",
|
||||||
"radicle-httpd/**/*",
|
"radicle-httpd/**/*",
|
||||||
|
|||||||
@@ -81,13 +81,6 @@
|
|||||||
packages = {
|
packages = {
|
||||||
default = self.packages.${system}.radicle-explorer;
|
default = self.packages.${system}.radicle-explorer;
|
||||||
|
|
||||||
twemoji-assets = pkgs.fetchFromGitHub {
|
|
||||||
owner = "twitter";
|
|
||||||
repo = "twemoji";
|
|
||||||
rev = "v14.0.2";
|
|
||||||
hash = "sha256-YoOnZ5uVukzi/6bLi22Y8U5TpplPzB7ji42l+/ys5xI=";
|
|
||||||
};
|
|
||||||
|
|
||||||
radicle-explorer = pkgs.callPackage ({
|
radicle-explorer = pkgs.callPackage ({
|
||||||
lib,
|
lib,
|
||||||
buildNpmPackage,
|
buildNpmPackage,
|
||||||
@@ -97,12 +90,9 @@
|
|||||||
pname = "radicle-explorer";
|
pname = "radicle-explorer";
|
||||||
version = (builtins.fromJSON (builtins.readFile ./package.json)).version;
|
version = (builtins.fromJSON (builtins.readFile ./package.json)).version;
|
||||||
src = ./.;
|
src = ./.;
|
||||||
npmDepsHash = "sha256-RC+QQXtvXC48uM0oOAFA0ni5AU/l9m8k1LgrxykSu5M=";
|
npmDepsHash = "sha256-pUyB56EJjuMYU//oMaFNV27Fg+Ga5jD0HQOB/v+gHa8=";
|
||||||
postPatch = ''
|
postPatch = ''
|
||||||
patchShebangs --build ./scripts
|
patchShebangs --build ./scripts
|
||||||
mkdir -p "public/twemoji"
|
|
||||||
cp -t public/twemoji -r -- ${self.packages.${system}.twemoji-assets}/assets/svg/*
|
|
||||||
: >scripts/install-twemoji-assets
|
|
||||||
'';
|
'';
|
||||||
dontConfigure = true;
|
dontConfigure = true;
|
||||||
|
|
||||||
|
|||||||
Generated
+7
-1
@@ -7,8 +7,8 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "radicle-explorer",
|
"name": "radicle-explorer",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"hasInstallScript": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@twemoji/svg": "^15.0.0",
|
||||||
"@wooorm/starry-night": "^3.10.0",
|
"@wooorm/starry-night": "^3.10.0",
|
||||||
"async-mutex": "^0.5.0",
|
"async-mutex": "^0.5.0",
|
||||||
"baconjs": "^3.0.23",
|
"baconjs": "^3.0.23",
|
||||||
@@ -1183,6 +1183,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@twemoji/svg": {
|
||||||
|
"version": "15.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@twemoji/svg/-/svg-15.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZSPef2B6nBaYnfgdTbAy4jgW95o7pi2xPGwGCU+WMTxo7J6B1lMPTWwSq/wTuiMq+N0khQ90CcvYp1wFoQpo/w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@tybys/wasm-util": {
|
"node_modules/@tybys/wasm-util": {
|
||||||
"version": "0.10.3",
|
"version": "0.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
|
||||||
|
|||||||
+2
-2
@@ -6,8 +6,7 @@
|
|||||||
"start": "vite",
|
"start": "vite",
|
||||||
"start:httpd": "cargo run -p radicle-httpd --release -- --listen 127.0.0.1:8080",
|
"start:httpd": "cargo run -p radicle-httpd --release -- --listen 127.0.0.1:8080",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"build": "vite build && scripts/copy-katex-assets && scripts/install-twemoji-assets",
|
"build": "vite build",
|
||||||
"postinstall": "scripts/copy-katex-assets && scripts/install-twemoji-assets",
|
|
||||||
"check": "scripts/check",
|
"check": "scripts/check",
|
||||||
"check:radicle-httpd": "cargo clippy --all --tests && cargo fmt --all --check",
|
"check:radicle-httpd": "cargo clippy --all --tests && cargo fmt --all --check",
|
||||||
"format": "npx prettier '**/*.@(ts|js|svelte|json|css|html|yml)' --write",
|
"format": "npx prettier '**/*.@(ts|js|svelte|json|css|html|yml)' --write",
|
||||||
@@ -63,6 +62,7 @@
|
|||||||
"wait-on": "^9.0.10"
|
"wait-on": "^9.0.10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@twemoji/svg": "^15.0.0",
|
||||||
"@wooorm/starry-night": "^3.10.0",
|
"@wooorm/starry-night": "^3.10.0",
|
||||||
"async-mutex": "^0.5.0",
|
"async-mutex": "^0.5.0",
|
||||||
"baconjs": "^3.0.23",
|
"baconjs": "^3.0.23",
|
||||||
|
|||||||
+7
-2
@@ -3,5 +3,10 @@ set -e
|
|||||||
|
|
||||||
npx tsc --noEmit
|
npx tsc --noEmit
|
||||||
npx svelte-check --tsconfig tsconfig.json --config ./vite.config.ts --fail-on-warnings --compiler-warnings options_missing_custom_element:ignore
|
npx svelte-check --tsconfig tsconfig.json --config ./vite.config.ts --fail-on-warnings --compiler-warnings options_missing_custom_element:ignore
|
||||||
npx eslint --cache --cache-location node_modules/.cache/eslint --max-warnings 0 .
|
# Both caches sit outside node_modules, which `npm ci` deletes, and both are
|
||||||
npx prettier "**/*.@(ts|js|svelte|json|css|html|yml)" --ignore-path .gitignore --ignore-path .prettierignore --check --cache
|
# keyed on file contents rather than mtimes, which a fresh checkout changes.
|
||||||
|
# CI can then carry them between builds. They share one directory because a
|
||||||
|
# Buildkite cache path is always a directory, never a file.
|
||||||
|
mkdir -p .lintcache
|
||||||
|
npx eslint --cache --cache-location .lintcache/eslint --cache-strategy content --max-warnings 0 .
|
||||||
|
npx prettier "**/*.@(ts|js|svelte|json|css|html|yml)" --ignore-path .gitignore --ignore-path .prettierignore --check --cache --cache-location .lintcache/prettier
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
echo "Copying katex assets into bundle directory"
|
|
||||||
|
|
||||||
cp -r node_modules/katex/dist/katex.min.css public/katex.min.css
|
|
||||||
cp -r node_modules/katex/dist/fonts/* public/fonts/
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeou pipefail
|
|
||||||
|
|
||||||
version="$(node -e 'console.log(require("twemoji/package.json").version)')"
|
|
||||||
|
|
||||||
echo "Installing Twemoji SVG assets v${version}"
|
|
||||||
|
|
||||||
curl -sSL "https://github.com/twitter/twemoji/archive/refs/tags/v${version}.tar.gz" \
|
|
||||||
| tar -x -z -C public/twemoji/ --strip-components=3 "twemoji-${version}/assets/svg"
|
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import type { Plugin } from "vite";
|
||||||
|
|
||||||
|
import fs from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import { createRequire } from "node:module";
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
function packageDir(name: string): string {
|
||||||
|
return path.dirname(require.resolve(`${name}/package.json`));
|
||||||
|
}
|
||||||
|
|
||||||
|
function packageVersion(name: string): string {
|
||||||
|
const { version } = require(`${name}/package.json`) as { version: string };
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
// index.html loads /katex.min.css, so the stylesheet and the fonts it
|
||||||
|
// references must be in the public directory.
|
||||||
|
async function copyKatexAssets(publicDir: string) {
|
||||||
|
const dist = path.join(packageDir("katex"), "dist");
|
||||||
|
|
||||||
|
await fs.copyFile(
|
||||||
|
path.join(dist, "katex.min.css"),
|
||||||
|
path.join(publicDir, "katex.min.css"),
|
||||||
|
);
|
||||||
|
await fs.cp(path.join(dist, "fonts"), path.join(publicDir, "fonts"), {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emoji URLs are built at runtime, so the SVGs cannot be bundled and must be
|
||||||
|
// in the public directory. The version stamp skips the copy of ~3700 files
|
||||||
|
// when the assets are already installed. It lives outside the public directory
|
||||||
|
// because Vite copies that directory into the build as it is. node_modules is
|
||||||
|
// the right place for it: an install is what changes the package, and it is
|
||||||
|
// also what discards the stamp.
|
||||||
|
//
|
||||||
|
// The stamp is not enough on its own, because it and the assets sit in
|
||||||
|
// different places and either one can go without the other. The Docker e2e
|
||||||
|
// setup keeps node_modules in a volume that outlives the working tree, so a
|
||||||
|
// `git clean` there removes the assets and leaves the stamp behind. Checking
|
||||||
|
// the target as well stops a stamp that has outlived its assets from producing
|
||||||
|
// a build with no emoji in it, which nothing else would report.
|
||||||
|
async function copyTwemojiAssets(root: string, publicDir: string) {
|
||||||
|
const version = packageVersion("@twemoji/svg");
|
||||||
|
const target = path.join(publicDir, "twemoji");
|
||||||
|
const stamp = path.join(root, "node_modules", ".cache", "twemoji-version");
|
||||||
|
|
||||||
|
const installed = await fs.readFile(stamp, "utf8").catch(() => undefined);
|
||||||
|
const present = await fs.stat(target).catch(() => undefined);
|
||||||
|
if (installed === version && present) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = packageDir("@twemoji/svg");
|
||||||
|
await fs.cp(source, target, {
|
||||||
|
recursive: true,
|
||||||
|
// The package also holds a readme, a license and package.json.
|
||||||
|
filter: entry => entry === source || entry.endsWith(".svg"),
|
||||||
|
});
|
||||||
|
await fs.mkdir(path.dirname(stamp), { recursive: true });
|
||||||
|
await fs.writeFile(stamp, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assets(): Plugin {
|
||||||
|
return {
|
||||||
|
name: "assets",
|
||||||
|
// Vitest loads this config too, but does not need the assets.
|
||||||
|
apply: () => !process.env.VITEST,
|
||||||
|
// The dev server reads the public directory right after the config is
|
||||||
|
// resolved, so the assets have to be in place before that.
|
||||||
|
async configResolved(config) {
|
||||||
|
await copyKatexAssets(config.publicDir);
|
||||||
|
await copyTwemojiAssets(config.root, config.publicDir);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ import path from "node:path";
|
|||||||
import { defineConfig } from "vitest/config";
|
import { defineConfig } from "vitest/config";
|
||||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||||
|
|
||||||
|
import { assets } from "./vite-plugin-assets";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
define: {
|
define: {
|
||||||
buildTimeConfig: JSON.stringify(config.util.toObject()),
|
buildTimeConfig: JSON.stringify(config.util.toObject()),
|
||||||
@@ -13,6 +15,7 @@ export default defineConfig({
|
|||||||
reporters: "verbose",
|
reporters: "verbose",
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
assets(),
|
||||||
svelte({
|
svelte({
|
||||||
extensions: [".svelte", ".md"],
|
extensions: [".svelte", ".md"],
|
||||||
// Reference: https://github.com/sveltejs/vite-plugin-svelte/issues/270#issuecomment-1033190138
|
// Reference: https://github.com/sveltejs/vite-plugin-svelte/issues/270#issuecomment-1033190138
|
||||||
|
|||||||
Reference in New Issue
Block a user