⬆️ Update deps (#11960)

* 🐛 Migrate openUIApi schema to Zod v4 function syntax

Zod 4 removed z.function().args(), which broke the
plugins-runtime build with implicit-any errors on every
openUIApi parameter and knock-on possibly-null errors on
the modal in plugin-manager.

Declare the inputs with z.function({ input: [...] }) so the
parameter and return types infer again; behavior is unchanged.

Add a regression spec covering delegation, optional args and
rejection of invalid theme and title values.

AI-assisted-by: muse-spark-1.3-contributor

* 📚 Fix deprecated markdown-it-anchor permalink option in docs

Migrate docs Eleventy config to the markdown-it-anchor v10 API.
Replace the deprecated boolean permalink option with
linkInsideHeader, keeping the same symbol and class.
Bump markdown-it-anchor to v10 and related docs deps.

AI-assisted-by: muse-spark-1.3-contributor

* ⬆️ Update deps

* ⬆️ Update base docker images

* 📎 Fix mcp tests
This commit is contained in:
Andrey Antukh 2026-09-29 09:07:34 +02:00 committed by GitHub
parent 61189fbebe
commit f38c7dd639
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 3022 additions and 2908 deletions

View File

@ -135,8 +135,8 @@
- Groups relationship loads by source and target table pair.
- Resolves relationship endpoints with joins.
- Does not use `createArrowRelTable` for UUID relationship endpoints.
- Keeps the Arrow `RootAllocator` alive until Ladybug releases staged buffers.
- Closes the allocator after the connection and database close sequence.
- The Arrow `RootAllocator` nests outside the Ladybug connection and closes after the connection and database close sequence; Ladybug keeps staged buffers until connection close, so an inner allocator leaks.
- `with-allocator!` keeps the body exception when the allocator close also throws, attaching the close failure as suppressed.
### `app.graph.ingest`
@ -150,6 +150,7 @@
- Writes graph metadata last.
- Treats the final metadata write as the complete-build marker.
- Supports a persistent database path and an open connection.
- `ingest-on-connection!` requires a caller-owned `:arrow-alloc` that outlives the connection; it raises `:missing-arrow-allocator` without one.
### `app.graph.projection.document`
@ -244,7 +245,7 @@
- The map key is the string form of `profile-id`.
- One profile has one graph session.
- Loading another file first destroys the old session.
- A session stores the Ladybug database and connection.
- A session stores the Ladybug database, connection, and Arrow allocator; the allocator closes after the connection and database.
- A session stores a shared lock for graph access.
- A session stores file metadata.
- A session stores the incremental sync index.
@ -450,8 +451,8 @@
- `:graph` is defined in `common/src/app/common/flags.cljc`.
- The flag is off by default.
- `com.ladybugdb/lbug` version `0.19.1` is a backend dependency.
- `org.apache.arrow/arrow-memory-netty` version `18.2.0` supports Arrow `RootAllocator`.
- `com.ladybugdb/lbug` version `0.20.4` is a backend dependency.
- `org.apache.arrow/arrow-memory-netty` version `19.0.0` supports Arrow `RootAllocator`.
- The JVM uses `--enable-native-access=ALL-UNNAMED`.
- The JVM uses `--add-opens=java.base/java.nio=ALL-UNNAMED`.
- The JVM uses `--sun-misc-unsafe-memory-access=allow`.
@ -463,7 +464,7 @@
### `backend-tests.graph-sync-parity-test`
- Uses two Ladybug `:memory:` databases.
- Uses two Ladybug `:memory:` databases under one Arrow allocator outer to both; an allocator closed before its connections leaks.
- Does not use PostgreSQL or a live graph session.
- Projects initial file data into database A.
- Applies changes to database A through incremental sync.
@ -473,6 +474,7 @@
- Reports differences by table, row key, and column.
- Covers shape add, shape modification, shape deletion, movement, and page changes.
- Contains a test that injects a sync defect and expects a graph difference.
- Contains a test that `ingest-on-connection!` without `:arrow-alloc` raises `:missing-arrow-allocator`.
- Does not cover all component change variants.
- Does not cover every movement insertion mode.
@ -530,7 +532,7 @@
- Sessions have no TTL.
- Sessions remain until unload, replacement, or process shutdown.
- Each session owns native Ladybug memory.
- Each session owns native Ladybug memory and its Arrow allocator.
- Many profiles can create many native databases.
- A profile load replaces its previous session.
- Two browser tabs for one profile share one graph session.
@ -590,7 +592,7 @@
- A graph query from the console must be read-only.
- A graph session must serialize connection access.
- Graph routes must remain behind the `:graph` flag and `/dbg` access control.
- The Arrow allocator must outlive all Ladybug operations that use its buffers.
- The Arrow allocator nests outside the Ladybug connection and must outlive all Ladybug operations that use its buffers; a session owns its allocator until unload.
- `GraphMeta` must be written after the full ingest and transforms finish.
## Key Files

View File

@ -3,10 +3,10 @@
:deps
{penpot/common {:local/root "../common"}
org.clojure/clojure {:mvn/version "1.12.5"}
org.clojure/clojure {:mvn/version "1.12.6"}
org.clojure/tools.namespace {:mvn/version "1.5.1"}
com.github.luben/zstd-jni {:mvn/version "1.5.7-12"}
com.github.luben/zstd-jni {:mvn/version "1.5.7-20"}
io.prometheus/simpleclient {:mvn/version "0.16.0"}
io.prometheus/simpleclient_hotspot {:mvn/version "0.16.0"}
@ -17,7 +17,7 @@
io.prometheus/simpleclient_httpserver {:mvn/version "0.16.0"}
io.lettuce/lettuce-core {:mvn/version "7.7.0.RELEASE"}
io.lettuce/lettuce-core {:mvn/version "7.8.0.RELEASE"}
;; Minimal dependencies required by lettuce, we need to include them
;; explicitly because clojure dependency management does not support
;; yet the BOM format.
@ -36,7 +36,7 @@
com.github.seancorfield/next.jdbc
{:mvn/version "1.3.1118"}
metosin/reitit-core {:mvn/version "0.10.1"}
metosin/reitit-core {:mvn/version "0.11.0"}
nrepl/nrepl {:mvn/version "1.7.0"}
org.postgresql/postgresql {:mvn/version "42.7.13"}
@ -55,21 +55,21 @@
org.jsoup/jsoup {:mvn/version "1.23.2"}
at.yawk.lz4/lz4-java
{:mvn/version "1.11.2"}
{:mvn/version "1.12.0"}
org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"}
dawran6/emoji {:mvn/version "0.2.0"}
markdown-clj/markdown-clj {:mvn/version "1.12.9"}
markdown-clj/markdown-clj {:mvn/version "1.12.10"}
;; Pretty Print specs
pretty-spec/pretty-spec {:mvn/version "0.1.4"}
software.amazon.awssdk/s3 {:mvn/version "2.54.5"}
software.amazon.awssdk/sts {:mvn/version "2.54.5"}
software.amazon.awssdk/s3 {:mvn/version "2.55.6"}
software.amazon.awssdk/sts {:mvn/version "2.55.6"}
com.ladybugdb/lbug {:mvn/version "0.19.1"}
com.ladybugdb/lbug {:mvn/version "0.20.4"}
;; Required by Arrow RootAllocator (lbug only pulls arrow-memory-core).
org.apache.arrow/arrow-memory-netty {:mvn/version "18.2.0"}}
org.apache.arrow/arrow-memory-netty {:mvn/version "19.0.0"}}
:paths ["src" "resources" "target/classes"]
:aliases

