mirror of
https://github.com/penpot/penpot.git
synced 2026-10-02 16:56:16 +00:00
⬆️ 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:
parent
61189fbebe
commit
f38c7dd639
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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!
|
||||
|
||||
@ -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?]
|
||||
|
||||
@ -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")))
|
||||
|
||||
26
backend/test/e2e/ldap-test-curl.sh
Normal file
26
backend/test/e2e/ldap-test-curl.sh
Normal 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
|
||||
@ -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"}
|
||||
|
||||
@ -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
20
common/pnpm-lock.yaml
generated
@ -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: {}
|
||||
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -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/"
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
48
docs/pnpm-lock.yaml
generated
@ -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
|
||||
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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",
|
||||
|
||||
40
exporter/pnpm-lock.yaml
generated
40
exporter/pnpm-lock.yaml
generated
@ -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
1
frontend/config.js
Normal 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
1
frontend/config2.js
Normal file
@ -0,0 +1 @@
|
||||
var penpotPublicURI = "http://localhost:3450/penpot/";
|
||||
@ -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"}}}
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
BIN
frontend/paste-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
1995
frontend/pnpm-lock.yaml
generated
1995
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
}
|
||||
|
||||
@ -26,6 +26,6 @@
|
||||
"packageManager": "pnpm@12.6.0+sha512.3ef68f951cb111ac204b4a5a16f0b2ddf0da56a96e0413e81d855d9f0b55ef926714709028e1cd00c405c2c5fb7b9e8ec4dc46777c805d0373c2f2ff00fd20ec",
|
||||
"devDependencies": {
|
||||
"concurrently": "^10.0.5",
|
||||
"prettier": "^3.9.6"
|
||||
"prettier": "^3.9.9"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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
550
mcp/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
|
||||
79
plugins/libs/plugins-runtime/src/lib/api/openUI.api.spec.ts
Normal file
79
plugins/libs/plugins-runtime/src/lib/api/openUI.api.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
@ -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,
|
||||
|
||||
@ -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
2760
plugins/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user