mirror of
https://github.com/penpot/penpot.git
synced 2026-09-08 13:09:22 +00:00
Merge remote-tracking branch 'origin/main' into staging
This commit is contained in:
commit
d78074307f
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -63,7 +63,7 @@ jobs:
|
|||||||
|
|
||||||
echo "$PUB_DOCKER_PASSWORD" | skopeo login --username "$PUB_DOCKER_USERNAME" --password-stdin docker.io
|
echo "$PUB_DOCKER_PASSWORD" | skopeo login --username "$PUB_DOCKER_USERNAME" --password-stdin docker.io
|
||||||
|
|
||||||
IMAGES=("frontend" "backend" "exporter" "storybook")
|
IMAGES=("frontend" "backend" "exporter" "mcp" "storybook")
|
||||||
SHORT_TAG=${TAG%.*}
|
SHORT_TAG=${TAG%.*}
|
||||||
|
|
||||||
for image in "${IMAGES[@]}"; do
|
for image in "${IMAGES[@]}"; do
|
||||||
|
|||||||
69
.github/workflows/tests.yml
vendored
69
.github/workflows/tests.yml
vendored
@ -24,7 +24,11 @@ jobs:
|
|||||||
if: ${{ !github.event.pull_request.draft }}
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
name: "Linter"
|
name: "Linter"
|
||||||
runs-on: penpot-runner-02
|
runs-on: penpot-runner-02
|
||||||
container: penpotapp/devenv:latest
|
container:
|
||||||
|
image: penpotapp/devenv:latest
|
||||||
|
volumes:
|
||||||
|
- /tmp/.m2:/root/.m2
|
||||||
|
- /tmp/.gitlibs:/root/.gitlibs
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@ -84,7 +88,11 @@ jobs:
|
|||||||
if: ${{ !github.event.pull_request.draft }}
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
name: "Common Tests"
|
name: "Common Tests"
|
||||||
runs-on: penpot-runner-02
|
runs-on: penpot-runner-02
|
||||||
container: penpotapp/devenv:latest
|
container:
|
||||||
|
image: penpotapp/devenv:latest
|
||||||
|
volumes:
|
||||||
|
- /tmp/.m2:/root/.m2
|
||||||
|
- /tmp/.gitlibs:/root/.gitlibs
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@ -99,7 +107,11 @@ jobs:
|
|||||||
if: ${{ !github.event.pull_request.draft }}
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
name: Plugins Runtime Linter & Tests
|
name: Plugins Runtime Linter & Tests
|
||||||
runs-on: penpot-runner-02
|
runs-on: penpot-runner-02
|
||||||
container: penpotapp/devenv:latest
|
container:
|
||||||
|
image: penpotapp/devenv:latest
|
||||||
|
volumes:
|
||||||
|
- /tmp/.m2:/root/.m2
|
||||||
|
- /tmp/.gitlibs:/root/.gitlibs
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
@ -150,7 +162,11 @@ jobs:
|
|||||||
if: ${{ !github.event.pull_request.draft }}
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
name: "Frontend Tests"
|
name: "Frontend Tests"
|
||||||
runs-on: penpot-runner-02
|
runs-on: penpot-runner-02
|
||||||
container: penpotapp/devenv:latest
|
container:
|
||||||
|
image: penpotapp/devenv:latest
|
||||||
|
volumes:
|
||||||
|
- /tmp/.m2:/root/.m2
|
||||||
|
- /tmp/.gitlibs:/root/.gitlibs
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@ -172,7 +188,11 @@ jobs:
|
|||||||
if: ${{ !github.event.pull_request.draft }}
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
name: "Render WASM Tests"
|
name: "Render WASM Tests"
|
||||||
runs-on: penpot-runner-02
|
runs-on: penpot-runner-02
|
||||||
container: penpotapp/devenv:latest
|
container:
|
||||||
|
image: penpotapp/devenv:latest
|
||||||
|
volumes:
|
||||||
|
- /tmp/.m2:/root/.m2
|
||||||
|
- /tmp/.gitlibs:/root/.gitlibs
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@ -197,7 +217,11 @@ jobs:
|
|||||||
if: ${{ !github.event.pull_request.draft }}
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
name: "Backend Tests"
|
name: "Backend Tests"
|
||||||
runs-on: penpot-runner-02
|
runs-on: penpot-runner-02
|
||||||
container: penpotapp/devenv:latest
|
container:
|
||||||
|
image: penpotapp/devenv:latest
|
||||||
|
volumes:
|
||||||
|
- /tmp/.m2:/root/.m2
|
||||||
|
- /tmp/.gitlibs:/root/.gitlibs
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
@ -237,7 +261,11 @@ jobs:
|
|||||||
if: ${{ !github.event.pull_request.draft }}
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
name: "Library Tests"
|
name: "Library Tests"
|
||||||
runs-on: penpot-runner-02
|
runs-on: penpot-runner-02
|
||||||
container: penpotapp/devenv:latest
|
container:
|
||||||
|
image: penpotapp/devenv:latest
|
||||||
|
volumes:
|
||||||
|
- /tmp/.m2:/root/.m2
|
||||||
|
- /tmp/.gitlibs:/root/.gitlibs
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@ -252,7 +280,11 @@ jobs:
|
|||||||
if: ${{ !github.event.pull_request.draft }}
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
name: "Build Integration Bundle"
|
name: "Build Integration Bundle"
|
||||||
runs-on: penpot-runner-02
|
runs-on: penpot-runner-02
|
||||||
container: penpotapp/devenv:latest
|
container:
|
||||||
|
image: penpotapp/devenv:latest
|
||||||
|
volumes:
|
||||||
|
- /tmp/.m2:/root/.m2
|
||||||
|
- /tmp/.gitlibs:/root/.gitlibs
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@ -273,7 +305,12 @@ jobs:
|
|||||||
if: ${{ !github.event.pull_request.draft }}
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
name: "Integration Tests 1/3"
|
name: "Integration Tests 1/3"
|
||||||
runs-on: penpot-runner-02
|
runs-on: penpot-runner-02
|
||||||
container: penpotapp/devenv:latest
|
container:
|
||||||
|
image: penpotapp/devenv:latest
|
||||||
|
volumes:
|
||||||
|
- /tmp/.m2:/root/.m2
|
||||||
|
- /tmp/.gitlibs:/root/.gitlibs
|
||||||
|
|
||||||
needs: build-integration
|
needs: build-integration
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -304,7 +341,12 @@ jobs:
|
|||||||
if: ${{ !github.event.pull_request.draft }}
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
name: "Integration Tests 2/3"
|
name: "Integration Tests 2/3"
|
||||||
runs-on: penpot-runner-02
|
runs-on: penpot-runner-02
|
||||||
container: penpotapp/devenv:latest
|
container:
|
||||||
|
image: penpotapp/devenv:latest
|
||||||
|
volumes:
|
||||||
|
- /tmp/.m2:/root/.m2
|
||||||
|
- /tmp/.gitlibs:/root/.gitlibs
|
||||||
|
|
||||||
needs: build-integration
|
needs: build-integration
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -335,7 +377,12 @@ jobs:
|
|||||||
if: ${{ !github.event.pull_request.draft }}
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
name: "Integration Tests 3/3"
|
name: "Integration Tests 3/3"
|
||||||
runs-on: penpot-runner-02
|
runs-on: penpot-runner-02
|
||||||
container: penpotapp/devenv:latest
|
container:
|
||||||
|
image: penpotapp/devenv:latest
|
||||||
|
volumes:
|
||||||
|
- /tmp/.m2:/root/.m2
|
||||||
|
- /tmp/.gitlibs:/root/.gitlibs
|
||||||
|
|
||||||
needs: build-integration
|
needs: build-integration
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
70
CHANGES.md
70
CHANGES.md
@ -127,33 +127,69 @@
|
|||||||
- Fix library updates reappear after being applied and the file is reloaded [Taiga #14040](https://tree.taiga.io/project/penpot/issue/14040)
|
- Fix library updates reappear after being applied and the file is reloaded [Taiga #14040](https://tree.taiga.io/project/penpot/issue/14040)
|
||||||
- Fix dependency libraries remaining visible in UI after unlinking main library [Taiga #14020](https://tree.taiga.io/project/penpot/issue/14020)
|
- Fix dependency libraries remaining visible in UI after unlinking main library [Taiga #14020](https://tree.taiga.io/project/penpot/issue/14020)
|
||||||
|
|
||||||
## 2.15.0 (Unreleased)
|
## 2.15.2
|
||||||
|
|
||||||
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
|
- Fix mcp related internal config for docker images [Github #9565](https://github.com/penpot/penpot/pull/9565)
|
||||||
|
|
||||||
|
|
||||||
|
## 2.15.1
|
||||||
|
|
||||||
|
### :sparkles: New features & Enhancements
|
||||||
|
|
||||||
|
- Add support for chunked uploading of fonts [Github #9560](https://github.com/penpot/penpot/issues/9560)
|
||||||
|
|
||||||
|
|
||||||
|
## 2.15.0
|
||||||
|
|
||||||
### :sparkles: New features & Enhancements
|
### :sparkles: New features & Enhancements
|
||||||
|
|
||||||
- Add MCP server integration [Github #9174](https://github.com/penpot/penpot/issues/9174)
|
- Add MCP server integration [Github #9174](https://github.com/penpot/penpot/issues/9174)
|
||||||
- Add chunked upload API for large media and binary files (removes previous upload size limits) [Github #8909](https://github.com/penpot/penpot/pull/8909)
|
(PR: [#9032](https://github.com/penpot/penpot/pull/9032), [#9321](https://github.com/penpot/penpot/pull/9321))
|
||||||
- Improve team name validation [Github #9176](https://github.com/penpot/penpot/pull/9176)
|
- Add chunked upload API for large media and binary files (removes previous upload size limits) [Github #9516](https://github.com/penpot/penpot/issues/9516)
|
||||||
|
(PR: [#8909](https://github.com/penpot/penpot/pull/8909))
|
||||||
|
- Add anonymous telemetry event collection [Github #9467](https://github.com/penpot/penpot/issues/9467)
|
||||||
|
(PR: [#9065](https://github.com/penpot/penpot/pull/9065), [#9483](https://github.com/penpot/penpot/pull/9483))
|
||||||
|
- Improve team name validation [Github #9517](https://github.com/penpot/penpot/issues/9517)
|
||||||
|
(PR: [#9176](https://github.com/penpot/penpot/pull/9176))
|
||||||
|
- Enhance readability of applied tokens in plugins API [Github #9175](https://github.com/penpot/penpot/issues/9175)
|
||||||
|
(PR: [#8607](https://github.com/penpot/penpot/pull/8607))
|
||||||
|
- Encourage use of flex/grid layouts in designs generated via MCP [Github #9081](https://github.com/penpot/penpot/issues/9081)
|
||||||
|
(PR: [#9084](https://github.com/penpot/penpot/pull/9084))
|
||||||
|
- Improve MCP server logging, adding Loki support [Github #9415](https://github.com/penpot/penpot/issues/9415)
|
||||||
|
(PR: [#9425](https://github.com/penpot/penpot/pull/9425))
|
||||||
|
- Add security headers to Nginx on Docker images [Github #9519](https://github.com/penpot/penpot/issues/9519)
|
||||||
|
(PR: [#9473](https://github.com/penpot/penpot/pull/9473))
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
- Fix MCP integrations URL copy action to match the URL displayed in settings [Github #9238](https://github.com/penpot/penpot/issues/9238)
|
|
||||||
- Fix Plugin API token methods rejecting JS array of strings [Github #9162](https://github.com/penpot/penpot/issues/9162)
|
|
||||||
- Harden Nginx responses with standard security headers and hide upstream `X-Powered-By` headers
|
|
||||||
- Fix keep-alive interval leak in PluginBridge (by @opcode81) [Github #9435](https://github.com/penpot/penpot/pull/9435)
|
|
||||||
- Fix MCP "active in another tab" notification not clearing (by @Dexterity104) [Github #9321](https://github.com/penpot/penpot/pull/9321)
|
|
||||||
- Fix swapped analytics event names on MCP tab-switch dialog (by @Dexterity104) [Github #9322](https://github.com/penpot/penpot/pull/9322)
|
|
||||||
- Fix MCP ReplServer binding to all interfaces (0.0.0.0) instead of localhost, allowing unauthenticated RCE [Github #9400] (https://github.com/penpot/penpot/pull/9400)
|
|
||||||
- Fix incorrect handling of version restore operation [Github #9041](https://github.com/penpot/penpot/pull/9041)
|
|
||||||
- Fix SSRF in media URL import and restrict unauthenticated asset access to public buckets only [Github #9390](https://github.com/penpot/penpot/pull/9390)
|
|
||||||
- Fix text edition mode not exited when changing selection, blocking token application [Github #9346](https://github.com/penpot/penpot/issues/9346)
|
- Fix text edition mode not exited when changing selection, blocking token application [Github #9346](https://github.com/penpot/penpot/issues/9346)
|
||||||
- Use base64 envelope for Uint8Array task results to avoid JSON expansion (by @opcode81) [Github #9431](https://github.com/penpot/penpot/pull/9431)
|
(PR: [#9355](https://github.com/penpot/penpot/pull/9355))
|
||||||
- Fix empty warning on login [Github #9056](https://github.com/penpot/penpot/pull/9056)
|
- Reduce memory usage of MCP server when handling images (by @opcode81) [Github #9420](https://github.com/penpot/penpot/issues/9420)
|
||||||
- Fix layer hierarchy to match old and new SCSS [Github #9126](https://github.com/penpot/penpot/pull/9126)
|
(PR: [#9431](https://github.com/penpot/penpot/pull/9431))
|
||||||
- Fix multiple selection on shapes with token applied to stroke color [Github #9110](https://github.com/penpot/penpot/pull/9110)
|
- Fix Plugin API token methods rejecting JS array of strings (by @boskodev790) [Github #9162](https://github.com/penpot/penpot/issues/9162)
|
||||||
- Fix onboarding modals appearing behind libraries and templates panel [Github #9178](https://github.com/penpot/penpot/pull/9178)
|
(PR: [#9166](https://github.com/penpot/penpot/pull/9166))
|
||||||
- Fix release notes modal appearing behind the dashboard sidebar (by @RenzoMXD) [Github #8296](https://github.com/penpot/penpot/issues/8296)
|
- Fix release notes modal appearing behind the dashboard sidebar (by @RenzoMXD) [Github #8296](https://github.com/penpot/penpot/issues/8296)
|
||||||
|
(PR: [#9126](https://github.com/penpot/penpot/pull/9126), [#9233](https://github.com/penpot/penpot/pull/9233))
|
||||||
|
- Fix empty warning on login [Github #9520](https://github.com/penpot/penpot/issues/9520)
|
||||||
|
(PR: [#9056](https://github.com/penpot/penpot/pull/9056))
|
||||||
- Fix maximum call stack size exceeded in SSE read-stream [Github #9470](https://github.com/penpot/penpot/issues/9470)
|
- Fix maximum call stack size exceeded in SSE read-stream [Github #9470](https://github.com/penpot/penpot/issues/9470)
|
||||||
|
(PR: [#9484](https://github.com/penpot/penpot/pull/9484))
|
||||||
|
- Fix incorrect handling of version restore operation [Github #9515](https://github.com/penpot/penpot/issues/9515)
|
||||||
|
(PR: [#9041](https://github.com/penpot/penpot/pull/9041))
|
||||||
|
- Fix MCP ReplServer binding to all interfaces (0.0.0.0) instead of localhost, allowing unauthenticated RCE [Github #9518](https://github.com/penpot/penpot/issues/9518)
|
||||||
|
(PR: [#9400](https://github.com/penpot/penpot/pull/9400))
|
||||||
|
- Fix MCP integrations URL copy action to match the URL displayed in settings [Github #9238](https://github.com/penpot/penpot/issues/9238)
|
||||||
|
(PR: [#9239](https://github.com/penpot/penpot/pull/9239))
|
||||||
|
- Fix swapped analytics event names on MCP tab-switch dialog (by @Dexterity104) [Github #9496](https://github.com/penpot/penpot/issues/9496)
|
||||||
|
(PR: [#9322](https://github.com/penpot/penpot/pull/9322))
|
||||||
|
- Fix multiple selection on shapes with token applied to stroke color [Github #9522](https://github.com/penpot/penpot/issues/9522)
|
||||||
|
(PR: [#9110](https://github.com/penpot/penpot/pull/9110))
|
||||||
|
- Fix onboarding modals appearing behind libraries and templates panel [Github #9521](https://github.com/penpot/penpot/issues/9521)
|
||||||
|
(PR: [#9178](https://github.com/penpot/penpot/pull/9178))
|
||||||
|
- Fix keep-alive interval leak in PluginBridge (by @opcode81) [Github #9430](https://github.com/penpot/penpot/issues/9430)
|
||||||
|
(PR: [#9435](https://github.com/penpot/penpot/pull/9435))
|
||||||
|
|
||||||
## 2.14.5
|
## 2.14.5
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
:deps
|
:deps
|
||||||
{penpot/common {:local/root "../common"}
|
{penpot/common {:local/root "../common"}
|
||||||
org.clojure/clojure {:mvn/version "1.12.4"}
|
org.clojure/clojure {:mvn/version "1.12.5"}
|
||||||
org.clojure/tools.namespace {:mvn/version "1.5.0"}
|
org.clojure/tools.namespace {:mvn/version "1.5.0"}
|
||||||
|
|
||||||
com.github.luben/zstd-jni {:mvn/version "1.5.7-4"}
|
com.github.luben/zstd-jni {:mvn/version "1.5.7-4"}
|
||||||
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
io.prometheus/simpleclient_httpserver {:mvn/version "0.16.0"}
|
io.prometheus/simpleclient_httpserver {:mvn/version "0.16.0"}
|
||||||
|
|
||||||
io.lettuce/lettuce-core {:mvn/version "6.8.1.RELEASE"}
|
io.lettuce/lettuce-core {:mvn/version "7.5.1.RELEASE"}
|
||||||
;; Minimal dependencies required by lettuce, we need to include them
|
;; Minimal dependencies required by lettuce, we need to include them
|
||||||
;; explicitly because clojure dependency management does not support
|
;; explicitly because clojure dependency management does not support
|
||||||
;; yet the BOM format.
|
;; yet the BOM format.
|
||||||
@ -28,18 +28,18 @@
|
|||||||
com.google.guava/guava {:mvn/version "33.4.8-jre"}
|
com.google.guava/guava {:mvn/version "33.4.8-jre"}
|
||||||
|
|
||||||
funcool/yetti
|
funcool/yetti
|
||||||
{:git/tag "v11.9"
|
{:git/tag "v11.10"
|
||||||
:git/sha "5fad7a9"
|
:git/sha "88701f4"
|
||||||
:git/url "https://github.com/funcool/yetti.git"
|
:git/url "https://github.com/funcool/yetti.git"
|
||||||
:exclusions [org.slf4j/slf4j-api]}
|
:exclusions [org.slf4j/slf4j-api]}
|
||||||
|
|
||||||
com.github.seancorfield/next.jdbc
|
com.github.seancorfield/next.jdbc
|
||||||
{:mvn/version "1.3.1070"}
|
{:mvn/version "1.3.1093"}
|
||||||
|
|
||||||
metosin/reitit-core {:mvn/version "0.9.1"}
|
metosin/reitit-core {:mvn/version "0.9.1"}
|
||||||
nrepl/nrepl {:mvn/version "1.4.0"}
|
nrepl/nrepl {:mvn/version "1.7.0"}
|
||||||
|
|
||||||
org.postgresql/postgresql {:mvn/version "42.7.9"}
|
org.postgresql/postgresql {:mvn/version "42.7.11"}
|
||||||
org.xerial/sqlite-jdbc {:mvn/version "3.50.3.0"}
|
org.xerial/sqlite-jdbc {:mvn/version "3.50.3.0"}
|
||||||
|
|
||||||
com.zaxxer/HikariCP {:mvn/version "7.0.2"}
|
com.zaxxer/HikariCP {:mvn/version "7.0.2"}
|
||||||
@ -49,7 +49,7 @@
|
|||||||
buddy/buddy-hashers {:mvn/version "2.0.167"}
|
buddy/buddy-hashers {:mvn/version "2.0.167"}
|
||||||
buddy/buddy-sign {:mvn/version "3.6.1-359"}
|
buddy/buddy-sign {:mvn/version "3.6.1-359"}
|
||||||
|
|
||||||
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.3"}
|
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.4"}
|
||||||
|
|
||||||
org.jsoup/jsoup {:mvn/version "1.21.2"}
|
org.jsoup/jsoup {:mvn/version "1.21.2"}
|
||||||
org.im4java/im4java
|
org.im4java/im4java
|
||||||
@ -57,7 +57,8 @@
|
|||||||
:git/sha "e2b3e16"
|
:git/sha "e2b3e16"
|
||||||
:git/url "https://github.com/penpot/im4java"}
|
:git/url "https://github.com/penpot/im4java"}
|
||||||
|
|
||||||
org.lz4/lz4-java {:mvn/version "1.8.0"}
|
at.yawk.lz4/lz4-java
|
||||||
|
{:mvn/version "1.11.0"}
|
||||||
|
|
||||||
org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"}
|
org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"}
|
||||||
|
|
||||||
@ -66,17 +67,17 @@
|
|||||||
|
|
||||||
;; Pretty Print specs
|
;; Pretty Print specs
|
||||||
pretty-spec/pretty-spec {:mvn/version "0.1.4"}
|
pretty-spec/pretty-spec {:mvn/version "0.1.4"}
|
||||||
software.amazon.awssdk/s3 {:mvn/version "2.41.21"}}
|
software.amazon.awssdk/s3 {:mvn/version "2.44.4"}}
|
||||||
|
|
||||||
:paths ["src" "resources" "target/classes"]
|
:paths ["src" "resources" "target/classes"]
|
||||||
:aliases
|
:aliases
|
||||||
{:dev
|
{:dev
|
||||||
{:extra-deps
|
{:extra-deps
|
||||||
{com.bhauman/rebel-readline {:mvn/version "RELEASE"}
|
{com.bhauman/rebel-readline {:mvn/version "0.1.5"}
|
||||||
clojure-humanize/clojure-humanize {:mvn/version "0.2.2"}
|
clojure-humanize/clojure-humanize {:mvn/version "0.2.2"}
|
||||||
org.clojure/data.csv {:mvn/version "RELEASE"}
|
org.clojure/data.csv {:mvn/version "1.1.1"}
|
||||||
com.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"}
|
com.clojure-goes-fast/clj-async-profiler {:mvn/version "2.0.0-beta1"}
|
||||||
mockery/mockery {:mvn/version "RELEASE"}}
|
mockery/mockery {:mvn/version "0.1.4"}}
|
||||||
:extra-paths ["test" "dev"]}
|
:extra-paths ["test" "dev"]}
|
||||||
|
|
||||||
:build
|
:build
|
||||||
@ -92,7 +93,7 @@
|
|||||||
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}}
|
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}}
|
||||||
|
|
||||||
:outdated
|
:outdated
|
||||||
{:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"}}
|
{:extra-deps {com.github.liquidz/antq {:mvn/version "2.11.1276"}}
|
||||||
:main-opts ["-m" "antq.core"]}
|
:main-opts ["-m" "antq.core"]}
|
||||||
|
|
||||||
:jmx-remote
|
:jmx-remote
|
||||||
|
|||||||
@ -72,6 +72,7 @@
|
|||||||
:telemetry-uri "https://telemetry.penpot.app/"
|
:telemetry-uri "https://telemetry.penpot.app/"
|
||||||
|
|
||||||
:media-max-file-size (* 1024 1024 30) ; 30MiB
|
:media-max-file-size (* 1024 1024 30) ; 30MiB
|
||||||
|
:font-max-file-size (* 1024 1024 30) ; 30MiB
|
||||||
|
|
||||||
:ldap-user-query "(|(uid=:username)(mail=:username))"
|
:ldap-user-query "(|(uid=:username)(mail=:username))"
|
||||||
:ldap-attrs-username "uid"
|
:ldap-attrs-username "uid"
|
||||||
@ -120,6 +121,7 @@
|
|||||||
[:auto-file-snapshot-timeout {:optional true} ::ct/duration]
|
[:auto-file-snapshot-timeout {:optional true} ::ct/duration]
|
||||||
|
|
||||||
[:media-max-file-size {:optional true} ::sm/int]
|
[:media-max-file-size {:optional true} ::sm/int]
|
||||||
|
[:font-max-file-size {:optional true} ::sm/int]
|
||||||
[:deletion-delay {:optional true} ::ct/duration]
|
[:deletion-delay {:optional true} ::ct/duration]
|
||||||
[:file-clean-delay {:optional true} ::ct/duration]
|
[:file-clean-delay {:optional true} ::ct/duration]
|
||||||
[:telemetry-enabled {:optional true} ::sm/boolean]
|
[:telemetry-enabled {:optional true} ::sm/boolean]
|
||||||
|
|||||||
@ -61,21 +61,15 @@
|
|||||||
::mdef/help "A total number of bytes processed by update-file."
|
::mdef/help "A total number of bytes processed by update-file."
|
||||||
::mdef/type :counter}
|
::mdef/type :counter}
|
||||||
|
|
||||||
:rpc-mutation-timing
|
:rpc-main-timing
|
||||||
{::mdef/name "penpot_rpc_mutation_timing"
|
{::mdef/name "penpot_rpc_main_timing"
|
||||||
::mdef/help "RPC mutation method call timing."
|
::mdef/help "RPC command method call timing for main"
|
||||||
::mdef/labels ["name"]
|
::mdef/labels ["name"]
|
||||||
::mdef/type :histogram}
|
::mdef/type :histogram}
|
||||||
|
|
||||||
:rpc-command-timing
|
:rpc-management-timing
|
||||||
{::mdef/name "penpot_rpc_command_timing"
|
{::mdef/name "penpot_rpc_management_timing"
|
||||||
::mdef/help "RPC command method call timing."
|
::mdef/help "RPC command method call timing for management."
|
||||||
::mdef/labels ["name"]
|
|
||||||
::mdef/type :histogram}
|
|
||||||
|
|
||||||
:rpc-query-timing
|
|
||||||
{::mdef/name "penpot_rpc_query_timing"
|
|
||||||
::mdef/help "RPC query method call timing."
|
|
||||||
::mdef/labels ["name"]
|
::mdef/labels ["name"]
|
||||||
::mdef/type :histogram}
|
::mdef/type :histogram}
|
||||||
|
|
||||||
|
|||||||
@ -38,9 +38,6 @@
|
|||||||
org.im4java.core.ConvertCmd
|
org.im4java.core.ConvertCmd
|
||||||
org.im4java.core.IMOperation))
|
org.im4java.core.IMOperation))
|
||||||
|
|
||||||
(def default-max-file-size
|
|
||||||
(* 1024 1024 10)) ; 10 MiB
|
|
||||||
|
|
||||||
(def schema:upload
|
(def schema:upload
|
||||||
[:map {:title "Upload"}
|
[:map {:title "Upload"}
|
||||||
[:filename :string]
|
[:filename :string]
|
||||||
@ -79,6 +76,20 @@
|
|||||||
max-size)))
|
max-size)))
|
||||||
upload))
|
upload))
|
||||||
|
|
||||||
|
(defn validate-font-size!
|
||||||
|
"Validates that the font file `upload` does not exceed the configured
|
||||||
|
`:font-max-file-size` limit. Accepts the same map shape as
|
||||||
|
`validate-media-size!` — requires a `:size` key in bytes."
|
||||||
|
[upload]
|
||||||
|
(let [max-size (cf/get :font-max-file-size)]
|
||||||
|
(when (> (:size upload) max-size)
|
||||||
|
(ex/raise :type :restriction
|
||||||
|
:code :font-max-file-size-reached
|
||||||
|
:hint (str/ffmt "the uploaded font size % is greater than the maximum %"
|
||||||
|
(:size upload)
|
||||||
|
max-size)))
|
||||||
|
upload))
|
||||||
|
|
||||||
(defmulti process :cmd)
|
(defmulti process :cmd)
|
||||||
(defmulti process-error class)
|
(defmulti process-error class)
|
||||||
|
|
||||||
@ -296,9 +307,7 @@
|
|||||||
[{:keys [::http/client]} uri]
|
[{:keys [::http/client]} uri]
|
||||||
(letfn [(parse-and-validate [{:keys [status headers] :as response}]
|
(letfn [(parse-and-validate [{:keys [status headers] :as response}]
|
||||||
(let [size (some-> (get headers "content-length") d/parse-integer)
|
(let [size (some-> (get headers "content-length") d/parse-integer)
|
||||||
mtype (get headers "content-type")
|
mtype (get headers "content-type")]
|
||||||
format (cm/mtype->format mtype)
|
|
||||||
max-size (cf/get :media-max-file-size default-max-file-size)]
|
|
||||||
|
|
||||||
(when-not (<= 200 status 299)
|
(when-not (<= 200 status 299)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
@ -310,19 +319,9 @@
|
|||||||
:code :unknown-size
|
:code :unknown-size
|
||||||
:hint "seems like the url points to resource with unknown size"))
|
:hint "seems like the url points to resource with unknown size"))
|
||||||
|
|
||||||
(when (> size max-size)
|
(-> {:size size :mtype mtype}
|
||||||
(ex/raise :type :validation
|
(validate-media-type!)
|
||||||
:code :file-too-large
|
(validate-media-size!))))]
|
||||||
:hint (str/ffmt "the file size % is greater than the maximum %"
|
|
||||||
size
|
|
||||||
default-max-file-size)))
|
|
||||||
|
|
||||||
(when (nil? format)
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :media-type-not-allowed
|
|
||||||
:hint "seems like the url points to an invalid media object"))
|
|
||||||
|
|
||||||
{:size size :mtype mtype :format format}))]
|
|
||||||
|
|
||||||
(let [{:keys [body] :as response}
|
(let [{:keys [body] :as response}
|
||||||
(try
|
(try
|
||||||
|
|||||||
@ -9,7 +9,8 @@
|
|||||||
[app.binfile.common :as bfc]
|
[app.binfile.common :as bfc]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.media :as cmedia]
|
[app.common.logging :as l]
|
||||||
|
[app.common.media :as cm]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
@ -23,6 +24,7 @@
|
|||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
[app.rpc.climit :as-alias climit]
|
[app.rpc.climit :as-alias climit]
|
||||||
[app.rpc.commands.files :as files]
|
[app.rpc.commands.files :as files]
|
||||||
|
[app.rpc.commands.media :refer [assemble-chunks]]
|
||||||
[app.rpc.commands.projects :as projects]
|
[app.rpc.commands.projects :as projects]
|
||||||
[app.rpc.commands.teams :as teams]
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
@ -31,6 +33,8 @@
|
|||||||
[app.storage :as sto]
|
[app.storage :as sto]
|
||||||
[app.storage.tmp :as tmp]
|
[app.storage.tmp :as tmp]
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
|
[cuerdas.core :as str]
|
||||||
|
[datoteka.fs :as fs]
|
||||||
[datoteka.io :as io])
|
[datoteka.io :as io])
|
||||||
(:import
|
(:import
|
||||||
java.io.InputStream
|
java.io.InputStream
|
||||||
@ -91,33 +95,92 @@
|
|||||||
(declare create-font-variant)
|
(declare create-font-variant)
|
||||||
|
|
||||||
(def ^:private schema:create-font-variant
|
(def ^:private schema:create-font-variant
|
||||||
[:map {:title "create-font-variant"}
|
[:and
|
||||||
[:team-id ::sm/uuid]
|
[:map {:title "create-font-variant"}
|
||||||
[:data [:map-of ::sm/text [:or ::sm/bytes
|
[:team-id ::sm/uuid]
|
||||||
[::sm/vec ::sm/bytes]]]]
|
[:font-id ::sm/uuid]
|
||||||
[:font-id ::sm/uuid]
|
[:font-family ::sm/text]
|
||||||
[:font-family ::sm/text]
|
[:font-weight [::sm/one-of {:format "number"} valid-weight]]
|
||||||
[:font-weight [::sm/one-of {:format "number"} valid-weight]]
|
[:font-style [::sm/one-of {:format "string"} valid-style]]
|
||||||
[:font-style [::sm/one-of {:format "string"} valid-style]]
|
[:data {:optional true} [:map-of ::sm/text [:or ::sm/bytes [::sm/vec ::sm/bytes]]]]
|
||||||
[:variant-name {:optional true} [:maybe ::sm/text]]])
|
[:uploads {:optional true} [:map-of ::sm/text ::sm/uuid]]]
|
||||||
|
[:fn {:error/message "one of :data or :uploads is required"}
|
||||||
|
(fn [{:keys [data uploads]}]
|
||||||
|
(or (seq data) (seq uploads)))]])
|
||||||
|
|
||||||
;; FIXME: IMPORTANT: refactor this, we should not hold a whole db
|
;; FIXME: IMPORTANT: refactor this, we should not hold a whole db
|
||||||
;; connection around the font creation
|
;; connection around the font creation
|
||||||
|
|
||||||
|
(defn- prepare-font-data-from-uploads
|
||||||
|
"Assembles each chunked-upload session in `uploads` (a `{mtype →
|
||||||
|
session-id}` map) into a temp file, validates the media type and
|
||||||
|
size of every entry, and returns a `{mtype → path}` data map."
|
||||||
|
[cfg {:keys [uploads] :as params}]
|
||||||
|
(let [data (reduce-kv
|
||||||
|
(fn [acc mtype session-id]
|
||||||
|
(let [assembled (assemble-chunks cfg session-id)]
|
||||||
|
(-> {:mtype mtype :size (:size assembled)}
|
||||||
|
(media/validate-media-type! cm/font-types)
|
||||||
|
(media/validate-font-size!))
|
||||||
|
(assoc acc mtype (:path assembled))))
|
||||||
|
{}
|
||||||
|
uploads)]
|
||||||
|
|
||||||
|
(-> params
|
||||||
|
(assoc :data data)
|
||||||
|
(dissoc :uploads))))
|
||||||
|
|
||||||
|
(defn- prepare-font-data-from-legacy
|
||||||
|
"Validates the media type and size of every entry in the legacy
|
||||||
|
`:data` map (a `{mtype → bytes | [bytes]}` map). Normalises every
|
||||||
|
entry to a tempfile. Returns params with a normalised
|
||||||
|
`{mtype → path}` data map."
|
||||||
|
[{:keys [data] :as params}]
|
||||||
|
(let [data (reduce-kv
|
||||||
|
(fn [acc mtype content]
|
||||||
|
(let [tmp (tmp/tempfile :prefix "penpot.tempfont." :suffix "")
|
||||||
|
chunks (if (vector? content) content [content])
|
||||||
|
streams (map io/input-stream chunks)
|
||||||
|
streams (Collections/enumeration streams)]
|
||||||
|
|
||||||
|
;; Generate the tempfile from all chunks
|
||||||
|
(with-open [^OutputStream output (io/output-stream tmp)
|
||||||
|
^InputStream input (SequenceInputStream. streams)]
|
||||||
|
(io/copy input output))
|
||||||
|
|
||||||
|
;; Validate
|
||||||
|
(-> {:mtype mtype :size (fs/size tmp)}
|
||||||
|
(media/validate-media-type! cm/font-types)
|
||||||
|
(media/validate-font-size!))
|
||||||
|
|
||||||
|
(assoc acc mtype tmp)))
|
||||||
|
{}
|
||||||
|
data)]
|
||||||
|
(assoc params :data data)))
|
||||||
|
|
||||||
(sv/defmethod ::create-font-variant
|
(sv/defmethod ::create-font-variant
|
||||||
|
"Upload a font variant. Font data may be provided either as a
|
||||||
|
Transit-encoded `:data` map (keyed by mime-type) for small fonts, or
|
||||||
|
as an `:uploads` map (keyed by mime-type, values are upload-session
|
||||||
|
UUIDs from the chunked-upload API) for large fonts. Exactly one of
|
||||||
|
the two must be present."
|
||||||
{::doc/added "1.18"
|
{::doc/added "1.18"
|
||||||
|
::doc/changes ["2.16" "Add :uploads param for chunked upload support"]
|
||||||
::climit/id [[:process-font/by-profile ::rpc/profile-id]
|
::climit/id [[:process-font/by-profile ::rpc/profile-id]
|
||||||
[:process-font/global]]
|
[:process-font/global]]
|
||||||
::webhooks/event? true
|
::webhooks/event? true
|
||||||
::sm/params schema:create-font-variant}
|
::sm/params schema:create-font-variant}
|
||||||
[cfg {:keys [::rpc/profile-id team-id] :as params}]
|
[cfg {:keys [::rpc/profile-id team-id uploads] :as params}]
|
||||||
(db/tx-run! cfg
|
(db/tx-run! cfg
|
||||||
(fn [{:keys [::db/conn] :as cfg}]
|
(fn [{:keys [::db/conn] :as cfg}]
|
||||||
(teams/check-edition-permissions! conn profile-id team-id)
|
(teams/check-edition-permissions! conn profile-id team-id)
|
||||||
(quotes/check! cfg {::quotes/id ::quotes/font-variants-per-team
|
(quotes/check! cfg {::quotes/id ::quotes/font-variants-per-team
|
||||||
::quotes/profile-id profile-id
|
::quotes/profile-id profile-id
|
||||||
::quotes/team-id team-id})
|
::quotes/team-id team-id})
|
||||||
(create-font-variant cfg (assoc params :profile-id profile-id)))))
|
(let [params (if (some? uploads)
|
||||||
|
(prepare-font-data-from-uploads cfg params)
|
||||||
|
(prepare-font-data-from-legacy params))]
|
||||||
|
(create-font-variant cfg (assoc params :profile-id profile-id))))))
|
||||||
|
|
||||||
(defn create-font-variant
|
(defn create-font-variant
|
||||||
[{:keys [::sto/storage ::db/conn]} {:keys [data] :as params}]
|
[{:keys [::sto/storage ::db/conn]} {:keys [data] :as params}]
|
||||||
@ -132,23 +195,6 @@
|
|||||||
:hint "invalid font upload, unable to generate missing font assets"))
|
:hint "invalid font upload, unable to generate missing font assets"))
|
||||||
data))
|
data))
|
||||||
|
|
||||||
(process-chunks [chunks]
|
|
||||||
(let [tmp (tmp/tempfile :prefix "penpot.tempfont." :suffix "")
|
|
||||||
streams (map io/input-stream chunks)
|
|
||||||
streams (Collections/enumeration streams)]
|
|
||||||
(with-open [^OutputStream output (io/output-stream tmp)
|
|
||||||
^InputStream input (SequenceInputStream. streams)]
|
|
||||||
(io/copy input output))
|
|
||||||
tmp))
|
|
||||||
|
|
||||||
(join-chunks [data]
|
|
||||||
(reduce-kv (fn [data mtype content]
|
|
||||||
(if (vector? content)
|
|
||||||
(assoc data mtype (process-chunks content))
|
|
||||||
data))
|
|
||||||
data
|
|
||||||
data))
|
|
||||||
|
|
||||||
(prepare-font [data mtype]
|
(prepare-font [data mtype]
|
||||||
(when-let [resource (get data mtype)]
|
(when-let [resource (get data mtype)]
|
||||||
|
|
||||||
@ -191,11 +237,38 @@
|
|||||||
:otf-file-id (:id otf)
|
:otf-file-id (:id otf)
|
||||||
:ttf-file-id (:id ttf)}))]
|
:ttf-file-id (:id ttf)}))]
|
||||||
|
|
||||||
(let [data (join-chunks data)
|
(let [tpoint (ct/tpoint)
|
||||||
data (generate-missing data)
|
mtypes (vec (keys data))
|
||||||
assets (persist-fonts-files! data)
|
total-size (reduce-kv (fn [acc _ content]
|
||||||
result (insert-font-variant! assets)]
|
(+ acc (if (bytes? content)
|
||||||
(vary-meta result assoc ::audit/replace-props (update params :data (comp vec keys))))))
|
(alength ^bytes content)
|
||||||
|
(fs/size content))))
|
||||||
|
0
|
||||||
|
data)]
|
||||||
|
|
||||||
|
(l/dbg :hint "create-font-variant"
|
||||||
|
:step "init"
|
||||||
|
:font-family (:font-family params)
|
||||||
|
:font-weight (:font-weight params)
|
||||||
|
:font-style (:font-style params)
|
||||||
|
:mtypes (str/join mtypes ",")
|
||||||
|
:size total-size)
|
||||||
|
|
||||||
|
(let [data (generate-missing data)
|
||||||
|
assets (persist-fonts-files! data)
|
||||||
|
result (insert-font-variant! assets)
|
||||||
|
elapsed (tpoint)]
|
||||||
|
|
||||||
|
(l/dbg :hint "create-font-variant"
|
||||||
|
:step "end"
|
||||||
|
:font-family (:font-family params)
|
||||||
|
:font-weight (:font-weight params)
|
||||||
|
:font-style (:font-style params)
|
||||||
|
:mtypes (str/join mtypes ",")
|
||||||
|
:size total-size
|
||||||
|
:elapsed (ct/format-duration elapsed))
|
||||||
|
|
||||||
|
(vary-meta result assoc ::audit/replace-props (update params :data (comp vec keys)))))))
|
||||||
|
|
||||||
;; --- UPDATE FONT FAMILY
|
;; --- UPDATE FONT FAMILY
|
||||||
|
|
||||||
@ -326,7 +399,7 @@
|
|||||||
[v mtype]
|
[v mtype]
|
||||||
(str (:font-family v) "-" (:font-weight v)
|
(str (:font-family v) "-" (:font-weight v)
|
||||||
(when-not (= "normal" (:font-style v)) (str "-" (:font-style v)))
|
(when-not (= "normal" (:font-style v)) (str "-" (:font-style v)))
|
||||||
(cmedia/mtype->extension mtype)))
|
(cm/mtype->extension mtype)))
|
||||||
|
|
||||||
(def ^:private schema:download-font
|
(def ^:private schema:download-font
|
||||||
[:map {:title "download-font"}
|
[:map {:title "download-font"}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
|
[app.common.logging :as l]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
@ -58,8 +59,8 @@
|
|||||||
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
||||||
;; We get the minimal file for proper checking if
|
;; We get the minimal file for proper checking if
|
||||||
;; file is not already deleted
|
;; file is not already deleted
|
||||||
(let [_ (files/get-minimal-file conn file-id)
|
(let [_ (files/get-minimal-file conn file-id)
|
||||||
mobj (create-file-media-object cfg params)]
|
mobj (create-file-media-object cfg params)]
|
||||||
|
|
||||||
(db/update! conn :file
|
(db/update! conn :file
|
||||||
{:modified-at (ct/now)
|
{:modified-at (ct/now)
|
||||||
@ -149,20 +150,49 @@
|
|||||||
|
|
||||||
(defn- create-file-media-object
|
(defn- create-file-media-object
|
||||||
[{:keys [::sto/storage ::db/conn] :as cfg}
|
[{:keys [::sto/storage ::db/conn] :as cfg}
|
||||||
{:keys [id file-id is-local name content]}]
|
{:keys [id file-id is-local name content from-url? from-chunks?]}]
|
||||||
(let [result (process-image content)
|
|
||||||
image (sto/put-object! storage (::image result))
|
|
||||||
thumb (when-let [params (::thumb result)]
|
|
||||||
(sto/put-object! storage params))]
|
|
||||||
|
|
||||||
(db/exec-one! conn [sql:create-file-media-object
|
(let [tpoint (ct/tpoint)
|
||||||
(or id (uuid/next))
|
id (or id (uuid/next))
|
||||||
file-id is-local name
|
origin (cond
|
||||||
(:id image)
|
from-url?
|
||||||
(:id thumb)
|
"url"
|
||||||
(:width result)
|
from-chunks?
|
||||||
(:height result)
|
"chunks"
|
||||||
(:mtype result)])))
|
:else
|
||||||
|
"direct")]
|
||||||
|
|
||||||
|
(l/dbg :hint "create file-media-object"
|
||||||
|
:step "init"
|
||||||
|
:id (str id)
|
||||||
|
:mtype (:mtype content)
|
||||||
|
:size (:size content)
|
||||||
|
:path (str (:path content))
|
||||||
|
:origin origin)
|
||||||
|
|
||||||
|
(let [result (process-image content)
|
||||||
|
image (sto/put-object! storage (::image result))
|
||||||
|
thumb (when-let [params (::thumb result)]
|
||||||
|
(sto/put-object! storage params))
|
||||||
|
elapsed (tpoint)]
|
||||||
|
|
||||||
|
(l/dbg :hint "create file-media-object"
|
||||||
|
:step "end"
|
||||||
|
:id (str id)
|
||||||
|
:mtype (:mtype content)
|
||||||
|
:size (:size content)
|
||||||
|
:path (str (:path content))
|
||||||
|
:origin origin
|
||||||
|
:elapsed (ct/format-duration elapsed))
|
||||||
|
|
||||||
|
(db/exec-one! conn [sql:create-file-media-object
|
||||||
|
id
|
||||||
|
file-id is-local name
|
||||||
|
(:id image)
|
||||||
|
(:id thumb)
|
||||||
|
(:width result)
|
||||||
|
(:height result)
|
||||||
|
(:mtype result)]))))
|
||||||
|
|
||||||
;; --- Create File Media Object (from URL)
|
;; --- Create File Media Object (from URL)
|
||||||
|
|
||||||
@ -198,6 +228,7 @@
|
|||||||
[cfg {:keys [url name] :as params}]
|
[cfg {:keys [url name] :as params}]
|
||||||
(let [content (media/download-image cfg url)
|
(let [content (media/download-image cfg url)
|
||||||
params (-> params
|
params (-> params
|
||||||
|
(assoc :from-url? true)
|
||||||
(assoc :content content)
|
(assoc :content content)
|
||||||
(assoc :name (d/nilv name "unknown")))]
|
(assoc :name (d/nilv name "unknown")))]
|
||||||
|
|
||||||
@ -305,7 +336,14 @@
|
|||||||
:hint "chunk index is out of range for this session"
|
:hint "chunk index is out of range for this session"
|
||||||
:session-id session-id
|
:session-id session-id
|
||||||
:total-chunks (:total-chunks session)
|
:total-chunks (:total-chunks session)
|
||||||
:index index)))
|
:index index))
|
||||||
|
|
||||||
|
|
||||||
|
(l/trc :hint "upload-chunk"
|
||||||
|
:session-id session-id
|
||||||
|
:chunk (str index "/" (:total-chunks session))
|
||||||
|
:size (:size content)
|
||||||
|
:path (:path content)))
|
||||||
|
|
||||||
(let [storage (sto/resolve cfg)
|
(let [storage (sto/resolve cfg)
|
||||||
data (sto/content (:path content))]
|
data (sto/content (:path content))]
|
||||||
@ -399,14 +437,15 @@
|
|||||||
|
|
||||||
(db/tx-run! cfg
|
(db/tx-run! cfg
|
||||||
(fn [{:keys [::db/conn] :as cfg}]
|
(fn [{:keys [::db/conn] :as cfg}]
|
||||||
(let [{:keys [path size]} (assemble-chunks cfg session-id)
|
(let [content (assemble-chunks cfg session-id)
|
||||||
content {:filename "upload"
|
content (-> content
|
||||||
:size size
|
(assoc :filename (str "upload:" name))
|
||||||
:path path
|
(assoc :mtype mtype)
|
||||||
:mtype mtype}
|
(media/validate-media-type!)
|
||||||
_ (media/validate-media-type! content)
|
(media/validate-media-size!))
|
||||||
mobj (create-file-media-object cfg (assoc params
|
mobj (create-file-media-object cfg (assoc params
|
||||||
:id (or id (uuid/next))
|
:id id
|
||||||
|
:from-chunks? true
|
||||||
:content content))]
|
:content content))]
|
||||||
|
|
||||||
(db/update! conn :file
|
(db/update! conn :file
|
||||||
|
|||||||
@ -265,6 +265,7 @@
|
|||||||
[cfg {:keys [::rpc/profile-id file] :as params}]
|
[cfg {:keys [::rpc/profile-id file] :as params}]
|
||||||
;; Validate incoming mime type
|
;; Validate incoming mime type
|
||||||
(media/validate-media-type! file #{"image/jpeg" "image/png" "image/webp"})
|
(media/validate-media-type! file #{"image/jpeg" "image/png" "image/webp"})
|
||||||
|
(media/validate-media-size! file)
|
||||||
(update-profile-photo cfg (assoc params :profile-id profile-id)))
|
(update-profile-photo cfg (assoc params :profile-id profile-id)))
|
||||||
|
|
||||||
(defn update-profile-photo
|
(defn update-profile-photo
|
||||||
|
|||||||
@ -918,6 +918,7 @@
|
|||||||
;; Validate incoming mime type
|
;; Validate incoming mime type
|
||||||
|
|
||||||
(media/validate-media-type! file #{"image/jpeg" "image/png" "image/webp"})
|
(media/validate-media-type! file #{"image/jpeg" "image/png" "image/webp"})
|
||||||
|
(media/validate-media-size! file)
|
||||||
(update-team-photo cfg (assoc params :profile-id profile-id)))
|
(update-team-photo cfg (assoc params :profile-id profile-id)))
|
||||||
|
|
||||||
(defn update-team-photo
|
(defn update-team-photo
|
||||||
|
|||||||
@ -17,7 +17,9 @@
|
|||||||
[clojure.test :as t]
|
[clojure.test :as t]
|
||||||
[datoteka.fs :as fs]
|
[datoteka.fs :as fs]
|
||||||
[datoteka.io :as io]
|
[datoteka.io :as io]
|
||||||
[mockery.core :refer [with-mocks]]))
|
[mockery.core :refer [with-mocks]])
|
||||||
|
(:import
|
||||||
|
java.io.RandomAccessFile))
|
||||||
|
|
||||||
(t/use-fixtures :once th/state-init)
|
(t/use-fixtures :once th/state-init)
|
||||||
(t/use-fixtures :each th/database-reset)
|
(t/use-fixtures :each th/database-reset)
|
||||||
@ -327,3 +329,499 @@
|
|||||||
(let [error (:error out)
|
(let [error (:error out)
|
||||||
error-data (ex-data error)]
|
error-data (ex-data error)]
|
||||||
(t/is (th/ex-info? error))))))
|
(t/is (th/ex-info? error))))))
|
||||||
|
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
;; Helpers for chunked-upload font tests
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
(defn- split-bytes-into-chunks
|
||||||
|
"Splits `data` (byte array) into chunks of at most `chunk-size` bytes.
|
||||||
|
Returns a vector of byte arrays."
|
||||||
|
[^bytes data chunk-size]
|
||||||
|
(let [length (alength data)]
|
||||||
|
(loop [offset 0 chunks []]
|
||||||
|
(if (>= offset length)
|
||||||
|
chunks
|
||||||
|
(let [remaining (- length offset)
|
||||||
|
size (min chunk-size remaining)
|
||||||
|
buf (byte-array size)]
|
||||||
|
(System/arraycopy data offset buf 0 size)
|
||||||
|
(recur (+ offset size) (conj chunks buf)))))))
|
||||||
|
|
||||||
|
(defn- make-chunk-mfile
|
||||||
|
"Writes `data` (byte array) to a tempfile and returns a map
|
||||||
|
compatible with the upload-chunk :content parameter."
|
||||||
|
[^bytes data mtype]
|
||||||
|
(let [tmp (fs/create-tempfile :dir "/tmp/penpot" :prefix "test-font-chunk-")]
|
||||||
|
(io/write* tmp data)
|
||||||
|
{:filename "chunk"
|
||||||
|
:path tmp
|
||||||
|
:mtype mtype
|
||||||
|
:size (alength data)}))
|
||||||
|
|
||||||
|
(defn- create-upload-session!
|
||||||
|
"Creates an upload session for `prof` with `total-chunks`. Returns the session-id UUID."
|
||||||
|
[prof total-chunks]
|
||||||
|
(let [out (th/command! {::th/type :create-upload-session
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:total-chunks total-chunks})]
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(:session-id (:result out))))
|
||||||
|
|
||||||
|
(defn- upload-font-chunked!
|
||||||
|
"Splits `font-bytes` into chunks of `chunk-size` bytes, creates an upload
|
||||||
|
session, uploads all chunks, and returns the session-id UUID."
|
||||||
|
[prof ^bytes font-bytes mtype chunk-size]
|
||||||
|
(let [chunks (split-bytes-into-chunks font-bytes chunk-size)
|
||||||
|
session-id (create-upload-session! prof (count chunks))]
|
||||||
|
(doseq [[idx chunk-data] (map-indexed vector chunks)]
|
||||||
|
(let [mfile (make-chunk-mfile chunk-data mtype)
|
||||||
|
out (th/command! {::th/type :upload-chunk
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:session-id session-id
|
||||||
|
:index idx
|
||||||
|
:content mfile})]
|
||||||
|
(t/is (nil? (:error out)))))
|
||||||
|
session-id))
|
||||||
|
|
||||||
|
(defn- assert-font-variant-result
|
||||||
|
"Checks that a successful create-font-variant result has valid UUIDs and
|
||||||
|
the expected scalar fields matching `params`."
|
||||||
|
[params result]
|
||||||
|
(t/is (uuid? (:id result)))
|
||||||
|
(t/is (uuid? (:ttf-file-id result)))
|
||||||
|
(t/is (uuid? (:otf-file-id result)))
|
||||||
|
(t/is (uuid? (:woff1-file-id result)))
|
||||||
|
(t/are [k] (= (get params k) (get result k))
|
||||||
|
:team-id
|
||||||
|
:font-id
|
||||||
|
:font-family
|
||||||
|
:font-weight
|
||||||
|
:font-style))
|
||||||
|
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
;; Path 1 – Normal (direct :data bytes)
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-normal-ttf
|
||||||
|
(with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 10)
|
||||||
|
data (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*))
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "chunked-test"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:data {"font/ttf" data}}
|
||||||
|
out (th/command! params)]
|
||||||
|
(t/is (= 1 (:call-count @mock)))
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(assert-font-variant-result params (:result out)))))
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-normal-otf
|
||||||
|
(with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 11)
|
||||||
|
data (-> (io/resource "backend_tests/test_files/font-1.otf") (io/read*))
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "chunked-test"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:data {"font/otf" data}}
|
||||||
|
out (th/command! params)]
|
||||||
|
(t/is (= 1 (:call-count @mock)))
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(assert-font-variant-result params (:result out)))))
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-normal-woff
|
||||||
|
(with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 12)
|
||||||
|
data (-> (io/resource "backend_tests/test_files/font-1.woff") (io/read*))
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "chunked-test"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:data {"font/woff" data}}
|
||||||
|
out (th/command! params)]
|
||||||
|
(t/is (= 1 (:call-count @mock)))
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(assert-font-variant-result params (:result out)))))
|
||||||
|
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
;; Path 2 – Legacy chunking (:data with vector of byte-arrays per mtype)
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-legacy-chunked-ttf
|
||||||
|
"Upload a TTF via the legacy :data path where each mtype value is a
|
||||||
|
vector of byte-array chunks (4 MiB each) instead of a single byte-array."
|
||||||
|
(with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 20)
|
||||||
|
full-bytes (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*))
|
||||||
|
;; Simulate 4 MiB legacy chunks – font is small so a single chunk suffices
|
||||||
|
chunks (split-bytes-into-chunks full-bytes (* 4 1024 1024))
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "legacy-chunked"
|
||||||
|
:font-weight 700
|
||||||
|
:font-style "italic"
|
||||||
|
:data {"font/ttf" (vec chunks)}}
|
||||||
|
out (th/command! params)]
|
||||||
|
(t/is (= 1 (:call-count @mock)))
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(assert-font-variant-result params (:result out)))))
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-legacy-chunked-woff
|
||||||
|
"Upload a WOFF via the legacy :data path with multiple sub-4 KiB chunks
|
||||||
|
to exercise the SequenceInputStream concatenation path."
|
||||||
|
(with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 21)
|
||||||
|
full-bytes (-> (io/resource "backend_tests/test_files/font-1.woff") (io/read*))
|
||||||
|
;; Split into small chunks to exercise the SequenceInputStream path
|
||||||
|
chunks (split-bytes-into-chunks full-bytes 512)
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "legacy-chunked-woff"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:data {"font/woff" (vec chunks)}}
|
||||||
|
out (th/command! params)]
|
||||||
|
(t/is (= 1 (:call-count @mock)))
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(assert-font-variant-result params (:result out)))))
|
||||||
|
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
;; Path 3 – New standardized chunked upload (:uploads map)
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-chunked-upload-ttf
|
||||||
|
"Upload a TTF via the new :uploads path (chunked-upload API)."
|
||||||
|
(with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 30)
|
||||||
|
font-bytes (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*))
|
||||||
|
session-id (upload-font-chunked! prof font-bytes "font/ttf" (* 4 1024 1024))
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "new-chunked"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:uploads {"font/ttf" session-id}}
|
||||||
|
out (th/command! params)]
|
||||||
|
;; quotes/check! is called at least once (for the font-variant quota) plus
|
||||||
|
;; once during session creation — assert it fired at least once.
|
||||||
|
(t/is (>= (:call-count @mock) 1))
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(assert-font-variant-result params (:result out)))))
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-chunked-upload-otf
|
||||||
|
"Upload an OTF via the new :uploads path."
|
||||||
|
(with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 31)
|
||||||
|
font-bytes (-> (io/resource "backend_tests/test_files/font-1.otf") (io/read*))
|
||||||
|
session-id (upload-font-chunked! prof font-bytes "font/otf" (* 4 1024 1024))
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "new-chunked-otf"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:uploads {"font/otf" session-id}}
|
||||||
|
out (th/command! params)]
|
||||||
|
(t/is (>= (:call-count @mock) 1))
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(assert-font-variant-result params (:result out)))))
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-chunked-upload-woff
|
||||||
|
"Upload a WOFF via the new :uploads path."
|
||||||
|
(with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 32)
|
||||||
|
font-bytes (-> (io/resource "backend_tests/test_files/font-1.woff") (io/read*))
|
||||||
|
session-id (upload-font-chunked! prof font-bytes "font/woff" (* 4 1024 1024))
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "new-chunked-woff"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:uploads {"font/woff" session-id}}
|
||||||
|
out (th/command! params)]
|
||||||
|
(t/is (>= (:call-count @mock) 1))
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(assert-font-variant-result params (:result out)))))
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-chunked-upload-multi-chunk
|
||||||
|
"Upload a WOFF split into many small chunks to exercise multi-chunk assembly."
|
||||||
|
(with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 33)
|
||||||
|
font-bytes (-> (io/resource "backend_tests/test_files/font-1.woff") (io/read*))
|
||||||
|
;; Use a chunk-size smaller than 4 MiB to force multiple chunks while
|
||||||
|
;; staying within the 20-chunk-per-session quota limit (29836 / 2000 = ~15 chunks).
|
||||||
|
session-id (upload-font-chunked! prof font-bytes "font/woff" 2000)
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "multi-chunk-woff"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:uploads {"font/woff" session-id}}
|
||||||
|
out (th/command! params)]
|
||||||
|
(t/is (>= (:call-count @mock) 1))
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(assert-font-variant-result params (:result out)))))
|
||||||
|
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
;; Error cases
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-missing-data-and-uploads
|
||||||
|
"Neither :data nor :uploads is present — schema validation must reject it."
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 40)
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "bad"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"}
|
||||||
|
out (th/command! params)]
|
||||||
|
(t/is (some? (:error out)))
|
||||||
|
(t/is (= :validation (-> out :error ex-data :type)))))
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-chunked-upload-missing-chunks
|
||||||
|
"When only some chunks are uploaded the assembly step must fail."
|
||||||
|
(with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 41)
|
||||||
|
font-bytes (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*))
|
||||||
|
;; 5000-byte chunks → 68640/5000 = 14 chunks; declare 15 but only upload 13
|
||||||
|
chunks (split-bytes-into-chunks font-bytes 5000)
|
||||||
|
;; Declare one extra chunk so assembly will fail (not all chunks present)
|
||||||
|
session-id (create-upload-session! prof (inc (count chunks)))]
|
||||||
|
|
||||||
|
;; Upload all real chunks except the last one (omit it so the session is incomplete)
|
||||||
|
(doseq [[idx chunk-data] (map-indexed vector (butlast chunks))]
|
||||||
|
(let [mfile (make-chunk-mfile chunk-data "font/ttf")
|
||||||
|
out (th/command! {::th/type :upload-chunk
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:session-id session-id
|
||||||
|
:index idx
|
||||||
|
:content mfile})]
|
||||||
|
(t/is (nil? (:error out)))))
|
||||||
|
|
||||||
|
(let [out (th/command! {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "missing-chunks"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:uploads {"font/ttf" session-id}})]
|
||||||
|
(t/is (some? (:error out)))))))
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-chunked-upload-invalid-session
|
||||||
|
"Passing a non-existent session-id must fail at assembly time."
|
||||||
|
(with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 42)
|
||||||
|
out (th/command! {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "bad-session"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:uploads {"font/ttf" (uuid/next)}})]
|
||||||
|
(t/is (some? (:error out))))))
|
||||||
|
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
;; Font size validation tests
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-size-exceeded-normal
|
||||||
|
"Direct :data upload exceeding font-max-file-size must be rejected."
|
||||||
|
(with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(with-redefs [app.config/config (assoc app.config/config :font-max-file-size 1)]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 50)
|
||||||
|
data (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*))
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "size-exceeded"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:data {"font/ttf" data}}
|
||||||
|
out (th/command! params)]
|
||||||
|
(t/is (some? (:error out)))
|
||||||
|
(t/is (= :restriction (-> out :error ex-data :type)))
|
||||||
|
(t/is (= :font-max-file-size-reached (-> out :error ex-data :code)))))))
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-size-exceeded-legacy-chunked
|
||||||
|
"Legacy :data chunk-vector upload exceeding font-max-file-size must be rejected."
|
||||||
|
(with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(with-redefs [app.config/config (assoc app.config/config :font-max-file-size 1)]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 51)
|
||||||
|
full-bytes (-> (io/resource "backend_tests/test_files/font-1.woff") (io/read*))
|
||||||
|
chunks (split-bytes-into-chunks full-bytes (* 4 1024 1024))
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "size-exceeded-legacy"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:data {"font/woff" (vec chunks)}}
|
||||||
|
out (th/command! params)]
|
||||||
|
(t/is (some? (:error out)))
|
||||||
|
(t/is (= :restriction (-> out :error ex-data :type)))
|
||||||
|
(t/is (= :font-max-file-size-reached (-> out :error ex-data :code)))))))
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-size-exceeded-chunked-upload
|
||||||
|
"New :uploads path exceeding font-max-file-size must be rejected after assembly."
|
||||||
|
(with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 52)
|
||||||
|
font-bytes (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*))
|
||||||
|
session-id (upload-font-chunked! prof font-bytes "font/ttf" (* 4 1024 1024))]
|
||||||
|
(with-redefs [app.config/config (assoc app.config/config :font-max-file-size 1)]
|
||||||
|
(let [out (th/command! {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "size-exceeded-chunked"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:uploads {"font/ttf" session-id}})]
|
||||||
|
(t/is (some? (:error out)))
|
||||||
|
(t/is (= :restriction (-> out :error ex-data :type)))
|
||||||
|
(t/is (= :font-max-file-size-reached (-> out :error ex-data :code))))))))
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-size-within-limit
|
||||||
|
"Upload exactly at the limit must succeed."
|
||||||
|
(with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 53)
|
||||||
|
font-bytes (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*))
|
||||||
|
font-size (alength ^bytes font-bytes)]
|
||||||
|
(with-redefs [app.config/config (assoc app.config/config :font-max-file-size font-size)]
|
||||||
|
(let [params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "size-at-limit"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:data {"font/ttf" font-bytes}}
|
||||||
|
out (th/command! params)]
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(assert-font-variant-result params (:result out)))))))
|
||||||
|
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
;; Font media-type validation tests
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-invalid-type-normal
|
||||||
|
"Direct :data upload with a disallowed mtype must be rejected."
|
||||||
|
(with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 60)
|
||||||
|
data (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*))
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "invalid-type"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:data {"application/octet-stream" data}}
|
||||||
|
out (th/command! params)]
|
||||||
|
(t/is (some? (:error out)))
|
||||||
|
(t/is (= :validation (-> out :error ex-data :type)))
|
||||||
|
(t/is (= :media-type-not-allowed (-> out :error ex-data :code))))))
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-invalid-type-legacy-chunked
|
||||||
|
"Legacy :data chunk-vector upload with a disallowed mtype must be rejected."
|
||||||
|
(with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 61)
|
||||||
|
full-bytes (-> (io/resource "backend_tests/test_files/font-1.woff") (io/read*))
|
||||||
|
chunks (split-bytes-into-chunks full-bytes (* 4 1024 1024))
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "invalid-type-legacy"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:data {"image/png" (vec chunks)}}
|
||||||
|
out (th/command! params)]
|
||||||
|
(t/is (some? (:error out)))
|
||||||
|
(t/is (= :validation (-> out :error ex-data :type)))
|
||||||
|
(t/is (= :media-type-not-allowed (-> out :error ex-data :code))))))
|
||||||
|
|
||||||
|
(t/deftest create-font-variant-invalid-type-chunked-upload
|
||||||
|
"New :uploads path with a disallowed mtype must be rejected after assembly."
|
||||||
|
(with-mocks [_mock {:target 'app.rpc.quotes/check! :return nil}]
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
font-id (uuid/custom 10 62)
|
||||||
|
font-bytes (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*))
|
||||||
|
;; Upload the bytes under a valid session but lie about the mtype
|
||||||
|
;; when calling create-font-variant.
|
||||||
|
session-id (upload-font-chunked! prof font-bytes "font/ttf" (* 4 1024 1024))
|
||||||
|
out (th/command! {::th/type :create-font-variant
|
||||||
|
::rpc/profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family "invalid-type-chunked"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:uploads {"image/jpeg" session-id}})]
|
||||||
|
(t/is (some? (:error out)))
|
||||||
|
(t/is (= :validation (-> out :error ex-data :type)))
|
||||||
|
(t/is (= :media-type-not-allowed (-> out :error ex-data :code))))))
|
||||||
|
|||||||
@ -1,23 +1,23 @@
|
|||||||
{:deps
|
{:deps
|
||||||
{org.clojure/clojure {:mvn/version "1.12.4"}
|
{org.clojure/clojure {:mvn/version "1.12.5"}
|
||||||
org.clojure/data.json {:mvn/version "2.5.1"}
|
org.clojure/data.json {:mvn/version "2.5.2"}
|
||||||
org.clojure/tools.cli {:mvn/version "1.1.230"}
|
org.clojure/tools.cli {:mvn/version "1.1.230"}
|
||||||
org.clojure/test.check {:mvn/version "1.1.1"}
|
org.clojure/test.check {:mvn/version "1.1.1"}
|
||||||
org.clojure/data.fressian {:mvn/version "1.1.0"}
|
org.clojure/data.fressian {:mvn/version "1.1.1"}
|
||||||
org.clojure/clojurescript {:mvn/version "1.12.42"}
|
org.clojure/clojurescript {:mvn/version "1.12.42"}
|
||||||
|
|
||||||
org.apache.commons/commons-pool2 {:mvn/version "2.12.1"}
|
org.apache.commons/commons-pool2 {:mvn/version "2.12.1"}
|
||||||
|
|
||||||
;; Logging
|
;; Logging
|
||||||
org.apache.logging.log4j/log4j-api {:mvn/version "2.25.3"}
|
org.apache.logging.log4j/log4j-api {:mvn/version "2.26.0"}
|
||||||
org.apache.logging.log4j/log4j-core {:mvn/version "2.25.3"}
|
org.apache.logging.log4j/log4j-core {:mvn/version "2.26.0"}
|
||||||
org.apache.logging.log4j/log4j-web {:mvn/version "2.25.3"}
|
org.apache.logging.log4j/log4j-web {:mvn/version "2.26.0"}
|
||||||
org.apache.logging.log4j/log4j-jul {:mvn/version "2.25.3"}
|
org.apache.logging.log4j/log4j-jul {:mvn/version "2.26.0"}
|
||||||
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.25.3"}
|
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.26.0"}
|
||||||
org.slf4j/slf4j-api {:mvn/version "2.0.17"}
|
org.slf4j/slf4j-api {:mvn/version "2.0.18"}
|
||||||
pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.41"}
|
pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.41"}
|
||||||
|
|
||||||
selmer/selmer {:mvn/version "1.12.70"}
|
selmer/selmer {:mvn/version "1.13.1"}
|
||||||
criterium/criterium {:mvn/version "0.4.6"}
|
criterium/criterium {:mvn/version "0.4.6"}
|
||||||
|
|
||||||
metosin/jsonista {:mvn/version "0.3.13"}
|
metosin/jsonista {:mvn/version "0.3.13"}
|
||||||
@ -55,12 +55,12 @@
|
|||||||
:aliases
|
:aliases
|
||||||
{:dev
|
{:dev
|
||||||
{:extra-deps
|
{:extra-deps
|
||||||
{org.clojure/tools.namespace {:mvn/version "RELEASE"}
|
{org.clojure/tools.namespace {:mvn/version "1.5.0"}
|
||||||
thheller/shadow-cljs {:mvn/version "3.2.0"}
|
thheller/shadow-cljs {:mvn/version "3.2.0"}
|
||||||
com.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"}
|
com.clojure-goes-fast/clj-async-profiler {:mvn/version "2.0.0-beta1"}
|
||||||
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
|
com.bhauman/rebel-readline {:mvn/version "0.1.5"}
|
||||||
criterium/criterium {:mvn/version "0.4.6"}
|
criterium/criterium {:mvn/version "0.4.6"}
|
||||||
mockery/mockery {:mvn/version "RELEASE"}}
|
mockery/mockery {:mvn/version "0.1.4"}}
|
||||||
:extra-paths ["test" "dev"]}
|
:extra-paths ["test" "dev"]}
|
||||||
|
|
||||||
:build
|
:build
|
||||||
|
|||||||
@ -13,8 +13,7 @@
|
|||||||
#{"font/ttf"
|
#{"font/ttf"
|
||||||
"font/woff"
|
"font/woff"
|
||||||
"font/woff2"
|
"font/woff2"
|
||||||
"font/otf"
|
"font/otf"})
|
||||||
"font/opentype"})
|
|
||||||
|
|
||||||
(def image-types
|
(def image-types
|
||||||
#{"image/jpeg"
|
#{"image/jpeg"
|
||||||
|
|||||||
@ -5,7 +5,7 @@ ENV LANG='C.UTF-8' \
|
|||||||
LC_ALL='C.UTF-8' \
|
LC_ALL='C.UTF-8' \
|
||||||
JAVA_HOME="/opt/jdk" \
|
JAVA_HOME="/opt/jdk" \
|
||||||
DEBIAN_FRONTEND=noninteractive \
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
NODE_VERSION=v22.22.0 \
|
NODE_VERSION=v24.15.0 \
|
||||||
TZ=Etc/UTC
|
TZ=Etc/UTC
|
||||||
|
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
@ -46,12 +46,12 @@ RUN set -eux; \
|
|||||||
ARCH="$(dpkg --print-architecture)"; \
|
ARCH="$(dpkg --print-architecture)"; \
|
||||||
case "${ARCH}" in \
|
case "${ARCH}" in \
|
||||||
aarch64|arm64) \
|
aarch64|arm64) \
|
||||||
ESUM='9903c6b19183a33725ca1dfdae5b72400c9d00995c76fafc4a0d31c5152f33f7'; \
|
ESUM='cc1b459dc442d7422b46a3b5fe52acaea54879fa7913e29a05650cef54687f5f'; \
|
||||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-linux_aarch64.tar.gz'; \
|
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-linux_aarch64.tar.gz'; \
|
||||||
;; \
|
;; \
|
||||||
amd64|x86_64) \
|
amd64|x86_64) \
|
||||||
ESUM='946ad9766d98fc6ab495a1a120072197db54997f6925fb96680f1ecd5591db4e'; \
|
ESUM='7d6663ea8d4298df65de065e32f9f449745ff607d30ba5d13777cb92e9d4613d'; \
|
||||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-linux_x64.tar.gz'; \
|
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-linux_x64.tar.gz'; \
|
||||||
;; \
|
;; \
|
||||||
*) \
|
*) \
|
||||||
echo "Unsupported arch: ${ARCH}"; \
|
echo "Unsupported arch: ${ARCH}"; \
|
||||||
@ -68,7 +68,7 @@ RUN set -eux; \
|
|||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
--strip-debug \
|
--strip-debug \
|
||||||
--add-modules java.base,jdk.management.agent,java.se,jdk.compiler,jdk.javadoc,jdk.attach,jdk.unsupported,jdk.jfr,jdk.jcmd \
|
--add-modules java.base,jdk.net,jdk.management.agent,java.se,jdk.compiler,jdk.javadoc,jdk.attach,jdk.unsupported,jdk.jfr,jdk.jcmd \
|
||||||
--output /opt/jre;
|
--output /opt/jre;
|
||||||
|
|
||||||
FROM ubuntu:24.04 AS image
|
FROM ubuntu:24.04 AS image
|
||||||
|
|||||||
@ -3,7 +3,7 @@ LABEL maintainer="Penpot <docker@penpot.app>"
|
|||||||
|
|
||||||
ENV LANG=en_US.UTF-8 \
|
ENV LANG=en_US.UTF-8 \
|
||||||
LC_ALL=en_US.UTF-8 \
|
LC_ALL=en_US.UTF-8 \
|
||||||
NODE_VERSION=v22.22.0 \
|
NODE_VERSION=v24.15.0 \
|
||||||
DEBIAN_FRONTEND=noninteractive \
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
PATH=/opt/node/bin:/opt/imagick/bin:$PATH \
|
PATH=/opt/node/bin:/opt/imagick/bin:$PATH \
|
||||||
PLAYWRIGHT_BROWSERS_PATH=/opt/penpot/browsers
|
PLAYWRIGHT_BROWSERS_PATH=/opt/penpot/browsers
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
FROM nginxinc/nginx-unprivileged:1.29.1
|
FROM nginxinc/nginx-unprivileged:1.30.0
|
||||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
FROM nginxinc/nginx-unprivileged:1.29.1
|
FROM nginxinc/nginx-unprivileged:1.30.0
|
||||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
# WARNING: if you're exposing Penpot to the internet, you should remove the flags
|
# WARNING: if you're exposing Penpot to the internet, you should remove the flags
|
||||||
# 'disable-secure-session-cookies' and 'disable-email-verification'
|
# 'disable-secure-session-cookies' and 'disable-email-verification'
|
||||||
x-flags: &penpot-flags
|
x-flags: &penpot-flags
|
||||||
PENPOT_FLAGS: disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies
|
PENPOT_FLAGS: disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies enable-mcp
|
||||||
|
|
||||||
x-uri: &penpot-public-uri
|
x-uri: &penpot-public-uri
|
||||||
PENPOT_PUBLIC_URI: http://localhost:9001
|
PENPOT_PUBLIC_URI: http://localhost:9001
|
||||||
@ -78,7 +78,7 @@ services:
|
|||||||
# - "443:443"
|
# - "443:443"
|
||||||
|
|
||||||
penpot-frontend:
|
penpot-frontend:
|
||||||
image: "penpotapp/frontend:${PENPOT_VERSION:-latest}"
|
image: "penpotapp/frontend:${PENPOT_VERSION:-2.15}"
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- 9001:8080
|
- 9001:8080
|
||||||
@ -108,7 +108,7 @@ services:
|
|||||||
<< : [*penpot-flags, *penpot-http-body-size, *penpot-public-uri]
|
<< : [*penpot-flags, *penpot-http-body-size, *penpot-public-uri]
|
||||||
|
|
||||||
penpot-backend:
|
penpot-backend:
|
||||||
image: "penpotapp/backend:${PENPOT_VERSION:-latest}"
|
image: "penpotapp/backend:${PENPOT_VERSION:-2.15}"
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
@ -176,8 +176,14 @@ services:
|
|||||||
PENPOT_SMTP_TLS: false
|
PENPOT_SMTP_TLS: false
|
||||||
PENPOT_SMTP_SSL: false
|
PENPOT_SMTP_SSL: false
|
||||||
|
|
||||||
|
penpot-mcp:
|
||||||
|
image: "penpotapp/mcp:${PENPOT_VERSION:-2.15}"
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- penpot
|
||||||
|
|
||||||
penpot-exporter:
|
penpot-exporter:
|
||||||
image: "penpotapp/exporter:${PENPOT_VERSION:-latest}"
|
image: "penpotapp/exporter:${PENPOT_VERSION:-2.15}"
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@ -43,9 +43,10 @@ update_oidc_name /var/www/app/js/config.js
|
|||||||
export PENPOT_BACKEND_URI=${PENPOT_BACKEND_URI:-http://penpot-backend:6060}
|
export PENPOT_BACKEND_URI=${PENPOT_BACKEND_URI:-http://penpot-backend:6060}
|
||||||
export PENPOT_EXPORTER_URI=${PENPOT_EXPORTER_URI:-http://penpot-exporter:6061}
|
export PENPOT_EXPORTER_URI=${PENPOT_EXPORTER_URI:-http://penpot-exporter:6061}
|
||||||
export PENPOT_NITRATE_URI=${PENPOT_NITRATE_URI:-http://penpot-nitrate:3000}
|
export PENPOT_NITRATE_URI=${PENPOT_NITRATE_URI:-http://penpot-nitrate:3000}
|
||||||
export PENPOT_MCP_URI=${PENPOT_MCP_URI:-http://penpot-mcp}
|
export PENPOT_MCP_URI=${PENPOT_MCP_URI:-http://penpot-mcp:4401}
|
||||||
|
export PENPOT_MCP_URI_WS=${PENPOT_MCP_URI_WS:-http://penpot-mcp:4402}
|
||||||
export PENPOT_HTTP_SERVER_MAX_BODY_SIZE=${PENPOT_HTTP_SERVER_MAX_BODY_SIZE:-367001600} # Default to 350MiB
|
export PENPOT_HTTP_SERVER_MAX_BODY_SIZE=${PENPOT_HTTP_SERVER_MAX_BODY_SIZE:-367001600} # Default to 350MiB
|
||||||
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_NITRATE_URI,\$PENPOT_MCP_URI,\$PENPOT_HTTP_SERVER_MAX_BODY_SIZE" \
|
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_NITRATE_URI,\$PENPOT_MCP_URI,\$PENPOT_MCP_URI_WS,\$PENPOT_HTTP_SERVER_MAX_BODY_SIZE" \
|
||||||
< /tmp/nginx.conf.template > /etc/nginx/nginx.conf
|
< /tmp/nginx.conf.template > /etc/nginx/nginx.conf
|
||||||
|
|
||||||
PENPOT_DEFAULT_INTERNAL_RESOLVER="$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf)"
|
PENPOT_DEFAULT_INTERNAL_RESOLVER="$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf)"
|
||||||
|
|||||||
@ -142,17 +142,17 @@ http {
|
|||||||
location /mcp/ws {
|
location /mcp/ws {
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection 'upgrade';
|
proxy_set_header Connection 'upgrade';
|
||||||
proxy_pass $PENPOT_MCP_URI:4402;
|
proxy_pass $PENPOT_MCP_URI_WS;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /mcp/stream {
|
location /mcp/stream {
|
||||||
proxy_pass $PENPOT_MCP_URI:4401/mcp;
|
proxy_pass $PENPOT_MCP_URI/mcp;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /mcp/sse {
|
location /mcp/sse {
|
||||||
proxy_pass $PENPOT_MCP_URI:4401/sse;
|
proxy_pass $PENPOT_MCP_URI/sse;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -425,6 +425,12 @@ In a high-availability (HA) scenario, managing the state outside of replicas is
|
|||||||
- Valkey: Penpot only needs one Valkey instance to function correctly. Due to the nature of the data it manages, replication isn't even essential.
|
- Valkey: Penpot only needs one Valkey instance to function correctly. Due to the nature of the data it manages, replication isn't even essential.
|
||||||
- User media storage: This should not be configured with local storage but rather with centralized storage, such as Kubernetes PVC or S3.
|
- User media storage: This should not be configured with local storage but rather with centralized storage, such as Kubernetes PVC or S3.
|
||||||
|
|
||||||
|
|
||||||
|
__Since version 2.15.0__
|
||||||
|
|
||||||
|
Starting with version 2.15, we have introduced the MCP server. Due to architectural constraints, using the MCP server requires running only a single instance of Penpot.
|
||||||
|
If the MCP server is not installed, then Penpot can scale normally and multiple application instances may be deployed without restrictions.
|
||||||
|
|
||||||
## Backend
|
## Backend
|
||||||
|
|
||||||
This section enumerates the backend only configuration variables.
|
This section enumerates the backend only configuration variables.
|
||||||
|
|||||||
@ -2,12 +2,12 @@
|
|||||||
:deps
|
:deps
|
||||||
{penpot/common {:local/root "../common"}
|
{penpot/common {:local/root "../common"}
|
||||||
org.clojure/clojure {:mvn/version "1.12.2"}
|
org.clojure/clojure {:mvn/version "1.12.2"}
|
||||||
binaryage/devtools {:mvn/version "RELEASE"}
|
binaryage/devtools {:mvn/version "1.0.7"}
|
||||||
metosin/reitit-core {:mvn/version "0.9.1"}
|
metosin/reitit-core {:mvn/version "0.9.1"}
|
||||||
}
|
}
|
||||||
:aliases
|
:aliases
|
||||||
{:outdated
|
{:outdated
|
||||||
{:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"}
|
{:extra-deps {com.github.liquidz/antq {:mvn/version"2.11.1276"}
|
||||||
;; org.slf4j/slf4j-nop {:mvn/version "RELEASE"}
|
;; org.slf4j/slf4j-nop {:mvn/version "RELEASE"}
|
||||||
}
|
}
|
||||||
:main-opts ["-m" "antq.core"]}
|
:main-opts ["-m" "antq.core"]}
|
||||||
|
|||||||
@ -11,22 +11,22 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^8.0.0",
|
||||||
"cookies": "^0.9.1",
|
"cookies": "^0.9.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"generic-pool": "^3.9.0",
|
"generic-pool": "^3.9.0",
|
||||||
"inflation": "^2.1.0",
|
"inflation": "^2.1.0",
|
||||||
"ioredis": "^5.8.2",
|
"ioredis": "^5.10.1",
|
||||||
"playwright": "^1.57.0",
|
"playwright": "^1.60.0",
|
||||||
"raw-body": "^3.0.2",
|
"raw-body": "^3.0.2",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"svgo": "penpot/svgo#v3.1",
|
"svgo": "penpot/svgo#v3.1",
|
||||||
"undici": "^7.16.0",
|
"undici": "^8.2.0",
|
||||||
"xml-js": "^1.6.11",
|
"xml-js": "^1.6.11",
|
||||||
"xregexp": "^5.1.2"
|
"xregexp": "^5.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ws": "^8.18.3"
|
"ws": "^8.20.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target",
|
"clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target",
|
||||||
|
|||||||
532
exporter/pnpm-lock.yaml
generated
532
exporter/pnpm-lock.yaml
generated
@ -9,8 +9,8 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
archiver:
|
archiver:
|
||||||
specifier: ^7.0.1
|
specifier: ^8.0.0
|
||||||
version: 7.0.1
|
version: 8.0.0
|
||||||
cookies:
|
cookies:
|
||||||
specifier: ^0.9.1
|
specifier: ^0.9.1
|
||||||
version: 0.9.1
|
version: 0.9.1
|
||||||
@ -24,11 +24,11 @@ importers:
|
|||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
ioredis:
|
ioredis:
|
||||||
specifier: ^5.8.2
|
specifier: ^5.10.1
|
||||||
version: 5.8.2
|
version: 5.10.1
|
||||||
playwright:
|
playwright:
|
||||||
specifier: ^1.57.0
|
specifier: ^1.60.0
|
||||||
version: 1.57.0
|
version: 1.60.0
|
||||||
raw-body:
|
raw-body:
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.2
|
version: 3.0.2
|
||||||
@ -39,8 +39,8 @@ importers:
|
|||||||
specifier: penpot/svgo#v3.1
|
specifier: penpot/svgo#v3.1
|
||||||
version: https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180
|
version: https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180
|
||||||
undici:
|
undici:
|
||||||
specifier: ^7.16.0
|
specifier: ^8.2.0
|
||||||
version: 7.16.0
|
version: 8.2.0
|
||||||
xml-js:
|
xml-js:
|
||||||
specifier: ^1.6.11
|
specifier: ^1.6.11
|
||||||
version: 1.6.11
|
version: 1.6.11
|
||||||
@ -49,8 +49,8 @@ importers:
|
|||||||
version: 5.1.2
|
version: 5.1.2
|
||||||
devDependencies:
|
devDependencies:
|
||||||
ws:
|
ws:
|
||||||
specifier: ^8.18.3
|
specifier: ^8.20.1
|
||||||
version: 8.18.3
|
version: 8.20.1
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@ -58,16 +58,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==}
|
resolution: {integrity: sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@ioredis/commands@1.4.0':
|
'@ioredis/commands@1.5.1':
|
||||||
resolution: {integrity: sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==}
|
resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==}
|
||||||
|
|
||||||
'@isaacs/cliui@8.0.2':
|
|
||||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
'@pkgjs/parseargs@0.11.0':
|
|
||||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
|
||||||
engines: {node: '>=14'}
|
|
||||||
|
|
||||||
'@trysound/sax@0.2.0':
|
'@trysound/sax@0.2.0':
|
||||||
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
|
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
|
||||||
@ -77,43 +69,24 @@ packages:
|
|||||||
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
||||||
engines: {node: '>=6.5'}
|
engines: {node: '>=6.5'}
|
||||||
|
|
||||||
ansi-regex@5.0.1:
|
archiver@8.0.0:
|
||||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
resolution: {integrity: sha512-fV1orZfsnPn9BaSByR/qE67rJCLJEy2Ox5bq7nJh+jquWaNh6Sfec75kJ2T6PtdGUbPQlrVoSVCEOa5SdiTQ1g==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
ansi-regex@6.2.2:
|
|
||||||
resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
ansi-styles@4.3.0:
|
|
||||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
ansi-styles@6.2.3:
|
|
||||||
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
archiver-utils@5.0.2:
|
|
||||||
resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==}
|
|
||||||
engines: {node: '>= 14'}
|
|
||||||
|
|
||||||
archiver@7.0.1:
|
|
||||||
resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==}
|
|
||||||
engines: {node: '>= 14'}
|
|
||||||
|
|
||||||
async@3.2.6:
|
async@3.2.6:
|
||||||
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
|
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
|
||||||
|
|
||||||
b4a@1.7.3:
|
b4a@1.8.1:
|
||||||
resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==}
|
resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react-native-b4a: '*'
|
react-native-b4a: '*'
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
react-native-b4a:
|
react-native-b4a:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
balanced-match@1.0.2:
|
balanced-match@4.0.4:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
||||||
|
engines: {node: 18 || 20 || >=22}
|
||||||
|
|
||||||
bare-events@2.8.2:
|
bare-events@2.8.2:
|
||||||
resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==}
|
resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==}
|
||||||
@ -123,14 +96,48 @@ packages:
|
|||||||
bare-abort-controller:
|
bare-abort-controller:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
bare-fs@4.7.1:
|
||||||
|
resolution: {integrity: sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==}
|
||||||
|
engines: {bare: '>=1.16.0'}
|
||||||
|
peerDependencies:
|
||||||
|
bare-buffer: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
bare-buffer:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
bare-os@3.9.1:
|
||||||
|
resolution: {integrity: sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==}
|
||||||
|
engines: {bare: '>=1.14.0'}
|
||||||
|
|
||||||
|
bare-path@3.0.0:
|
||||||
|
resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==}
|
||||||
|
|
||||||
|
bare-stream@2.13.1:
|
||||||
|
resolution: {integrity: sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==}
|
||||||
|
peerDependencies:
|
||||||
|
bare-abort-controller: '*'
|
||||||
|
bare-buffer: '*'
|
||||||
|
bare-events: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
bare-abort-controller:
|
||||||
|
optional: true
|
||||||
|
bare-buffer:
|
||||||
|
optional: true
|
||||||
|
bare-events:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
bare-url@2.4.3:
|
||||||
|
resolution: {integrity: sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==}
|
||||||
|
|
||||||
base64-js@1.5.1:
|
base64-js@1.5.1:
|
||||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||||
|
|
||||||
boolbase@1.0.0:
|
boolbase@1.0.0:
|
||||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||||
|
|
||||||
brace-expansion@2.0.2:
|
brace-expansion@5.0.6:
|
||||||
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
|
resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
|
||||||
|
engines: {node: 18 || 20 || >=22}
|
||||||
|
|
||||||
buffer-crc32@1.0.0:
|
buffer-crc32@1.0.0:
|
||||||
resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==}
|
resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==}
|
||||||
@ -150,16 +157,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
|
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
compress-commons@7.0.1:
|
||||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
resolution: {integrity: sha512-g0S8KAD8qf4+V//pr3BfB1aBnARLXNz2Gx+jmHU0LEriUuoQUOPOulVquHKTJ8+EAIIO7fhseNDr9wK5Q9FKBQ==}
|
||||||
engines: {node: '>=7.0.0'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
color-name@1.1.4:
|
|
||||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
|
||||||
|
|
||||||
compress-commons@6.0.2:
|
|
||||||
resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==}
|
|
||||||
engines: {node: '>= 14'}
|
|
||||||
|
|
||||||
cookies@0.9.1:
|
cookies@0.9.1:
|
||||||
resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==}
|
resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==}
|
||||||
@ -176,13 +176,9 @@ packages:
|
|||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
crc32-stream@6.0.0:
|
crc32-stream@7.0.1:
|
||||||
resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==}
|
resolution: {integrity: sha512-IBWsY8xznyQrcHn8h4bC8/4ErNke5elzgG8GcqF4RFPw6aHkWWRc7Tgw6upjaTX/CT/yQgqYENkxYsTYN+hW2g==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
|
||||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
|
||||||
engines: {node: '>= 8'}
|
|
||||||
|
|
||||||
css-select@5.2.2:
|
css-select@5.2.2:
|
||||||
resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
|
resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
|
||||||
@ -236,15 +232,6 @@ packages:
|
|||||||
domutils@3.2.2:
|
domutils@3.2.2:
|
||||||
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
|
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
|
||||||
|
|
||||||
eastasianwidth@0.2.0:
|
|
||||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
|
||||||
|
|
||||||
emoji-regex@8.0.0:
|
|
||||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
|
||||||
|
|
||||||
emoji-regex@9.2.2:
|
|
||||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
|
||||||
|
|
||||||
entities@4.5.0:
|
entities@4.5.0:
|
||||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||||
engines: {node: '>=0.12'}
|
engines: {node: '>=0.12'}
|
||||||
@ -263,10 +250,6 @@ packages:
|
|||||||
fast-fifo@1.3.2:
|
fast-fifo@1.3.2:
|
||||||
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
||||||
|
|
||||||
foreground-child@3.3.1:
|
|
||||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
|
||||||
engines: {node: '>=14'}
|
|
||||||
|
|
||||||
fsevents@2.3.2:
|
fsevents@2.3.2:
|
||||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
@ -276,13 +259,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==}
|
resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
glob@10.5.0:
|
|
||||||
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
graceful-fs@4.2.11:
|
|
||||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
|
||||||
|
|
||||||
http-errors@2.0.1:
|
http-errors@2.0.1:
|
||||||
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@ -301,27 +277,17 @@ packages:
|
|||||||
inherits@2.0.4:
|
inherits@2.0.4:
|
||||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||||
|
|
||||||
ioredis@5.8.2:
|
ioredis@5.10.1:
|
||||||
resolution: {integrity: sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==}
|
resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==}
|
||||||
engines: {node: '>=12.22.0'}
|
engines: {node: '>=12.22.0'}
|
||||||
|
|
||||||
is-fullwidth-code-point@3.0.0:
|
is-stream@4.0.1:
|
||||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
is-stream@2.0.1:
|
|
||||||
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
isarray@1.0.0:
|
isarray@1.0.0:
|
||||||
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||||
|
|
||||||
isexe@2.0.0:
|
|
||||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
|
||||||
|
|
||||||
jackspeak@3.4.3:
|
|
||||||
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
|
||||||
|
|
||||||
keygrip@1.1.0:
|
keygrip@1.1.0:
|
||||||
resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==}
|
resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@ -340,26 +306,15 @@ packages:
|
|||||||
lodash@4.17.21:
|
lodash@4.17.21:
|
||||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||||
|
|
||||||
lru-cache@10.4.3:
|
|
||||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
|
||||||
|
|
||||||
mdn-data@2.0.28:
|
mdn-data@2.0.28:
|
||||||
resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
|
resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
|
||||||
|
|
||||||
mdn-data@2.12.2:
|
mdn-data@2.12.2:
|
||||||
resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
|
resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
|
||||||
|
|
||||||
minimatch@5.1.6:
|
minimatch@10.2.5:
|
||||||
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
|
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: 18 || 20 || >=22}
|
||||||
|
|
||||||
minimatch@9.0.5:
|
|
||||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
|
||||||
|
|
||||||
minipass@7.1.2:
|
|
||||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
|
||||||
|
|
||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
@ -371,24 +326,13 @@ packages:
|
|||||||
nth-check@2.1.1:
|
nth-check@2.1.1:
|
||||||
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
||||||
|
|
||||||
package-json-from-dist@1.0.1:
|
playwright-core@1.60.0:
|
||||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==}
|
||||||
|
|
||||||
path-key@3.1.1:
|
|
||||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
path-scurry@1.11.1:
|
|
||||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
|
||||||
engines: {node: '>=16 || 14 >=14.18'}
|
|
||||||
|
|
||||||
playwright-core@1.57.0:
|
|
||||||
resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==}
|
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
playwright@1.57.0:
|
playwright@1.60.0:
|
||||||
resolution: {integrity: sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==}
|
resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@ -410,8 +354,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==}
|
resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
|
||||||
readdir-glob@1.1.3:
|
readdir-glob@3.0.0:
|
||||||
resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==}
|
resolution: {integrity: sha512-AhNB2KgKeVJr16nK9LLZbJNWnYoT23ZrumNKFDebHBdkC8KHSqWo871JAUhoWC/RtjEVdqNMFpM6qrwRbaUqpw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
redis-errors@1.2.0:
|
redis-errors@1.2.0:
|
||||||
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
|
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
|
||||||
@ -436,18 +381,6 @@ packages:
|
|||||||
setprototypeof@1.2.0:
|
setprototypeof@1.2.0:
|
||||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
|
||||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
shebang-regex@3.0.0:
|
|
||||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
signal-exit@4.1.0:
|
|
||||||
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
|
||||||
engines: {node: '>=14'}
|
|
||||||
|
|
||||||
source-map-js@1.2.1:
|
source-map-js@1.2.1:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -466,16 +399,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
streamx@2.23.0:
|
streamx@2.25.0:
|
||||||
resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==}
|
resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==}
|
||||||
|
|
||||||
string-width@4.2.3:
|
|
||||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
string-width@5.1.2:
|
|
||||||
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
string_decoder@1.1.1:
|
string_decoder@1.1.1:
|
||||||
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
||||||
@ -483,24 +408,19 @@ packages:
|
|||||||
string_decoder@1.3.0:
|
string_decoder@1.3.0:
|
||||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||||
|
|
||||||
strip-ansi@6.0.1:
|
|
||||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
strip-ansi@7.1.2:
|
|
||||||
resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
svgo@https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180:
|
svgo@https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180:
|
||||||
resolution: {tarball: https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180}
|
resolution: {tarball: https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180}
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
tar-stream@3.1.7:
|
tar-stream@3.2.0:
|
||||||
resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
|
resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==}
|
||||||
|
|
||||||
text-decoder@1.2.3:
|
teex@1.0.1:
|
||||||
resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==}
|
resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==}
|
||||||
|
|
||||||
|
text-decoder@1.2.7:
|
||||||
|
resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==}
|
||||||
|
|
||||||
toidentifier@1.0.1:
|
toidentifier@1.0.1:
|
||||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||||
@ -510,9 +430,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
|
resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
|
||||||
engines: {node: '>=0.6.x'}
|
engines: {node: '>=0.6.x'}
|
||||||
|
|
||||||
undici@7.16.0:
|
undici@8.2.0:
|
||||||
resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==}
|
resolution: {integrity: sha512-Z+4Hx9GE26Lh9Upwfnc8C7SsrpBPGaM/Gm6kMFtiG7c+5IvQKlXi/t+9x9DrrCh29cww5TSP9YdVaBcnLDs5fQ==}
|
||||||
engines: {node: '>=20.18.1'}
|
engines: {node: '>=22.19.0'}
|
||||||
|
|
||||||
unpipe@1.0.0:
|
unpipe@1.0.0:
|
||||||
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
|
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
|
||||||
@ -521,21 +441,8 @@ packages:
|
|||||||
util-deprecate@1.0.2:
|
util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
which@2.0.2:
|
ws@8.20.1:
|
||||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==}
|
||||||
engines: {node: '>= 8'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
wrap-ansi@7.0.0:
|
|
||||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
|
|
||||||
wrap-ansi@8.1.0:
|
|
||||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
ws@8.18.3:
|
|
||||||
resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
|
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
bufferutil: ^4.0.1
|
bufferutil: ^4.0.1
|
||||||
@ -553,9 +460,9 @@ packages:
|
|||||||
xregexp@5.1.2:
|
xregexp@5.1.2:
|
||||||
resolution: {integrity: sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==}
|
resolution: {integrity: sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==}
|
||||||
|
|
||||||
zip-stream@6.0.1:
|
zip-stream@7.0.5:
|
||||||
resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
|
resolution: {integrity: sha512-dSvYKdvLsAHCDqPOhIwk/q5CvuWtTB3Dgpoe0uVEFjTzIOAmsQpprX25InCvrvJsirEbu1OHyy67n/kAj1Sw/w==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
@ -563,19 +470,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
core-js-pure: 3.47.0
|
core-js-pure: 3.47.0
|
||||||
|
|
||||||
'@ioredis/commands@1.4.0': {}
|
'@ioredis/commands@1.5.1': {}
|
||||||
|
|
||||||
'@isaacs/cliui@8.0.2':
|
|
||||||
dependencies:
|
|
||||||
string-width: 5.1.2
|
|
||||||
string-width-cjs: string-width@4.2.3
|
|
||||||
strip-ansi: 7.1.2
|
|
||||||
strip-ansi-cjs: strip-ansi@6.0.1
|
|
||||||
wrap-ansi: 8.1.0
|
|
||||||
wrap-ansi-cjs: wrap-ansi@7.0.0
|
|
||||||
|
|
||||||
'@pkgjs/parseargs@0.11.0':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@trysound/sax@0.2.0': {}
|
'@trysound/sax@0.2.0': {}
|
||||||
|
|
||||||
@ -583,54 +478,67 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
event-target-shim: 5.0.1
|
event-target-shim: 5.0.1
|
||||||
|
|
||||||
ansi-regex@5.0.1: {}
|
archiver@8.0.0:
|
||||||
|
|
||||||
ansi-regex@6.2.2: {}
|
|
||||||
|
|
||||||
ansi-styles@4.3.0:
|
|
||||||
dependencies:
|
dependencies:
|
||||||
color-convert: 2.0.1
|
|
||||||
|
|
||||||
ansi-styles@6.2.3: {}
|
|
||||||
|
|
||||||
archiver-utils@5.0.2:
|
|
||||||
dependencies:
|
|
||||||
glob: 10.5.0
|
|
||||||
graceful-fs: 4.2.11
|
|
||||||
is-stream: 2.0.1
|
|
||||||
lazystream: 1.0.1
|
|
||||||
lodash: 4.17.21
|
|
||||||
normalize-path: 3.0.0
|
|
||||||
readable-stream: 4.7.0
|
|
||||||
|
|
||||||
archiver@7.0.1:
|
|
||||||
dependencies:
|
|
||||||
archiver-utils: 5.0.2
|
|
||||||
async: 3.2.6
|
async: 3.2.6
|
||||||
buffer-crc32: 1.0.0
|
buffer-crc32: 1.0.0
|
||||||
|
is-stream: 4.0.1
|
||||||
|
lazystream: 1.0.1
|
||||||
|
normalize-path: 3.0.0
|
||||||
readable-stream: 4.7.0
|
readable-stream: 4.7.0
|
||||||
readdir-glob: 1.1.3
|
readdir-glob: 3.0.0
|
||||||
tar-stream: 3.1.7
|
tar-stream: 3.2.0
|
||||||
zip-stream: 6.0.1
|
zip-stream: 7.0.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bare-abort-controller
|
- bare-abort-controller
|
||||||
|
- bare-buffer
|
||||||
- react-native-b4a
|
- react-native-b4a
|
||||||
|
|
||||||
async@3.2.6: {}
|
async@3.2.6: {}
|
||||||
|
|
||||||
b4a@1.7.3: {}
|
b4a@1.8.1: {}
|
||||||
|
|
||||||
balanced-match@1.0.2: {}
|
balanced-match@4.0.4: {}
|
||||||
|
|
||||||
bare-events@2.8.2: {}
|
bare-events@2.8.2: {}
|
||||||
|
|
||||||
|
bare-fs@4.7.1:
|
||||||
|
dependencies:
|
||||||
|
bare-events: 2.8.2
|
||||||
|
bare-path: 3.0.0
|
||||||
|
bare-stream: 2.13.1(bare-events@2.8.2)
|
||||||
|
bare-url: 2.4.3
|
||||||
|
fast-fifo: 1.3.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bare-abort-controller
|
||||||
|
- react-native-b4a
|
||||||
|
|
||||||
|
bare-os@3.9.1: {}
|
||||||
|
|
||||||
|
bare-path@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
bare-os: 3.9.1
|
||||||
|
|
||||||
|
bare-stream@2.13.1(bare-events@2.8.2):
|
||||||
|
dependencies:
|
||||||
|
streamx: 2.25.0
|
||||||
|
teex: 1.0.1
|
||||||
|
optionalDependencies:
|
||||||
|
bare-events: 2.8.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- react-native-b4a
|
||||||
|
|
||||||
|
bare-url@2.4.3:
|
||||||
|
dependencies:
|
||||||
|
bare-path: 3.0.0
|
||||||
|
|
||||||
base64-js@1.5.1: {}
|
base64-js@1.5.1: {}
|
||||||
|
|
||||||
boolbase@1.0.0: {}
|
boolbase@1.0.0: {}
|
||||||
|
|
||||||
brace-expansion@2.0.2:
|
brace-expansion@5.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match: 1.0.2
|
balanced-match: 4.0.4
|
||||||
|
|
||||||
buffer-crc32@1.0.0: {}
|
buffer-crc32@1.0.0: {}
|
||||||
|
|
||||||
@ -645,17 +553,11 @@ snapshots:
|
|||||||
|
|
||||||
cluster-key-slot@1.1.2: {}
|
cluster-key-slot@1.1.2: {}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
compress-commons@7.0.1:
|
||||||
dependencies:
|
|
||||||
color-name: 1.1.4
|
|
||||||
|
|
||||||
color-name@1.1.4: {}
|
|
||||||
|
|
||||||
compress-commons@6.0.2:
|
|
||||||
dependencies:
|
dependencies:
|
||||||
crc-32: 1.2.2
|
crc-32: 1.2.2
|
||||||
crc32-stream: 6.0.0
|
crc32-stream: 7.0.1
|
||||||
is-stream: 2.0.1
|
is-stream: 4.0.1
|
||||||
normalize-path: 3.0.0
|
normalize-path: 3.0.0
|
||||||
readable-stream: 4.7.0
|
readable-stream: 4.7.0
|
||||||
|
|
||||||
@ -670,17 +572,11 @@ snapshots:
|
|||||||
|
|
||||||
crc-32@1.2.2: {}
|
crc-32@1.2.2: {}
|
||||||
|
|
||||||
crc32-stream@6.0.0:
|
crc32-stream@7.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
crc-32: 1.2.2
|
crc-32: 1.2.2
|
||||||
readable-stream: 4.7.0
|
readable-stream: 4.7.0
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
|
||||||
dependencies:
|
|
||||||
path-key: 3.1.1
|
|
||||||
shebang-command: 2.0.0
|
|
||||||
which: 2.0.2
|
|
||||||
|
|
||||||
css-select@5.2.2:
|
css-select@5.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
boolbase: 1.0.0
|
boolbase: 1.0.0
|
||||||
@ -733,12 +629,6 @@ snapshots:
|
|||||||
domelementtype: 2.3.0
|
domelementtype: 2.3.0
|
||||||
domhandler: 5.0.3
|
domhandler: 5.0.3
|
||||||
|
|
||||||
eastasianwidth@0.2.0: {}
|
|
||||||
|
|
||||||
emoji-regex@8.0.0: {}
|
|
||||||
|
|
||||||
emoji-regex@9.2.2: {}
|
|
||||||
|
|
||||||
entities@4.5.0: {}
|
entities@4.5.0: {}
|
||||||
|
|
||||||
event-target-shim@5.0.1: {}
|
event-target-shim@5.0.1: {}
|
||||||
@ -753,27 +643,11 @@ snapshots:
|
|||||||
|
|
||||||
fast-fifo@1.3.2: {}
|
fast-fifo@1.3.2: {}
|
||||||
|
|
||||||
foreground-child@3.3.1:
|
|
||||||
dependencies:
|
|
||||||
cross-spawn: 7.0.6
|
|
||||||
signal-exit: 4.1.0
|
|
||||||
|
|
||||||
fsevents@2.3.2:
|
fsevents@2.3.2:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
generic-pool@3.9.0: {}
|
generic-pool@3.9.0: {}
|
||||||
|
|
||||||
glob@10.5.0:
|
|
||||||
dependencies:
|
|
||||||
foreground-child: 3.3.1
|
|
||||||
jackspeak: 3.4.3
|
|
||||||
minimatch: 9.0.5
|
|
||||||
minipass: 7.1.2
|
|
||||||
package-json-from-dist: 1.0.1
|
|
||||||
path-scurry: 1.11.1
|
|
||||||
|
|
||||||
graceful-fs@4.2.11: {}
|
|
||||||
|
|
||||||
http-errors@2.0.1:
|
http-errors@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
depd: 2.0.0
|
depd: 2.0.0
|
||||||
@ -792,9 +666,9 @@ snapshots:
|
|||||||
|
|
||||||
inherits@2.0.4: {}
|
inherits@2.0.4: {}
|
||||||
|
|
||||||
ioredis@5.8.2:
|
ioredis@5.10.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ioredis/commands': 1.4.0
|
'@ioredis/commands': 1.5.1
|
||||||
cluster-key-slot: 1.1.2
|
cluster-key-slot: 1.1.2
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
denque: 2.1.0
|
denque: 2.1.0
|
||||||
@ -806,20 +680,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
is-fullwidth-code-point@3.0.0: {}
|
is-stream@4.0.1: {}
|
||||||
|
|
||||||
is-stream@2.0.1: {}
|
|
||||||
|
|
||||||
isarray@1.0.0: {}
|
isarray@1.0.0: {}
|
||||||
|
|
||||||
isexe@2.0.0: {}
|
|
||||||
|
|
||||||
jackspeak@3.4.3:
|
|
||||||
dependencies:
|
|
||||||
'@isaacs/cliui': 8.0.2
|
|
||||||
optionalDependencies:
|
|
||||||
'@pkgjs/parseargs': 0.11.0
|
|
||||||
|
|
||||||
keygrip@1.1.0:
|
keygrip@1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
tsscmp: 1.0.6
|
tsscmp: 1.0.6
|
||||||
@ -834,21 +698,13 @@ snapshots:
|
|||||||
|
|
||||||
lodash@4.17.21: {}
|
lodash@4.17.21: {}
|
||||||
|
|
||||||
lru-cache@10.4.3: {}
|
|
||||||
|
|
||||||
mdn-data@2.0.28: {}
|
mdn-data@2.0.28: {}
|
||||||
|
|
||||||
mdn-data@2.12.2: {}
|
mdn-data@2.12.2: {}
|
||||||
|
|
||||||
minimatch@5.1.6:
|
minimatch@10.2.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 2.0.2
|
brace-expansion: 5.0.6
|
||||||
|
|
||||||
minimatch@9.0.5:
|
|
||||||
dependencies:
|
|
||||||
brace-expansion: 2.0.2
|
|
||||||
|
|
||||||
minipass@7.1.2: {}
|
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
@ -858,20 +714,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
boolbase: 1.0.0
|
boolbase: 1.0.0
|
||||||
|
|
||||||
package-json-from-dist@1.0.1: {}
|
playwright-core@1.60.0: {}
|
||||||
|
|
||||||
path-key@3.1.1: {}
|
playwright@1.60.0:
|
||||||
|
|
||||||
path-scurry@1.11.1:
|
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache: 10.4.3
|
playwright-core: 1.60.0
|
||||||
minipass: 7.1.2
|
|
||||||
|
|
||||||
playwright-core@1.57.0: {}
|
|
||||||
|
|
||||||
playwright@1.57.0:
|
|
||||||
dependencies:
|
|
||||||
playwright-core: 1.57.0
|
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
|
|
||||||
@ -904,9 +751,9 @@ snapshots:
|
|||||||
process: 0.11.10
|
process: 0.11.10
|
||||||
string_decoder: 1.3.0
|
string_decoder: 1.3.0
|
||||||
|
|
||||||
readdir-glob@1.1.3:
|
readdir-glob@3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
minimatch: 5.1.6
|
minimatch: 10.2.5
|
||||||
|
|
||||||
redis-errors@1.2.0: {}
|
redis-errors@1.2.0: {}
|
||||||
|
|
||||||
@ -924,14 +771,6 @@ snapshots:
|
|||||||
|
|
||||||
setprototypeof@1.2.0: {}
|
setprototypeof@1.2.0: {}
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
|
||||||
dependencies:
|
|
||||||
shebang-regex: 3.0.0
|
|
||||||
|
|
||||||
shebang-regex@3.0.0: {}
|
|
||||||
|
|
||||||
signal-exit@4.1.0: {}
|
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
source-map-support@0.5.21:
|
source-map-support@0.5.21:
|
||||||
@ -945,27 +784,15 @@ snapshots:
|
|||||||
|
|
||||||
statuses@2.0.2: {}
|
statuses@2.0.2: {}
|
||||||
|
|
||||||
streamx@2.23.0:
|
streamx@2.25.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
events-universal: 1.0.1
|
events-universal: 1.0.1
|
||||||
fast-fifo: 1.3.2
|
fast-fifo: 1.3.2
|
||||||
text-decoder: 1.2.3
|
text-decoder: 1.2.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bare-abort-controller
|
- bare-abort-controller
|
||||||
- react-native-b4a
|
- react-native-b4a
|
||||||
|
|
||||||
string-width@4.2.3:
|
|
||||||
dependencies:
|
|
||||||
emoji-regex: 8.0.0
|
|
||||||
is-fullwidth-code-point: 3.0.0
|
|
||||||
strip-ansi: 6.0.1
|
|
||||||
|
|
||||||
string-width@5.1.2:
|
|
||||||
dependencies:
|
|
||||||
eastasianwidth: 0.2.0
|
|
||||||
emoji-regex: 9.2.2
|
|
||||||
strip-ansi: 7.1.2
|
|
||||||
|
|
||||||
string_decoder@1.1.1:
|
string_decoder@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer: 5.1.2
|
safe-buffer: 5.1.2
|
||||||
@ -974,14 +801,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer: 5.2.1
|
safe-buffer: 5.2.1
|
||||||
|
|
||||||
strip-ansi@6.0.1:
|
|
||||||
dependencies:
|
|
||||||
ansi-regex: 5.0.1
|
|
||||||
|
|
||||||
strip-ansi@7.1.2:
|
|
||||||
dependencies:
|
|
||||||
ansi-regex: 6.2.2
|
|
||||||
|
|
||||||
svgo@https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180:
|
svgo@https://codeload.github.com/penpot/svgo/tar.gz/a46262c12c0d967708395972c374eb2adead4180:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@trysound/sax': 0.2.0
|
'@trysound/sax': 0.2.0
|
||||||
@ -990,18 +809,27 @@ snapshots:
|
|||||||
csso: 5.0.5
|
csso: 5.0.5
|
||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
|
|
||||||
tar-stream@3.1.7:
|
tar-stream@3.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
b4a: 1.7.3
|
b4a: 1.8.1
|
||||||
|
bare-fs: 4.7.1
|
||||||
fast-fifo: 1.3.2
|
fast-fifo: 1.3.2
|
||||||
streamx: 2.23.0
|
streamx: 2.25.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bare-abort-controller
|
||||||
|
- bare-buffer
|
||||||
|
- react-native-b4a
|
||||||
|
|
||||||
|
teex@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
streamx: 2.25.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bare-abort-controller
|
- bare-abort-controller
|
||||||
- react-native-b4a
|
- react-native-b4a
|
||||||
|
|
||||||
text-decoder@1.2.3:
|
text-decoder@1.2.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
b4a: 1.7.3
|
b4a: 1.8.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- react-native-b4a
|
- react-native-b4a
|
||||||
|
|
||||||
@ -1009,29 +837,13 @@ snapshots:
|
|||||||
|
|
||||||
tsscmp@1.0.6: {}
|
tsscmp@1.0.6: {}
|
||||||
|
|
||||||
undici@7.16.0: {}
|
undici@8.2.0: {}
|
||||||
|
|
||||||
unpipe@1.0.0: {}
|
unpipe@1.0.0: {}
|
||||||
|
|
||||||
util-deprecate@1.0.2: {}
|
util-deprecate@1.0.2: {}
|
||||||
|
|
||||||
which@2.0.2:
|
ws@8.20.1: {}
|
||||||
dependencies:
|
|
||||||
isexe: 2.0.0
|
|
||||||
|
|
||||||
wrap-ansi@7.0.0:
|
|
||||||
dependencies:
|
|
||||||
ansi-styles: 4.3.0
|
|
||||||
string-width: 4.2.3
|
|
||||||
strip-ansi: 6.0.1
|
|
||||||
|
|
||||||
wrap-ansi@8.1.0:
|
|
||||||
dependencies:
|
|
||||||
ansi-styles: 6.2.3
|
|
||||||
string-width: 5.1.2
|
|
||||||
strip-ansi: 7.1.2
|
|
||||||
|
|
||||||
ws@8.18.3: {}
|
|
||||||
|
|
||||||
xml-js@1.6.11:
|
xml-js@1.6.11:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -1041,8 +853,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime-corejs3': 7.28.4
|
'@babel/runtime-corejs3': 7.28.4
|
||||||
|
|
||||||
zip-stream@6.0.1:
|
zip-stream@7.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
archiver-utils: 5.0.2
|
compress-commons: 7.0.1
|
||||||
compress-commons: 6.0.2
|
normalize-path: 3.0.0
|
||||||
readable-stream: 4.7.0
|
readable-stream: 4.7.0
|
||||||
|
|||||||
@ -90,7 +90,7 @@
|
|||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"opentype.js": "^1.3.4",
|
"opentype.js": "^1.3.4",
|
||||||
"p-limit": "^7.3.0",
|
"p-limit": "^7.3.0",
|
||||||
"playwright": "1.59.1",
|
"playwright": "1.60.0",
|
||||||
"postcss": "^8.5.8",
|
"postcss": "^8.5.8",
|
||||||
"postcss-clean": "^1.2.2",
|
"postcss-clean": "^1.2.2",
|
||||||
"postcss-modules": "^6.0.1",
|
"postcss-modules": "^6.0.1",
|
||||||
|
|||||||
400
frontend/pnpm-lock.yaml
generated
400
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -14,6 +14,7 @@
|
|||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.main.data.event :as ev]
|
[app.main.data.event :as ev]
|
||||||
[app.main.data.notifications :as ntf]
|
[app.main.data.notifications :as ntf]
|
||||||
|
[app.main.data.uploads :as uploads]
|
||||||
[app.main.fonts :as fonts]
|
[app.main.fonts :as fonts]
|
||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
@ -24,24 +25,14 @@
|
|||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[potok.v2.core :as ptk]))
|
[potok.v2.core :as ptk]))
|
||||||
|
|
||||||
(def ^:const default-chunk-size
|
|
||||||
(* 1024 1024 4)) ;; 4MiB
|
|
||||||
|
|
||||||
(defn- chunk-array
|
|
||||||
[data chunk-size]
|
|
||||||
(let [total-size (alength data)]
|
|
||||||
(loop [offset 0
|
|
||||||
chunks []]
|
|
||||||
(if (< offset total-size)
|
|
||||||
(let [end (min (+ offset chunk-size) total-size)
|
|
||||||
chunk (.subarray ^js data offset end)]
|
|
||||||
(recur end (conj chunks chunk)))
|
|
||||||
chunks))))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; General purpose events & IMPL
|
;; General purpose events & IMPL
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(def ^:private font-upload-chunk-size
|
||||||
|
"Size in bytes of each chunk when uploading font files (10 MiB)."
|
||||||
|
(* 1024 1024 10))
|
||||||
|
|
||||||
(defn fonts-fetched
|
(defn fonts-fetched
|
||||||
[fonts]
|
[fonts]
|
||||||
(letfn [;; Prepare font to the internal font database format.
|
(letfn [;; Prepare font to the internal font database format.
|
||||||
@ -94,9 +85,44 @@
|
|||||||
(->> (rp/cmd! :get-font-variants {:team-id team-id})
|
(->> (rp/cmd! :get-font-variants {:team-id team-id})
|
||||||
(rx/map fonts-fetched)))))
|
(rx/map fonts-fetched)))))
|
||||||
|
|
||||||
|
(defn upload-font-variant
|
||||||
|
"Uploads a single font variant item using the chunked upload API.
|
||||||
|
|
||||||
|
For each mime-type in `data`, creates a Blob and uploads it via the
|
||||||
|
session-based chunked upload. Once all sessions are created, calls
|
||||||
|
`create-font-variant` with the resulting `:uploads` map so the server
|
||||||
|
can assemble the chunks and materialise the final font-variant record.
|
||||||
|
|
||||||
|
Returns an observable that emits the created font-variant."
|
||||||
|
[{:keys [data team-id font-id font-family font-weight font-style] :as _item}]
|
||||||
|
;; Upload each mtype as a separate chunked session in parallel, collect
|
||||||
|
;; all [mtype session-id] pairs, then call create-font-variant with :uploads.
|
||||||
|
(->> (rx/from (seq data))
|
||||||
|
(rx/mapcat (fn [[mtype buffer]]
|
||||||
|
(let [blob (js/Blob. #js [buffer] #js {:type mtype})]
|
||||||
|
(->> (uploads/upload-blob-chunked blob :chunk-size font-upload-chunk-size)
|
||||||
|
(rx/map (fn [{:keys [session-id]}]
|
||||||
|
[mtype session-id]))))))
|
||||||
|
(rx/reduce (fn [acc [mtype session-id]]
|
||||||
|
(assoc acc mtype session-id))
|
||||||
|
{})
|
||||||
|
(rx/mapcat (fn [uploads]
|
||||||
|
(rp/cmd! :create-font-variant
|
||||||
|
{:team-id team-id
|
||||||
|
:font-id font-id
|
||||||
|
:font-family font-family
|
||||||
|
:font-weight font-weight
|
||||||
|
:font-style font-style
|
||||||
|
:uploads uploads})))))
|
||||||
|
|
||||||
(defn process-upload
|
(defn process-upload
|
||||||
"Given a seq of blobs and the team id, creates a ready-to-use fonts
|
"Given a seq of blobs and the team id, creates a ready-to-use fonts
|
||||||
map with temporal ID's associated to each font entry."
|
map with temporal ID's associated to each font entry.
|
||||||
|
|
||||||
|
Each font entry's `:data` is a map of `{mtype -> ArrayBuffer}`. The
|
||||||
|
raw `ArrayBuffer` is kept as-is so that `upload-font-variant` can
|
||||||
|
wrap it in a `Blob` and hand it directly to `upload-blob-chunked`
|
||||||
|
without any intermediate client-side chunking."
|
||||||
[blobs team-id]
|
[blobs team-id]
|
||||||
(letfn [(prepare [{:keys [font type name data] :as params}]
|
(letfn [(prepare [{:keys [font type name data] :as params}]
|
||||||
(if font
|
(if font
|
||||||
@ -134,7 +160,7 @@
|
|||||||
(not= hhea-ascender os2-ascent)
|
(not= hhea-ascender os2-ascent)
|
||||||
(not= hhea-descender os2-descent))))
|
(not= hhea-descender os2-descent))))
|
||||||
data (js/Uint8Array. data)]
|
data (js/Uint8Array. data)]
|
||||||
{:content {:data (chunk-array data default-chunk-size)
|
{:content {:data data
|
||||||
:name name
|
:name name
|
||||||
:type type}
|
:type type}
|
||||||
:font-family (or family "")
|
:font-family (or family "")
|
||||||
@ -152,7 +178,7 @@
|
|||||||
(str/trim))
|
(str/trim))
|
||||||
family-name (if (str/blank? raw-family-name) base-name raw-family-name)
|
family-name (if (str/blank? raw-family-name) base-name raw-family-name)
|
||||||
data (js/Uint8Array. data)]
|
data (js/Uint8Array. data)]
|
||||||
{:content {:data (chunk-array data default-chunk-size)
|
{:content {:data data
|
||||||
:name name
|
:name name
|
||||||
:type type}
|
:type type}
|
||||||
:font-family family-name
|
:font-family family-name
|
||||||
|
|||||||
@ -24,10 +24,6 @@
|
|||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
[beicon.v2.core :as rx]))
|
[beicon.v2.core :as rx]))
|
||||||
|
|
||||||
;; Size of each upload chunk in bytes. Reads the penpotUploadChunkSize global
|
|
||||||
;; variable at startup; defaults to 25 MiB (overridden in production).
|
|
||||||
(def ^:private chunk-size cf/upload-chunk-size)
|
|
||||||
|
|
||||||
(def ^:private max-parallel-chunk-uploads
|
(def ^:private max-parallel-chunk-uploads
|
||||||
"Maximum number of chunk upload requests that may be in-flight at the
|
"Maximum number of chunk upload requests that may be in-flight at the
|
||||||
same time within a single chunked upload session."
|
same time within a single chunked upload session."
|
||||||
@ -44,8 +40,11 @@
|
|||||||
Returns an observable that emits exactly one map:
|
Returns an observable that emits exactly one map:
|
||||||
`{:session-id <uuid>}`
|
`{:session-id <uuid>}`
|
||||||
|
|
||||||
The caller is responsible for the final step (assemble / import)."
|
The caller is responsible for the final step (assemble / import).
|
||||||
[blob]
|
|
||||||
|
The optional `opts` map accepts:
|
||||||
|
`:chunk-size` – size in bytes of each chunk (default: `cf/upload-chunk-size`, 25 MiB)."
|
||||||
|
[blob & {:keys [chunk-size] :or {chunk-size cf/upload-chunk-size}}]
|
||||||
(let [total-size (.-size blob)
|
(let [total-size (.-size blob)
|
||||||
total-chunks (js/Math.ceil (/ total-size chunk-size))]
|
total-chunks (js/Math.ceil (/ total-size chunk-size))]
|
||||||
(->> (rp/cmd! :create-upload-session
|
(->> (rp/cmd! :create-upload-session
|
||||||
|
|||||||
@ -109,7 +109,7 @@
|
|||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn [{:keys [id] :as item}]
|
(fn [{:keys [id] :as item}]
|
||||||
(swap! uploading* conj id)
|
(swap! uploading* conj id)
|
||||||
(->> (rp/cmd! :create-font-variant item)
|
(->> (df/upload-font-variant item)
|
||||||
(rx/delay-at-least 2000)
|
(rx/delay-at-least 2000)
|
||||||
(rx/subs! (fn [font]
|
(rx/subs! (fn [font]
|
||||||
(swap! fonts* dissoc id)
|
(swap! fonts* dissoc id)
|
||||||
|
|||||||
@ -9,12 +9,12 @@
|
|||||||
|
|
||||||
:aliases
|
:aliases
|
||||||
{:outdated
|
{:outdated
|
||||||
{:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"}}
|
{:extra-deps {com.github.liquidz/antq {:mvn/version "2.11.1276"}}
|
||||||
:main-opts ["-m" "antq.core"]}
|
:main-opts ["-m" "antq.core"]}
|
||||||
|
|
||||||
:jvm-repl
|
:jvm-repl
|
||||||
{:extra-deps
|
{:extra-deps
|
||||||
{com.bhauman/rebel-readline {:mvn/version "RELEASE"}}
|
{com.bhauman/rebel-readline {:mvn/version "0.1.5"}}
|
||||||
:main-opts ["-m" "rebel-readline.main"]
|
:main-opts ["-m" "rebel-readline.main"]
|
||||||
:jvm-opts ["--sun-misc-unsafe-memory-access=allow"]}
|
:jvm-opts ["--sun-misc-unsafe-memory-access=allow"]}
|
||||||
|
|
||||||
@ -22,9 +22,9 @@
|
|||||||
{:extra-paths ["dev"]
|
{:extra-paths ["dev"]
|
||||||
:extra-deps
|
:extra-deps
|
||||||
{thheller/shadow-cljs {:mvn/version "3.2.1"}
|
{thheller/shadow-cljs {:mvn/version "3.2.1"}
|
||||||
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
|
com.bhauman/rebel-readline {:mvn/version "0.1.5"}
|
||||||
org.clojure/tools.namespace {:mvn/version "RELEASE"}
|
org.clojure/tools.namespace {:mvn/version "1.5.0"}
|
||||||
criterium/criterium {:mvn/version "RELEASE"}}}
|
criterium/criterium {:mvn/version "0.4.6"}}}
|
||||||
|
|
||||||
:shadow-cljs
|
:shadow-cljs
|
||||||
{:main-opts ["-m" "shadow.cljs.devtools.cli"]
|
{:main-opts ["-m" "shadow.cljs.devtools.cli"]
|
||||||
|
|||||||
208
scripts/check-commit
Executable file
208
scripts/check-commit
Executable file
@ -0,0 +1,208 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Check commit messages against Penpot's commit guidelines.
|
||||||
|
|
||||||
|
Validates commit messages using the rules defined in:
|
||||||
|
- .github/workflows/commit-checker.yml (regex pattern)
|
||||||
|
- CONTRIBUTING.md (formatting rules, subject length, DCO)
|
||||||
|
|
||||||
|
By default, checks HEAD. Use --commit to specify a different commit.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
./scripts/check-commit
|
||||||
|
./scripts/check-commit --commit HEAD~1
|
||||||
|
./scripts/check-commit -c abc1234
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# ── Emoji list ───────────────────────────────────────────────────────────────
|
||||||
|
# Combined from commit-checker.yml AND CONTRIBUTING.md
|
||||||
|
VALID_EMOJIS = (
|
||||||
|
"lipstick|globe_with_meridians|wrench|books|"
|
||||||
|
"arrow_up|arrow_down|zap|ambulance|construction|"
|
||||||
|
"boom|fire|whale|bug|sparkles|paperclip|tada|"
|
||||||
|
"recycle|rewind|construction_worker|rocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Regex from .github/workflows/commit-checker.yml ──────────────────────────
|
||||||
|
# Matches:
|
||||||
|
# 1) ":emoji: <Capitalized subject without trailing dot>"
|
||||||
|
# 2) "Merge|Revert|Reapply ... without trailing dot"
|
||||||
|
COMMIT_PATTERN = re.compile(
|
||||||
|
r"^((:(" + VALID_EMOJIS + r"):\s[A-Z].*[^.]))$"
|
||||||
|
)
|
||||||
|
|
||||||
|
MERGE_PATTERN = re.compile(r"^(Merge|Revert|Reapply).+[^.]$")
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# Helpers
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def run_git(args):
|
||||||
|
"""Run a git command and return (returncode, stdout, stderr)."""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["git"] + args,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
return result.returncode, result.stdout, result.stderr
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("ERROR: git not found. Is it installed?", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_commit_message(commit_ref):
|
||||||
|
"""Return the full commit message for *commit_ref*."""
|
||||||
|
rc, out, err = run_git(["log", "--format=%B", "-n", "1", commit_ref])
|
||||||
|
if rc != 0:
|
||||||
|
print(f"ERROR: could not read commit {commit_ref}: {err.strip()}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if not out.strip():
|
||||||
|
print(f"ERROR: commit {commit_ref} has no message", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
return out.rstrip("\n")
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# Validators
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def check_regex(message):
|
||||||
|
"""Check the commit message against the CI regex pattern."""
|
||||||
|
# Normalise: strip trailing newlines for single-line matching
|
||||||
|
first_line = message.split("\n")[0]
|
||||||
|
|
||||||
|
if MERGE_PATTERN.match(first_line):
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
if COMMIT_PATTERN.match(first_line):
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
return False, (
|
||||||
|
"Commit subject must match one of:\n"
|
||||||
|
" :emoji: <Capitalized subject without trailing dot>\n"
|
||||||
|
" Merge|Revert|Reapply <rest without trailing dot>\n"
|
||||||
|
f"Got: {first_line!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_subject_length(message):
|
||||||
|
"""Subject line must be ≤ 90 characters."""
|
||||||
|
first_line = message.split("\n")[0]
|
||||||
|
if len(first_line) > 90:
|
||||||
|
return False, (
|
||||||
|
f"Subject line exceeds 90 characters ({len(first_line)} chars):\n"
|
||||||
|
f" {first_line}"
|
||||||
|
)
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
|
def check_subject_no_trailing_dot(message):
|
||||||
|
"""Subject line must not end with a period ('.')."""
|
||||||
|
first_line = message.split("\n")[0]
|
||||||
|
if first_line.endswith("."):
|
||||||
|
return False, (
|
||||||
|
"Subject line must not end with a period:\n"
|
||||||
|
f" {first_line}"
|
||||||
|
)
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
|
def check_subject_capitalized(message):
|
||||||
|
"""Subject must be capitalized, but only if it's a regular commit (not Merge/Revert/Reapply)."""
|
||||||
|
first_line = message.split("\n")[0]
|
||||||
|
|
||||||
|
# Skip check for Merge/Revert/Reapply commits
|
||||||
|
if MERGE_PATTERN.match(first_line):
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
# Strip emoji prefix before checking capitalization
|
||||||
|
emoji_match = re.match(r"^:([a-z_]+):\s+(.*)", first_line)
|
||||||
|
if emoji_match:
|
||||||
|
rest = emoji_match.group(2)
|
||||||
|
else:
|
||||||
|
rest = first_line
|
||||||
|
|
||||||
|
if rest and not rest[0].isupper():
|
||||||
|
return False, (
|
||||||
|
"Subject line must start with a capital letter "
|
||||||
|
"(after the emoji prefix):\n"
|
||||||
|
f" {first_line}"
|
||||||
|
)
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
|
def check_body_blank_line(message):
|
||||||
|
"""If a body exists, there must be a blank line between subject and body."""
|
||||||
|
lines = message.split("\n")
|
||||||
|
if len(lines) >= 3 and lines[1] != "":
|
||||||
|
return False, (
|
||||||
|
"A blank line must separate the subject from the body."
|
||||||
|
)
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
|
def check_signed_off_by(message):
|
||||||
|
"""Check for the DCO Signed-off-by line (required for code changes)."""
|
||||||
|
if "Signed-off-by:" not in message:
|
||||||
|
return False, (
|
||||||
|
"Missing 'Signed-off-by:' line in the commit footer.\n"
|
||||||
|
" Add it with 'git commit -s' or append it manually:\n"
|
||||||
|
" Signed-off-by: Your Real Name <your.email@example.com>"
|
||||||
|
)
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Check a commit message against Penpot commit guidelines."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-c", "--commit",
|
||||||
|
default="HEAD",
|
||||||
|
help="Commit to check (default: HEAD)",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
commit_ref = args.commit
|
||||||
|
message = get_commit_message(commit_ref)
|
||||||
|
|
||||||
|
print(f"Checking commit {commit_ref} ...\n")
|
||||||
|
|
||||||
|
validators = [
|
||||||
|
("Regex pattern", check_regex),
|
||||||
|
("Subject ≤ 90 chars", check_subject_length),
|
||||||
|
("No trailing period in subject", check_subject_no_trailing_dot),
|
||||||
|
("Subject capitalized", check_subject_capitalized),
|
||||||
|
("Blank line after subject", check_body_blank_line),
|
||||||
|
]
|
||||||
|
|
||||||
|
all_ok = True
|
||||||
|
|
||||||
|
for name, validator in validators:
|
||||||
|
ok, error_msg = validator(message)
|
||||||
|
status = "✓" if ok else "✗"
|
||||||
|
print(f" [{status}] {name}")
|
||||||
|
if not ok:
|
||||||
|
all_ok = False
|
||||||
|
print(f" {error_msg}", file=sys.stderr)
|
||||||
|
|
||||||
|
print()
|
||||||
|
if all_ok:
|
||||||
|
print("All checks passed.")
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print("Some checks FAILED. See messages above.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
x
Reference in New Issue
Block a user