View File

@ -79,15 +79,28 @@
(defn with-allocator!
"Invoke `(f allocator)` with a fresh Arrow `RootAllocator`.
The allocator must outlive the Ladybug connection, because Ladybug releases
its references to the staged buffers only when the Arrow tables are dropped —
which happens on connection close at the latest. Closing it first surfaces as
The allocator must outlive the Ladybug connection, because Ladybug keeps
its references to the staged buffers until the Arrow tables are dropped —
which happens on connection close at the latest. Nest this *outside*
`ladybug/with-connection!`, and close the allocator after the connection
and database close sequence. Closing it first surfaces as
`IllegalStateException: Memory was leaked`, *thrown while unwinding*, which
hides whatever actually failed. Any diagnostic here must catch inside this
scope."
[f]
(with-open [allocator (RootAllocator.)]
(f allocator)))
(let [allocator (RootAllocator.)]
(try
(let [result (f allocator)]
(.close allocator)
result)
(catch Throwable t
;; The allocator close can itself throw a leak error while unwinding
;; from the body failure; keep the original as the thrown one.
(try
(.close allocator)
(catch Throwable close-cause
(.addSuppressed t close-cause)))
(throw t)))))
;; ------------------------------------------------------ Ladybug type → Field

View File

@ -20,7 +20,9 @@
[promesa.exec.csp :as sp])
(:import
com.ladybugdb.Connection
com.ladybugdb.Database))
com.ladybugdb.Database
org.apache.arrow.memory.BufferAllocator
org.apache.arrow.memory.RootAllocator))
(set! *warn-on-reflection* true)
@ -46,7 +48,12 @@
(str profile-id))
(defn- destroy-session!
[{:keys [conn db sync-ch msgbus]}]
"Close a session's Ladybug resources.
The Arrow allocator outlives the connection: Ladybug keeps the staged
buffers until its tables are dropped, no later than connection close, so
the allocator closes last — see `app.graph.arrow/with-allocator!`."
[{:keys [conn db allocator sync-ch msgbus]}]
(when sync-ch
(sp/close! sync-ch)
(when msgbus
@ -54,7 +61,9 @@
(when conn
(ex/ignoring (.close ^Connection conn)))
(when db
(ex/ignoring (.close ^Database db))))
(ex/ignoring (.close ^Database db)))
(when allocator
(ex/ignoring (.close ^BufferAllocator allocator))))
(defn- slim-ingest-meta
"Drop full projection rows from session meta.
@ -162,10 +171,15 @@
(swap! sessions dissoc (session-key profile-id)))
(defn load-session!
"Ingest `file-id` into a new in-memory Ladybug database for `profile-id`."
"Ingest `file-id` into a new in-memory Ladybug database for `profile-id`.
The session owns its Arrow allocator for its lifetime: the allocator stays
open until the connection and database close, so it is created here, passed
to ingest, and closed by `destroy-session!` — never inside ingest."
[cfg profile-id file-id]
(unload-session! profile-id)
(let [^Database db (Database.)
(let [^BufferAllocator allocator (RootAllocator.)
^Database db (Database.)
^Connection conn (Connection. db)
msgbus (::mbus/msgbus cfg)]
(.setQueryTimeout conn 0)
@ -174,7 +188,8 @@
(let [meta (graph.ingest/ingest-on-connection! cfg conn file-id
:db-path ":memory:"
:skip-stats? true
:skip-validation? true)
:skip-validation? true
:arrow-alloc allocator)
index (graph.sync/build-index file-id (:revn meta) (:projection meta))
;; Discard projection rows after indexing — they are only needed
;; to seed the sync index and would otherwise leak heap on each Load.
@ -185,6 +200,7 @@
;; binding gives no thread-safety guarantee for one Connection.
(-> {:db db
:conn conn
:allocator allocator
:lock (Object.)
:file-id file-id
:meta meta
@ -196,7 +212,7 @@
(swap! sessions assoc (session-key profile-id) session)
meta)
(catch Throwable cause
(destroy-session! {:conn conn :db db :msgbus msgbus})
(destroy-session! {:conn conn :db db :allocator allocator :msgbus msgbus})
(throw cause)))))
(defn query-session!

View File

@ -77,15 +77,17 @@
(defn ingest-on-connection!
"Project `file-id` into an already open Ladybug `conn`.
Takes an `:arrow-alloc` when the caller already owns one; otherwise it makes
a short-lived allocator around this call. A caller that opened the connection
itself should pass its own, because the allocator has to be closed *after*
the connection — see `app.graph.arrow/with-allocator!`."
Takes `:arrow-alloc`, owned by the caller. The allocator must stay open
until the connection (and database) close — see
`app.graph.arrow/with-allocator!` — so a short-lived allocator made inside
this call would close too early and leak; the caller nests the allocator
outside its connection and passes it here."
[system ^Connection conn file-id & {:keys [arrow-alloc] :as opts}]
(if arrow-alloc
(ingest-on-connection*! system conn file-id arrow-alloc opts)
(graph.arrow/with-allocator!
(fn [allocator] (ingest-on-connection*! system conn file-id allocator opts)))))
(when-not arrow-alloc
(ex/raise :type :validation
:code :missing-arrow-allocator
:hint "ingest-on-connection! requires :arrow-alloc; nest with-allocator! outside the connection"))
(ingest-on-connection*! system conn file-id arrow-alloc opts))
(defn ingest-file!
[system file-id & {:keys [db-path reset-db? skip-stats? skip-validation?]

View File

@ -25,6 +25,7 @@
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[app.graph.arrow :as arrow]
[app.graph.ingest :as ingest]
[app.graph.ladybug :as ladybug]
[app.graph.projection.document :as projection.document]
[app.graph.projection.transforms :as projection.transforms]
@ -131,12 +132,13 @@
(defn- load-graph!
"Create the schema on `conn`, project `data` into it, run the transforms.
`allocator` must outlive `conn` — see `app.graph.arrow/with-allocator!` —
so the caller nests the allocator outside its connections and passes it in.
Returns the projection, which is also what the sync index is built from."
[conn data file]
[conn allocator data file]
(let [projection (projection.document/projection-data data file)]
(ladybug/exec-on-connection! conn (nodes/ddl-statements))
(arrow/with-allocator!
(fn [allocator] (arrow/load-projection! conn projection allocator)))
(arrow/load-projection! conn projection allocator)
(projection.transforms/apply-transforms! nil conn data file)
projection))
@ -222,12 +224,19 @@
[[kind table] d])))
(defn- with-two-connections
"Open two `:memory:` databases under one Arrow allocator.
The allocator stays outer to both connections: Ladybug keeps the staged
buffers until connection close, so an allocator closed first leaks —
see `app.graph.arrow/with-allocator!`."
[f]
(ladybug/with-connection! ":memory:"
(fn [conn-a]
(arrow/with-allocator!
(fn [allocator]
(ladybug/with-connection! ":memory:"
(fn [conn-b]
(f conn-a conn-b))))))
(fn [conn-a]
(ladybug/with-connection! ":memory:"
(fn [conn-b]
(f conn-a conn-b allocator))))))))
(defn- round-trip
"Sync `change-list` into A, rebuild the same file into B, return the diff."
@ -236,11 +245,11 @@
data1 (cfc/process-changes data0 change-list)
revn1 (inc base-revn)]
(with-two-connections
(fn [conn-a conn-b]
(let [projection (load-graph! conn-a data0 (file-row base-revn))
(fn [conn-a conn-b allocator]
(let [projection (load-graph! conn-a allocator data0 (file-row base-revn))
index (sync/build-index file-id base-revn projection)
result (sync/apply-changes! conn-a index change-list revn1)]
(load-graph! conn-b data1 (file-row revn1))
(load-graph! conn-b allocator data1 (file-row revn1))
{:diff (diff (snapshot conn-a) (snapshot conn-b))
:applied (:applied result)
:skipped (:skipped result)})))))
@ -261,6 +270,15 @@
(str "cold projection and sync replay disagree on "
(pr-str (keys diff)) "\n" (pr-str diff)))))
(t/deftest ingest-on-connection-requires-allocator
;; The allocator must outlive the connection, so ingest cannot make its own
;; inside the call — see `app.graph.arrow/with-allocator!`.
(try
(ingest/ingest-on-connection! nil nil file-id)
(t/is false "expected :missing-arrow-allocator")
(catch clojure.lang.ExceptionInfo e
(t/is (= :missing-arrow-allocator (:code (ex-data e)))))))
(t/deftest the-diff-catches-an-injected-sync-bug
;; The round trip is only worth running if it fails when sync is wrong.
;; `apply-mov-objects` maintains `IsChildOf`; drop the change from the list
@ -270,11 +288,11 @@
crippled (remove #(= :mov-objects (:type %)) changes)
revn1 (inc base-revn)
result (with-two-connections
(fn [conn-a conn-b]
(let [projection (load-graph! conn-a data0 (file-row base-revn))
(fn [conn-a conn-b allocator]
(let [projection (load-graph! conn-a allocator data0 (file-row base-revn))
index (sync/build-index file-id base-revn projection)]
(sync/apply-changes! conn-a index crippled revn1)
(load-graph! conn-b data1 (file-row revn1))
(load-graph! conn-b allocator data1 (file-row revn1))
(diff (snapshot conn-a) (snapshot conn-b)))))]
(t/is (contains? result [:edges "IsChildOf"])
"a sync that skips a reparent must show up as an IsChildOf difference")))

View File

@ -0,0 +1,26 @@
#!/bin/bash
# LDAP injection test script — run against running Penpot backend
# Usage: bash backend/test/e2e/ldap-test-curl.sh
BASE="http://localhost:3450/api/main/methods/login-with-ldap"
H1='Content-Type: application/json'
H2='Accept: application/json'
#echo "=== 1. Normal login (fry/fry) ==="
#curl -s -X POST "$BASE" -H "$H1" -H "$H2" \
# -d '{"email":"fry@planetexpress.com","password":"fry"}' | python3 -m json.tool
#echo ""
#echo "=== 2. Wildcard injection (*@planetexpress.com + amy) ==="
#curl -s -X POST "$BASE" -H "$H1" -H "$H2" \
# -d '{"email":"*@planetexpress.com","password":"amy"}' | python3 -m json.tool
echo ""
echo "=== 3. Identity swap (hubert@ + professor password) ==="
curl -s -X POST "$BASE" -H "$H1" -H "$H2" \
-d '{"email":"hubert@planetexpress.com","password":"professor"}' | python3 -m json.tool
#echo ""
#echo "=== 4. Wrong password ==="
#curl -s -X POST "$BASE" -H "$H1" -H "$H2" \
# -d '{"email":"fry@planetexpress.com","password":"wrong"}' | python3 -m json.tool

View File

@ -1,5 +1,5 @@
{:deps
{org.clojure/clojure {:mvn/version "1.12.5"}
{org.clojure/clojure {:mvn/version "1.12.6"}
org.clojure/data.json {:mvn/version "2.5.2"}
org.clojure/tools.cli {:mvn/version "1.4.256"}
org.clojure/test.check {:mvn/version "1.1.3"}
@ -14,20 +14,20 @@
org.apache.logging.log4j/log4j-web {:mvn/version "2.26.1"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.26.1"}
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.26.1"}
org.slf4j/slf4j-api {:mvn/version "2.0.18"}
org.slf4j/slf4j-api {:mvn/version "2.0.20"}
pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.43"}
selmer/selmer {:mvn/version "1.13.5"}
criterium/criterium {:mvn/version "0.4.6"}
metosin/jsonista {:mvn/version "1.0.0"
metosin/jsonista {:mvn/version "1.0.1"
:exclusions [com.fasterxml.jackson.core/jackson-core
com.fasterxml.jackson.core/jackson-databind]}
com.fasterxml.jackson.core/jackson-core {:mvn/version "2.22.2"}
com.fasterxml.jackson.core/jackson-databind {:mvn/version "2.22.2"}
com.fasterxml.jackson.core/jackson-core {:mvn/version "2.22.3"}
com.fasterxml.jackson.core/jackson-databind {:mvn/version "2.22.3"}
metosin/malli {:mvn/version "0.20.1"}
metosin/malli {:mvn/version "0.20.2"}
expound/expound {:mvn/version "0.9.0"}
com.cognitect/transit-clj {:mvn/version "1.1.363"}
@ -60,7 +60,7 @@
{:dev
{:extra-deps
{org.clojure/tools.namespace {:mvn/version "1.5.1"}
thheller/shadow-cljs {:mvn/version "3.5.0"}
thheller/shadow-cljs {:mvn/version "3.5.3"}
com.clojure-goes-fast/clj-async-profiler {:mvn/version "2.0.0-beta1"}
com.bhauman/rebel-readline {:mvn/version "0.1.11"}
criterium/criterium {:mvn/version "0.4.6"}

View File

@ -13,9 +13,9 @@
"devDependencies": {
"concurrently": "^10.0.5",
"nodemon": "^3.1.14",
"prettier": "3.9.6",
"prettier": "3.9.9",
"source-map-support": "^0.5.21",
"ws": "^8.21.3"
"ws": "^8.22.0"
},
"dependencies": {
"date-fns": "^4.4.0"

20
common/pnpm-lock.yaml generated
View File

@ -177,14 +177,14 @@ importers:
specifier: ^3.1.14
version: 3.1.14
prettier:
specifier: 3.9.6
version: 3.9.6
specifier: 3.9.9
version: 3.9.9
source-map-support:
specifier: ^0.5.21
version: 0.5.21
ws:
specifier: ^8.21.3
version: 8.21.3
specifier: ^8.22.0
version: 8.22.0
packages:
@ -319,8 +319,8 @@ packages:
resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
engines: {node: '>=8.6'}
prettier@3.9.6:
resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==}
prettier@3.9.9:
resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==}
engines: {node: '>=14'}
hasBin: true
@ -392,8 +392,8 @@ packages:
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
engines: {node: '>=18'}
ws@8.21.3:
resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==}
ws@8.22.0:
resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
@ -536,7 +536,7 @@ snapshots:
picomatch@2.3.2: {}
prettier@3.9.6: {}
prettier@3.9.9: {}
pstree.remy@1.1.8: {}
@ -597,7 +597,7 @@ snapshots:
string-width: 7.2.0
strip-ansi: 7.2.0
ws@8.21.3: {}
ws@8.22.0: {}
y18n@5.0.8: {}

View File

@ -1,4 +1,4 @@
FROM dhi.io/node:24.20.0-debian13-dev
FROM dhi.io/node:24.21.0-debian13-dev
LABEL maintainer="Penpot <docker@penpot.app>"
ENV LANG=en_US.UTF-8 \

View File

@ -1,4 +1,4 @@
FROM dhi.io/nginx:1.31.1-debian13-dev
FROM dhi.io/nginx:1.31.6-debian13-dev
LABEL maintainer="Penpot <docker@penpot.app>"
ENV DEBIAN_FRONTEND=noninteractive

View File

@ -1,4 +1,4 @@
FROM dhi.io/node:24.20.0-debian13-dev AS build
FROM dhi.io/node:24.21.0-debian13-dev AS build
LABEL maintainer="Penpot <docker@penpot.app>"
ENV DEBIAN_FRONTEND=noninteractive
@ -42,7 +42,7 @@ RUN set -eux; \
RUN ./setup
FROM dhi.io/node:24.20.0-debian13 AS image
FROM dhi.io/node:24.21.0-debian13 AS image
LABEL maintainer="Penpot <docker@penpot.app>"
ENV LANG=en_US.UTF-8 \

View File

@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM dhi.io/node:24.20.0-debian13-dev
FROM dhi.io/node:24.21.0-debian13-dev
LABEL maintainer="Penpot <docker@penpot.app>"
ENV LANG=en_US.UTF-8 \

View File

@ -1,4 +1,4 @@
FROM dhi.io/nginx:1.31.1-alpine3.22
FROM dhi.io/nginx:1.31.6-alpine3.24
LABEL maintainer="Penpot <docker@penpot.app>"
ARG BUNDLE_PATH="./bundle-storybook/"

View File

@ -70,9 +70,10 @@ export default function (eleventyConfig) {
linkify: true,
})
.use(markdownItAnchor, {
permalink: true,
permalinkClass: "direct-link",
permalinkSymbol: "#",
permalink: markdownItAnchor.permalink.linkInsideHeader({
symbol: "#",
class: "direct-link",
}),
})
.use(markdownItPlantUML, {});
eleventyConfig.setLibrary("md", markdownLibrary);

View File

@ -31,12 +31,12 @@
"@tigersway/eleventy-plugin-ancestry": "^1.0.3",
"@types/markdown-it": "14.2.0",
"elasticlunr": "^0.9.5",
"eleventy-plugin-metagen": "^1.8.4",
"eleventy-plugin-metagen": "^1.8.5",
"eleventy-plugin-nesting-toc": "^1.3.0",
"eleventy-plugin-youtube-embed": "^1.13.2",
"luxon": "^3.7.2",
"markdown-it": "^15.0.0",
"markdown-it-anchor": "^9.2.1",
"markdown-it": "^15.0.2",
"markdown-it-anchor": "^10.0.0",
"markdown-it-plantuml": "^1.4.1"
},
"packageManager": "pnpm@12.6.0+sha512.3ef68f951cb111ac204b4a5a16f0b2ddf0da56a96e0413e81d855d9f0b55ef926714709028e1cd00c405c2c5fb7b9e8ec4dc46777c805d0373c2f2ff00fd20ec"

48
docs/pnpm-lock.yaml generated
View File

@ -188,8 +188,8 @@ importers:
specifier: ^0.9.5
version: 0.9.5
eleventy-plugin-metagen:
specifier: ^1.8.4
version: 1.8.4
specifier: ^1.8.5
version: 1.8.5
eleventy-plugin-nesting-toc:
specifier: ^1.3.0
version: 1.3.0
@ -200,11 +200,11 @@ importers:
specifier: ^3.7.2
version: 3.7.2
markdown-it:
specifier: ^15.0.0
version: 15.0.0
specifier: ^15.0.2
version: 15.0.2
markdown-it-anchor:
specifier: ^9.2.1
version: 9.2.1(@types/markdown-it@14.2.0)(markdown-it@15.0.0)
specifier: ^10.0.0
version: 10.0.0(@types/markdown-it@14.2.0)(markdown-it@15.0.2)
markdown-it-plantuml:
specifier: ^1.4.1
version: 1.4.1
@ -427,8 +427,8 @@ packages:
elasticlunr@0.9.5:
resolution: {integrity: sha512-5YM9LFQgVYfuLNEoqMqVWIBuF2UNCA+xu/jz1TyryLN/wmBcQSb+GNAwvLKvEpGESwgGN8XA1nbLAt6rKlyHYQ==}
eleventy-plugin-metagen@1.8.4:
resolution: {integrity: sha512-v8yIjwx7W/A6HKktVltk601KQUb3Sy/syHhtKXmlYMsb7IvIp17KugK3dko8z1oy+z2GDV57PY7vXZLtZs7Fzw==}
eleventy-plugin-metagen@1.8.5:
resolution: {integrity: sha512-SsjlV+cJ84RMeH3zZLWlNGRsgB4lWp3KkADCXeorFGbFURhT5XjrhmHJhVDV07YdRq+fqIxO981QuqcrQkd92A==}
eleventy-plugin-nesting-toc@1.3.0:
resolution: {integrity: sha512-WZzVkz28nw3A0DpJFQWXZzQxniyjvOZKxVix5x7WVAS0H1OD1hYJbR0go/Lr1kK2SrmxfbsUHUlekR+mQPvqMA==}
@ -642,8 +642,8 @@ packages:
resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}
engines: {node: '>=12'}
markdown-it-anchor@9.2.1:
resolution: {integrity: sha512-p6APiLJDFAW2GEvaavDvhIBn7jrX2jLv77NkBGgNacFTurbORYc4pyYySg/mI6mpR6cHQuAtzKtmqgQr4K8dsQ==}
markdown-it-anchor@10.0.0:
resolution: {integrity: sha512-Q8g7Z6OBdH3eXF0lujFqukIxUvgCDZcjzCrJxmD6QiEzU1HWR9fVN+Bf6gxmM+0yrPFs+jUw8gNjLmUQqt6Eew==}
peerDependencies:
'@types/markdown-it': '*'
markdown-it: '*'
@ -651,19 +651,19 @@ packages:
markdown-it-plantuml@1.4.1:
resolution: {integrity: sha512-13KgnZaGYTHBp4iUmGofzZSBz+Zj6cyqfR0SXUIc9wgWTto5Xhn7NjaXYxY0z7uBeTUMlc9LMQq5uP4OM5xCHg==}
markdown-it@14.3.0:
resolution: {integrity: sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==}
markdown-it@14.3.2:
resolution: {integrity: sha512-sHHjZ5fJKlgrG4qns2YwVcdNep35h5fERrfkD2YNsb9UFk0UIHarbiTaHKVMlPuWAoiilyK8Fv/jAm11slsY7Q==}
hasBin: true
markdown-it@15.0.0:
resolution: {integrity: sha512-Lf8ajvVNdRpzSNB4VegxNy7gjs8gU35l4b4+ET49LrQC5PKYwLZ72u60LeJ9gv3qiaesuYjJWCyVeQmv/QWKQw==}
markdown-it@15.0.2:
resolution: {integrity: sha512-q4IGxMv56jCqT4OCRCADBoDP3LO4MhmTXjFbphHPXs4g3j9Xg5RDnxqN8IF/3vIWEU+VCnUq+7JUg/cfy2E6Qw==}
hasBin: true
mdurl@2.1.0:
resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==}
meta-generator@0.1.5:
resolution: {integrity: sha512-stImEDLa5k2TfIMfpFomJKM9LuYzwIIsxS/ejBop0CifPqc4nHoLdTQkABnjrzzkZgLhmXdN144R7OT4dnbKqw==}
meta-generator@0.1.6:
resolution: {integrity: sha512-jdwiUXuZ36V28RCGQlAXWG71WyELolwRD77kcW7JknQtl14o91DEPMRqGrAVmPaw6rSC3PFcKQegLs4X+sX4kA==}
mime-db@1.54.0:
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
@ -992,7 +992,7 @@ snapshots:
kleur: 4.1.5
liquidjs: 10.29.0
luxon: 3.7.2
markdown-it: 14.3.0
markdown-it: 14.3.2
minimist: 1.2.8
moo: 0.5.2
node-retrieve-globals: 6.0.1
@ -1203,10 +1203,10 @@ snapshots:
elasticlunr@0.9.5: {}
eleventy-plugin-metagen@1.8.4:
eleventy-plugin-metagen@1.8.5:
dependencies:
'@11ty/eleventy': 3.1.6
meta-generator: 0.1.5
meta-generator: 0.1.6
transitivePeerDependencies:
- bufferutil
- supports-color
@ -1400,14 +1400,14 @@ snapshots:
luxon@3.7.2: {}
markdown-it-anchor@9.2.1(@types/markdown-it@14.2.0)(markdown-it@15.0.0):
markdown-it-anchor@10.0.0(@types/markdown-it@14.2.0)(markdown-it@15.0.2):
dependencies:
'@types/markdown-it': 14.2.0
markdown-it: 15.0.0
markdown-it: 15.0.2
markdown-it-plantuml@1.4.1: {}
markdown-it@14.3.0:
markdown-it@14.3.2:
dependencies:
argparse: 2.0.1
entities: 4.5.0
@ -1416,7 +1416,7 @@ snapshots:
punycode.js: 2.3.1
uc.micro: 2.1.0
markdown-it@15.0.0:
markdown-it@15.0.2:
dependencies:
argparse: 3.0.1
entities: 8.0.0
@ -1427,7 +1427,7 @@ snapshots:
mdurl@2.1.0: {}
meta-generator@0.1.5:
meta-generator@0.1.6:
dependencies:
get-tag: 0.1.10

View File

@ -1,9 +1,9 @@
{:paths ["src" "vendor" "resources" "test"]
:deps
{penpot/common {:local/root "../common"}
org.clojure/clojure {:mvn/version "1.12.5"}
org.clojure/clojure {:mvn/version "1.12.6"}
binaryage/devtools {:mvn/version "1.0.7"}
metosin/reitit-core {:mvn/version "0.10.1"}
metosin/reitit-core {:mvn/version "0.11.0"}
}
:aliases
{:outdated
@ -14,7 +14,7 @@
:dev
{:extra-deps
{thheller/shadow-cljs {:mvn/version "3.5.0"}}}
{thheller/shadow-cljs {:mvn/version "3.5.3"}}}
:shadow-cljs
{:main-opts ["-m" "shadow.cljs.devtools.cli"]

View File

@ -13,20 +13,20 @@
"dependencies": {
"@penpot/svgo": "penpot/svgo#3.3.0",
"archiver": "8.0.0",
"cookies": "^0.9.1",
"cookies": "^0.9.2",
"date-fns": "^4.4.0",
"generic-pool": "^3.9.0",
"inflation": "^2.1.0",
"ioredis": "^6.0.0",
"playwright": "1.62.1",
"playwright": "1.63.0",
"raw-body": "^4.0.0",
"source-map-support": "^0.5.21",
"undici": "^8.10.0",
"undici": "^8.11.2",
"xml-js": "^1.6.11",
"xregexp": "^5.1.2"
"xregexp": "^5.1.3"
},
"devDependencies": {
"ws": "^8.21.3"
"ws": "^8.22.0"
},
"scripts": {
"clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target",

View File

@ -179,8 +179,8 @@ importers:
specifier: 8.0.0
version: 8.0.0
cookies:
specifier: ^0.9.1
version: 0.9.1
specifier: ^0.9.2
version: 0.9.2
date-fns:
specifier: ^4.4.0
version: 4.4.0
@ -203,18 +203,18 @@ importers:
specifier: ^0.5.21
version: 0.5.21
undici:
specifier: ^8.10.0
version: 8.10.0
specifier: ^8.11.2
version: 8.11.2
xml-js:
specifier: ^1.6.11
version: 1.6.11
xregexp:
specifier: ^5.1.2
version: 5.1.2
specifier: ^5.1.3
version: 5.1.3
devDependencies:
ws:
specifier: ^8.21.3
version: 8.21.3
specifier: ^8.22.0
version: 8.22.0
packages:
@ -326,8 +326,8 @@ packages:
resolution: {integrity: sha512-g0S8KAD8qf4+V//pr3BfB1aBnARLXNz2Gx+jmHU0LEriUuoQUOPOulVquHKTJ8+EAIIO7fhseNDr9wK5Q9FKBQ==}
engines: {node: '>=18'}
cookies@0.9.1:
resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==}
cookies@0.9.2:
resolution: {integrity: sha512-8BIbcC6tPZMv2/PD4PrB3CtKks7LmAWhA7FRZK/6/b/WJ2RUz00KCga7gq50S4/RhZ22kxefRFGgsuts0v3eoA==}
engines: {node: '>= 0.8'}
core-js-pure@3.49.0:
@ -578,15 +578,15 @@ packages:
resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
engines: {node: '>=0.6.x'}
undici@8.10.0:
resolution: {integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==}
undici@8.11.2:
resolution: {integrity: sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==}
engines: {node: '>=22.19.0'}
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
ws@8.21.3:
resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==}
ws@8.22.0:
resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
@ -601,8 +601,8 @@ packages:
resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
hasBin: true
xregexp@5.1.2:
resolution: {integrity: sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==}
xregexp@5.1.3:
resolution: {integrity: sha512-7uOg8BVk9kt6HnkeBdyW3VEq7KXzs4un/Efge4AN1FsAMzM6fYexggbWWkQcfHExQpQEoNkWXnr4wSWpqtx49Q==}
zip-stream@7.0.5:
resolution: {integrity: sha512-dSvYKdvLsAHCDqPOhIwk/q5CvuWtTB3Dgpoe0uVEFjTzIOAmsQpprX25InCvrvJsirEbu1OHyy67n/kAj1Sw/w==}
@ -711,7 +711,7 @@ snapshots:
normalize-path: 3.0.0
readable-stream: 4.7.0
cookies@0.9.1:
cookies@0.9.2:
dependencies:
depd: 2.0.0
keygrip: 1.1.0
@ -960,17 +960,17 @@ snapshots:
tsscmp@1.0.6: {}
undici@8.10.0: {}
undici@8.11.2: {}
util-deprecate@1.0.2: {}
ws@8.21.3: {}
ws@8.22.0: {}
xml-js@1.6.11:
dependencies:
sax: 1.6.0
xregexp@5.1.2:
xregexp@5.1.3:
dependencies:
'@babel/runtime-corejs3': 7.29.7

1
frontend/config.js Normal file
View File

@ -0,0 +1 @@
var penpotFlags = "enable-login-with-google enable-login-with-oidc enable-access-tokens enable-mcp enable-render-wasm";

1
frontend/config2.js Normal file
View File

@ -0,0 +1 @@
var penpotPublicURI = "http://localhost:3450/penpot/";

View File

@ -4,9 +4,9 @@
penpot/common
{:local/root "../common"}
org.clojure/clojure {:mvn/version "1.12.5"}
org.clojure/clojure {:mvn/version "1.12.6"}
binaryage/devtools {:mvn/version "RELEASE"}
metosin/reitit-core {:mvn/version "0.10.1"}
metosin/reitit-core {:mvn/version "0.11.0"}
funcool/okulary {:mvn/version "2022.04.11-16"}
funcool/tubax
@ -51,7 +51,7 @@
"--enable-native-access=ALL-UNNAMED"]
:extra-deps
{thheller/shadow-cljs {:mvn/version "3.5.0"}
{thheller/shadow-cljs {:mvn/version "3.5.3"}
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
org.clojure/tools.namespace {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "0.4.6"}}}

View File

@ -58,79 +58,79 @@
"@penpot/text-editor": "link:text-editor",
"@penpot/tokenscript": "link:packages/tokenscript",
"@penpot/ua-parser": "penpot/ua-parser#1.0.0",
"@playwright/test": "1.62.1",
"@storybook/addon-docs": "10.5.10",
"@storybook/addon-themes": "10.5.10",
"@storybook/addon-vitest": "10.5.10",
"@storybook/react-vite": "10.5.10",
"@playwright/test": "1.63.0",
"@storybook/addon-docs": "10.6.0",
"@storybook/addon-themes": "10.6.0",
"@storybook/addon-vitest": "10.6.0",
"@storybook/react-vite": "10.6.0",
"@tokens-studio/sd-transforms": "2.0.3",
"@types/node": "^26.4.0",
"@vitest/browser": "4.1.11",
"@vitest/browser-playwright": "4.1.11",
"@vitest/coverage-v8": "4.1.11",
"@types/node": "^26.6.3",
"@vitest/browser": "5.0.2",
"@vitest/browser-playwright": "5.0.2",
"@vitest/coverage-v8": "5.0.2",
"@zip.js/zip.js": "2.8.34",
"autoprefixer": "^10.5.4",
"compression": "^1.8.1",
"autoprefixer": "^10.6.1",
"compression": "^1.8.2",
"concurrently": "^10.0.5",
"date-fns": "^4.4.0",
"esbuild": "^0.28.2",
"eventsource-parser": "^4.1.0",
"eventsource-parser": "^4.1.1",
"express": "^5.1.0",
"fancy-log": "^2.0.0",
"getopts": "^2.3.0",
"gettext-parser": "^9.1.1",
"highlight.js": "^11.12.0",
"js-beautify": "^2.0.3",
"jsdom": "^30.0.1",
"jsdom": "^30.1.1",
"lodash": "^4.18.1",
"lodash.debounce": "^4.0.8",
"map-stream": "0.0.7",
"marked": "^18.0.11",
"marked": "^18.0.14",
"mkdirp": "^3.0.1",
"mustache": "^4.2.0",
"nodemon": "^3.1.14",
"npm-run-all": "^4.1.5",
"opentype.js": "^2.0.0",
"p-limit": "^7.3.1",
"playwright": "1.62.1",
"postcss": "^8.5.26",
"p-limit": "^7.3.3",
"playwright": "1.63.0",
"postcss": "^8.5.28",
"postcss-clean": "^1.2.2",
"postcss-modules": "^9.0.1",
"postcss-scss": "^4.0.9",
"prettier": "3.9.6",
"prettier": "3.9.9",
"pretty-time": "^1.1.0",
"prop-types": "^15.8.1",
"randomcolor": "^0.6.2",
"react": "19.2.8",
"react-dom": "19.2.8",
"react-error-boundary": "^6.1.3",
"react": "19.3.0",
"react-dom": "19.3.0",
"react-error-boundary": "^6.1.6",
"react-virtualized": "^9.22.6",
"rimraf": "^6.1.3",
"rxjs": "8.0.0-alpha.14",
"sass": "^1.103.1",
"sass-embedded": "^1.103.1",
"sass": "^1.105.0",
"sass-embedded": "^1.105.0",
"sax": "^1.6.1",
"scheduler": "^0.27.0",
"scheduler": "^0.28.0",
"source-map-support": "^0.5.21",
"storybook": "10.5.10",
"style-dictionary": "5.5.2",
"stylelint": "^17.14.1",
"storybook": "10.6.0",
"style-dictionary": "5.5.5",
"stylelint": "^17.15.0",
"stylelint-config-standard-scss": "^17.0.0",
"stylelint-plugin-logical-css": "^2.1.0",
"stylelint-scss": "^7.2.0",
"stylelint-scss": "^7.3.0",
"svg-sprite": "^2.0.4",
"tdigest": "^0.1.3",
"tinycolor2": "^1.6.0",
"typescript": "^6.0.2",
"vite": "^8.2.2",
"vitest": "^4.1.11",
"vite": "^8.3.1",
"vitest": "^5.0.2",
"wait-on": "^9.1.0",
"watcher": "^2.3.1",
"workerpool": "^10.0.3",
"xregexp": "^5.1.2"
"xregexp": "^5.1.3"
},
"dependencies": {
"@penpot/ui": "link:packages/ui",
"react-aria-components": "^1.20.0"
"react-aria-components": "^1.21.1"
}
}

View File

@ -23,23 +23,23 @@
"build": "vite build"
},
"devDependencies": {
"@babel/core": "^8.0.1",
"@babel/core": "^8.0.6",
"@babel/preset-react": "^8.0.1",
"@storybook/react": "10.5.10",
"@storybook/react-vite": "10.5.10",
"@testing-library/dom": "10.4.1",
"@testing-library/react": "16.3.2",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.5",
"@vitejs/plugin-react": "^6.1.0",
"@storybook/react": "10.6.0",
"@storybook/react-vite": "10.6.0",
"@testing-library/dom": "10.4.2",
"@testing-library/react": "16.3.3",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@vitejs/plugin-react": "^6.1.1",
"babel-plugin-react-compiler": "^1.0.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "7.1.1",
"react-compiler-runtime": "^1.0.0",
"storybook": "10.5.10",
"vite-plugin-dts": "^5.0.3"
"storybook": "10.6.0",
"vite-plugin-dts": "^5.1.1"
},
"dependencies": {
"react-aria-components": "^1.20.0"

BIN
frontend/paste-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

1995
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -15,18 +15,18 @@
"test:watch:e2e": "vitest --browser"
},
"devDependencies": {
"@playwright/test": "1.62.1",
"@types/node": "^26.1.2",
"@vitest/browser": "^4.1.10",
"@vitest/coverage-v8": "^4.1.10",
"@vitest/ui": "^4.1.11",
"@playwright/test": "1.63.0",
"@types/node": "^26.6.3",
"@vitest/browser": "^5.0.2",
"@vitest/coverage-v8": "^5.0.2",
"@vitest/ui": "^5.0.2",
"canvas": "^3.2.3",
"esbuild": "^0.28.0",
"jsdom": "^30.0.1",
"playwright": "1.62.1",
"playwright": "1.63.0",
"prettier": "^3.9.6",
"vite": "^8.2.0",
"vitest": "^4.1.10"
"vitest": "^5.0.2"
},
"packageManager": "pnpm@12.6.0+sha512.3ef68f951cb111ac204b4a5a16f0b2ddf0da56a96e0413e81d855d9f0b55ef926714709028e1cd00c405c2c5fb7b9e8ec4dc46777c805d0373c2f2ff00fd20ec"
}

View File

@ -26,6 +26,6 @@
"packageManager": "pnpm@12.6.0+sha512.3ef68f951cb111ac204b4a5a16f0b2ddf0da56a96e0413e81d855d9f0b55ef926714709028e1cd00c405c2c5fb7b9e8ec4dc46777c805d0373c2f2ff00fd20ec",
"devDependencies": {
"concurrently": "^10.0.5",
"prettier": "^3.9.6"
"prettier": "^3.9.9"
}
}

