Compare commits

2 Commits
Author SHA1 Message Date
sneak e82b1cc3df Lint static/js/ with ESLint in Docker (closes #120)
check / check (push) Successful in 3m29s
ESLint, pinned by package.json and yarn.lock, runs in a new js-lint
stage of the Dockerfile on the pinned node 24 LTS image. It starts from
a js-deps stage that installs ESLint and stays cached until those two
files change. script/lint builds js-lint after the Go lint, and the
build stage depends on it, so make check and the image build both fail
on a violation. eslint.config.mjs turns on the styleguide's checkable
rules: no-var and prefer-const.

Model: opus-5-5
2026-10-03 02:12:38 +00:00
clawbot d8c60c9b67 Event log: show attempt and delivery times, label replays, zone event times (closes #386)
check / check (push) Successful in 3m21s
In the event log and on an event's page, attempts and deliveries showed no time, event times had no zone, and a replay looked like the original it repeated. Each attempt now shows when its result was recorded and each delivery when it was created, as how long ago with the full UTC time on hover, and the event log's event times read the same way. A delivery created by Replay records it in a new `replay` column and is labelled a replay in the event's summary line and its delivery list; replays made before this change are not labelled. A delivery's row is now drawn by one template, `delivery_row`, that both pages share.

Model: opus-5-5
2026-10-03 04:12:27 +02:00
23 changed files with 811 additions and 46 deletions
+3
View File
@@ -15,6 +15,9 @@ bin/
# Extracted from 3p/ by `make assets` inside the build; a host copy is not
# needed. The tarball in 3p/ must stay in the context.
static/js/alpine.min.js
# The js-deps stage installs ESLint; a host copy would overwrite it at the
# js-lint stage's `COPY . .`.
node_modules/
.env
.env.*
*.db
+1 -1
View File
@@ -33,5 +33,5 @@ jobs:
# and built cannot report success from cache.
run: git rev-parse HEAD > .ci-fingerprint
- name: Build Docker image (runs make fmt-check, golangci-lint, the stylesheet check, make test, make build)
- name: Build Docker image (runs make fmt-check, golangci-lint, the stylesheet check, ESLint, make test, make build)
run: script/cibuild
+3
View File
@@ -15,6 +15,9 @@ bin/
# Go vendor directory
vendor/
# ESLint and its dependencies, installed from yarn.lock
node_modules/
# IDE specific files
.idea/
*.swp
+17 -1
View File
@@ -65,15 +65,31 @@ RUN sed 's/}/}\n/g' static/css/tailwind.css > /tmp/committed.css \
exit 1; \
}
# JavaScript lint stages: ESLint, at the version package.json and yarn.lock
# pin, checks static/js/ against eslint.config.mjs. js-deps installs it and
# stays cached until those two files change. script/lint forces only js-lint
# to re-run, and the build stage below runs it too. COPY . . brings in the CI
# cache barrier described in the lint stage above.
# node:24.21.0-alpine (LTS, with yarn 1.22.22), 2026-09-18
FROM node:24.21.0-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1 AS js-deps
WORKDIR /src
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --ignore-scripts
FROM js-deps AS js-lint
COPY . .
RUN --network=none node_modules/.bin/eslint static/js
# Build stage
# golang:1.26.1-bookworm (Debian-based), 2026-03-17
# Using Debian-based image because gorm.io/driver/sqlite pulls in
# mattn/go-sqlite3 (CGO), which does not compile on Alpine musl.
FROM golang:1.26.1-bookworm@sha256:4465644228bc2857a954b092167e12aa59c006a3492282a6c820bf4755fd64a4 AS builder
# Depend on the lint and stylesheet check stages passing
# Depend on the lint, stylesheet check and JavaScript lint stages passing
COPY --from=lint /src/go.sum /dev/null
COPY --from=css-check /out/tailwind.css /dev/null
COPY --from=js-lint /src/yarn.lock /dev/null
# jq is a runtime dependency of script/ci-mark-superseded, which the test
# suite executes. git is what script/version derives the version with.
+43 -13
View File
@@ -26,7 +26,9 @@ before deploying one.
golangci-lint is not a prerequisite and must not be installed on the
host: `script/bootstrap` does not install it, and `make lint` runs the
digest-pinned linter image via `Dockerfile.lint`. The same holds for
tailwindcss (see [Stylesheet](#stylesheet)).
tailwindcss (see [Stylesheet](#stylesheet)). ESLint, node and yarn are
not prerequisites either, and `make lint` never uses a host copy of them
(see [Linting](#linting)).
### Quick Start
@@ -58,7 +60,7 @@ make setup # Bootstrap + install git pre-commit hook
make assets # Extract Alpine.js from 3p/ (test, check, build, dev run it)
make fmt # Format code (gofmt + goimports)
make fmt-check # Fail if gofmt would change anything (writes nothing)
make lint # Run golangci-lint in Docker (Dockerfile.lint)
make lint # Run golangci-lint and ESLint in Docker
make test # Run tests with race detection
make test-browser # Run the browser test in Docker (Dockerfile.browser)
make check # test + lint + fmt-check + css-check (CI gate)
@@ -1318,7 +1320,8 @@ We provide:
- `script/test` — run the test suite
- `script/test-browser` — run the browser test in Docker (see
[Third-party browser assets](#third-party-browser-assets))
- `script/lint` — run golangci-lint in Docker (see Linting below)
- `script/lint` — run golangci-lint and ESLint in Docker (see Linting
below)
- `script/fmt` — format all code (writes)
- `script/fmt-check` — check formatting (read-only)
- `script/css` — regenerate `static/css/tailwind.css` in Docker (writes;
@@ -1831,6 +1834,7 @@ status across potentially multiple attempts.
| `target_id`| UUID | Foreign key → Target |
| `status` | DeliveryStatus | One of: `pending`, `delivered`, `failed`, `retrying` |
| `finished_at` | timestamp | When the delivery became `delivered` or `failed` (nullable; empty while `pending` or `retrying`) |
| `replay` | boolean | Whether the delivery was created by **Replay** |
**Relations:** Belongs to Event. Belongs to Target. Has many
DeliveryResults.
@@ -1850,7 +1854,8 @@ NEW `pending` delivery for the same event and target and hands it to
the engine on the ordinary path — same retries, same SSRF guard, same
circuit breaker as a first attempt. It never touches the delivery it
repeats: that row's status, timestamps and recorded attempts stand as
the record of what happened.
the record of what happened. The new delivery records `replay`, and the
event log and the event's page label it a replay.
What is re-sent is the stored event body, against the target's
configuration **as it stands now** — the point of a replay is to
@@ -1904,6 +1909,10 @@ A `database` or `log` target sends no HTTP request, so in the event log and
on the event's page its attempts show no status: a successful one reads
"archived" or "written to the log".
The event log and the event's page show when each attempt was recorded and
when each delivery was created, as the recent events list shows when an event
arrived: how long ago, with the full UTC time on hover.
**Relations:** Belongs to Delivery.
#### EventTotals, TargetTotals and EntrypointTotals
@@ -3200,12 +3209,14 @@ webhooker/
│ └── js/alpine.min.js # Alpine.js CSP build, extracted from 3p/ by make assets, not committed
├── templates/ # Go HTML templates (base, login, sources, etc.)
├── script/ # Scripts to Rule Them All entrypoints
├── Dockerfile # Stages: lint, stylesheet, test+build, Alpine runtime
├── Dockerfile # Stages: lint, stylesheet, JavaScript lint, test+build, Alpine runtime
├── Dockerfile.lint # Lint-only image built by script/lint
├── Dockerfile.browser # Browser test image built by script/test-browser
├── Makefile # 13 of 19 targets shim script/; 6 are inline
├── go.mod / go.sum
└── .golangci.yml # Linter configuration
├── package.json / yarn.lock # ESLint, pinned, for the JavaScript lint stage
├── eslint.config.mjs # ESLint configuration for static/js/
└── .golangci.yml # golangci-lint configuration
```
### Dependency Injection
@@ -3515,6 +3526,20 @@ Three properties are load-bearing:
`golangci-lint run` silently ignores config keys it does not
recognize, so a typo would disable a setting with no warning.
ESLint never runs on the host either. It lints `static/js/` (not the
extracted Alpine.js) in the Dockerfile's `js-lint` stage, which
`script/lint` builds after `Dockerfile.lint` and the image build runs
before the builder stage. Its version is pinned in `package.json` and
every package's hash in `yarn.lock`. The `js-deps` stage before it
installs ESLint and stays cached until either file changes, so only the
lint step re-runs and ESLint is not downloaded again.
`eslint.config.mjs` turns on the rules of the JavaScript styleguide
`REPO_POLICIES.md` links to that a linter can check: `no-var` and
`prefer-const`. ESLint prints nothing on a pass, so `script/lint` has no
summary line to look for; it names the stage once for both `--target`
and `--no-cache-filter`, and `--target` fails on a name that matches no
stage.
### Docker
The Dockerfile uses a multi-stage build. Each stage is pinned by
@@ -3533,8 +3558,12 @@ linter's version is fixed independently of the compiler's:
`css-check` fails when the committed file differs from the generated
one, and `make css` writes the generated file out from `css-output`
(see [Stylesheet](#stylesheet)).
3. **Builder stage** (`golang:1.26.1-bookworm`) — depends on the lint
and `css-check` stages passing (it copies a file from each), runs
3. **JavaScript lint stages** (`node:24.21.0-alpine`, with yarn) —
`js-deps` installs ESLint from `yarn.lock` and `js-lint` runs it over
`static/js/` (see [Linting](#linting)).
4. **Builder stage** (`golang:1.26.1-bookworm`) — depends on the lint,
`css-check` and `js-lint` stages passing (it copies a file from
each), runs
`make test` and `make build` (both extract Alpine.js from `3p/`
first), and finally
rebuilds the binary with `CGO_ENABLED=1` and static linking so it
@@ -3544,7 +3573,7 @@ linter's version is fixed independently of the compiler's:
given, otherwise derived from the `.git` in the context, and the
stage fails if a context with `.git` would stamp `unknown` (see
[Version stamping](#version-stamping)).
4. **Runtime stage** (`alpine:3.21`) — copies the static binary and
5. **Runtime stage** (`alpine:3.21`) — copies the static binary and
`deploy/docker-entrypoint.sh`, creates the `/var/lib/webhooker`
directory for all SQLite databases, exposes port 8080, and includes
a health check against `/.well-known/healthcheck`. It sets no
@@ -3564,8 +3593,9 @@ linked, which is what lets it run on the Alpine runtime image.
`script/cibuild` — `docker build .` — is the CI gate: the checks run
inside the image, so a build that succeeds is a repo that is formatted,
linted, tested and compiled, with a current stylesheet. `script/lint`
also uses Docker (`Dockerfile.lint`, see Linting above), so `make lint`
and `make check` run the same pinned linter version the gate does; of
also uses Docker (`Dockerfile.lint` and the `js-lint` stage, see Linting
above), so `make lint` and `make check` run the same pinned linter
versions the gate does; of
the steps `make check` runs, only `script/test` and `script/fmt-check`
run on the host.
@@ -3579,8 +3609,8 @@ the hash of the commit being checked, so every commit, docs-only ones
and a squash merge whose tree matches an already-built branch included,
gets a new fingerprint, invalidates the `COPY . .` layer of every check
stage, and really runs `make fmt-check`, `golangci-lint`, the stylesheet
check, `make test`, and `make build`. A run that reports success ran
them.
check, ESLint, `make test`, and `make build`. A run that reports success
ran them.
The module download layer sits above `COPY . .` and stays cached.
+18
View File
@@ -0,0 +1,18 @@
// ESLint configuration for static/js/. script/lint and the image build run
// ESLint in the Dockerfile's js-lint stage, never on the host.
//
// The rules are the ones the JavaScript styleguide linked from
// REPO_POLICIES.md states that a linter can check: const for everything,
// let only for a variable that is reassigned, never var.
export default [
// Alpine.js, extracted from 3p/ by make assets; not ours to lint.
{ ignores: ["static/js/alpine.min.js"] },
{
// The pages load static/js/app.js as a classic script, not a module.
languageOptions: { sourceType: "script" },
rules: {
"no-var": "error",
"prefer-const": "error",
},
},
];
+4
View File
@@ -56,6 +56,10 @@ type Delivery struct {
// the index.
FinishedAt *time.Time `gorm:"index:idx_deliveries_status,priority:3" json:"finishedAt,omitempty"`
// Replay is set on a delivery created by the event log's Replay
// action, so the pages can tell it from the delivery it repeats.
Replay bool `gorm:"not null;default:false" json:"replay"`
// Relations. No model marshals the record it belongs to:
// Event.Deliveries and Target.Deliveries lead back here, and the
// JSON could loop.
+1
View File
@@ -282,6 +282,7 @@ func createReplayDelivery(
EventID: event.ID,
TargetID: target.ID,
Status: database.DeliveryStatusPending,
Replay: true,
}
err := webhookDB.Transaction(func(tx *gorm.DB) error {
+56
View File
@@ -3,6 +3,7 @@ package handlers_test
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -528,3 +529,58 @@ func TestHandleSourceLogs_RendersReplayControlAndBanner(t *testing.T) {
assert.NotContains(t, unknown, "alert-success")
assert.NotContains(t, unknown, "made-up")
}
// TestHandleDeliveryReplay_LabelsTheReplay proves a delivery created
// by Replay is labelled as a replay in the event's summary line in the
// event log, and in the list of the event's deliveries there and on
// the event's page, while the delivery it repeats is not.
func TestHandleDeliveryReplay_LabelsTheReplay(t *testing.T) {
t.Parallel()
var (
h *handlers.Handlers
sess *session.Session
db *database.Database
dbMgr *database.WebhookDBManager
)
app := newTestApp(t, &h, &sess, &db, &dbMgr)
app.RequireStart()
t.Cleanup(app.RequireStop)
wh := seedWebhook(t, db)
tgt := seedConfiguredTarget(
t, db, wh.ID, database.TargetTypeHTTP,
`{"url":"`+replayTargetURL+`"}`,
)
event, original := seedFailedDelivery(t, dbMgr, wh.ID, tgt.ID)
w := postReplay(t, h, sess, wh.ID, original.ID)
require.Equal(t, http.StatusSeeOther, w.Code)
eventLog := renderSourceLogsPage(t, h, sess, wh.ID)
assert.Contains(t, eventLog, tgt.Name+": failed")
assert.Contains(t, eventLog, tgt.Name+" (replay): pending")
w = serveEventPage(t, h, sess, wh.ID, event.ID)
require.Equal(t, http.StatusOK, w.Code)
// In each delivery list a row names the target, then the label if
// it is a replay, then its status: the replay is still pending, the
// original failed.
replayRow := tgt.Name + `</span> ` +
`<span class="text-xs text-gray-500">replay</span> ` +
`<span class="text-xs text-gray-400">pending</span>`
originalRow := tgt.Name + `</span> ` +
`<span class="text-xs text-red-600">failed</span>`
for _, page := range []string{eventLog, w.Body.String()} {
page = strings.Join(strings.Fields(page), " ")
assert.Contains(t, page, replayRow)
assert.Contains(t, page, originalRow)
}
}
@@ -3,6 +3,7 @@ package handlers
import (
"time"
"github.com/dustin/go-humanize"
"sneak.berlin/go/webhooker/internal/delivery"
)
@@ -47,6 +48,11 @@ type DeliveryResultView struct {
AttemptNum int
Success bool
// Ran is how long ago the attempt was recorded, and RanUTC the
// full timestamp the page shows on hover.
Ran string
RanUTC string
// StatusCode is 0 when the attempt never got a response,
// which is why the page asks HasStatusCode rather than
// printing the number.
@@ -160,6 +166,8 @@ func (r *deliveryResultRow) view(
return DeliveryResultView{
AttemptNum: r.AttemptNum,
Success: r.Success,
Ran: humanize.Time(r.CreatedAt),
RanUTC: r.CreatedAt.UTC().Format(time.DateTime) + " UTC",
StatusCode: r.StatusCode,
Error: redactor.Redact(r.Error),
DurationMS: r.Duration,
+52
View File
@@ -0,0 +1,52 @@
package handlers_test
import (
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sneak.berlin/go/webhooker/internal/database"
)
// TestEventLog_TimesCarryTheirZone proves that the event log shows
// when an event arrived, and that it and the event's page show when
// each delivery was created and each attempt recorded: each as how
// long ago, with the full UTC time on hover.
func TestEventLog_TimesCarryTheirZone(t *testing.T) {
t.Parallel()
f := newRecentEventsFixture(t)
target := seedTarget(t, f.db, f.webhook.ID, database.TargetTypeHTTP)
now := time.Now().UTC().Truncate(time.Second)
receivedAt := now.Add(-3 * time.Hour)
createdAt := now.Add(-90 * time.Minute)
ranAt := now.Add(-30 * time.Minute)
event := f.event(t, contentTypeJSON, "{}", receivedAt)
dlv := f.deliveryQueuedAt(
t, event, target.ID, database.DeliveryStatusDelivered, createdAt,
)
f.attempt(t, dlv, http.StatusOK, ranAt.Sub(createdAt))
w := serveEventPage(t, f.h, f.sess, f.webhook.ID, event.ID)
require.Equal(t, http.StatusOK, w.Code)
eventPage := w.Body.String()
eventLog := renderSourceLogsPage(t, f.h, f.sess, f.webhook.ID)
received := receivedAt.Format(time.DateTime)
assert.Contains(t, eventLog, `title="`+received+` UTC">3 hours ago</span>`)
assert.NotContains(t, eventLog, received+"</span>",
"an event's time must not be written without its zone")
assert.Contains(t, eventPage, received+" UTC")
for _, page := range []string{eventLog, eventPage} {
assert.Contains(t, page, `title="`+createdAt.Format(time.DateTime)+
` UTC">created 1 hour ago</span>`)
assert.Contains(t, page, `title="`+ranAt.Format(time.DateTime)+
` UTC">30 minutes ago</span>`)
}
}
+9 -2
View File
@@ -3,6 +3,8 @@ package handlers
import (
"time"
"unicode/utf8"
"github.com/dustin/go-humanize"
)
// eventLogColumns is the event log's projection. The casts to
@@ -28,10 +30,14 @@ const eventColumns = "id, created_at, method, content_type, " +
// DeliveryView and TargetView.
type EventLogView struct {
ID string
CreatedAt time.Time
Method string
ContentType string
// Received is how long ago the event arrived, and ReceivedUTC
// the full timestamp.
Received string
ReceivedUTC string
Body BodyView
// ResubmittedFromID names the event this one was copied
@@ -77,9 +83,10 @@ func (r *eventLogRow) view(webhookID string) EventLogView {
return EventLogView{
ID: r.ID,
CreatedAt: r.CreatedAt,
Method: r.Method,
ContentType: r.ContentType,
Received: humanize.Time(r.CreatedAt),
ReceivedUTC: r.CreatedAt.UTC().Format(time.DateTime) + " UTC",
Body: newBodyView(
"/hook/"+webhookID+"/events/"+r.ID, r.Body, r.BodyBytes,
),
+4 -2
View File
@@ -165,10 +165,12 @@ func New(
),
"source_edit.html": parsePageTemplate("source_edit.html"),
"source_logs.html": parsePageTemplate(
"source_logs.html", "event_body.html", "delivery_attempts.html",
"source_logs.html", "event_body.html", "delivery_row.html",
"delivery_attempts.html",
),
"event_detail.html": parsePageTemplate(
"event_detail.html", "event_body.html", "delivery_attempts.html",
"event_detail.html", "event_body.html", "delivery_row.html",
"delivery_attempts.html",
),
"target_edit.html": parsePageTemplate("target_edit.html"),
"error.html": parsePageTemplate("error.html"),
+13
View File
@@ -11,6 +11,7 @@ import (
"strings"
"time"
"github.com/dustin/go-humanize"
"github.com/go-chi/chi"
"github.com/google/uuid"
"gorm.io/gorm"
@@ -95,6 +96,14 @@ type DeliveryView struct {
Status database.DeliveryStatus
Target delivery.TargetView
// Replay is set on a delivery the Replay action created.
Replay bool
// Created is how long ago the delivery was created, and
// CreatedUTC the full timestamp the page shows on hover.
Created string
CreatedUTC string
// Results is this delivery's attempts in attempt order,
// bounded by maxRenderedAttempts. Without them a failure
// renders as the status word alone and says nothing about
@@ -1410,6 +1419,7 @@ func (h *Handlers) newDeliveryViews(
for i := range deliveries {
target := targetMap[deliveries[i].TargetID]
rows := attempts[deliveries[i].ID]
created := deliveries[i].CreatedAt
results, omitted := renderedAttempts(
rows, target.Redactor,
@@ -1419,6 +1429,9 @@ func (h *Handlers) newDeliveryViews(
ID: deliveries[i].ID,
Status: deliveries[i].Status,
Target: target.View,
Replay: deliveries[i].Replay,
Created: humanize.Time(created),
CreatedUTC: created.UTC().Format(time.DateTime) + " UTC",
Results: results,
AttemptCount: len(rows),
AttemptsOmitted: omitted,
+1 -2
View File
@@ -88,8 +88,7 @@ func (h *Handlers) deliveryPausedView(
}
// newPausedView is a PausedView of deliveries paused until the given
// time. A time not on the current UTC day is written with its date, as
// the event log writes its times.
// time. A time not on the current UTC day is written with its date.
func newPausedView(until time.Time) *PausedView {
until = until.UTC()
+6
View File
@@ -0,0 +1,6 @@
{
"private": true,
"devDependencies": {
"eslint": "10.11.0"
}
}
+2 -2
View File
@@ -3,8 +3,8 @@
# this repo. Idempotent: every install is guarded by a check so already
# installed tools are skipped. Base tooling comes from nix, apt, brew,
# or apk (detected in that order); assumes NOTHING is present (not git,
# make, or go). golangci-lint is deliberately not installed: linting runs
# only in docker, via script/lint and Dockerfile.lint.
# make, or go). golangci-lint, node and ESLint are deliberately not
# installed: linting runs only in docker, via script/lint.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
+18 -2
View File
@@ -1,6 +1,8 @@
#!/bin/sh
# script/lint: run the linter. golangci-lint is never installed locally: it
# runs via docker only, one way, everywhere — script/lint builds
# script/lint: run the linters, golangci-lint over the Go code and then
# ESLint over static/js/. Neither is ever installed locally.
#
# golangci-lint runs via docker only, one way, everywhere — script/lint builds
# Dockerfile.lint, which COPYs the repo into the pinned golangci-lint image
# and lints as a build step. This works even when the docker daemon is remote
# and bind mounts are impossible, and it removes the host linter's shared
@@ -50,6 +52,20 @@ main() {
echo " still matches a stage in Dockerfile.lint." >&2
exit 1
fi
# ESLint runs in the Dockerfile's js-lint stage, which the image build
# runs too. It prints nothing on a pass, so there is no summary to look
# for. Instead the stage is named once, for both flags: --target fails
# on a name that matches no stage, so a rename cannot leave
# --no-cache-filter silently ignored. The js-deps stage, which installs
# ESLint, keeps its cache, so ESLint is not downloaded again.
js_stage=js-lint
docker build \
--target "$js_stage" \
--no-cache-filter="$js_stage" \
--progress=plain \
--output=type=cacheonly \
.
}
main "$@"
+1
View File
@@ -9,6 +9,7 @@
<div class="rounded-md bg-white border border-gray-200 p-2">
<div class="flex flex-wrap items-center gap-3 text-xs">
<span class="text-gray-500">Attempt {{.AttemptNum}}</span>
<span class="text-gray-500" title="{{.RanUTC}}">{{.Ran}}</span>
<span class="{{if .Success}}text-green-600{{else}}text-red-600{{end}}">{{if not .Success}}failure{{else if eq $.Target.Type "database"}}archived{{else if eq $.Target.Type "log"}}written to the log{{else}}success{{end}}</span>
{{/* A database or log target sends no HTTP request, so its attempts have no status code. */}}
{{if not (eq $.Target.Type "database" "log")}}
+16
View File
@@ -0,0 +1,16 @@
{{define "delivery_row"}}
<!-- The line that heads a delivery, as handlers.DeliveryView holds it:
in the event log, inside the button that shows its attempts, and on
an event's own page. Spans only, since a button may hold no div. -->
<span class="flex flex-1 flex-wrap items-center justify-between gap-3">
<span class="flex flex-wrap items-center gap-3">
<span class="text-sm text-gray-700">{{.Target.DisplayName}}</span>
{{if .Replay}}<span class="text-xs text-gray-500">replay</span>{{end}}
<span class="text-xs {{if eq .Status "delivered"}}text-green-600{{else if eq .Status "failed"}}text-red-600{{else if eq .Status "retrying"}}text-yellow-600{{else}}text-gray-400{{end}}">{{with .Paused}}waiting: target paused after repeated failures, next try no earlier than {{.Until}} ({{.Relative}}){{else}}{{.Status}}{{end}}</span>
</span>
<span class="flex flex-wrap items-center gap-3">
<span class="text-xs text-gray-400" title="{{.CreatedUTC}}">created {{.Created}}</span>
<span class="text-xs text-gray-400">{{.AttemptCount}} attempt{{if ne .AttemptCount 1}}s{{end}}</span>
</span>
</span>
{{end}}
+2 -8
View File
@@ -21,7 +21,7 @@
</div>
<div class="flex flex-wrap gap-2">
<dt class="w-32 flex-shrink-0 text-gray-500">Received</dt>
<dd class="text-gray-900">{{.CreatedAt.UTC.Format "2006-01-02 15:04:05"}} UTC</dd>
<dd class="text-gray-900">{{.ReceivedUTC}}</dd>
</div>
<div class="flex flex-wrap gap-2">
<dt class="w-32 flex-shrink-0 text-gray-500">Method</dt>
@@ -66,13 +66,7 @@
<div class="divide-y divide-gray-100">
{{range .Deliveries}}
<div class="p-4">
<div class="flex flex-wrap items-center justify-between gap-3">
<span class="text-sm text-gray-700">{{.Target.DisplayName}}</span>
<span class="flex flex-wrap items-center gap-3">
<span class="text-xs {{if eq .Status "delivered"}}text-green-600{{else if eq .Status "failed"}}text-red-600{{else if eq .Status "retrying"}}text-yellow-600{{else}}text-gray-400{{end}}">{{with .Paused}}waiting: target paused after repeated failures, next try no earlier than {{.Until}} ({{.Relative}}){{else}}{{.Status}}{{end}}</span>
<span class="text-xs text-gray-400">{{.AttemptCount}} attempt{{if ne .AttemptCount 1}}s{{end}}</span>
</span>
</div>
{{template "delivery_row" .}}
<div class="mt-2 space-y-2">
{{template "delivery_attempts" .}}
</div>
+7 -13
View File
@@ -33,10 +33,10 @@
<span class="flex flex-wrap items-center gap-4">
{{range .Deliveries}}
<span class="text-xs {{if eq .Status "delivered"}}text-green-600{{else if eq .Status "failed"}}text-red-600{{else if eq .Status "retrying"}}text-yellow-600{{else}}text-gray-400{{end}}">
{{.Target.DisplayName}}: {{if .Paused}}waiting{{else}}{{.Status}}{{end}}
{{.Target.DisplayName}}{{if .Replay}} (replay){{end}}: {{if .Paused}}waiting{{else}}{{.Status}}{{end}}
</span>
{{end}}
<span class="text-xs text-gray-400">{{.CreatedAt.Format "2006-01-02 15:04:05"}}</span>
<span class="text-xs text-gray-400" title="{{.ReceivedUTC}}">{{.Received}}</span>
<!-- The caret has no text to select, so a click on it toggles at once. -->
<svg class="w-4 h-4 text-gray-400 transition-transform" :class="caretClass" @click.stop="toggle" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
@@ -64,17 +64,11 @@
{{range .Deliveries}}
<div class="py-2" x-data="collapsible">
<div class="flex items-center gap-3">
<button type="button" class="btn-small flex-1 flex-wrap justify-between gap-2 text-left" @click="toggle">
<span class="flex flex-wrap items-center gap-3">
<span class="text-sm text-gray-700">{{.Target.DisplayName}}</span>
<span class="text-xs {{if eq .Status "delivered"}}text-green-600{{else if eq .Status "failed"}}text-red-600{{else if eq .Status "retrying"}}text-yellow-600{{else}}text-gray-400{{end}}">{{with .Paused}}waiting: target paused after repeated failures, next try no earlier than {{.Until}} ({{.Relative}}){{else}}{{.Status}}{{end}}</span>
</span>
<span class="flex flex-wrap items-center gap-3">
<span class="text-xs text-gray-400">{{.AttemptCount}} attempt{{if ne .AttemptCount 1}}s{{end}}</span>
<svg class="w-3 h-3 text-gray-400 transition-transform" :class="caretClass" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</span>
<button type="button" class="btn-small flex-1 gap-3 text-left" @click="toggle">
{{template "delivery_row" .}}
<svg class="w-3 h-3 text-gray-400 transition-transform" :class="caretClass" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
{{if and .Status.Terminal (not .Target.Deleted)}}
<form method="POST" action="/hook/{{$.Webhook.ID}}/deliveries/{{.ID}}/replay" class="inline">
+526
View File
@@ -0,0 +1,526 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@cacheable/memory@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@cacheable/memory/-/memory-2.2.0.tgz#72aeb8b051f6d597d10ac8595b94ad8302bd2239"
integrity sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==
dependencies:
"@cacheable/utils" "^2.5.0"
"@keyv/bigmap" "^1.3.1"
hookified "^1.15.1"
keyv "^5.6.0"
"@cacheable/utils@^2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@cacheable/utils/-/utils-2.5.0.tgz#534c91113aa48fe43baedb169550b6ee070ef303"
integrity sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==
dependencies:
hashery "^1.5.1"
keyv "^5.6.0"
"@eslint-community/eslint-utils@^4.8.0":
version "4.10.1"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz#8911bd72b2c3640a543609e0400b8c4d2e7e7cb6"
integrity sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==
dependencies:
eslint-visitor-keys "^3.4.3"
"@eslint-community/regexpp@^4.12.2":
version "4.12.2"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b"
integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==
"@eslint/config-array@^0.23.5":
version "0.23.5"
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.5.tgz#56e86d243049195d8acc0c06a1b3dfdc3fa3de95"
integrity sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==
dependencies:
"@eslint/object-schema" "^3.0.5"
debug "^4.3.1"
minimatch "^10.2.4"
"@eslint/config-helpers@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.7.0.tgz#09ee4aa07b73f059ec2d4c74bf4b2ff02b322377"
integrity sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==
dependencies:
"@eslint/core" "^1.2.1"
"@eslint/core@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.2.1.tgz#c1da7cd1b82fa8787f98b5629fb811848a1b63ce"
integrity sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==
dependencies:
"@types/json-schema" "^7.0.15"
"@eslint/object-schema@^3.0.5":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.5.tgz#88e9bf4d11d2b19c082e78ebe7ce88724a5eb091"
integrity sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==
"@eslint/plugin-kit@^0.7.3":
version "0.7.3"
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz#cc7268cc36405b331ef92db1bc37971f21d66fe1"
integrity sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==
dependencies:
"@eslint/core" "^1.2.1"
levn "^0.4.1"
"@humanfs/core@^0.19.2":
version "0.19.2"
resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.2.tgz#a8272ca03b2acf492670222b2320b6c421bfde60"
integrity sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==
dependencies:
"@humanfs/types" "^0.15.0"
"@humanfs/node@^0.16.6":
version "0.16.8"
resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.8.tgz#8f800cccc13f4f8cd3116e2d9c0a94939da3e3ed"
integrity sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==
dependencies:
"@humanfs/core" "^0.19.2"
"@humanfs/types" "^0.15.0"
"@humanwhocodes/retry" "^0.4.0"
"@humanfs/types@^0.15.0":
version "0.15.0"
resolved "https://registry.yarnpkg.com/@humanfs/types/-/types-0.15.0.tgz#f2a09f62012390b2bff3fc6fb248ddec8c09a090"
integrity sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==
"@humanwhocodes/module-importer@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2":
version "0.4.3"
resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba"
integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==
"@keyv/bigmap@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@keyv/bigmap/-/bigmap-1.3.1.tgz#fc82fa83947e7ff68c6798d08907db842771ef2c"
integrity sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==
dependencies:
hashery "^1.4.0"
hookified "^1.15.0"
"@keyv/serialize@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@keyv/serialize/-/serialize-1.1.1.tgz#0c01dd3a3483882af7cf3878d4e71d505c81fc4a"
integrity sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==
"@types/esrecurse@^4.3.1":
version "4.3.1"
resolved "https://registry.yarnpkg.com/@types/esrecurse/-/esrecurse-4.3.1.tgz#6f636af962fbe6191b830bd676ba5986926bccec"
integrity sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==
"@types/estree@^1.0.6", "@types/estree@^1.0.8":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24"
integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==
"@types/json-schema@^7.0.15":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn@^8.16.0:
version "8.18.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.18.0.tgz#4faf01b2d6d326bfeed97aea1f52220b5f4c1940"
integrity sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==
ajv@^6.14.0:
version "6.15.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.15.0.tgz#07e982c74626167aa7a2495c53817892d7139492"
integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
balanced-match@^4.0.2:
version "4.0.4"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a"
integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==
brace-expansion@^5.0.8:
version "5.0.12"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.12.tgz#995fbb4750a77c4d16a7dc942ff2ca6ef8e675ec"
integrity sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==
dependencies:
balanced-match "^4.0.2"
cacheable@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/cacheable/-/cacheable-2.5.0.tgz#d142d41043e5a865f6053cc70ef4e3ad068bd5ce"
integrity sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==
dependencies:
"@cacheable/memory" "^2.2.0"
"@cacheable/utils" "^2.5.0"
hookified "^1.15.0"
keyv "^5.6.0"
qified "^0.10.1"
cross-spawn@^7.0.6:
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
which "^2.0.1"
debug@^4.3.1, debug@^4.3.2:
version "4.4.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
dependencies:
ms "^2.1.3"
deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
eslint-scope@^9.1.2:
version "9.1.2"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-9.1.2.tgz#b9de6ace2fab1cff24d2e58d85b74c8fcea39802"
integrity sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==
dependencies:
"@types/esrecurse" "^4.3.1"
"@types/estree" "^1.0.8"
esrecurse "^4.3.0"
estraverse "^5.2.0"
eslint-visitor-keys@^3.4.3:
version "3.4.3"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
eslint-visitor-keys@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be"
integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==
eslint@10.11.0:
version "10.11.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.11.0.tgz#304d1591b7c6a327e3f64b4f550f99fda160ae20"
integrity sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==
dependencies:
"@eslint-community/eslint-utils" "^4.8.0"
"@eslint-community/regexpp" "^4.12.2"
"@eslint/config-array" "^0.23.5"
"@eslint/config-helpers" "^0.7.0"
"@eslint/core" "^1.2.1"
"@eslint/plugin-kit" "^0.7.3"
"@humanfs/node" "^0.16.6"
"@humanwhocodes/module-importer" "^1.0.1"
"@humanwhocodes/retry" "^0.4.2"
"@types/estree" "^1.0.6"
ajv "^6.14.0"
cross-spawn "^7.0.6"
debug "^4.3.2"
escape-string-regexp "^4.0.0"
eslint-scope "^9.1.2"
eslint-visitor-keys "^5.0.1"
espree "^11.2.0"
esquery "^1.7.0"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
file-entry-cache "11.1.5 || >11.1.6 <12"
find-up "^5.0.0"
glob-parent "^6.0.2"
ignore "^5.2.0"
imurmurhash "^0.1.4"
is-glob "^4.0.0"
json-stable-stringify-without-jsonify "^1.0.1"
minimatch "^10.2.5"
natural-compare "^1.4.0"
optionator "^0.9.3"
espree@^11.2.0:
version "11.2.0"
resolved "https://registry.yarnpkg.com/espree/-/espree-11.2.0.tgz#01d5e47dc332aaba3059008362454a8cc34ccaa5"
integrity sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==
dependencies:
acorn "^8.16.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^5.0.1"
esquery@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d"
integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==
dependencies:
estraverse "^5.1.0"
esrecurse@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
dependencies:
estraverse "^5.2.0"
estraverse@^5.1.0, estraverse@^5.2.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
fast-levenshtein@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
"file-entry-cache@11.1.5 || >11.1.6 <12":
version "11.1.5"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-11.1.5.tgz#c8210eb055de63e68685ccfb6a017e386d4577d0"
integrity sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==
dependencies:
flat-cache "^6.1.23"
find-up@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
dependencies:
locate-path "^6.0.0"
path-exists "^4.0.0"
flat-cache@^6.1.23:
version "6.1.23"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-6.1.23.tgz#735dc888c271868d7301b3bbb8edba5028afb61d"
integrity sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==
dependencies:
cacheable "^2.5.0"
flatted "^3.4.2"
hookified "^1.15.0"
flatted@^3.4.2:
version "3.4.4"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.4.tgz#aeeca2a506303f0cee61c59e6c9f2a88d2f29fc6"
integrity sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==
glob-parent@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
dependencies:
is-glob "^4.0.3"
hashery@^1.4.0, hashery@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/hashery/-/hashery-1.5.1.tgz#4ba82ad54911ac617467870845d57a9fe508a400"
integrity sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==
dependencies:
hookified "^1.15.0"
hookified@^1.15.0, hookified@^1.15.1:
version "1.15.1"
resolved "https://registry.yarnpkg.com/hookified/-/hookified-1.15.1.tgz#b1fafeaa5489cdc29cb85546a8f837ed4ffbbcb6"
integrity sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==
hookified@^2.1.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/hookified/-/hookified-2.2.0.tgz#1d024ac1668973dd5bcc4a96ab9ccdb7639ef8d4"
integrity sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==
ignore@^5.2.0:
version "5.3.2"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-glob@^4.0.0, is-glob@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
dependencies:
is-extglob "^2.1.1"
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
keyv@^5.6.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-5.6.0.tgz#03044074c6b4d072d0a62c7b9fa649537baf0105"
integrity sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==
dependencies:
"@keyv/serialize" "^1.1.1"
levn@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
dependencies:
prelude-ls "^1.2.1"
type-check "~0.4.0"
locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
dependencies:
p-locate "^5.0.0"
minimatch@^10.2.4, minimatch@^10.2.5:
version "10.2.6"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.6.tgz#fd956bbe0b77241e9f15ac5dccb1c638060968ef"
integrity sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==
dependencies:
brace-expansion "^5.0.8"
ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
optionator@^0.9.3:
version "0.9.4"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
dependencies:
deep-is "^0.1.3"
fast-levenshtein "^2.0.6"
levn "^0.4.1"
prelude-ls "^1.2.1"
type-check "^0.4.0"
word-wrap "^1.2.5"
p-limit@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
dependencies:
yocto-queue "^0.1.0"
p-locate@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
dependencies:
p-limit "^3.0.2"
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
punycode@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
qified@^0.10.1:
version "0.10.1"
resolved "https://registry.yarnpkg.com/qified/-/qified-0.10.1.tgz#0640bf21bbe6ca540db290ad8f5fde4d870b8bda"
integrity sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==
dependencies:
hookified "^2.1.1"
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
dependencies:
shebang-regex "^3.0.0"
shebang-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
dependencies:
prelude-ls "^1.2.1"
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
dependencies:
punycode "^2.1.0"
which@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
dependencies:
isexe "^2.0.0"
word-wrap@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==