View File

@ -19,7 +19,7 @@
"devDependencies": {
"cross-env": "^10.1.0",
"typescript": "^6.0.3",
"vite": "^8.2.0",
"vite": "^8.3.1",
"vite-live-preview": "^0.4.0"
}
}

View File

@ -29,33 +29,33 @@
},
"packageManager": "pnpm@12.6.0+sha512.3ef68f951cb111ac204b4a5a16f0b2ddf0da56a96e0413e81d855d9f0b55ef926714709028e1cd00c405c2c5fb7b9e8ec4dc46777c805d0373c2f2ff00fd20ec",
"dependencies": {
"@modelcontextprotocol/node": "^2.0.0",
"@modelcontextprotocol/server": "^2.0.0",
"@modelcontextprotocol/node": "^2.1.0",
"@modelcontextprotocol/server": "^2.1.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.15.1",
"express": "^5.2.1",
"ioredis": "^6.0.0",
"js-yaml": "^5.2.3",
"js-yaml": "^5.4.2",
"nrepl-client": "^0.3.0",
"pino": "^10.3.1",
"pino-loki": "^3.0.0",
"pino-pretty": "^13.1.3",
"reflect-metadata": "^0.2.2",
"sharp": "^0.35.3",
"ws": "^8.21.1",
"zod": "^4.4.3"
"sharp": "^0.35.5",
"ws": "^8.22.0",
"zod": "^4.6.5"
},
"devDependencies": {
"@modelcontextprotocol/client": "^2.0.0",
"@modelcontextprotocol/client": "^2.1.0",
"@penpot/mcp-common": "workspace:../common",
"@types/express": "^5.0.6",
"@types/js-yaml": "^4.0.9",
"@types/node": "^26.1.2",
"@types/node": "^26.6.3",
"@types/ws": "^8.18.1",
"cross-env": "^10.1.0",
"esbuild": "^0.28.1",
"ts-node": "^10.9.2",
"tsx": "^4.23.5",
"tsx": "^4.23.15",
"typescript": "^6.0.3"
},
"ts-node": {

View File

@ -32,6 +32,7 @@ async function modernRequest(method: string, params: Record<string, unknown> = {
headers: {
"Content-Type": "application/json",
Accept: "application/json, text/event-stream",
"MCP-Protocol-Version": "2026-07-28",
"Mcp-Method": method,
...(typeof params.name === "string" ? { "Mcp-Name": params.name } : {}),
},

550
mcp/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,8 @@
"version": "1.5.0",
"dependencies": {
"@penpot/plugin-types": "workspace:^",
"ses": "^2.1.0",
"zod": "^3.25.76"
"ses": "^2.3.0",
"zod": "^4.6.5"
},
"module": "./dist/index.js",
"typings": "./dist/index.d.ts",

View File

@ -0,0 +1,79 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { openUIApi } from './openUI.api.js';
import { createModal } from '../create-modal.js';
import type { PluginModalElement } from '../modal/plugin-modal.js';
vi.mock('../create-modal.js', () => ({
createModal: vi.fn(),
}));
describe('openUIApi', () => {
const mockModal = {} as PluginModalElement;
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(createModal).mockReturnValue(mockModal);
});
it('should delegate all arguments to createModal and return the modal', () => {
const options = { width: 400, height: 300 };
const result = openUIApi(
'Test Modal',
'https://example.com/plugin',
'light',
options,
true,
false,
true,
);
expect(createModal).toHaveBeenCalledWith(
'Test Modal',
'https://example.com/plugin',
'light',
options,
true,
false,
true,
);
expect(result).toBe(mockModal);
});
it('should accept only the required arguments', () => {
const result = openUIApi(
'Test Modal',
'https://example.com/plugin',
'dark',
);
expect(createModal).toHaveBeenCalledWith(
'Test Modal',
'https://example.com/plugin',
'dark',
undefined,
undefined,
undefined,
undefined,
);
expect(result).toBe(mockModal);
});
it('should reject an invalid theme', () => {
// Cast through unknown: the invalid value must reach runtime validation.
const invalidTheme = 'blue' as unknown as 'light';
expect(() =>
openUIApi('Test Modal', 'https://example.com/plugin', invalidTheme),
).toThrow();
expect(createModal).not.toHaveBeenCalled();
});
it('should reject non-string titles', () => {
// Cast through unknown: the invalid value must reach runtime validation.
const invalidTitle = 42 as unknown as string;
expect(() =>
openUIApi(invalidTitle, 'https://example.com/plugin', 'light'),
).toThrow();
expect(createModal).not.toHaveBeenCalled();
});
});

View File

@ -3,16 +3,17 @@ import { openUISchema } from '../models/open-ui-options.schema.js';
import { createModal } from '../create-modal.js';
export const openUIApi = z
.function()
.args(
z.string(),
z.string(),
z.enum(['dark', 'light']),
openUISchema.optional(),
z.boolean().optional(),
z.boolean().optional(),
z.boolean().optional(),
)
.function({
input: [
z.string(),
z.string(),
z.enum(['dark', 'light']),
openUISchema.optional(),
z.boolean().optional(),
z.boolean().optional(),
z.boolean().optional(),
],
})
.implement(
(
title,

View File

@ -32,22 +32,22 @@
},
"private": true,
"devDependencies": {
"@angular-devkit/core": "22.1.6",
"@angular-devkit/schematics": "22.1.6",
"@angular-eslint/eslint-plugin": "22.1.0",
"@angular-eslint/eslint-plugin-template": "22.1.0",
"@angular-eslint/template-parser": "22.1.0",
"@angular/build": "22.1.6",
"@angular/cli": "22.1.6",
"@angular/compiler-cli": "22.1.3",
"@angular/language-service": "22.1.3",
"@angular-devkit/core": "22.2.0",
"@angular-devkit/schematics": "22.2.0",
"@angular-eslint/eslint-plugin": "22.5.0",
"@angular-eslint/eslint-plugin-template": "22.5.0",
"@angular-eslint/template-parser": "22.5.0",
"@angular/build": "22.2.0",
"@angular/cli": "22.2.0",
"@angular/compiler-cli": "22.2.0",
"@angular/language-service": "22.2.0",
"@eslint/js": "10.0.1",
"@schematics/angular": "22.1.6",
"@schematics/angular": "22.2.0",
"@types/feather-icons": "^4.29.4",
"@types/node": "26.1.2",
"@types/node": "26.6.3",
"@types/yargs": "^17.0.35",
"@typescript-eslint/eslint-plugin": "8.68.0",
"@typescript-eslint/parser": "8.68.0",
"@typescript-eslint/eslint-plugin": "8.70.1",
"@typescript-eslint/parser": "8.70.1",
"@typescript-eslint/utils": "^8.68.0",
"@vitest/coverage-v8": "4.1.11",
"@vitest/ui": "4.1.11",
@ -80,19 +80,19 @@
"yargs": "^18.1.0"
},
"dependencies": {
"@angular/common": "22.1.3",
"@angular/compiler": "22.1.3",
"@angular/core": "22.1.3",
"@angular/forms": "22.1.3",
"@angular/platform-browser": "22.1.3",
"@angular/router": "22.1.3",
"@angular/common": "22.2.0",
"@angular/compiler": "22.2.0",
"@angular/core": "22.2.0",
"@angular/forms": "22.2.0",
"@angular/platform-browser": "22.2.0",
"@angular/router": "22.2.0",
"axios": "^1.20.0",
"feather-icons": "^4.29.2",
"puppeteer": "^25.9.0",
"puppeteer": "^25.12.0",
"rxjs": "~7.8.2",
"ses": "^2.2.0",
"ses": "^2.3.0",
"tslib": "^2.8.1",
"zod": "^4.4.3",
"zone.js": "0.16.2"
"zod": "^4.6.5",
"zone.js": "0.16.3"
}
}

2760
plugins/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff